Kundenangebote (customer_offers) + Absender-Uebernahme-Fix
- Bootstrap: Collection customer_offers (Kunde+Produkt, Name-Zusatz, Aufschlaege/Stueck, mehrseitig erlauben, Notiz) + Order-Felder customer_offer/product_name_suffix. - orders: calculatePrice addiert Angebots-Aufschlaege (serverseitig geprueft), Endpunkt /my-offers, POST / speichert Angebot + Name-Zusatz. - produktion: 'In manuellen Druck uebernehmen' traegt jetzt auch Kuvert-Absender + Groesse mit. - neuauftrag: uebernimmt Absender-Felder aus dem Auftrag. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -527,6 +527,25 @@ const handler = (router, { services, getSchema, logger }) => {
|
||||
let net = lines.reduce((s, l) => s + l.amount, 0);
|
||||
|
||||
// 5) Gutschein
|
||||
// Kundenangebot (customer_offers): frei benannte Aufschläge pro Stück, wenn der
|
||||
// eingeloggte Kunde ein Angebot gewählt hat. Serverseitig geprüft (Zugehörigkeit).
|
||||
let offer = null;
|
||||
if (input.offer) {
|
||||
const off = (await svc('customer_offers').readByQuery({
|
||||
filter: { id: { _eq: input.offer }, active: { _eq: true } }, limit: 1,
|
||||
fields: ['id', 'customer', 'product.key', 'name_suffix', 'surcharges', 'allow_multi_page'],
|
||||
}))?.[0];
|
||||
// Nur anwenden, wenn das Angebot dem anfragenden Kunden UND dem Produkt gehört.
|
||||
if (off && input.customerId && String(off.customer) === String(input.customerId)
|
||||
&& (!off.product?.key || off.product.key === input.product)) {
|
||||
offer = off;
|
||||
for (const s of (Array.isArray(off.surcharges) ? off.surcharges : [])) {
|
||||
const betrag = Number(s?.amount) || 0;
|
||||
if (betrag) { const gesamt = betrag * menge; add(s.label || 'Aufschlag', gesamt, true); net += gesamt; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let voucher = null;
|
||||
if (input.voucher_code) {
|
||||
const v = (await svc('vouchers').readByQuery({
|
||||
@@ -556,7 +575,7 @@ const handler = (router, { services, getSchema, logger }) => {
|
||||
const paypalAllowed = net < limit;
|
||||
|
||||
return { product, lines, net_total: net, unit_net_total: unitNet, vat_amount: vat, gross_total: gross,
|
||||
mwst_percent: mwst, paypal_allowed: paypalAllowed, paypal_limit_net: limit, voucher };
|
||||
mwst_percent: mwst, paypal_allowed: paypalAllowed, paypal_limit_net: limit, voucher, offer };
|
||||
}
|
||||
|
||||
function validate(body) {
|
||||
@@ -578,6 +597,7 @@ const handler = (router, { services, getSchema, logger }) => {
|
||||
const body = req.body || {};
|
||||
const quote = await calculatePrice(schema, {
|
||||
...body, quantity: body.quantity ?? (body.entries || []).length,
|
||||
customerId: req.accountability?.user || null,
|
||||
});
|
||||
return res.json({
|
||||
lines: quote.lines, net_total: quote.net_total, unit_net_total: quote.unit_net_total,
|
||||
@@ -590,6 +610,26 @@ const handler = (router, { services, getSchema, logger }) => {
|
||||
}
|
||||
});
|
||||
|
||||
// ── Angebote des eingeloggten Kunden (Auswahl im Konfigurator) ────────────
|
||||
router.get('/my-offers', async (req, res) => {
|
||||
const uid = req.accountability?.user;
|
||||
if (!uid) return res.json({ offers: [] });
|
||||
try {
|
||||
const schema = await getSchema();
|
||||
const svc = (c) => new ItemsService(c, { schema, accountability: null });
|
||||
const filter = { customer: { _eq: uid }, active: { _eq: true } };
|
||||
if (req.query.product) filter.product = { key: { _eq: String(req.query.product) } };
|
||||
const offers = await svc('customer_offers').readByQuery({
|
||||
filter, limit: -1, sort: ['label'],
|
||||
fields: ['id', 'label', 'name_suffix', 'note', 'allow_multi_page', 'surcharges', 'product.key', 'product.name'],
|
||||
});
|
||||
return res.json({ offers: offers || [] });
|
||||
} catch (err) {
|
||||
logger.error(`[skrift-orders] my-offers: ${err.stack || err.message}`);
|
||||
return res.status(500).json({ error: 'Angebote konnten nicht geladen werden.' });
|
||||
}
|
||||
});
|
||||
|
||||
// ── Vorschau ─────────────────────────────────────────────────────────────
|
||||
/**
|
||||
* Erzeugt bis zu PREVIEW_MAX_DOCS Vorschau-SVGs über das Backend.
|
||||
@@ -1682,7 +1722,7 @@ const handler = (router, { services, getSchema, logger }) => {
|
||||
const { settings } = await loadPricingContext(schema);
|
||||
const clean = makeSanitizer(settings.char_whitelist || '\\x20-\\x7E');
|
||||
|
||||
const quote = await calculatePrice(schema, { ...body, quantity: entries.length });
|
||||
const quote = await calculatePrice(schema, { ...body, quantity: entries.length, customerId: req.accountability?.user || null });
|
||||
|
||||
// Zahlungsart erzwingen, wenn über dem PayPal-Limit
|
||||
let paymentMethod = body.payment_method === 'paypal' ? 'paypal' : 'rechnung';
|
||||
@@ -1774,6 +1814,9 @@ const handler = (router, { services, getSchema, logger }) => {
|
||||
customer: customer.id,
|
||||
customer_email: email,
|
||||
product: quote.product.id,
|
||||
// Gewähltes Kundenangebot (Aufschläge sind bereits im Preis) + Namens-Zusatz.
|
||||
customer_offer: quote.offer?.id ?? null,
|
||||
product_name_suffix: quote.offer?.name_suffix ?? null,
|
||||
format: formatId,
|
||||
person_type: body.person_type === 'unternehmen' ? 'unternehmen' : 'privat',
|
||||
font: ['tilda', 'alva', 'ellie'].includes(body.font) ? body.font : 'tilda',
|
||||
|
||||
Reference in New Issue
Block a user