Versand-Seite: eine Zeile je FTP-Sendung (Buendel) statt je Auftrag
Die oeffentliche Versand-Seite listet jetzt die FTP-Sendungen (gebuendelte batch_submissions, gruppiert nach bundle_name = FTP-Dateiname <Zugang>_<Datum>_<HHMM>), nicht mehr einzelne Auftraege. "Versendet" markiert das ganze Buendel (alle Uebermittlungen darin) als versendet; danach verschwindet es aus der Liste. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -368,13 +368,13 @@ const handler = (router, { services, getSchema, logger }) => {
|
||||
// „versendet" markiert (Status-API liefert dann „shipped"). Schutz = geheimer
|
||||
// Pfad-Token (kein Konto). Ohne gesetzten Token ist die Seite aus.
|
||||
const escH = (s) => String(s == null ? '' : s).replace(/[&<>"]/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"' }[c]));
|
||||
const VS_LABEL = { eingegangen: 'Eingegangen', im_druck: 'Im Druck', kuvertieren: 'Kuvertierung', versandfertig: 'Versandfertig' };
|
||||
const versandSeite = ({ orders = [], message = '', error = '' } = {}) => {
|
||||
const rows = orders.length ? orders.map((o) => `
|
||||
// Eine Zeile je FTP-Sendung (Bündel), benannt wie die FTP-Datei (<Zugang>_<Datum>_<HHMM>).
|
||||
const versandSeite = ({ bundles = [], message = '', error = '' } = {}) => {
|
||||
const rows = bundles.length ? bundles.map((b) => `
|
||||
<div class="row">
|
||||
<div class="info"><b>${escH(o.order_number)}</b><span>${escH(VS_LABEL[o.production_status] || o.production_status || 'offen')} · ${Number(o.entries_count) || 1} Empf. · ${escH(String(o.date_created || '').slice(0, 10))}</span></div>
|
||||
<form method="post"><input type="hidden" name="order_number" value="${escH(o.order_number)}"><button type="submit">Versendet ✓</button></form>
|
||||
</div>`).join('') : '<p class="muted">Keine offenen Aufträge – alles versendet.</p>';
|
||||
<div class="info"><b>${escH(b.name)}</b><span>${Number(b.count) || 0} Empfänger · ${escH(String(b.date || '').slice(0, 10))}</span></div>
|
||||
<form method="post"><input type="hidden" name="bundle" value="${escH(b.name)}"><button type="submit">Versendet ✓</button></form>
|
||||
</div>`).join('') : '<p class="muted">Keine offenen Sendungen – alles versendet.</p>';
|
||||
return `<!doctype html><html lang="de"><head>
|
||||
<meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>Versand bestätigen</title>
|
||||
<style>
|
||||
@@ -385,54 +385,57 @@ const handler = (router, { services, getSchema, logger }) => {
|
||||
.ok{background:#e7f6ec;border:1px solid #34a853;color:#1e7e34;border-radius:10px;padding:12px 14px;margin:0 0 10px}
|
||||
.err{background:#fdeaea;border:1px solid #c62828;color:#b3261e;border-radius:10px;padding:12px 14px;margin:0 0 10px}
|
||||
.row{display:flex;justify-content:space-between;align-items:center;gap:12px;padding:12px 14px;border:1px solid #e2e8f0;border-radius:10px;margin-top:8px}
|
||||
.info{display:flex;flex-direction:column;min-width:0} .info b{font-size:16px} .info span{font-size:12px;color:#8a97a8}
|
||||
.info{display:flex;flex-direction:column;min-width:0} .info b{font-size:16px;word-break:break-all} .info span{font-size:12px;color:#8a97a8}
|
||||
form{margin:0} button{font-size:14px;font-weight:700;padding:10px 16px;border:none;border-radius:9px;background:#1E50A5;color:#fff;cursor:pointer;white-space:nowrap}
|
||||
</style></head><body><div class="card">
|
||||
<h1>Versand bestätigen</h1>
|
||||
<p>Alle offenen Aufträge. Bitte pro Auftrag „Versendet" klicken, sobald er verschickt ist.</p>
|
||||
<p>Jede Zeile ist eine FTP-Sendung. Bitte „Versendet" klicken, sobald die Sendung verschickt ist.</p>
|
||||
${error ? `<div class="err">${escH(error)}</div>` : ''}
|
||||
${message ? `<div class="ok">${escH(message)}</div>` : ''}
|
||||
${rows}
|
||||
</div></body></html>`;
|
||||
};
|
||||
|
||||
// Offene Aufträge (noch nicht versendet/storniert) – nur Nummer/Status/Menge/Datum.
|
||||
async function ladeOffeneAuftraege(svc) {
|
||||
return svc('orders').readByQuery({
|
||||
filter: { production_status: { _nin: ['versendet', 'storniert'] }, status: { _neq: 'entwurf' } },
|
||||
sort: ['-date_created'], limit: 500,
|
||||
fields: ['id', 'order_number', 'production_status', 'entries_count', 'date_created'],
|
||||
// Offene FTP-Sendungen: gebündelte Übermittlungen, noch nicht versendet, je bundle_name gruppiert.
|
||||
async function ladeBuendel(svc) {
|
||||
const rows = await svc('batch_submissions').readByQuery({
|
||||
filter: { status: { _eq: 'bundled' }, versendet: { _neq: true }, bundle_name: { _nnull: true } },
|
||||
limit: -1, sort: ['-bundle_name'], fields: ['id', 'bundle_name', 'date_created'],
|
||||
});
|
||||
const m = new Map();
|
||||
for (const r of rows || []) {
|
||||
if (!m.has(r.bundle_name)) m.set(r.bundle_name, { name: r.bundle_name, count: 0, date: r.date_created });
|
||||
m.get(r.bundle_name).count += 1;
|
||||
}
|
||||
return [...m.values()];
|
||||
}
|
||||
|
||||
router.get('/v1/versand/:token', async (req, res) => {
|
||||
if (!VERSAND_TOKEN || req.params.token !== VERSAND_TOKEN) return res.status(404).type('html').send('Not found.');
|
||||
try {
|
||||
const svc = svcFactory(await getSchema());
|
||||
return res.type('html').send(versandSeite({ orders: (await ladeOffeneAuftraege(svc)) || [] }));
|
||||
return res.type('html').send(versandSeite({ bundles: await ladeBuendel(svc) }));
|
||||
} catch (e) {
|
||||
logger.error(`[skrift-api] versand list: ${e.message}`);
|
||||
return res.type('html').send(versandSeite({ error: 'Aufträge konnten nicht geladen werden.' }));
|
||||
return res.type('html').send(versandSeite({ error: 'Sendungen konnten nicht geladen werden.' }));
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/v1/versand/:token', async (req, res) => {
|
||||
if (!VERSAND_TOKEN || req.params.token !== VERSAND_TOKEN) return res.status(404).type('html').send('Not found.');
|
||||
const nr = String((req.body && (req.body.order_number ?? req.body.nummer)) || req.query.order_number || '').trim();
|
||||
const bundle = String((req.body && (req.body.bundle ?? req.body.name)) || req.query.bundle || '').trim();
|
||||
try {
|
||||
const svc = svcFactory(await getSchema());
|
||||
let message = '', error = '';
|
||||
if (!nr) {
|
||||
error = 'Bitte einen Auftrag wählen.';
|
||||
if (!bundle) {
|
||||
error = 'Bitte eine Sendung wählen.';
|
||||
} else {
|
||||
const rows = await svc('orders').readByQuery({ filter: { order_number: { _eq: nr } }, limit: 1, fields: ['id', 'order_number', 'production_status'] });
|
||||
const order = rows?.[0];
|
||||
if (!order) error = `Auftrag ${nr} wurde nicht gefunden.`;
|
||||
else if (order.production_status === 'versendet') message = `Auftrag ${nr} war bereits als versendet markiert.`;
|
||||
else if (order.production_status === 'storniert') error = `Auftrag ${nr} ist storniert.`;
|
||||
else { await svc('orders').updateOne(order.id, { production_status: 'versendet', versandt_am: new Date().toISOString() }); message = `Auftrag ${nr} als versendet markiert.`; }
|
||||
const rows = await svc('batch_submissions').readByQuery({ filter: { bundle_name: { _eq: bundle }, status: { _eq: 'bundled' } }, limit: -1, fields: ['id'] });
|
||||
const ids = (rows || []).map((r) => r.id);
|
||||
if (!ids.length) error = `Sendung ${bundle} nicht gefunden oder bereits versendet.`;
|
||||
else { await svc('batch_submissions').updateMany(ids, { versendet: true }); message = `Sendung ${bundle} als versendet markiert (${ids.length}).`; }
|
||||
}
|
||||
return res.type('html').send(versandSeite({ orders: (await ladeOffeneAuftraege(svc)) || [], message, error }));
|
||||
return res.type('html').send(versandSeite({ bundles: await ladeBuendel(svc), message, error }));
|
||||
} catch (e) {
|
||||
logger.error(`[skrift-api] versand: ${e.stack || e.message}`);
|
||||
return res.type('html').send(versandSeite({ error: 'Konnte nicht gespeichert werden. Bitte erneut versuchen.' }));
|
||||
|
||||
Reference in New Issue
Block a user