@flowselections/layout-maker 1.36.7 → 1.38.4

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.
Files changed (67) hide show
  1. package/README.md +139 -139
  2. package/dist-lib/components/AfdrukKnop.d.ts +19 -19
  3. package/dist-lib/components/NetwerkPrintersDialog.d.ts +7 -7
  4. package/dist-lib/components/PaginaDocument.d.ts +4 -1
  5. package/dist-lib/components/PaginaDocument.d.ts.map +1 -1
  6. package/dist-lib/components/PaginaDocument.js +10 -5
  7. package/dist-lib/components/PakbonBuilderPage.d.ts.map +1 -1
  8. package/dist-lib/components/PakbonBuilderPage.js +4 -2
  9. package/dist-lib/components/PakbonPreview.d.ts +4 -2
  10. package/dist-lib/components/PakbonPreview.d.ts.map +1 -1
  11. package/dist-lib/components/PakbonPreview.js +55 -12
  12. package/dist-lib/components/PakbonSectiesPanel.d.ts +2 -2
  13. package/dist-lib/components/PakbonSectiesPanel.d.ts.map +1 -1
  14. package/dist-lib/components/PakbonSectiesPanel.js +3 -2
  15. package/dist-lib/components/PrinterSetupWizard.js +3 -3
  16. package/dist-lib/components/PrintersRedirectPage.d.ts +1 -1
  17. package/dist-lib/components/PrintersRedirectPage.js +14 -14
  18. package/dist-lib/index.d.ts +4 -2
  19. package/dist-lib/index.d.ts.map +1 -1
  20. package/dist-lib/index.js +3 -2
  21. package/dist-lib/integrations/supabase/auth-middleware.d.ts +1240 -812
  22. package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
  23. package/dist-lib/integrations/supabase/client.server.d.ts +1240 -812
  24. package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
  25. package/dist-lib/integrations/supabase/client.server.js +1 -1
  26. package/dist-lib/integrations/supabase/moduleClient.d.ts +24008 -0
  27. package/dist-lib/integrations/supabase/moduleClient.d.ts.map +1 -0
  28. package/dist-lib/integrations/supabase/moduleClient.js +29 -0
  29. package/dist-lib/integrations/supabase/types.d.ts +1231 -821
  30. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  31. package/dist-lib/lib/aiLayout.functions.d.ts +1240 -812
  32. package/dist-lib/lib/aiLayout.functions.d.ts.map +1 -1
  33. package/dist-lib/lib/aiLayout.server.js +9 -9
  34. package/dist-lib/lib/browserPrint.js +1 -1
  35. package/dist-lib/lib/code128.d.ts +5 -0
  36. package/dist-lib/lib/code128.d.ts.map +1 -0
  37. package/dist-lib/lib/code128.js +51 -0
  38. package/dist-lib/lib/htmlPdf.server.d.ts.map +1 -1
  39. package/dist-lib/lib/htmlPdf.server.js +58 -0
  40. package/dist-lib/lib/layoutPreview.d.ts.map +1 -1
  41. package/dist-lib/lib/layoutPreview.js +11 -8
  42. package/dist-lib/lib/orderData.server.d.ts.map +1 -1
  43. package/dist-lib/lib/orderData.server.js +6 -0
  44. package/dist-lib/lib/pakbonBuilder.d.ts +28 -2
  45. package/dist-lib/lib/pakbonBuilder.d.ts.map +1 -1
  46. package/dist-lib/lib/pakbonBuilder.js +69 -8
  47. package/dist-lib/lib/pakbonHtml.d.ts +14 -2
  48. package/dist-lib/lib/pakbonHtml.d.ts.map +1 -1
  49. package/dist-lib/lib/pakbonHtml.js +155 -63
  50. package/dist-lib/lib/pakbonScan.d.ts +44 -0
  51. package/dist-lib/lib/pakbonScan.d.ts.map +1 -0
  52. package/dist-lib/lib/pakbonScan.js +105 -0
  53. package/dist-lib/lib/printQueue.server.d.ts +7 -0
  54. package/dist-lib/lib/printQueue.server.d.ts.map +1 -1
  55. package/dist-lib/lib/printQueue.server.js +247 -26
  56. package/dist-lib/lib/printService.functions.d.ts +4445 -2733
  57. package/dist-lib/lib/printService.functions.d.ts.map +1 -1
  58. package/dist-lib/lib/productiebonHtml.js +15 -15
  59. package/dist-lib/lib/profielHtml.js +18 -18
  60. package/dist-lib/lib/supabaseAdmin.server.d.ts +5 -5
  61. package/dist-lib/lib/supabaseAdmin.server.js +43 -43
  62. package/dist-lib/lib/supabaseClient.d.ts +1 -3
  63. package/dist-lib/lib/supabaseClient.d.ts.map +1 -1
  64. package/dist-lib/lib/supabaseClient.js +4 -8
  65. package/dist-lib/lib/testpagina.js +3 -3
  66. package/dist-lib/lib/testprint.js +4 -4
  67. package/package.json +151 -151
