Uebersicht: Empfaenger als Hauptinfo, Status-getrennte Tabellen, Aufgabe, Versand-Toggle

- Empfaenger (erster/letzter) ist die fuehrende Spalte; Ladefehler behoben
  (order _in statt Relationsfilter). Auftrag/Kunde sekundaer.
- Je Status eine eigene Tabelle (Im Druck / Kuvertierung / Versandfertig /
  Eingegangen).
- Neue Spalte "Aufgabe" (kuvertieren/frankieren/Versandart aus Status abgeleitet).
- Versandart pro Auftrag umschaltbar (Sammel/Einzel).
- Maschinen-Panel zaehlt nur WARTENDE Jobs (nicht "im Druck").

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
s4luorth
2026-10-01 11:08:29 +02:00
parent 1eb3b14a13
commit d2559de22c

View File

@@ -85,12 +85,13 @@ const Board = {
machines.value = m.data?.data || [];
// Erster/letzter Empfänger je Auftrag (für die Adressspalte).
const idx = {};
const ids = orders.value.map((x) => x.id);
try {
const ent = await api.get('/items/order_entries', { params: {
limit: -1, sort: 'order,letter_number',
filter: { order: { production_status: { _in: AKTIV_STATUS } } },
const ent = ids.length ? await api.get('/items/order_entries', { params: {
limit: -1, sort: 'letter_number',
filter: { order: { _in: ids } },
fields: 'order,first_name,last_name,company,city,letter_number,free_text',
} });
} }) : { data: { data: [] } };
for (const e of (ent.data?.data || [])) {
const k = String(e.order?.id ?? e.order);
if (!k || k === 'null') continue;
@@ -111,7 +112,12 @@ const Board = {
const eingeplantSet = computed(() => new Set(jobs.value.map((j) => String(j.order?.id)).filter((x) => x && x !== 'null')));
const queuedByFormat = computed(() => {
const map = {};
for (const j of jobs.value) { const f = j.format || '?'; if (!map[f]) map[f] = { n: 0, label: j.format_label || FORMAT_LABEL[f] || f }; map[f].n += 1; }
for (const j of jobs.value) {
if (j.status !== 'queued') continue; // nur wartende Jobs (nicht „im Druck")
const f = j.format || '?';
if (!map[f]) map[f] = { n: 0, label: j.format_label || FORMAT_LABEL[f] || f };
map[f].n += 1;
}
return map;
});
const gefiltert = computed(() => {
@@ -154,33 +160,53 @@ const Board = {
const th = (t, extra) => h('th', { style: `text-align:left;font-size:12px;font-weight:500;color:var(--foreground-subdued);text-transform:uppercase;letter-spacing:.03em;padding:10px 12px;border-bottom:1px solid var(--border-normal);white-space:nowrap;${extra || ''}` }, t);
const td = (kids, extra) => h('td', { style: `padding:11px 12px;border-bottom:1px solid var(--border-subdued);font-size:14px;vertical-align:top;${extra || ''}` }, kids);
// Nächste konkrete Aufgabe für den Mitarbeiter (aus Status + Kuvert + Versandart).
function aufgabe(o) {
const einzeln = o.shipping_type === 'einzeln';
switch (o.production_status) {
case 'eingegangen': return { t: 'Wartet auf Druck', c: 'var(--foreground-subdued)', i: 'hourglass_empty' };
case 'im_druck': return { t: 'Im Druck', c: 'var(--primary,#1e50a5)', i: 'print' };
case 'kuvertieren': return { t: (o.needs_envelope ? 'Kuvertieren' : 'Vorbereiten') + ' + frankieren', c: '#6a3fd0', i: 'mail' };
case 'versandfertig': return { t: einzeln ? 'Einzelversand' : 'Sammelversand', c: '#8a6d00', i: 'local_shipping' };
default: return { t: STATUS_LABEL[o.production_status] || '', c: 'var(--foreground-subdued)', i: 'info' };
}
}
async function setVersand(o, v) {
try { await api.patch('/items/orders/' + o.id, { shipping_type: v }); o.shipping_type = v; }
catch (e) { err.value = 'Versandart konnte nicht gesetzt werden.'; }
}
function versandToggle(o) {
const einzeln = o.shipping_type === 'einzeln';
return h('button', { type: 'button', title: 'Umschalten Sammel/Einzel', onClick: () => setVersand(o, einzeln ? 'sammel' : 'einzeln'),
style: 'cursor:pointer;border:1px solid var(--border-normal);background:var(--background-page,#fff);border-radius:999px;padding:4px 11px;font:inherit;font-size:12.5px;font-weight:500;display:inline-flex;align-items:center;gap:5px;' },
[icon(einzeln ? 'person' : 'groups'), einzeln ? 'Einzeln' : 'Sammel']);
}
function zeile(o) {
const e = entriesByOrder.value[String(o.id)];
const faellig = faelligBis(o.date_created, o.shipping_day);
const empf = e
? h('div', { style: 'line-height:1.5;' }, [
h('div', {}, empfKurz(e.erster) || '—'),
e.anzahl > 1 ? h('div', { style: 'color:var(--foreground-subdued);' }, '… ' + empfKurz(e.letzter)) : null,
])
: h('span', { style: 'color:var(--foreground-subdued);' }, '—');
const briefOk = !!o.brief_gedruckt;
const kuvOk = !!o.kuvert_gedruckt;
const ag = aufgabe(o);
const eingeplant = eingeplantSet.value.has(String(o.id));
return h('tr', { key: 'o' + o.id }, [
td(h('div', { style: 'line-height:1.4;' }, [
o.order_name ? h('div', { style: 'font-weight:500;color:var(--primary,#1e50a5);' }, o.order_name) : null,
h('div', { style: 'font-weight:500;' }, kundeName(o) || `#${o.order_number}`),
h('div', { style: 'font-size:12.5px;color:var(--foreground-subdued);' }, `#${o.order_number} · ${o.entries_count || 0} Empf.`),
// Empfänger = Hauptinfo.
td(h('div', { style: 'line-height:1.45;' }, [
h('div', { style: 'font-weight:500;' }, empfKurz(e?.erster) || '—'),
(e && e.anzahl > 1) ? h('div', { style: 'font-size:12.5px;color:var(--foreground-subdued);' }, '… ' + empfKurz(e.letzter)) : null,
])),
// Auftrag = sekundär.
td(h('div', { style: 'line-height:1.4;font-size:13px;' }, [
o.order_name ? h('div', { style: 'font-weight:500;color:var(--primary,#1e50a5);' }, o.order_name) : null,
h('div', {}, kundeName(o) || `#${o.order_number}`),
h('div', { style: 'color:var(--foreground-subdued);' }, `#${o.order_number} · ${o.entries_count || 0} Empf.`),
])),
td(empf),
td(h('span', {}, o.format?.name || FORMAT_LABEL[o.format?.key] || o.format?.key || '—')),
td(h('span', { style: `display:inline-flex;align-items:center;gap:5px;font-weight:500;color:${ag.c};` }, [icon(ag.i), ag.t])),
td(versandToggle(o)),
td(h('span', { style: faellig && faellig.getTime() < Date.now() ? 'color:var(--danger,#c62828);font-weight:500;' : '' }, dmY(faellig))),
td(h('div', { style: 'display:flex;flex-direction:column;gap:4px;align-items:flex-start;' }, [
druckBadge(briefOk, 'Brief'),
o.needs_envelope ? druckBadge(kuvOk, 'Kuvert') : h('span', { style: 'font-size:12px;color:var(--foreground-subdued);' }, 'ohne Kuvert'),
])),
td(eingeplantSet.value.has(String(o.id))
? h('span', { style: 'display:inline-flex;align-items:center;gap:4px;font-size:12.5px;font-weight:500;color:var(--primary,#1e50a5);' }, [icon('check'), 'eingeplant'])
: h('span', { style: 'font-size:12.5px;color:var(--foreground-subdued);' }, 'nein')),
druckBadge(!!o.brief_gedruckt, 'Brief'),
o.needs_envelope ? druckBadge(!!o.kuvert_gedruckt, 'Kuvert') : null,
eingeplant ? h('span', { style: 'display:inline-flex;align-items:center;gap:3px;font-size:11.5px;font-weight:500;color:var(--primary,#1e50a5);' }, [icon('check'), 'eingeplant']) : null,
].filter(Boolean))),
td(statusPill(o)),
]);
}
@@ -218,13 +244,24 @@ const Board = {
maschinenPanel(),
loading.value
? h('div', { style: 'color:var(--foreground-subdued);' }, 'Lädt …')
: h('div', { style: 'border:1px solid var(--border-normal);border-radius:12px;overflow:auto;background:var(--background-page,#fff);' }, [
h('table', { style: 'width:100%;border-collapse:collapse;' }, [
h('thead', {}, h('tr', {}, [th('Auftrag'), th('Empfänger (erster · letzter)'), th('Format'), th('Fällig'), th('Druck'), th('Eingeplant'), th('Status')])),
h('tbody', {}, gefiltert.value.length ? gefiltert.value.map(zeile) : [h('tr', {}, h('td', { colspan: 7, style: 'padding:24px;text-align:center;color:var(--foreground-subdued);' }, 'Keine Aufträge.'))]),
]),
]),
h('div', { style: 'margin-top:10px;font-size:12.5px;color:var(--foreground-subdued);' }, `${gefiltert.value.length} von ${orders.value.length} offenen Aufträgen`),
: (() => {
const GRUPPEN = [['im_druck', 'Im Druck'], ['kuvertieren', 'Kuvertierung'], ['versandfertig', 'Versandfertig'], ['eingegangen', 'Eingegangen']];
const kopf = () => h('thead', {}, h('tr', {}, [th('Empfänger (erster · letzter)'), th('Auftrag'), th('Format'), th('Aufgabe'), th('Versand'), th('Fällig'), th('Stand'), th('Status')]));
const sek = GRUPPEN.map(([st, label]) => {
const list = gefiltert.value.filter((o) => (o.production_status || 'eingegangen') === st);
if (!list.length) return null;
const m = STATUS_META[st];
return h('div', { key: 'sec' + st, style: 'margin-bottom:22px;' }, [
h('div', { style: 'display:flex;align-items:center;gap:10px;margin-bottom:10px;' }, [
h('span', { style: `display:inline-flex;align-items:center;gap:6px;font-size:16px;font-weight:500;color:${m.fg};` }, [icon(m.icon), label]),
h('span', { style: 'font-size:13px;color:var(--foreground-subdued);' }, `${list.length} Auftrag/Aufträge`),
]),
h('div', { style: 'border:1px solid var(--border-normal);border-radius:12px;overflow:auto;background:var(--background-page,#fff);' },
[h('table', { style: 'width:100%;border-collapse:collapse;' }, [kopf(), h('tbody', {}, list.map(zeile))])]),
]);
}).filter(Boolean);
return sek.length ? h('div', {}, sek) : h('div', { style: 'padding:28px;text-align:center;color:var(--foreground-subdued);border:1px solid var(--border-normal);border-radius:12px;background:var(--background-page,#fff);' }, 'Keine offenen Aufträge.');
})(),
]);
return h(resolveComponent('private-view'), { title: 'Übersicht' }, { default: () => inhalt });
};