B: Explizite lineare Druck-Warteschlange + Formatwechsel-Bestaetigung

Schema (bootstrap):
- jobs.position (float) = explizite lineare Reihenfolge; machines.pending_format = vom Agent angefordertes naechstes Format
- Service-Rechte: jobs.update +position, machines.update +pending_format

Einreihen (skrift-orders): enqueue/enqueue-bulk vergeben position (ans Ende je Maschine)

Board (produktion) Warteschlange: EINE lineare Liste je Maschine (nicht mehr nach
Format gebuendelt), Hoch/Runter/Anfang/Ende-Umsortieren, Formatwechsel-Marker,
laufende Jobs, Banner "Format einlegen -> bestaetigen" (setzt current_format, leert pending)

Uebersicht (Mitarbeiter): Maschinen-Panel mit Bestaetigen/Starten + Vorschau der
kommenden Format-Segmente; jobs nach position sortiert

ACHTUNG Deploy: Bootstrap VOR Directus-Neustart UND vor Agent-Update (neue Felder/Rechte).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
s4luorth
2026-10-01 13:41:12 +02:00
parent a1a2a615cc
commit 0f7f9c3bd9
4 changed files with 193 additions and 78 deletions

View File

@@ -1249,6 +1249,15 @@ const handler = (router, { services, getSchema, logger }) => {
const modus = (body.mode === 'direct' && !busy) ? 'direct' : 'queue';
const prio = modus === 'direct' ? 100 : 0;
// Nächste freie Position in der linearen Warteschlange dieser Maschine
// (neue Jobs hängen hinten an; im Board umsortierbar).
const letzte = await svc('jobs').readByQuery({
filter: { machine: { _eq: body.machine }, status: { _in: ['queued', 'printing'] } },
sort: ['-position'], limit: 1, fields: ['position'],
});
let pos = (letzte && letzte.length && Number.isFinite(Number(letzte[0].position))) ? Number(letzte[0].position) : 0;
const nextPos = () => (pos += 1);
// Reinigt eine Maß-Übersteuerung auf erlaubte Zahlenfelder (oder null).
const clampParams = (p) => {
if (!p || typeof p !== 'object') return null;
@@ -1274,7 +1283,7 @@ const handler = (router, { services, getSchema, logger }) => {
params: clampParams(sec.params), numbers: (typeof sec.numbers === 'string' && sec.numbers.trim()) ? sec.numbers.trim() : null,
// Rollenschonende Zeichenreihenfolge (A6 quer) – nur fürs Schriftstück sinnvoll.
spaltenweise: !!(sec.spaltenweise || body.spaltenweise),
status: 'queued', priority: prio,
status: 'queued', priority: prio, position: nextPos(),
}));
}
if (!jobs.length) return res.status(400).json({ error: 'Kein Abschnitt zum Drucken gewählt.' });
@@ -1284,14 +1293,14 @@ const handler = (router, { services, getSchema, logger }) => {
const briefTpl = await tplByKey(LETTER_TPL[order.format?.key] || 'a4_single');
jobs.push(await svc('jobs').createOne({
order: order.id, machine: body.machine, type: 'brief', template: briefTpl,
format: bf.key, format_label: bf.label, status: 'queued', priority: prio,
format: bf.key, format_label: bf.label, status: 'queued', priority: prio, position: nextPos(),
}));
if (order.needs_envelope && order.envelope_labeling && order.envelope_labeling !== 'keine') {
const ef = umschlagFormatOf(order);
const envTpl = await tplByKey(ENVELOPE_TPL[order.envelope_format] || 'kuvert_a_v1');
jobs.push(await svc('jobs').createOne({
order: order.id, machine: body.machine, type: 'umschlag', template: envTpl,
format: ef.key, format_label: ef.label, status: 'queued', priority: prio,
format: ef.key, format_label: ef.label, status: 'queued', priority: prio, position: nextPos(),
}));
}
}
@@ -1345,6 +1354,22 @@ const handler = (router, { services, getSchema, logger }) => {
return Object.keys(out).length ? out : null;
};
// Lineare Position je Maschine: Startwert = höchste vorhandene Position der
// Maschine, dann fortlaufend. So hängen die Sammel-Jobs hinten an und behalten
// die (nach Auftragsnummer sortierte) Reihenfolge – im Board umsortierbar.
const posByMachine = {};
const nextPos = async (machine) => {
const key = String(machine);
if (posByMachine[key] === undefined) {
const letzte = await svc('jobs').readByQuery({
filter: { machine: { _eq: machine }, status: { _in: ['queued', 'printing'] } },
sort: ['-position'], limit: 1, fields: ['position'],
});
posByMachine[key] = (letzte && letzte.length && Number.isFinite(Number(letzte[0].position))) ? Number(letzte[0].position) : 0;
}
return (posByMachine[key] += 1);
};
const jobs = [];
const uebersprungen = [];
const mkJob = async (order, typ, fmt) => {
@@ -1356,7 +1381,7 @@ const handler = (router, { services, getSchema, logger }) => {
jobs.push(await svc('jobs').createOne({
order: order.id, machine: cfg.machine, type: typ, template: cfg.template,
format: fmt.key, format_label: cfg.label || fmt.label, params: clampParams(cfg.params),
status: 'queued', priority: prioFor(cfg.machine),
status: 'queued', priority: prioFor(cfg.machine), position: await nextPos(cfg.machine),
}));
};
for (const order of orders) {