From 2c9ffbbc9b81b0196c5134808f35ec2a8b03b82c Mon Sep 17 00:00:00 2001 From: s4luorth Date: Fri, 18 Sep 2026 10:04:22 +0200 Subject: [PATCH] =?UTF-8?q?Produktion:=20Druckdatum=20je=20Auftrag,=20Char?= =?UTF-8?q?gen=20nach=20Druck-Durchgang=20trennen,=20Empf=C3=A4nger?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Chargen trennen jetzt zusätzlich nach dem Druck-Durchgang (Zeitpunkt „in den Druck gegeben", Minute) – zwei Druckläufe desselben Tages mischen sich nicht mehr. - Charge-Karte: Chip „🖨 gedruckt ". - „Weiteres" je Auftrag: „In den Druck: " sowie Erster/Letzter Empfänger (nach Briefnummer) zum Auseinandersortieren. - druckIndex (frühester/letzter Job-Zeitpunkt) + empfNamen aus order_entries. Co-Authored-By: Claude Opus 4.8 --- .../dist/index.js | 42 +++++++++++++++++-- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/extensions/directus-extension-skrift-produktion/dist/index.js b/extensions/directus-extension-skrift-produktion/dist/index.js index 55ba6b9..5280e43 100644 --- a/extensions/directus-extension-skrift-produktion/dist/index.js +++ b/extensions/directus-extension-skrift-produktion/dist/index.js @@ -240,20 +240,31 @@ const Board = defineComponent({ // (klein geschrieben). Damit findet die normale Suche oben auch Empfänger, // ohne dass man den Auftrag öffnen muss. Wird bei vollem Laden gebaut. const empfIndex = ref({}); + // Erster/letzter Empfänger je Auftrag (nach Briefnummer) – zum Auseinandersortieren. + const empfNamen = ref({}); + const entryName = (e) => [e.first_name, e.last_name].filter(Boolean).join(' ') + || (e.free_text ? String(e.free_text).split('\n')[0].trim() : '') + || [e.zip, e.city].filter(Boolean).join(' ') || '—'; async function ladeEmpfIndex() { try { const r = await api.get('/items/order_entries', { params: { - limit: -1, fields: 'order,salutation,first_name,last_name,street,house_no,zip,city,country,free_text', + limit: -1, sort: 'order,letter_number', + fields: 'order,letter_number,salutation,first_name,last_name,street,house_no,zip,city,country,free_text', } }); const idx = {}; + const namen = {}; for (const e of r.data?.data || []) { const oid = String(e.order?.id ?? e.order ?? ''); if (!oid) continue; const txt = [e.salutation, e.first_name, e.last_name, e.street, e.house_no, e.zip, e.city, e.country, e.free_text] .filter(Boolean).join(' ').toLowerCase(); idx[oid] = idx[oid] ? idx[oid] + ' · ' + txt : txt; + const nm = entryName(e); + if (!namen[oid]) namen[oid] = { erste: nm, letzte: nm, anzahl: 1 }; + else { namen[oid].letzte = nm; namen[oid].anzahl += 1; } } empfIndex.value = idx; + empfNamen.value = namen; } catch { /* Suche läuft dann ohne Empfänger weiter */ } } // Sende-Index über ALLE Jobs (auch bereits gedruckte): orderId → @@ -261,12 +272,16 @@ const Board = defineComponent({ // Zeigt am Auftrag „auf einen Blick", welche Nummern schon in den Druck // gesendet wurden (leeres numbers = „alle"). Rein informativ, kein Status. const sendIndex = ref({}); + // Druckzeitpunkt je Auftrag (frühester Job = „in den Druck gegeben"). Basis für + // das angezeigte Druckdatum UND die Trennung der Chargen nach Druck-Durchgang. + const druckIndex = ref({}); async function ladeSendIndex() { try { const r = await api.get('/items/jobs', { params: { - limit: -1, sort: 'date_created', fields: 'order,type,numbers', + limit: -1, sort: 'date_created', fields: 'order,type,numbers,date_created', } }); const idx = {}; + const dz = {}; for (const j of r.data?.data || []) { const oid = String(j.order?.id ?? j.order ?? ''); if (!oid) continue; @@ -274,10 +289,16 @@ const Board = defineComponent({ const num = (j.numbers && String(j.numbers).trim()) ? String(j.numbers).trim() : 'alle'; if (!idx[oid]) idx[oid] = { brief: [], umschlag: [] }; if (!idx[oid][typ].includes(num)) idx[oid][typ].push(num); + const ts = String(j.date_created || ''); + if (ts && (!dz[oid] || ts < dz[oid].erste)) dz[oid] = { erste: ts }; + if (ts && (!dz[oid].letzte || ts > dz[oid].letzte)) dz[oid].letzte = ts; } sendIndex.value = idx; + druckIndex.value = dz; } catch { /* Anzeige läuft dann ohne „gesendet" weiter */ } } + // Datum/Uhrzeit hübsch (dd.mm.yy HH:MM). + const dmYhm = (iso) => { if (!iso) return ''; const d = new Date(iso); return Number.isNaN(d.getTime()) ? '' : `${String(d.getDate()).padStart(2, '0')}.${String(d.getMonth() + 1).padStart(2, '0')}.${String(d.getFullYear()).slice(2)} ${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`; }; // Notizen je Charge (charge_key → Notiz) + Speichern von Auftrags-/Charge-Notizen. const chargeNotizen = ref({}); @@ -478,11 +499,16 @@ const Board = defineComponent({ const produkt = o.product?.name || '?'; const tag = o.shipping_day || ''; const eingang = String(o.date_created || '').slice(0, 10); - const key = `${kunde}|${produkt}|${tag}|${eingang}`; + // Druck-Durchgang: bereits gedruckte Aufträge desselben Tages werden nach + // dem Zeitpunkt „in den Druck gegeben" (Minute) getrennt – so mischen sich + // zwei Druckläufe desselben Tages nicht mehr. Ungedruckte: kein Batch. + const druckTs = druckIndex.value[String(o.id)]?.erste || ''; + const batch = druckTs ? druckTs.slice(0, 16) : ''; + const key = `${kunde}|${produkt}|${tag}|${eingang}|${batch}`; let c = map.get(key); if (!c) { c = { key, kundeName: o.api_client?.name || 'API-Kunde', produkt, versandtag: tag, eingang, orders: [], - faellig: faelligBis(o.date_created, tag) }; + druckZeit: druckTs, faellig: faelligBis(o.date_created, tag) }; map.set(key, c); } c.orders.push(o); @@ -1018,7 +1044,14 @@ const Board = defineComponent({ } // „Weiteres": Verknüpfen · In Kontakte speichern · Neu generieren · In manuellen Druck. function weiteresPanel(o) { + const dz = druckIndex.value[String(o.id)]; + const nm = empfNamen.value[String(o.id)]; return h('div', { style: 'margin-top:10px;padding:12px;border:1px solid var(--border-subdued);border-radius:8px;background:var(--background-subdued);display:flex;flex-direction:column;gap:10px;' }, [ + // Druckdatum + Erster/Letzter Empfänger – zum Wiederfinden/Auseinandersortieren. + h('div', { style: 'font-size:12px;color:var(--foreground-normal);line-height:1.6;' }, [ + h('div', {}, dz ? `🖨 In den Druck: ${dmYhm(dz.erste)}${dz.letzte && dz.letzte.slice(0, 16) !== dz.erste.slice(0, 16) ? ' – ' + dmYhm(dz.letzte) : ''}` : '🖨 Noch nicht gedruckt.'), + nm ? h('div', {}, `👥 Erster: ${nm.erste}${nm.anzahl > 1 ? ` · Letzter: ${nm.letzte}` : ''}`) : null, + ]), h('div', { style: 'display:flex;align-items:center;gap:8px;flex-wrap:wrap;' }, [ subdued('🔗 Verknüpfter Auftrag:', ''), h('input', { placeholder: 'Auftragsnr. (leer = keine)', value: o.linked_order?.order_number || '', @@ -1136,6 +1169,7 @@ const Board = defineComponent({ h('div', { style: 'font-weight:700;font-size:15px;' }, `📦 ${c.kundeName} · ${c.produkt}`), chip(`${c.orders.length} Aufträge`), c.versandtag ? chip('📅 ' + c.versandtag.charAt(0).toUpperCase() + c.versandtag.slice(1)) : null, + c.druckZeit ? chip('🖨 gedruckt ' + dmYhm(c.druckZeit), 'var(--primary-25,rgba(30,80,165,.14))') : null, c.faellig ? chip('⏰ fällig bis ' + dmY(c.faellig), faelligFarbe) : null, ]); const body = auf ? h('div', { style: 'margin-top:12px;display:flex;flex-direction:column;gap:2px;' }, c.orders.map((o) => orderCard(o, opts))) : null;