@@ -4,6 +4,8 @@
4
4
  // de juiste printer. Draait alleen op de server (cron / interne aanroep).
5
5
  // ============================================================================
6
6
  import { maakPakbonOrder, renderPakbonDocument, voorbeeldPakbonOrder, } from "./pakbonHtml.js";
7
+ import { bouwPakbonScanInhoud, bewaarPakbonScanInhoud } from "./pakbonScan.js";
8
+ import { pakbonLayoutVariant } from "./pakbonBuilder.js";
7
9
  import { renderProductiebonDocument } from "./productiebonHtml.js";
8
10
  import { heeftOrderInhoud, laadOrderVanuitDatabase } from "./orderData.server.js";
9
11
  import { profielHeeftInhoud, renderProfielDocument } from "./profielHtml.js";
@@ -47,6 +49,13 @@ export class GeenDocumentFout extends Error {
47
49
  this.name = "GeenDocumentFout";
48
50
  }
49
51
  }
52
+ /** De Engine heeft de taak gepland, maar de layout is nog niet aangeleverd. */
53
+ export class WachtOpLayoutFout extends Error {
54
+ constructor() {
55
+ super(...arguments);
56
+ this.name = "WachtOpLayoutFout";
57
+ }
58
+ }
50
59
  /**
51
60
  * Oudere/andere benamingen voor hetzelfde document. De Logistiek Engine noemt
52
61
  * de afleverbrief bijvoorbeeld nog "verzendbrief"; hier vertalen we dat naar
@@ -65,6 +74,85 @@ export function normaliseerDocumenttype(type) {
65
74
  return "document";
66
75
  return DOCUMENTTYPE_ALIASSEN[t] ?? t;
67
76
  }
77
+ // ---------------------------------------------------------------------------
78
+ // Contract met de Logistics Engine.
79
+ // De Engine bepaalt documentsoort, variant, layout en printplek; deze module
80
+ // rendert en print alleen. Alles hieronder leest die keuzes uit de opdracht en
81
+ // overruled ze nooit.
82
+ // ---------------------------------------------------------------------------
83
+ /** Het layout-id zoals de Engine het aanlevert (kolom of payload). */
84
+ export function leesEngineLayoutId(job) {
85
+ const p = (job.payload ?? {});
86
+ const d = (p.data ?? {});
87
+ for (const k of [job.layout_id, p.layout_id, p.layoutId, d.layout_id, d.layoutId]) {
88
+ const v = String(k ?? "").trim();
89
+ if (v)
90
+ return v;
91
+ }
92
+ return "";
93
+ }
94
+ /** Documentsoort zoals de Engine hem noemt (`document_code`). */
95
+ function leesDocumentCode(job) {
96
+ const p = (job.payload ?? {});
97
+ const d = (p.data ?? {});
98
+ for (const k of [job.document_code, p.document_code, p.documentCode, d.document_code]) {
99
+ const v = String(k ?? "").trim();
100
+ if (v)
101
+ return v;
102
+ }
103
+ return "";
104
+ }
105
+ /** Is dit een opdracht die door de Logistics Engine is gepland? */
106
+ function isEngineOpdracht(job) {
107
+ const p = (job.payload ?? {});
108
+ if (leesDocumentCode(job))
109
+ return true;
110
+ const bron = String(p.bron_module ?? p.owner_module ?? p.source_module ?? "").toLowerCase();
111
+ return bron.includes("logistic") || bron.includes("engine");
112
+ }
113
+ /** Handmatig opnieuw printen? */
114
+ function isHerprint(job) {
115
+ const p = (job.payload ?? {});
116
+ const d = (p.data ?? {});
117
+ return job.is_herprint === true || p.is_herprint === true || d.is_herprint === true;
118
+ }
119
+ /** De ordersleutel waarop een herprint bij de oorspronkelijke print hoort. */
120
+ function leesOrderRef(job) {
121
+ const p = (job.payload ?? {});
122
+ const d = (p.data ?? {});
123
+ for (const k of [job.order_ref, p.order_ref, p.orderRef, d.order_ref, d.orderRef, job.order_id]) {
124
+ const v = String(k ?? "").trim();
125
+ if (v)
126
+ return v;
127
+ }
128
+ return "";
129
+ }
130
+ /**
131
+ * Een herprint gebruikt dezelfde layout als de oorspronkelijke print. Stuurt de
132
+ * Engine geen layout mee, dan zoeken we die op bij de laatste geslaagde print
133
+ * van hetzelfde document van dezelfde order.
134
+ */
135
+ async function zoekHerprintLayoutId(db, job) {
136
+ const orderRef = leesOrderRef(job);
137
+ if (!orderRef)
138
+ return "";
139
+ const type = normaliseerDocumenttype(leesDocumentCode(job) || job.documenttype);
140
+ const { data } = await db
141
+ .from("print_jobs")
142
+ .select("gebruikte_layout_id, documenttype, created_at")
143
+ .eq("order_ref", orderRef)
144
+ .not("gebruikte_layout_id", "is", null)
145
+ .order("created_at", { ascending: false })
146
+ .limit(10);
147
+ for (const rij of arr(data)) {
148
+ if (normaliseerDocumenttype(rij.documenttype) !== type)
149
+ continue;
150
+ const id = String(rij.gebruikte_layout_id ?? "").trim();
151
+ if (id)
152
+ return id;
153
+ }
154
+ return "";
155
+ }
68
156
  /**
69
157
  * Zoekt de layout die bij de opdracht hoort.
70
158
  * Staat er een layout_id op de opdracht (bijv. gezet door de Logistiek
@@ -94,15 +182,19 @@ function magOntwerpWinnen(layout) {
94
182
  return String(layout.bron ?? "") !== "floriday";
95
183
  }
96
184
  async function zoekLayout(db, job) {
185
+ const engineJob = isEngineOpdracht(job);
97
186
  const heeftEigenOpmaak = typeof job.render_html === "string" && job.render_html.trim().length > 40;
98
- // Een layout-id in het kenmerkenveld is alleen een verwijzing, geen
99
- // koppeling: is er opmaak meegestuurd (bijv. een voorbeeldafdruk uit de
100
- // ontwerper), dan is die opmaak leidend.
101
- const uitPayload = heeftEigenOpmaak
187
+ const uitPayload = engineJob
102
188
  ? null
103
189
  : (job.payload?.layoutId ?? job.payload?.data?.layoutId ?? job.payload?.layout_id ?? null);
104
- const id = job.layout_id ?? uitPayload;
105
- const voorbeeld = heeftEigenOpmaak && isVoorbeeldAfdruk(job);
190
+ let id = leesEngineLayoutId(job) || job.layout_id || uitPayload;
191
+ if (engineJob && !id && isHerprint(job)) {
192
+ id = await zoekHerprintLayoutId(db, job);
193
+ }
194
+ if (engineJob && !id) {
195
+ throw new WachtOpLayoutFout("Wacht op layout_id van de Logistics Engine. Deze taak wordt niet met een vervangende layout uitgevoerd.");
196
+ }
197
+ const voorbeeld = !engineJob && heeftEigenOpmaak && isVoorbeeldAfdruk(job);
106
198
  if (id) {
107
199
  const { data } = await db
108
200
  .from("document_layouts")
@@ -113,19 +205,24 @@ async function zoekLayout(db, job) {
113
205
  throw new GeenDocumentFout("De layout die aan deze opdracht hangt bestaat niet meer. Koppel een geldige layout.");
114
206
  }
115
207
  const layout = data;
116
- return { layout, ontwerpWint: !voorbeeld && magOntwerpWinnen(layout) };
208
+ return {
209
+ layout,
210
+ // Een Engine-layout is altijd leidend, ook als er render_html is meegestuurd.
211
+ ontwerpWint: engineJob || (!voorbeeld && magOntwerpWinnen(layout)),
212
+ };
117
213
  }
214
+ const documenttype = normaliseerDocumenttype(leesDocumentCode(job) || job.documenttype);
118
215
  const { data } = await db
119
216
  .from("document_layouts")
120
217
  .select("*")
121
- .eq("documenttype", normaliseerDocumenttype(job.documenttype))
218
+ .eq("documenttype", documenttype)
122
219
  .eq("actief", true)
123
220
  .order("is_standaard", { ascending: false })
124
221
  .order("updated_at", { ascending: false })
125
222
  .limit(1);
126
223
  const gevonden = (arr(data)[0] ?? null);
127
224
  if (!gevonden && !job.render_html) {
128
- throw new GeenDocumentFout(`Geen layout gevonden voor documenttype '${normaliseerDocumenttype(job.documenttype)}'. ` +
225
+ throw new GeenDocumentFout(`Geen layout gevonden voor documenttype '${documenttype}'. ` +
129
226
  "Maak een layout aan en zet die op actief.");
130
227
  }
131
228
  return { layout: gevonden, ontwerpWint: !voorbeeld && magOntwerpWinnen(gevonden) };
@@ -148,7 +245,7 @@ async function zoekBronregel(db, job) {
148
245
  const regel = data;
149
246
  if (!regel || regel.bron === "auto")
150
247
  return null;
151
- if (regel.bron === "eigen_layout" && regel.layout_id && !job.layout_id) {
248
+ if (regel.bron === "eigen_layout" && regel.layout_id && !job.layout_id && !isEngineOpdracht(job)) {
152
249
  job.layout_id = regel.layout_id;
153
250
  }
154
251
  return regel.bron;
@@ -159,6 +256,8 @@ async function zoekBronregel(db, job) {
159
256
  */
