@flowselections/layout-maker 1.29.0 → 1.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-lib/integrations/supabase/auth-middleware.d.ts +93 -0
- package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.server.d.ts +93 -0
- package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.d.ts +93 -0
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/lib/aiLayout.functions.d.ts +93 -0
- package/dist-lib/lib/aiLayout.functions.d.ts.map +1 -1
- package/dist-lib/lib/htmlPdf.server.d.ts.map +1 -1
- package/dist-lib/lib/htmlPdf.server.js +68 -24
- package/dist-lib/lib/pakbonHtml.d.ts.map +1 -1
- package/dist-lib/lib/pakbonHtml.js +11 -3
- package/dist-lib/lib/previewPdf.d.ts +10 -0
- package/dist-lib/lib/previewPdf.d.ts.map +1 -0
- package/dist-lib/lib/previewPdf.js +170 -0
- package/dist-lib/lib/printQueue.server.d.ts +14 -1
- package/dist-lib/lib/printQueue.server.d.ts.map +1 -1
- package/dist-lib/lib/printQueue.server.js +26 -1
- package/dist-lib/lib/printService.functions.d.ts +372 -0
- package/dist-lib/lib/printService.functions.d.ts.map +1 -1
- package/dist-lib/lib/useDocumentData.d.ts.map +1 -1
- package/dist-lib/lib/useDocumentData.js +16 -2
- package/package.json +14 -2
|
@@ -258,7 +258,9 @@ function stijlVan(n, ouder) {
|
|
|
258
258
|
const cl = klassen(n);
|
|
259
259
|
if (n.tag === "th" || cl.includes("vet") || n.tag === "b" || n.tag === "strong")
|
|
260
260
|
s.vet = true;
|
|
261
|
-
|
|
261
|
+
// De stylesheet is leidend voor .sectitel; alleen zonder opmaakregels valt de
|
|
262
|
+
// motor terug op de bekende grijze kop.
|
|
263
|
+
if (cl.includes("sectitel") && !Object.keys(eig(n)).length) {
|
|
262
264
|
s.grootte = ouder.grootte * 0.9;
|
|
263
265
|
s.vet = true;
|
|
264
266
|
s.kleur = GRIJS;
|
|
@@ -335,6 +337,8 @@ class Tekenaar {
|
|
|
335
337
|
this.paginas = [];
|
|
336
338
|
this.voetHoogte = 0;
|
|
337
339
|
this.voetTekenaar = null;
|
|
340
|
+
/** Zolang dit aanstaat komt er geen nieuwe pagina bij (voet tekenen). */
|
|
341
|
+
this.vast = false;
|
|
338
342
|
this.pagina = pdf.addPage([breedte, hoogte]);
|
|
339
343
|
this.paginas.push(this.pagina);
|
|
340
344
|
this.y = hoogte - marge;
|
|
@@ -356,6 +360,8 @@ class Tekenaar {
|
|
|
356
360
|
this.voetTekenaar = teken;
|
|
357
361
|
}
|
|
358
362
|
nieuwePagina() {
|
|
363
|
+
if (this.vast)
|
|
364
|
+
return;
|
|
359
365
|
this.pagina = this.pdf.addPage([this.breedte, this.hoogte]);
|
|
360
366
|
this.paginas.push(this.pagina);
|
|
361
367
|
this.y = this.hoogte - this.marge;
|
|
@@ -392,6 +398,26 @@ class Tekenaar {
|
|
|
392
398
|
this.voetHoogte = bewaard.voet;
|
|
393
399
|
return hoogte;
|
|
394
400
|
}
|
|
401
|
+
/** Tekent iets op een bestaande pagina, bijvoorbeeld de vaste voet. */
|
|
402
|
+
async opPagina(p, y, teken) {
|
|
403
|
+
const bewaard = { pagina: this.pagina, y: this.y, paginas: this.paginas, voet: this.voetHoogte };
|
|
404
|
+
this.pagina = p;
|
|
405
|
+
this.y = y;
|
|
406
|
+
this.paginas = [p];
|
|
407
|
+
this.voetHoogte = 0;
|
|
408
|
+
this.vast = true;
|
|
409
|
+
try {
|
|
410
|
+
await teken();
|
|
411
|
+
}
|
|
412
|
+
catch {
|
|
413
|
+
// een voet mag het document nooit blokkeren
|
|
414
|
+
}
|
|
415
|
+
this.pagina = bewaard.pagina;
|
|
416
|
+
this.y = bewaard.y;
|
|
417
|
+
this.paginas = bewaard.paginas;
|
|
418
|
+
this.voetHoogte = bewaard.voet;
|
|
419
|
+
this.vast = false;
|
|
420
|
+
}
|
|
395
421
|
rondAf() {
|
|
396
422
|
if (!this.voetTekenaar)
|
|
397
423
|
return;
|
|
@@ -735,6 +761,13 @@ async function tekenTabel(ctx, tabel, vak, s) {
|
|
|
735
761
|
const padX = compact ? 0.8 * MM : 1 * MM;
|
|
736
762
|
const randen = heeft(tabel, "rand");
|
|
737
763
|
const kopRij = rijen.find((r) => r.kinderen.some((c) => c.tag === "th")) ?? null;
|
|
764
|
+
/** Celrand onderaan: uit de stylesheet, met de klasse `onder` als terugval. */
|
|
765
|
+
const celRandOnder = (c) => randVan(eig(c)["border-bottom"]) ??
|
|
766
|
+
randVan(eig(c)["border"]) ??
|
|
767
|
+
(klassen(c).includes("onder") || klassen(c).includes("rand") ? { dikte: 0.5, kleur: LIJN } : null);
|
|
768
|
+
// Zodra de cellen zelf randen dragen, is de automatische lijn onder de koprij
|
|
769
|
+
// dubbelop en landt hij op de verkeerde regel.
|
|
770
|
+
const eigenCelRanden = rijen.some((r) => r.kinderen.some((c) => (c.tag === "td" || c.tag === "th") && !!celRandOnder(c)));
|
|
738
771
|
const tekenRij = (rij, herhaald = false) => {
|
|
739
772
|
const cellen = rij.kinderen.filter((c) => c.tag === "td" || c.tag === "th");
|
|
740
773
|
const cellenStijl = cellen.map((c) => stijlVan(c, s));
|
|
@@ -775,14 +808,28 @@ async function tekenTabel(ctx, tabel, vak, s) {
|
|
|
775
808
|
borderColor: LIJN,
|
|
776
809
|
});
|
|
777
810
|
}
|
|
811
|
+
else {
|
|
812
|
+
const cr = celRandOnder(c);
|
|
813
|
+
if (cr) {
|
|
814
|
+
tk.pagina.drawLine({
|
|
815
|
+
start: { x, y: bovenY - hoogte },
|
|
816
|
+
end: { x: x + b, y: bovenY - hoogte },
|
|
817
|
+
thickness: cr.dikte,
|
|
818
|
+
color: cr.kleur,
|
|
819
|
+
});
|
|
820
|
+
}
|
|
821
|
+
}
|
|
778
822
|
x += b;
|
|
779
823
|
});
|
|
780
|
-
|
|
824
|
+
const rijRand = randVan(eig(rij)["border-bottom"]) ?? (heeft(rij, "onder") ? { dikte: 0.5, kleur: LIJN } : null);
|
|
825
|
+
const automatisch = !eigenCelRanden && rij === kopRij ? { dikte: 0.5, kleur: LIJN } : null;
|
|
826
|
+
const lijn = !randen ? (rijRand ?? automatisch) : null;
|
|
827
|
+
if (lijn) {
|
|
781
828
|
tk.pagina.drawLine({
|
|
782
829
|
start: { x: vak.x, y: bovenY - hoogte },
|
|
783
830
|
end: { x: vak.x + vak.breedte, y: bovenY - hoogte },
|
|
784
|
-
thickness:
|
|
785
|
-
color:
|
|
831
|
+
thickness: lijn.dikte,
|
|
832
|
+
color: lijn.kleur,
|
|
786
833
|
});
|
|
787
834
|
}
|
|
788
835
|
tk.y = bovenY - hoogte;
|
|
@@ -1130,32 +1177,29 @@ export async function htmlNaarPdfBytes(opties) {
|
|
|
1130
1177
|
const tk = new Tekenaar(pdf, breedte, hoogte, marge, normaal, vet);
|
|
1131
1178
|
const ctx = { tk, afbeeldingen, beeldFouten };
|
|
1132
1179
|
const stijl = { grootte: basis, vet: false, kleur: DONKER, uitlijning: "left" };
|
|
1133
|
-
// vaste paginavoet (bijv. totalen op elke pagina)
|
|
1180
|
+
// vaste paginavoet (bijv. totalen op elke pagina) — als volwaardig blok
|
|
1181
|
+
const voetVak = { x: marge, breedte: breedte - marge * 2 };
|
|
1134
1182
|
const voetKnoop = zoekVoet(wortel);
|
|
1135
|
-
|
|
1183
|
+
const voetOuder = voetKnoop
|
|
1184
|
+
? { tag: "div", attrs: {}, kinderen: [voetKnoop] }
|
|
1185
|
+
: null;
|
|
1186
|
+
let voetHoogte = 0;
|
|
1187
|
+
if (voetOuder) {
|
|
1136
1188
|
verwijder(wortel, voetKnoop);
|
|
1137
|
-
tk.
|
|
1189
|
+
voetHoogte = await tk.meet(() => tekenKinderen(ctx, voetOuder, voetVak, stijl));
|
|
1190
|
+
tk.zetVoet(voetHoogte + 2 * MM, () => { });
|
|
1138
1191
|
}
|
|
1139
1192
|
await tekenKinderen(ctx, wortel, { x: marge, breedte: breedte - marge * 2 }, stijl);
|
|
1140
|
-
if (
|
|
1141
|
-
const
|
|
1142
|
-
|
|
1143
|
-
p.drawLine({
|
|
1144
|
-
start: { x: marge, y: marge + regelHoogte(stijl) + 1.5 * MM },
|
|
1145
|
-
end: { x: breedte - marge, y: marge + regelHoogte(stijl) + 1.5 * MM },
|
|
1146
|
-
thickness: 0.6,
|
|
1147
|
-
color: LIJN,
|
|
1148
|
-
});
|
|
1149
|
-
p.drawText(veiligeTekst(tekst).slice(0, 300), {
|
|
1150
|
-
x: marge,
|
|
1151
|
-
y: marge + 2,
|
|
1152
|
-
size: stijl.grootte,
|
|
1153
|
-
font: normaal,
|
|
1154
|
-
color: DONKER,
|
|
1155
|
-
});
|
|
1193
|
+
if (voetOuder) {
|
|
1194
|
+
for (const p of [...tk.paginas]) {
|
|
1195
|
+
await tk.opPagina(p, marge + voetHoogte + 1 * MM, () => tekenKinderen(ctx, voetOuder, voetVak, stijl));
|
|
1156
1196
|
}
|
|
1157
1197
|
}
|
|
1158
|
-
|
|
1198
|
+
// De layout bepaalt of paginanummers meegaan; zonder voorkeur alleen bij
|
|
1199
|
+
// meerdere pagina's.
|
|
1200
|
+
const uitMeta = /name=["']pdf-paginanummers["']\s+content=["']ja["']/i.test(opties.html);
|
|
1201
|
+
const nummers = opties.paginanummers ?? (uitMeta ? true : tk.paginas.length > 1);
|
|
1202
|
+
if (nummers) {
|
|
1159
1203
|
tk.paginas.forEach((p, i) => {
|
|
1160
1204
|
const t = `Pagina ${i + 1} van ${tk.paginas.length}`;
|
|
1161
1205
|
p.drawText(t, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pakbonHtml.d.ts","sourceRoot":"","sources":["../../src/lib/pakbonHtml.ts"],"names":[],"mappings":"AAUA,OAAO,EAmBL,KAAK,SAAS,EAEd,KAAK,YAAY,EAGlB,MAAM,iBAAiB,CAAC;AAGzB,8EAA8E;AAC9E,MAAM,MAAM,WAAW,GAAG,SAAS,CAAC;AAEpC,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,kDAAkD;IAClD,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED,MAAM,WAAW,kBAAkB;IACjC,iDAAiD;IACjD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,0BAA0B;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,oEAAoE;IACpE,KAAK,CAAC,EAAE,UAAU,EAAE,CAAC;IACrB,wEAAwE;IACxE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAWD,wBAAgB,GAAG,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,CAKtC;AAQD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,WAAW,CAgE3D;
|
|
1
|
+
{"version":3,"file":"pakbonHtml.d.ts","sourceRoot":"","sources":["../../src/lib/pakbonHtml.ts"],"names":[],"mappings":"AAUA,OAAO,EAmBL,KAAK,SAAS,EAEd,KAAK,YAAY,EAGlB,MAAM,iBAAiB,CAAC;AAGzB,8EAA8E;AAC9E,MAAM,MAAM,WAAW,GAAG,SAAS,CAAC;AAEpC,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,kDAAkD;IAClD,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED,MAAM,WAAW,kBAAkB;IACjC,iDAAiD;IACjD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,0BAA0B;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,oEAAoE;IACpE,KAAK,CAAC,EAAE,UAAU,EAAE,CAAC;IACrB,wEAAwE;IACxE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAWD,wBAAgB,GAAG,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,CAKtC;AAQD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,WAAW,CAgE3D;AA8oBD,oEAAoE;AACpE,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,YAAY,GAAG,OAAO,EAC9B,KAAK,EAAE,WAAW,EAClB,MAAM,GAAE,kBAAuB,GAC9B,MAAM,CA8BR;AAgBD,gEAAgE;AAChE,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,YAAY,GAAG,OAAO,EAC9B,KAAK,EAAE,WAAW,EAClB,MAAM,GAAE,kBAAuB,GAC9B,MAAM,CAmER;AAED,iEAAiE;AACjE,wBAAgB,oBAAoB,IAAI,WAAW,CAElD;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CACzB,MAAM,EAAE,YAAY,GAAG,OAAO,EAC9B,KAAK,EAAE,WAAW,EAClB,MAAM,GAAE,kBAAuB,GAC9B,IAAI,CAsCN;AAED;;;;;GAKG;AACH,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,YAAY,GAAG,OAAO,EAC9B,KAAK,EAAE,WAAW,EAClB,MAAM,GAAE,kBAAuB,GAC9B,OAAO,CAAC,IAAI,CAAC,CAUf"}
|
|
@@ -421,13 +421,13 @@ function fustVerzamellijstHtml(order, s) {
|
|
|
421
421
|
const totaal = groepen.reduce((n, f) => n + f.aantal, 0);
|
|
422
422
|
const blokken = groepen
|
|
423
423
|
.map((f) => {
|
|
424
|
-
const kop = `<div class="
|
|
424
|
+
const kop = `<div class="fustgroepkop"><span>${esc([f.fusttype, toonHoogte ? f.hoogte : "", toonHoogte ? f.belading : ""]
|
|
425
425
|
.filter(Boolean)
|
|
426
426
|
.join(" · "))}</span><span>${f.aantal}× fust</span></div>`;
|
|
427
427
|
if (!kolommen.length)
|
|
428
428
|
return kop;
|
|
429
429
|
const koppen = kolommen
|
|
430
|
-
.map((k) => `<th
|
|
430
|
+
.map((k) => `<th${FUST_RECHTS.has(k) ? ' style="text-align:right"' : ""}>${esc(FUST_KOLOM_LABELS[k])}</th>`)
|
|
431
431
|
.join("");
|
|
432
432
|
const rijen = f.producten
|
|
433
433
|
.map((pr) => `<tr>${kolommen
|
|
@@ -643,8 +643,14 @@ export function renderPakbonDocument(config, order, opties = {}) {
|
|
|
643
643
|
const font = opties.lettertype ?? 'Arial, Helvetica, "Segoe UI", sans-serif';
|
|
644
644
|
const grootte = opties.lettergrootte ?? 8.5;
|
|
645
645
|
const body = renderPakbonBody(config, order, opties);
|
|
646
|
+
// De layout bepaalt of er paginanummers op de afdruk komen; de PDF-motor
|
|
647
|
+
// leest dit uit de meta, zodat de wachtrij niets extra hoeft door te geven.
|
|
648
|
+
const totalen = arr(leesPakbonConfig(config).secties).find((s) => s.type === "totalen");
|
|
649
|
+
const paginanummers = !!totalen && opt(totalen, "toon_paginanummer", false) === true;
|
|
646
650
|
return `<!doctype html><html lang="nl"><head><meta charset="utf-8" />
|
|
651
|
+
<meta name="pdf-paginanummers" content="${paginanummers ? "ja" : "nee"}" />
|
|
647
652
|
<title>${esc(opties.titel ?? "Pakbon")}</title>
|
|
653
|
+
|
|
648
654
|
<style>
|
|
649
655
|
@page { size: ${b}mm ${h}mm; margin: ${marge}mm; }
|
|
650
656
|
* { box-sizing: border-box; }
|
|
@@ -652,6 +658,8 @@ html, body { margin: 0; padding: 0; }
|
|
|
652
658
|
body { font-family: ${font}; font-size: ${grootte}pt; color: #111827; line-height: 1.35; }
|
|
653
659
|
.blok { margin-bottom: 2mm; }
|
|
654
660
|
.sectitel { font-size: 90%; font-weight: 600; text-transform: uppercase; color: #6b7280; margin-bottom: 0.6mm; }
|
|
661
|
+
.fustgroepkop { display: flex; justify-content: space-between; font-weight: 600; color: #111827; border-bottom: 0.45mm solid #111827; padding-bottom: 0.5mm; margin-bottom: 0.6mm; }
|
|
662
|
+
|
|
655
663
|
.vet { font-weight: 600; }
|
|
656
664
|
.kv { display: flex; justify-content: space-between; gap: 3mm; }
|
|
657
665
|
.kv .k { color: #6b7280; }
|
|
@@ -680,7 +688,7 @@ tr.zebra td { background: #f3f4f6; }
|
|
|
680
688
|
.laagkop.compact { border-bottom: none; }
|
|
681
689
|
.meta { display: flex; gap: 3mm; font-size: 88%; font-weight: 400; color: #4b5563; }
|
|
682
690
|
.totalen { display: flex; flex-wrap: wrap; gap: 5mm; border-top: 0.25mm solid #d1d5db; padding-top: 1.2mm; font-weight: 500; }
|
|
683
|
-
.paginavoet { position: fixed; bottom: 0; left: 0; right: 0; background: #fff; }
|
|
691
|
+
.paginavoet { position: fixed; bottom: 0; left: 0; right: 0; background: #fff; border-top: 0.35mm solid #9ca3af; padding-top: 1mm; }
|
|
684
692
|
.handtekening { margin-top: 3mm; }
|
|
685
693
|
.lijnveld { margin-top: 4mm; border-bottom: 0.25mm solid #6b7280; padding-bottom: 3mm; }
|
|
686
694
|
.lijnveld.hoog { padding-bottom: 10mm; }
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { SupabaseClient } from "@supabase/supabase-js";
|
|
2
|
+
type Db = SupabaseClient<any, any, any>;
|
|
3
|
+
/**
|
|
4
|
+
* POST { station_slug, queue_item_id } → PDF-bytes.
|
|
5
|
+
* Fouten komen terug als JSON met een code, en worden vastgelegd in
|
|
6
|
+
* `print_preview_events`.
|
|
7
|
+
*/
|
|
8
|
+
export declare function behandelPreviewPdf(request: Request, supabase: Db): Promise<Response>;
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=previewPdf.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"previewPdf.d.ts","sourceRoot":"","sources":["../../src/lib/previewPdf.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAE5D,KAAK,EAAE,GAAG,cAAc,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;AAkExC;;;;GAIG;AACH,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,GAAG,OAAO,CAAC,QAAQ,CAAC,CAoK1F"}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// src/lib/previewPdf.ts — afhandeling van /api/public/print/preview-pdf.
|
|
3
|
+
// Toont hoe een pickregel eruit komt als PDF, zonder printopdracht en zonder
|
|
4
|
+
// iets in de database te wijzigen. De shell levert de databaseverbinding.
|
|
5
|
+
//
|
|
6
|
+
// Eén bron van waarheid: de gegevens komen uit de read-only databasefunctie
|
|
7
|
+
// `pick_preview_job` van de Logistiek Engine — dezelfde opbouw als
|
|
8
|
+
// `pick_enqueue_print_job`. Deze module spiegelt die mapping bewust niet.
|
|
9
|
+
// ============================================================================
|
|
10
|
+
/** Maximaal aantal voorbeelden per station binnen het venster. */
|
|
11
|
+
const MAX_PER_VENSTER = 6;
|
|
12
|
+
const VENSTER_SEC = 60;
|
|
13
|
+
function gelijk(a, b) {
|
|
14
|
+
if (a.length !== b.length)
|
|
15
|
+
return false;
|
|
16
|
+
let verschil = 0;
|
|
17
|
+
for (let i = 0; i < a.length; i++)
|
|
18
|
+
verschil |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
19
|
+
return verschil === 0;
|
|
20
|
+
}
|
|
21
|
+
function leesSleutel(request) {
|
|
22
|
+
return (request.headers.get("apikey") ??
|
|
23
|
+
(request.headers.get("authorization") ?? "").replace(/^Bearer\s+/i, "")).trim();
|
|
24
|
+
}
|
|
25
|
+
/** Mag deze aanroeper voorbeelden opvragen? Zelfde regels als de queue-tick. */
|
|
26
|
+
async function magAanroepen(request, db) {
|
|
27
|
+
const meegegeven = leesSleutel(request);
|
|
28
|
+
if (!meegegeven)
|
|
29
|
+
return false;
|
|
30
|
+
const { isRunnerSleutel } = await import("./runnerRegistry.server.js");
|
|
31
|
+
if (await isRunnerSleutel(db, meegegeven))
|
|
32
|
+
return true;
|
|
33
|
+
const projectSleutel = process.env["SUPABASE_PUBLISHABLE_KEY"] ??
|
|
34
|
+
process.env["VITE_SUPABASE_PUBLISHABLE_KEY"] ??
|
|
35
|
+
"";
|
|
36
|
+
if (projectSleutel && gelijk(projectSleutel, meegegeven))
|
|
37
|
+
return true;
|
|
38
|
+
try {
|
|
39
|
+
const { data } = await db.auth.getUser(meegegeven);
|
|
40
|
+
return !!data?.user?.id;
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
47
|
+
/** Legt een weigering vindbaar vast; faalt nooit hard. */
|
|
48
|
+
async function weiger(db, station, queueItem, code, reden, status) {
|
|
49
|
+
try {
|
|
50
|
+
await db.rpc("print_preview_weiger", {
|
|
51
|
+
_station_slug: station,
|
|
52
|
+
_queue_item_id: queueItem && UUID.test(queueItem) ? queueItem : null,
|
|
53
|
+
_code: code,
|
|
54
|
+
_reden: reden,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
/* logboek mag een voorbeeld nooit blokkeren */
|
|
59
|
+
}
|
|
60
|
+
return Response.json({ ok: false, code, fout: reden }, { status });
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* POST { station_slug, queue_item_id } → PDF-bytes.
|
|
64
|
+
* Fouten komen terug als JSON met een code, en worden vastgelegd in
|
|
65
|
+
* `print_preview_events`.
|
|
66
|
+
*/
|
|
67
|
+
export async function behandelPreviewPdf(request, supabase) {
|
|
68
|
+
if (!supabase) {
|
|
69
|
+
return Response.json({ ok: false, code: "geen_database", fout: "De server kreeg geen databaseverbinding." }, { status: 500 });
|
|
70
|
+
}
|
|
71
|
+
if (!(await magAanroepen(request, supabase))) {
|
|
72
|
+
return Response.json({ ok: false, code: "geen_toegang", fout: "Het voorbeeld weigerde het verzoek: geen geldig toegangsbewijs." }, { status: 401 });
|
|
73
|
+
}
|
|
74
|
+
let body = {};
|
|
75
|
+
try {
|
|
76
|
+
body = (await request.json());
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
body = {};
|
|
80
|
+
}
|
|
81
|
+
const station = typeof body["station_slug"] === "string" ? body["station_slug"].trim() : "";
|
|
82
|
+
const queueItem = typeof body["queue_item_id"] === "string" ? body["queue_item_id"].trim() : "";
|
|
83
|
+
if (!station || !queueItem || !UUID.test(queueItem)) {
|
|
84
|
+
return weiger(supabase, station || null, null, "ongeldig_verzoek", "Geef een station_slug en een geldig queue_item_id mee.", 400);
|
|
85
|
+
}
|
|
86
|
+
// 1) Snelheidsbegrenzing (telt en logt in één databasestap).
|
|
87
|
+
const { data: tempo, error: tempoFout } = await supabase.rpc("print_preview_toestaan", {
|
|
88
|
+
_station_slug: station,
|
|
89
|
+
_queue_item_id: queueItem,
|
|
90
|
+
_max: MAX_PER_VENSTER,
|
|
91
|
+
_venster_sec: VENSTER_SEC,
|
|
92
|
+
});
|
|
93
|
+
if (tempoFout) {
|
|
94
|
+
return weiger(supabase, station, queueItem, "render_mislukt", `Voorbeeld niet mogelijk: ${tempoFout.message}`, 500);
|
|
95
|
+
}
|
|
96
|
+
const toestemming = (tempo ?? {});
|
|
97
|
+
if (toestemming["toegestaan"] === false) {
|
|
98
|
+
const wacht = Number(toestemming["wacht_sec"] ?? VENSTER_SEC);
|
|
99
|
+
return Response.json({
|
|
100
|
+
ok: false,
|
|
101
|
+
code: "te_snel",
|
|
102
|
+
fout: `Te veel voorbeelden achter elkaar. Probeer het over ${wacht} seconden opnieuw.`,
|
|
103
|
+
wacht_sec: wacht,
|
|
104
|
+
}, { status: 429, headers: { "Retry-After": String(wacht) } });
|
|
105
|
+
}
|
|
106
|
+
// 2) Gegevens ophalen: read-only, met stationvalidatie in de database zelf.
|
|
107
|
+
const { data: rij, error: rpcFout } = await supabase.rpc("pick_preview_job", {
|
|
108
|
+
_station_slug: station,
|
|
109
|
+
_queue_item_id: queueItem,
|
|
110
|
+
});
|
|
111
|
+
if (rpcFout) {
|
|
112
|
+
const melding = String(rpcFout.message ?? "");
|
|
113
|
+
// Ontbreekt de functie zelf (Engine nog niet bijgewerkt)? Dat is geen
|
|
114
|
+
// stationprobleem maar een installatieprobleem.
|
|
115
|
+
if (/could not find the function|does not exist/i.test(melding)) {
|
|
116
|
+
return weiger(supabase, station, queueItem, "rpc_ontbreekt", "De databasefunctie pick_preview_job ontbreekt. Werk de Logistiek Engine bij.", 503);
|
|
117
|
+
}
|
|
118
|
+
if (/is not unique/i.test(melding)) {
|
|
119
|
+
return weiger(supabase, station, queueItem, "rpc_dubbel", "Er bestaan meerdere varianten van pick_preview_job in de database. Verwijder de oude variant.", 503);
|
|
120
|
+
}
|
|
121
|
+
const code = /station/i.test(melding)
|
|
122
|
+
? "station_mismatch"
|
|
123
|
+
: /bestaat niet|niet gevonden|not found|onbekend/i.test(melding)
|
|
124
|
+
? "regel_onbekend"
|
|
125
|
+
: "render_mislukt";
|
|
126
|
+
return weiger(supabase, station, queueItem, code, melding || "Voorbeeld niet mogelijk.", code === "render_mislukt" ? 500 : 404);
|
|
127
|
+
}
|
|
128
|
+
const job = (Array.isArray(rij) ? rij[0] : rij);
|
|
129
|
+
if (!job) {
|
|
130
|
+
return weiger(supabase, station, queueItem, "regel_onbekend", "Deze pickregel bestaat niet of hoort niet bij dit station.", 404);
|
|
131
|
+
}
|
|
132
|
+
// 3) Exact dezelfde documentkeuze en opmaak als de printwachtrij.
|
|
133
|
+
const { bepaalDocumentKeuze, bouwHtml, heeftZichtbareInhoud, GeenDocumentFout } = await import("./printQueue.server.js");
|
|
134
|
+
const { htmlNaarPdfServer } = await import("./htmlNaarPdf.server.js");
|
|
135
|
+
try {
|
|
136
|
+
const { keuze, klaarPdf } = await bepaalDocumentKeuze(supabase, job);
|
|
137
|
+
const layout = keuze.layout;
|
|
138
|
+
const uitkomst = { bron: "extern_pdf" };
|
|
139
|
+
let pdfBase64 = klaarPdf;
|
|
140
|
+
if (!pdfBase64) {
|
|
141
|
+
const html = await bouwHtml(supabase, job, keuze, uitkomst);
|
|
142
|
+
if (!heeftZichtbareInhoud(html)) {
|
|
143
|
+
return weiger(supabase, station, queueItem, "layout_leeg", "Het document blijft leeg: de layout heeft geen blokken of de order heeft geen gegevens.", 422);
|
|
144
|
+
}
|
|
145
|
+
pdfBase64 = await htmlNaarPdfServer({
|
|
146
|
+
html,
|
|
147
|
+
breedteMm: Number(layout?.breedte_mm ?? 210),
|
|
148
|
+
hoogteMm: Number(layout?.hoogte_mm ?? 297),
|
|
149
|
+
margeMm: Number(layout?.marge_boven ?? 10),
|
|
150
|
+
landschap: String(layout?.orientatie ?? "portrait") === "landscape",
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
const bytes = Uint8Array.from(atob(pdfBase64), (c) => c.charCodeAt(0));
|
|
154
|
+
return new Response(bytes, {
|
|
155
|
+
status: 200,
|
|
156
|
+
headers: {
|
|
157
|
+
"Content-Type": "application/pdf",
|
|
158
|
+
"Content-Disposition": 'inline; filename="voorbeeld.pdf"',
|
|
159
|
+
"Cache-Control": "no-store",
|
|
160
|
+
"X-Preview-Bron": String(uitkomst.bron),
|
|
161
|
+
"X-Preview-Layout-Id": String(layout?.id ?? ""),
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
catch (e) {
|
|
166
|
+
const melding = e instanceof Error ? e.message : "Voorbeeld maken mislukte.";
|
|
167
|
+
const blijvend = e?.name === "GeenDocumentFout" || e instanceof GeenDocumentFout;
|
|
168
|
+
return weiger(supabase, station, queueItem, blijvend ? "layout_ontbreekt" : "render_mislukt", melding, blijvend ? 422 : 500);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
@@ -8,13 +8,17 @@ export type PrintDb = SupabaseClient<any, any, any>;
|
|
|
8
8
|
* meer nu de shell de client levert.
|
|
9
9
|
*/
|
|
10
10
|
export declare function controleerClient(db: PrintDb): Promise<string | null>;
|
|
11
|
+
/** Fout waarbij opnieuw proberen zinloos is (er valt niets te printen). */
|
|
12
|
+
export declare class GeenDocumentFout extends Error {
|
|
13
|
+
name: string;
|
|
14
|
+
}
|
|
11
15
|
/**
|
|
12
16
|
* `ontwerpWint` = deze layout is een echt ontwerp van deze module (actief, geen
|
|
13
17
|
* Floriday-systeemlayout) en gaat daarom vóór meegestuurde HTML. Er is bewust
|
|
14
18
|
* maar één schakelaar voor dit besluit: bron/systeem. Een tweede schakelaar
|
|
15
19
|
* ("stond er een layout_id op de opdracht?") is verwijderd.
|
|
16
20
|
*/
|
|
17
|
-
type LayoutKeuze = {
|
|
21
|
+
export type LayoutKeuze = {
|
|
18
22
|
layout: Job | null;
|
|
19
23
|
ontwerpWint: boolean;
|
|
20
24
|
};
|
|
@@ -50,5 +54,14 @@ export interface WachtrijResultaat {
|
|
|
50
54
|
}
|
|
51
55
|
/** Eén ronde: vastgelopen opdrachten opruimen, afdrukken en statussen bijwerken. */
|
|
52
56
|
export declare function verwerkWachtrij(db: PrintDb, max?: number): Promise<WachtrijResultaat>;
|
|
57
|
+
/**
|
|
58
|
+
* Dezelfde documentkeuze als de printwachtrij maakt, maar zonder printer en
|
|
59
|
+
* zonder statuswijziging. Gebruikt door het voorbeeld (`preview-pdf`) zodat
|
|
60
|
+
* het voorbeeld exact toont wat er straks uit de printer komt.
|
|
61
|
+
*/
|
|
62
|
+
export declare function bepaalDocumentKeuze(db: PrintDb, job: Job): Promise<{
|
|
63
|
+
keuze: LayoutKeuze;
|
|
64
|
+
klaarPdf: string | null;
|
|
65
|
+
}>;
|
|
53
66
|
export {};
|
|
54
67
|
//# sourceMappingURL=printQueue.server.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"printQueue.server.d.ts","sourceRoot":"","sources":["../../src/lib/printQueue.server.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAsB5D,KAAK,GAAG,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AAE/B,mFAAmF;AACnF,MAAM,MAAM,OAAO,GAAG,cAAc,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;AAEpD;;;;GAIG;AACH,wBAAsB,gBAAgB,CAAC,EAAE,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAW1E;
|
|
1
|
+
{"version":3,"file":"printQueue.server.d.ts","sourceRoot":"","sources":["../../src/lib/printQueue.server.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAsB5D,KAAK,GAAG,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AAE/B,mFAAmF;AACnF,MAAM,MAAM,OAAO,GAAG,cAAc,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;AAEpD;;;;GAIG;AACH,wBAAsB,gBAAgB,CAAC,EAAE,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAW1E;AAUD,2EAA2E;AAC3E,qBAAa,gBAAiB,SAAQ,KAAK;IAChC,IAAI,SAAsB;CACpC;AAED;;;;;GAKG;AACH,MAAM,MAAM,WAAW,GAAG;IAAE,MAAM,EAAE,GAAG,GAAG,IAAI,CAAC;IAAC,WAAW,EAAE,OAAO,CAAA;CAAE,CAAC;AAEvE,yEAAyE;AACzE,MAAM,MAAM,aAAa,GAAG,YAAY,GAAG,cAAc,GAAG,aAAa,GAAG,aAAa,CAAC;AAE1F,6EAA6E;AAC7E,MAAM,MAAM,YAAY,GAAG;IAAE,IAAI,EAAE,aAAa,CAAA;CAAE,CAAC;AAcnD,4EAA4E;AAC5E,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CAI7D;AAkHD;;;GAGG;AACH,wBAAsB,cAAc,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CA+BnF;AA+PD;;;;GAIG;AACH,wBAAsB,QAAQ,CAC5B,EAAE,EAAE,OAAO,EACX,GAAG,EAAE,GAAG,EACR,KAAK,EAAE,WAAW,EAClB,QAAQ,CAAC,EAAE,YAAY,GACtB,OAAO,CAAC,MAAM,CAAC,CA8IjB;AAGD;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAY1D;AAmRD,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,oFAAoF;AACpF,wBAAsB,eAAe,CAAC,EAAE,EAAE,OAAO,EAAE,GAAG,SAAI,GAAG,OAAO,CAAC,iBAAiB,CAAC,CA4BtF;AAED;;;;GAIG;AACH,wBAAsB,mBAAmB,CACvC,EAAE,EAAE,OAAO,EACX,GAAG,EAAE,GAAG,GACP,OAAO,CAAC;IAAE,KAAK,EAAE,WAAW,CAAC;IAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC,CA0B1D"}
|
|
@@ -41,7 +41,7 @@ async function log(db, jobId, gebeurtenis, detail) {
|
|
|
41
41
|
});
|
|
42
42
|
}
|
|
43
43
|
/** Fout waarbij opnieuw proberen zinloos is (er valt niets te printen). */
|
|
44
|
-
class GeenDocumentFout extends Error {
|
|
44
|
+
export class GeenDocumentFout extends Error {
|
|
45
45
|
constructor() {
|
|
46
46
|
super(...arguments);
|
|
47
47
|
this.name = "GeenDocumentFout";
|
|
@@ -864,3 +864,28 @@ export async function verwerkWachtrij(db, max = 5) {
|
|
|
864
864
|
bijgewerkt: await synchroniseerStatussen(db),
|
|
865
865
|
};
|
|
866
866
|
}
|
|
867
|
+
/**
|
|
868
|
+
* Dezelfde documentkeuze als de printwachtrij maakt, maar zonder printer en
|
|
869
|
+
* zonder statuswijziging. Gebruikt door het voorbeeld (`preview-pdf`) zodat
|
|
870
|
+
* het voorbeeld exact toont wat er straks uit de printer komt.
|
|
871
|
+
*/
|
|
872
|
+
export async function bepaalDocumentKeuze(db, job) {
|
|
873
|
+
let bron = await zoekBronregel(db, job);
|
|
874
|
+
if (await isFloridayLayoutKeuze(db, job)) {
|
|
875
|
+
bron = "extern_pdf";
|
|
876
|
+
job.layout_id = null;
|
|
877
|
+
}
|
|
878
|
+
if (bron === "eigen_layout") {
|
|
879
|
+
job.pdf_base64 = null;
|
|
880
|
+
job.pdf_url = null;
|
|
881
|
+
}
|
|
882
|
+
const klaar = await kantEnKlarePdf(db, job);
|
|
883
|
+
if (!klaar && bron === "extern_pdf") {
|
|
884
|
+
throw new GeenDocumentFout("Voor dit document is ingesteld dat het aangeleverde document wordt " +
|
|
885
|
+
"gebruikt, maar er is geen PDF meegestuurd.");
|
|
886
|
+
}
|
|
887
|
+
const keuze = klaar
|
|
888
|
+
? { layout: null, ontwerpWint: false }
|
|
889
|
+
: await zoekLayout(db, job);
|
|
890
|
+
return { keuze, klaarPdf: klaar };
|
|
891
|
+
}
|