Produktion: Druckdatum je Auftrag, Chargen nach Druck-Durchgang trennen, Empfänger

- 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 <Datum/Zeit>".
- „Weiteres" je Auftrag: „In den Druck: <Datum/Zeit>" 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 <noreply@anthropic.com>
This commit is contained in:
s4luorth
2026-09-18 10:04:22 +02:00
parent 881ff8a27b
commit 2c9ffbbc9b

View File

@@ -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;