160
257
  async function isFloridayLayoutKeuze(db, job) {
161
258
  const payload = (job.payload ?? {});
259
+ if (isEngineOpdracht(job))
260
+ return false;
162
261
  const id = job.layout_id ?? payload?.["layoutId"] ?? payload?.["layout_id"] ?? null;
163
262
  if (!id)
164
263
  return false;
@@ -329,13 +428,35 @@ async function zoekPrinter(db, job, layout) {
329
428
  .maybeSingle();
330
429
  return data && data.actief !== false ? data : null;
331
430
  };
332
- const type = normaliseerDocumenttype(job.documenttype);
333
- // 1. Printer die expliciet op de opdracht staat (bijv. handmatig herprinten).
334
- let printer = await pak(job.printer_id);
431
+ const type = normaliseerDocumenttype(leesDocumentCode(job) || job.documenttype);
432
+ // Bij Engine-opdrachten is print_plek_id leidend. Een eventueel oud
433
+ // printer_id mag die keuze niet overschrijven.
434
+ let printer = isEngineOpdracht(job) && leesPrintPlekId(job)
435
+ ? null
436
+ : await pak(job.printer_id);
335
437
  if (printer)
336
438
  return { printer, reden: "" };
337
439
  const werkplek = String(job.payload?.werkplek ?? "").trim();
338
440
  const pickstation = leesPickstation(job);
441
+ const explicietePlek = leesPrintPlekId(job);
442
+ // Een Engine-printplek is een harde keuze. Nooit terugvallen op computer,
443
+ // layout of bedrijfsregels wanneer de Engine een print_plek_id meegaf.
444
+ if (explicietePlek) {
445
+ const { data: pp } = await db
446
+ .from("document_printplek_printers")
447
+ .select("printer_id")
448
+ .eq("print_plek_id", explicietePlek)
449
+ .eq("documenttype", type)
450
+ .maybeSingle();
451
+ printer = await pak(pp?.printer_id);
452
+ if (printer)
453
+ return { printer, reden: "" };
454
+ const { data: plek } = await db.from("print_plekken").select("naam").eq("id", explicietePlek).maybeSingle();
455
+ return {
456
+ printer: null,
457
+ reden: `Printplek '${String(plek?.naam ?? explicietePlek)}' heeft geen printer voor documenttype '${type}'.`,
458
+ };
459
+ }
339
460
  // 2. Regel van de printplek waar de opdracht vandaan komt.
340
461
  const plekId = await zoekPrintPlekId(db, job);
341
462
  if (plekId) {
@@ -517,6 +638,13 @@ export async function bouwHtml(db, job, keuze, uitkomst) {
517
638
  : null;
518
639
  if ((pakbonSecties && pakbonSecties.length) || String(layout.documenttype) === "pakbon") {
519
640
  const orderBron = p.order ?? p.data?.order ?? p.data ?? p;
641
+ const pakbonMethode = Array.isArray(pakbonSecties)
642
+ ? String(pakbonSecties.find((s) => s?.type === "pickmethode")?.opties?.methode ?? "kar")
643
+ : "kar";
644
+ const engineVariant = job.layout_variant ?? p.layout_variant ?? p.layoutVariant ?? p.data?.layout_variant ?? null;
645
+ if (isEngineOpdracht(job) && pakbonMethode === "beide" && !["full_cart", "partial_order"].includes(String(engineVariant ?? ""))) {
646
+ throw new WachtOpLayoutFout("Wacht op layout_variant van de Logistics Engine: kies full_cart of partial_order.");
647
+ }
520
648
  const uitDb = heeftOrderInhoud(orderBron) ? null : await laadOrderVanuitDatabase(db, p);
521
649
  // Een voorbeeldafdruk zonder ordergegevens krijgt een voorbeeldorder, zodat
522
650
  // je ziet hoe het document er in de praktijk uitziet.
@@ -524,14 +652,82 @@ export async function bouwHtml(db, job, keuze, uitkomst) {
524
652
  (p.voorbeeld === true && !heeftOrderInhoud(orderBron)
525
653
  ? voorbeeldPakbonOrder()
526
654
  : orderBron);
527
- const order = maakPakbonOrder(bron);
528
- return renderPakbonDocument(layout.config, order, {
655
+ // Barcode en kargegevens van de Engine mogen niet verloren gaan wanneer
656
+ // de ordergegevens uit een subobject komen.
657
+ const engineBarcode = p.barcode ?? p.data?.cart?.barcode ?? null;
658
+ const engineCart = p.data?.cart ?? null;
659
+ const bronRecord = (bron && typeof bron === "object" ? bron : {});
660
+ // Verwijder order-/payloadbarcodes: bij Engine-opdrachten zijn alleen de
661
+ // twee contractvelden hierboven toegestaan als bron voor de afleverbriefscan.
662
+ const { barcode: _bronBarcode, cart: _bronCart, ...bronZonderEngineBarcode } = bronRecord;
663
+ let order = maakPakbonOrder({
664
+ ...bronZonderEngineBarcode,
665
+ ...(engineBarcode ? { barcode: engineBarcode } : {}),
666
+ ...(engineCart ? { cart: engineCart } : {}),
667
+ });
668
+ if (isEngineOpdracht(job)) {
669
+ const barcode = String(engineBarcode ?? "").trim();
670
+ if (!barcode) {
671
+ throw new WachtOpLayoutFout("Wacht op barcode van de Logistics Engine: iedere kar moet een eigen pakbonbarcode krijgen.");
672
+ }
673
+ const cartId = String(engineCart?.cart_id ?? "").trim();
674
+ const karren = Array.isArray(order.karren) ? order.karren : [];
675
+ const passendeKarren = cartId
676
+ ? karren.filter((kar) => String(kar.cart_id ?? "").trim() === cartId)
677
+ : karren;
678
+ // Eén Engine-opdracht staat voor precies één kar en dus één pakbon. Een
679
+ // opdracht met meerdere karren mag niet één barcode aan meerdere karren
680
+ // koppelen; de Engine moet daarvoor afzonderlijke jobs aanmaken.
681
+ if (passendeKarren.length > 1 || (!cartId && karren.length > 1)) {
682
+ throw new WachtOpLayoutFout("Wacht op afzonderlijke kar-opdrachten van de Logistics Engine: maak per kar één print_job met een eigen barcode.");
683
+ }
684
+ if (cartId && passendeKarren.length === 0 && karren.length > 1) {
685
+ throw new WachtOpLayoutFout("De door de Logistics Engine gekozen kar is niet gevonden in de pakbongegevens.");
686
+ }
687
+ const gekozenKar = passendeKarren[0] ?? (karren.length === 1 ? karren[0] : null);
688
+ if (gekozenKar) {
689
+ order = {
690
+ ...order,
691
+ cart: {
692
+ ...order.cart,
693
+ ...engineCart,
694
+ barcode,
695
+ cart_id: cartId || gekozenKar.cart_id,
696
+ cart_label: String(engineCart?.cart_label ?? gekozenKar.cart_label ?? "").trim() || undefined,
697
+ cart_index: Number(engineCart?.cart_index ?? gekozenKar.cart_index ?? 0) || undefined,
698
+ cart_total: Number(engineCart?.cart_total ?? gekozenKar.cart_total ?? 0) || undefined,
699
+ },
700
+ karren: [{ ...gekozenKar, barcode }],
701
+ };
702
+ }
703
+ }
704
+ const html = renderPakbonDocument(layout.config, order, {
529
705
  breedteMm,
530
706
  hoogteMm,
531
707
  margeMm: Number(layout.marge_boven ?? 12),
532
708
  titel: job.documentnaam ?? "Document",
709
+ layoutVariant: engineVariant,
533
710
  logos: await logosVoor(db, layout),
534
711
  });
712
+ // De Engine bepaalt de barcode. De module registreert uitsluitend de
713
+ // gerenderde inhoud onder die bestaande waarde voor de scanner. Als de
714
+ // registratie tijdelijk niet kan, mag dat een afdruk nooit blokkeren.
715
+ const scanInhoud = bouwPakbonScanInhoud(order, {
716
+ barcode: engineBarcode,
717
+ layoutVariant: pakbonLayoutVariant(engineVariant),
718
+ layoutId: String(layout.id ?? "") || null,
719
+ printJobId: String(job.id ?? "") || null,
720
+ isHerprint: isHerprint(job),
721
+ });
722
+ if (scanInhoud) {
723
+ try {
724
+ await bewaarPakbonScanInhoud(db, scanInhoud);
725
+ }
726
+ catch (e) {
727
+ await log(db, job.id, "barcode_inhoud_niet_opgeslagen", e instanceof Error ? e.message : "Registratie van barcode-inhoud mislukt");
728
+ }
729
+ }
730
+ return html;
535
731
  }
536
732
  // 2) Productiebon-layouts (blokken onder `productiebon.blokken`).
537
733
  if (Array.isArray(cfg.productiebon?.blokken) ||
@@ -623,6 +819,20 @@ function plattePayload(payload) {
623
819
  loop(payload, "", 0);
624
820
  return uit;
625
821
  }
822
+ async function wachtOpLayout(db, job, reden) {
823
+ await db
824
+ .from("print_jobs")
825
+ .update({
826
+ status: "wachtend",
827
+ foutmelding: reden.slice(0, 500),
828
+ volgende_poging: new Date(Date.now() + 5 * 60 * 1000).toISOString(),
829
+ opgepakt_op: null,
830
+ voltooid_op: null,
831
+ updated_at: new Date().toISOString(),
832
+ })
833
+ .eq("id", job.id);
834
+ await log(db, job.id, "wacht_op_engine_layout", reden);
835
+ }
626
836
  async function mislukt(db, job, reden, terugNaarWachtend) {
627
837
  const pogingen = Number(job.pogingen ?? 0);
628
838
  const opnieuw = terugNaarWachtend && pogingen < MAX_POGINGEN;
@@ -650,19 +860,20 @@ async function verwerkJob(db, job) {
650
860
  // 1) Bronregel: bepaalt of het meegestuurde document wordt gebruikt of dat
651
861
  // er altijd een eigen ontwerp wordt opgemaakt.
652
862
  let bron = await zoekBronregel(db, job);
653
- // 1b) Is er een vaste Floriday-layout gekozen? Die staat alleen voor het
654
- // kant-en-klare document uit Floriday: dat gaat 1-op-1 naar de printer.
655
- if (await isFloridayLayoutKeuze(db, job)) {
863
+ // 1b) Legacy Floriday-keuze; Engine-opdrachten volgen uitsluitend hun
864
+ // eigen layout_id en worden nooit teruggezet naar een externe PDF.
865
+ if (!isEngineOpdracht(job) && await isFloridayLayoutKeuze(db, job)) {
656
866
  bron = "extern_pdf";
657
867
  job.layout_id = null;
658
868
  }
659
- if (bron === "eigen_layout") {
869
+ if (bron === "eigen_layout" || isEngineOpdracht(job)) {
660
870
  job.pdf_base64 = null;
661
871
  job.pdf_url = null;
662
872
  }
663
- // 2) Kant-en-klare PDF (klantsticker, Floriday-document) gaat ongewijzigd door.
664
- const klaar = await kantEnKlarePdf(db, job);
665
- if (!klaar && bron === "extern_pdf") {
873
+ // 2) Kant-en-klare PDF gaat alleen buiten het Engine-contract ongewijzigd
874
+ // door. De Engine bepaalt altijd de layout, ook als er een PDF aanwezig is.
875
+ const klaar = isEngineOpdracht(job) ? null : await kantEnKlarePdf(db, job);
876
+ if (!klaar && bron === "extern_pdf" && !isEngineOpdracht(job)) {
666
877
  await mislukt(db, job, "Floriday-document ontbreekt: voor dit document is ingesteld dat het " +
667
878
  "aangeleverde document wordt gebruikt, maar er is geen PDF meegestuurd.", true);
668
879
  return "mislukt";
@@ -777,6 +988,10 @@ async function verwerkJob(db, job) {
777
988
  return "verstuurd";
778
989
  }
779
990
  catch (e) {
991
+ if (e instanceof WachtOpLayoutFout) {
992
+ await wachtOpLayout(db, job, e.message);
993
+ return "wachtend";
994
+ }
780
995
  // Ontbreekt er een document of layout? Dan heeft opnieuw proberen geen zin.
781
996
  const blijvend = e instanceof GeenDocumentFout;
782
997
  const tijdelijk = !blijvend && (e instanceof PdfRenderFout || e?.name === "PrintToegangFout");
@@ -923,10 +1138,13 @@ export async function verwerkWachtrij(db, max = 5) {
923
1138
  const jobs = [...pdfJobs, ...gewoneJobs];
924
1139
  let verstuurd = 0;
925
1140
  let mislukteJobs = 0;
1141
+ let wachtend = 0;
926
1142
  for (const job of jobs) {
927
1143
  const uitkomst = await verwerkJob(db, job);
928
1144
  if (uitkomst === "verstuurd")
929
1145
  verstuurd++;
1146
+ else if (uitkomst === "wachtend")
1147
+ wachtend++;
930
1148
  else
931
1149
  mislukteJobs++;
932
1150
  }
@@ -934,6 +1152,7 @@ export async function verwerkWachtrij(db, max = 5) {
934
1152
  opgepakt: jobs.length,
935
1153
  verstuurd,
936
1154
  mislukt: mislukteJobs,
1155
+ wachtend,
937
1156
  bijgewerkt: await synchroniseerStatussen(db),
938
1157
  };
939
1158
  }
@@ -944,16 +1163,18 @@ export async function verwerkWachtrij(db, max = 5) {
944
1163
  */
945
1164
  export async function bepaalDocumentKeuze(db, job) {
946
1165
  let bron = await zoekBronregel(db, job);
947
- if (await isFloridayLayoutKeuze(db, job)) {
1166
+ // Engine-opdrachten houden hun eigen layout_id; nooit vervangen door een
1167
+ // Floriday-PDF-keuze.
1168
+ if (!isEngineOpdracht(job) && (await isFloridayLayoutKeuze(db, job))) {
948
1169
  bron = "extern_pdf";
949
1170
  job.layout_id = null;
950
1171
  }
951
- if (bron === "eigen_layout") {
1172
+ if (bron === "eigen_layout" || isEngineOpdracht(job)) {
952
1173
  job.pdf_base64 = null;
953
1174
  job.pdf_url = null;
954
1175
  }
955
- const klaar = await kantEnKlarePdf(db, job);
956
- if (!klaar && bron === "extern_pdf") {
1176
+ const klaar = isEngineOpdracht(job) ? null : await kantEnKlarePdf(db, job);
1177
+ if (!klaar && bron === "extern_pdf" && !isEngineOpdracht(job)) {
957
1178
  throw new GeenDocumentFout("Voor dit document is ingesteld dat het aangeleverde document wordt " +
958
1179
  "gebruikt, maar er is geen PDF meegestuurd.");
959
1180
  }