Laender-Split (manuelle Auftraege): DE/INT getrennt anlegen
zielInland()-Erkennung (Land/Freitext/D-PLZ). manual-order: bei gemischten Empfaengern zwei Auftraege mit Suffix -DE / -INT (Empfaenger aufgeteilt), Generierung je Auftrag -> Schriftstuecke UND Kuverts je Zielgebiet getrennt. Einzelland unveraendert (kein Suffix). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -155,6 +155,21 @@ async function paypalToken() {
|
||||
return (await r.json()).access_token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Zielland-Erkennung je Empfänger für den Länder-Split.
|
||||
* Deutschland, wenn: Land = DE/leer, „Deutschland"/„Germany" im Freitext,
|
||||
* „D-" vor der PLZ, oder nur eine PLZ ohne Auslandshinweis. Sonst → weltweit (INT).
|
||||
*/
|
||||
function zielInland(e) {
|
||||
const land = String(e?.country || '').trim();
|
||||
if (land) return /^(de|deu|d|deutschland|germany)$/i.test(land);
|
||||
const txt = String(e?.free_text || '');
|
||||
if (/deutschland|germany/i.test(txt)) return true;
|
||||
if (/\bd-\s?\d{4,5}\b/i.test(txt)) return true;
|
||||
if (/(österreich|schweiz|austria|switzerland|frankreich|france|italien|italy|niederlande|netherlands|belgien|belgium|luxemburg|luxembourg|spanien|spain|portugal|usa|vereinigte staaten|united states|kanada|canada|england|großbritannien|grossbritannien|united kingdom|dänemark|denmark|schweden|sweden|norwegen|norway|finnland|finland|polen|poland|tschechien|czech|ungarn|hungary|griechenland|greece|irland|ireland|australien|australia|liechtenstein)/i.test(txt)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
const handler = (router, { services, getSchema, logger }) => {
|
||||
const { ItemsService, MailService, UsersService, RolesService, AssetsService, FilesService } = services;
|
||||
|
||||
@@ -1572,14 +1587,21 @@ const handler = (router, { services, getSchema, logger }) => {
|
||||
: (String(body.format || '').startsWith('a6') ? 'c6' : 'dinlang')))
|
||||
: null;
|
||||
|
||||
const istInland = (e) => !e.country || /^(de|deutschland|germany)$/i.test(String(e.country).trim());
|
||||
const istInland = zielInland;
|
||||
const countAusland = entries.filter((e) => !istInland(e)).length;
|
||||
|
||||
const jt = new Date();
|
||||
const praefix = `${String(jt.getDate()).padStart(2, '0')}-${String(jt.getMonth() + 1).padStart(2, '0')}-${String(jt.getFullYear()).slice(2)}`;
|
||||
const naechsteNummer = async () => {
|
||||
// Freie Basis-Nummer; prüft auch die Split-Varianten -DE / -INT auf Kollision.
|
||||
const freieBasis = async () => {
|
||||
const heutige = await svc('orders').readByQuery({ filter: { order_number: { _starts_with: `${praefix}-` } }, limit: -1, fields: ['id'] });
|
||||
return `${praefix}-${String((heutige?.length ?? 0) + 1).padStart(3, '0')}`;
|
||||
let n = (heutige?.length ?? 0) + 1;
|
||||
for (let v = 0; v < 30; v += 1, n += 1) {
|
||||
const basis = `${praefix}-${String(n).padStart(3, '0')}`;
|
||||
const koll = await svc('orders').readByQuery({ filter: { order_number: { _in: [basis, `${basis}-DE`, `${basis}-INT`] } }, limit: 1, fields: ['id'] });
|
||||
if (!koll?.length) return basis;
|
||||
}
|
||||
throw new Error('Keine freie Auftragsnummer gefunden.');
|
||||
};
|
||||
|
||||
const orderData = {
|
||||
@@ -1612,38 +1634,54 @@ const handler = (router, { services, getSchema, logger }) => {
|
||||
net_total: quote?.net_total ?? null, vat_amount: quote?.vat_amount ?? null, gross_total: quote?.gross_total ?? null,
|
||||
};
|
||||
|
||||
let orderId, orderNumber;
|
||||
for (let v = 0; v < 6; v += 1) {
|
||||
orderNumber = await naechsteNummer();
|
||||
try { orderId = await svc('orders').createOne({ order_number: orderNumber, ...orderData }); break; }
|
||||
catch (e) { if (v === 5 || !/unique|duplicate|bereits|exists/i.test(String(e?.message || ''))) throw e; }
|
||||
}
|
||||
|
||||
const entryFelder = (e, nr) => ({
|
||||
letter_number: nr,
|
||||
salutation: clean(e.salutation) ?? null, first_name: clean(e.first_name) ?? null, last_name: clean(e.last_name) ?? null,
|
||||
street: clean(e.street) ?? null, house_no: clean(e.house_no) ?? null, zip: clean(e.zip) ?? null,
|
||||
city: clean(e.city) ?? null, country: clean(e.country) ?? null, free_text: clean(e.free_text) ?? null,
|
||||
placeholders: e.placeholders && typeof e.placeholders === 'object'
|
||||
? Object.fromEntries(Object.entries(e.placeholders).map(([k, v]) => [k, clean(String(v))])) : null,
|
||||
});
|
||||
const entriesSvc = svc('order_entries');
|
||||
let n = 0;
|
||||
for (const e of entries) {
|
||||
n += 1;
|
||||
await entriesSvc.createOne({
|
||||
order: orderId, letter_number: n,
|
||||
salutation: clean(e.salutation) ?? null, first_name: clean(e.first_name) ?? null, last_name: clean(e.last_name) ?? null,
|
||||
street: clean(e.street) ?? null, house_no: clean(e.house_no) ?? null, zip: clean(e.zip) ?? null,
|
||||
city: clean(e.city) ?? null, country: clean(e.country) ?? null, free_text: clean(e.free_text) ?? null,
|
||||
placeholders: e.placeholders && typeof e.placeholders === 'object'
|
||||
? Object.fromEntries(Object.entries(e.placeholders).map(([k, v]) => [k, clean(String(v))])) : null,
|
||||
|
||||
// Länder-Split: sobald DE- UND Welt-Empfänger vorkommen → zwei getrennte
|
||||
// Aufträge (-DE / -INT), sonst ein Auftrag ohne Suffix. Gilt für Schriftstücke UND Kuverts.
|
||||
const deEntries = entries.filter((e) => istInland(e));
|
||||
const intEntries = entries.filter((e) => !istInland(e));
|
||||
const gemischt = deEntries.length > 0 && intEntries.length > 0;
|
||||
|
||||
const anlegen = async (subEntries, nummer, gebiet) => {
|
||||
const auslandN = subEntries.filter((e) => !istInland(e)).length;
|
||||
let q = null;
|
||||
try { q = await calculatePrice(schema, { ...body, quantity: subEntries.length }); } catch { /* nur Aufzeichnung */ }
|
||||
const id = await svc('orders').createOne({
|
||||
order_number: nummer,
|
||||
...orderData,
|
||||
entries_count: subEntries.length,
|
||||
count_inland: subEntries.length - auslandN,
|
||||
count_ausland: auslandN,
|
||||
net_total: q?.net_total ?? null, vat_amount: q?.vat_amount ?? null, gross_total: q?.gross_total ?? null,
|
||||
});
|
||||
let k = 0;
|
||||
for (const e of subEntries) { k += 1; await entriesSvc.createOne({ order: id, ...entryFelder(e, k) }); }
|
||||
await svc('status_history').createOne({ order: id, status: 'eingegangen', note: gebiet ? `Manuell angelegt (Länder-Split: ${gebiet}).` : 'Manuell im Admin angelegt.' });
|
||||
// Generierung IMMER anstoßen – das Backend entscheidet, was zu erzeugen ist.
|
||||
fetch(`${BACKEND_URL}/api/order/from-directus`, {
|
||||
method: 'POST', headers: { 'Content-Type': 'application/json', 'X-API-Token': BACKEND_TOKEN },
|
||||
body: JSON.stringify({ orderId: id }),
|
||||
}).catch((err) => logger.warn(`[skrift-orders] Manuell-Generierung nicht gestartet: ${err.message}`));
|
||||
return { order_number: nummer, id };
|
||||
};
|
||||
|
||||
if (gemischt) {
|
||||
const basis = await freieBasis();
|
||||
const de = await anlegen(deEntries, `${basis}-DE`, 'Deutschland');
|
||||
const int = await anlegen(intEntries, `${basis}-INT`, 'weltweit');
|
||||
return res.json({ ok: true, split: true, orders: [de, int], order_number: de.order_number, id: de.id });
|
||||
}
|
||||
|
||||
await svc('status_history').createOne({ order: orderId, status: 'eingegangen', note: 'Manuell im Admin angelegt.' });
|
||||
|
||||
// Generierung IMMER anstoßen – für jedes Produkt. Das Backend entscheidet,
|
||||
// was zu erzeugen ist (Schriftstücke bei Handschrift-Produkten, Kuverts immer
|
||||
// wenn gewünscht; bei Datei-Produkten NUR die Kuverts). Nichts = No-Op.
|
||||
fetch(`${BACKEND_URL}/api/order/from-directus`, {
|
||||
method: 'POST', headers: { 'Content-Type': 'application/json', 'X-API-Token': BACKEND_TOKEN },
|
||||
body: JSON.stringify({ orderId }),
|
||||
}).catch((e) => logger.warn(`[skrift-orders] Manuell-Generierung nicht gestartet: ${e.message}`));
|
||||
|
||||
return res.json({ ok: true, order_number: orderNumber, id: orderId });
|
||||
const basis = await freieBasis();
|
||||
const einzel = await anlegen(entries, basis, null);
|
||||
return res.json({ ok: true, order_number: einzel.order_number, id: einzel.id });
|
||||
} catch (err) {
|
||||
logger.error(`[skrift-orders] manual-order: ${err.stack || err.message}`);
|
||||
return res.status(400).json({ error: err.message });
|
||||
@@ -1737,7 +1775,7 @@ const handler = (router, { services, getSchema, logger }) => {
|
||||
: null;
|
||||
|
||||
// Empfänger nach Inland/Ausland aufteilen (für die Auftragsübersicht).
|
||||
const istInland = (e) => !e.country || /^(de|deutschland|germany)$/i.test(String(e.country).trim());
|
||||
const istInland = zielInland;
|
||||
const countAusland = entries.filter((e) => !istInland(e)).length;
|
||||
const countInland = entries.length - countAusland;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user