@flowselections/layout-maker 1.45.9 → 1.46.1

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 (96) hide show
  1. package/README.md +139 -139
  2. package/dist-lib/components/AfdrukKnop.d.ts +19 -19
  3. package/dist-lib/components/AfdrukKnop.js +64 -64
  4. package/dist-lib/components/DocumentenPage.d.ts.map +1 -1
  5. package/dist-lib/components/DocumentenPage.js +2 -3
  6. package/dist-lib/components/NetwerkPrintersDialog.d.ts +7 -7
  7. package/dist-lib/components/NetwerkPrintersDialog.js +79 -79
  8. package/dist-lib/components/PakbonPreview.d.ts.map +1 -1
  9. package/dist-lib/components/PakbonPreview.js +2 -2
  10. package/dist-lib/components/PrintQueuePage.d.ts.map +1 -1
  11. package/dist-lib/components/PrintQueuePage.js +45 -74
  12. package/dist-lib/components/PrintenPage.d.ts.map +1 -1
  13. package/dist-lib/components/PrintenPage.js +2 -3
  14. package/dist-lib/components/PrinterSetupWizard.d.ts.map +1 -1
  15. package/dist-lib/components/PrinterSetupWizard.js +5 -28
  16. package/dist-lib/components/PrintersRedirectPage.d.ts +1 -1
  17. package/dist-lib/components/PrintersRedirectPage.js +14 -14
  18. package/dist-lib/components/PrintplekDetailPage.d.ts.map +1 -1
  19. package/dist-lib/components/PrintplekDetailPage.js +3 -25
  20. package/dist-lib/components/PrintplekWizard.d.ts.map +1 -1
  21. package/dist-lib/components/PrintplekWizard.js +3 -85
  22. package/dist-lib/index.d.ts +1 -8
  23. package/dist-lib/index.d.ts.map +1 -1
  24. package/dist-lib/index.js +1 -10
  25. package/dist-lib/integrations/supabase/auth-middleware.d.ts +5386 -289
  26. package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
  27. package/dist-lib/integrations/supabase/client.server.d.ts +5386 -289
  28. package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
  29. package/dist-lib/integrations/supabase/client.server.js +1 -1
  30. package/dist-lib/integrations/supabase/moduleClient.d.ts +5386 -289
  31. package/dist-lib/integrations/supabase/moduleClient.d.ts.map +1 -1
  32. package/dist-lib/integrations/supabase/types.d.ts +5404 -243
  33. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  34. package/dist-lib/lib/aiLayout.functions.d.ts +5386 -289
  35. package/dist-lib/lib/aiLayout.functions.d.ts.map +1 -1
  36. package/dist-lib/lib/aiLayout.server.js +9 -9
  37. package/dist-lib/lib/browserPrint.js +1 -1
  38. package/dist-lib/lib/fieldRegistry.js +1 -0
  39. package/dist-lib/lib/layoutPreview.js +7 -7
  40. package/dist-lib/lib/logoLibrary.d.ts +2 -1
  41. package/dist-lib/lib/logoLibrary.d.ts.map +1 -1
  42. package/dist-lib/lib/logoLibrary.js +42 -2
  43. package/dist-lib/lib/orderData.server.d.ts.map +1 -1
  44. package/dist-lib/lib/orderData.server.js +13 -0
  45. package/dist-lib/lib/pakbonBuilder.d.ts +26 -0
  46. package/dist-lib/lib/pakbonBuilder.d.ts.map +1 -1
  47. package/dist-lib/lib/pakbonBuilder.js +37 -0
  48. package/dist-lib/lib/pakbonContent.d.ts.map +1 -1
  49. package/dist-lib/lib/pakbonContent.js +2 -2
  50. package/dist-lib/lib/pakbonHtml.d.ts.map +1 -1
  51. package/dist-lib/lib/pakbonHtml.js +60 -60
  52. package/dist-lib/lib/pakbonPresentation.d.ts.map +1 -1
  53. package/dist-lib/lib/pakbonPresentation.js +2 -4
  54. package/dist-lib/lib/printApi.d.ts +0 -2
  55. package/dist-lib/lib/printApi.d.ts.map +1 -1
  56. package/dist-lib/lib/printApi.js +0 -21
  57. package/dist-lib/lib/printQueue.server.d.ts +8 -0
  58. package/dist-lib/lib/printQueue.server.d.ts.map +1 -1
  59. package/dist-lib/lib/printQueue.server.js +162 -66
  60. package/dist-lib/lib/printService.functions.d.ts +51639 -52
  61. package/dist-lib/lib/printService.functions.d.ts.map +1 -1
  62. package/dist-lib/lib/printService.functions.js +69 -0
  63. package/dist-lib/lib/printService.server.d.ts +2 -0
  64. package/dist-lib/lib/printService.server.d.ts.map +1 -1
  65. package/dist-lib/lib/printService.server.js +10 -0
  66. package/dist-lib/lib/productiebonHtml.js +15 -15
  67. package/dist-lib/lib/profielHtml.d.ts.map +1 -1
  68. package/dist-lib/lib/profielHtml.js +22 -19
  69. package/dist-lib/lib/supabaseAdmin.server.d.ts +5 -5
  70. package/dist-lib/lib/supabaseAdmin.server.js +43 -43
  71. package/dist-lib/lib/testpagina.js +3 -3
  72. package/dist-lib/lib/testprint.js +4 -4
  73. package/dist-lib/lib/voorbeeldPrint.d.ts.map +1 -1
  74. package/dist-lib/lib/voorbeeldPrint.js +7 -39
  75. package/package.json +3 -3
  76. package/dist-lib/components/PdfMapAutoSaver.d.ts +0 -6
  77. package/dist-lib/components/PdfMapAutoSaver.d.ts.map +0 -1
  78. package/dist-lib/components/PdfMapAutoSaver.js +0 -12
  79. package/dist-lib/components/PdfMapGlobaleBanner.d.ts +0 -12
  80. package/dist-lib/components/PdfMapGlobaleBanner.d.ts.map +0 -1
  81. package/dist-lib/components/PdfMapGlobaleBanner.js +0 -30
  82. package/dist-lib/components/PdfMapToestemmingBanner.d.ts +0 -8
  83. package/dist-lib/components/PdfMapToestemmingBanner.d.ts.map +0 -1
  84. package/dist-lib/components/PdfMapToestemmingBanner.js +0 -245
  85. package/dist-lib/lib/pdfJobSaver.d.ts +0 -24
  86. package/dist-lib/lib/pdfJobSaver.d.ts.map +0 -1
  87. package/dist-lib/lib/pdfJobSaver.js +0 -376
  88. package/dist-lib/lib/pdfMap.d.ts +0 -83
  89. package/dist-lib/lib/pdfMap.d.ts.map +0 -1
  90. package/dist-lib/lib/pdfMap.js +0 -332
  91. package/dist-lib/lib/pdfOpslagStatus.d.ts +0 -23
  92. package/dist-lib/lib/pdfOpslagStatus.d.ts.map +0 -1
  93. package/dist-lib/lib/pdfOpslagStatus.js +0 -74
  94. package/dist-lib/lib/pdfPrinter.d.ts +0 -9
  95. package/dist-lib/lib/pdfPrinter.d.ts.map +0 -1
  96. package/dist-lib/lib/pdfPrinter.js +0 -34
@@ -6,7 +6,7 @@
6
6
  import { maakPakbonOrder, renderPakbonDocument, voorbeeldPakbonOrder, } from "./pakbonHtml.js";
7
7
  import { bouwPakbonScanInhoud, bewaarPakbonScanInhoud } from "./pakbonScan.js";
8
8
  import { bouwPakbonBarcodeVerzoek, geleverdeBarcodeVoorJob, vraagPakbonBarcodeAan, } from "./pakbonBarcodeVerzoek.js";
9
- import { deelkarRegels, leesAfleverbriefConfig, pakbonLayoutVariant } from "./pakbonBuilder.js";
9
+ import { bepaalVolleKarStand, deelkarRegels, leesAfleverbriefConfig, pakbonLayoutVariant, } from "./pakbonBuilder.js";
10
10
  import { renderProductiebonDocument } from "./productiebonHtml.js";
11
11
  import { heeftOrderInhoud, laadOrderVanuitDatabase, laadPakbonData } from "./orderData.server.js";
12
12
  import { profielHeeftInhoud, renderProfielDocument } from "./profielHtml.js";
@@ -43,6 +43,45 @@ async function log(db, jobId, gebeurtenis, detail) {
43
43
  ...(detail ? { detail: String(detail).slice(0, 1000) } : {}),
44
44
  });
45
45
  }
46
+ /**
47
+ * Bepaalt de positie van een volle kar uit de actuele taken van dezelfde
48
+ * printsessie. Dit vangt Engine-series op waarin elke afzonderlijke taak ten
49
+ * onrechte K1/cartIndex 1 bevat. Oude geannuleerde of mislukte taken tellen
50
+ * bewust niet mee.
51
+ */
52
+ async function volleKarReeksIndex(db, job) {
53
+ const payload = (job.payload && typeof job.payload === "object" ? job.payload : {});
54
+ const data = (payload.data && typeof payload.data === "object" ? payload.data : {});
55
+ const sessieId = String(payload.session_id ?? data.sessionId ?? data.session_id ?? "").trim();
56
+ const orderId = String(data.orderId ?? data.order_id ?? payload.order_id ?? "").trim();
57
+ const huidigeCartId = String(data.cart?.cartId ?? data.cart?.cart_id ?? payload.cart?.cartId ?? payload.cart?.cart_id ?? payload.cart_id ?? "").trim();
58
+ if (!sessieId || !orderId || !huidigeCartId)
59
+ return undefined;
60
+ const { data: rijen, error } = await db
61
+ .from("print_jobs")
62
+ .select("created_at, status, payload")
63
+ .eq("documenttype", "pakbon")
64
+ .eq("payload->>session_id", sessieId)
65
+ .order("created_at", { ascending: true });
66
+ if (error)
67
+ return undefined;
68
+ const geldigeCartIds = [];
69
+ for (const rij of arr(rijen)) {
70
+ if (["geannuleerd", "mislukt"].includes(String(rij.status ?? "")))
71
+ continue;
72
+ const p = (rij.payload && typeof rij.payload === "object" ? rij.payload : {});
73
+ const d = (p.data && typeof p.data === "object" ? p.data : {});
74
+ const variant = String(p.layout_variant ?? d.layoutVariant ?? d.layout_variant ?? "");
75
+ const isVolleKar = variant === "full_cart" || d.cart?.isFullCart === true || p.cart?.isFullCart === true;
76
+ const rijOrderId = String(d.orderId ?? d.order_id ?? p.order_id ?? "").trim();
77
+ const cartId = String(d.cart?.cartId ?? d.cart?.cart_id ?? p.cart?.cartId ?? p.cart?.cart_id ?? p.cart_id ?? "").trim();
78
+ if (!isVolleKar || rijOrderId !== orderId || !cartId || geldigeCartIds.includes(cartId))
79
+ continue;
80
+ geldigeCartIds.push(cartId);
81
+ }
82
+ const positie = geldigeCartIds.indexOf(huidigeCartId) + 1;
83
+ return positie > 0 ? positie : undefined;
84
+ }
46
85
  /** Fout waarbij opnieuw proberen zinloos is (er valt niets te printen). */
47
86
  export class GeenDocumentFout extends Error {
48
87
  constructor() {
@@ -880,6 +919,34 @@ export async function bouwHtml(db, job, keuze, uitkomst) {
880
919
  throw new EngineContractFout("De gekozen kar bevat geen productregels; de pakbon zou zonder producten worden afgedrukt.");
881
920
  }
882
921
  if (gekozenKar) {
922
+ // Bepaal de reeks één keer canoniek. Daarna krijgen zowel de karkop als
923
+ // de PDF uitsluitend deze herstelde waarden en niet het tegenstrijdige
924
+ // cartLabel dat de Engine soms meestuurt.
925
+ const data = (p.data && typeof p.data === "object" ? p.data : {});
926
+ const variant = pakbonLayoutVariant(engineVariant);
927
+ const ruweIndex = Number(engineCart?.cart_index ?? gekozenKar.cart_index ?? 0) || undefined;
928
+ const ruweTotaal = Number(engineCart?.cart_total ?? gekozenKar.cart_total ?? 0) || undefined;
929
+ let karIndex = ruweIndex;
930
+ let karTotaal = ruweTotaal;
931
+ let karLabel = karIndex && karTotaal ? `Kar ${karIndex} van ${karTotaal}` : undefined;
932
+ if (variant === "full_cart") {
933
+ try {
934
+ const reeksIndex = await volleKarReeksIndex(db, job);
935
+ const stand = bepaalVolleKarStand({
936
+ documentnaam: job.documentnaam ?? p.document_number,
937
+ volleKarren: data.fullCarts ?? data.full_carts,
938
+ cartIndex: ruweIndex,
939
+ cartTotal: ruweTotaal,
940
+ reeksIndex,
941
+ });
942
+ karIndex = stand?.index;
943
+ karTotaal = stand?.totaal;
944
+ karLabel = stand?.label;
945
+ }
946
+ catch (e) {
947
+ throw new EngineContractFout(e instanceof Error ? e.message : "De volle-karstand in de Engine-opdracht is ongeldig.");
948
+ }
949
+ }
883
950
  order = {
884
951
  ...order,
885
952
  cart: {
@@ -887,11 +954,20 @@ export async function bouwHtml(db, job, keuze, uitkomst) {
887
954
  ...engineCart,
888
955
  barcode,
889
956
  cart_id: cartId || gekozenKar.cart_id,
890
- cart_label: String(engineCart?.cart_label ?? gekozenKar.cart_label ?? "").trim() || undefined,
891
- cart_index: Number(engineCart?.cart_index ?? gekozenKar.cart_index ?? 0) || undefined,
892
- cart_total: Number(engineCart?.cart_total ?? gekozenKar.cart_total ?? 0) || undefined,
957
+ cart_label: karLabel,
958
+ cart_index: karIndex,
959
+ cart_total: karTotaal,
893
960
  },
894
- karren: [{ ...gekozenKar, barcode }],
961
+ karren: [
962
+ {
963
+ ...gekozenKar,
964
+ barcode,
965
+ ...(karLabel ? { cart_label: karLabel } : {}),
966
+ ...(karIndex ? { cart_index: karIndex } : {}),
967
+ ...(karTotaal ? { cart_total: karTotaal } : {}),
968
+ ...(karIndex ? { karnummer: karIndex } : {}),
969
+ },
970
+ ],
895
971
  };
896
972
  }
897
973
  }
@@ -1028,21 +1104,51 @@ function plattePayload(payload) {
1028
1104
  loop(payload, "", 0);
1029
1105
  return uit;
1030
1106
  }
1031
- async function wachtOpLayout(db, job, reden) {
1107
+ /** Hoe vaak een opdracht op een ontbrekende layout mag wachten. */
1108
+ export const MAX_WACHT_OP_LAYOUT = 3;
1109
+ /**
1110
+ * Zet een opdracht terug om later opnieuw te proberen, maar begrensd: na
1111
+ * MAX_WACHT_OP_LAYOUT pogingen wordt hij leesbaar "mislukt", zodat hij niet
1112
+ * eindeloos blijft rondgaan en onder de gewone opschoning valt.
1113
+ */
1114
+ export async function wachtOpLayout(db, job, reden) {
1115
+ const pogingen = Number(job.pogingen ?? 0) + 1;
1116
+ const nu = new Date().toISOString();
1117
+ if (pogingen >= MAX_WACHT_OP_LAYOUT) {
1118
+ const melding = `De layout voor dit document is na ${MAX_WACHT_OP_LAYOUT} pogingen nog steeds niet beschikbaar. ${reden}`.slice(0, 500);
1119
+ await db
1120
+ .from("print_jobs")
1121
+ .update({
1122
+ status: "mislukt",
1123
+ verwerkingsfase: "mislukt",
1124
+ foutcode: "blijvende_fout",
1125
+ foutmelding: melding,
1126
+ pogingen,
1127
+ volgende_poging: null,
1128
+ fase_voltooid_op: nu,
1129
+ voltooid_op: nu,
1130
+ updated_at: nu,
1131
+ })
1132
+ .eq("id", job.id)
1133
+ .neq("status", "geannuleerd");
1134
+ await log(db, job.id, "mislukt", melding);
1135
+ return "mislukt";
1136
+ }
1032
1137
  await db
1033
1138
  .from("print_jobs")
1034
1139
  .update({
1035
1140
  status: "wachtend",
1036
- lokale_pdf_status: "wachtend",
1037
- lokale_pdf_fout: reden.slice(0, 500),
1038
1141
  foutmelding: reden.slice(0, 500),
1142
+ pogingen,
1039
1143
  volgende_poging: new Date(Date.now() + 5 * 60 * 1000).toISOString(),
1040
1144
  opgepakt_op: null,
1041
1145
  voltooid_op: null,
1042
- updated_at: new Date().toISOString(),
1146
+ updated_at: nu,
1043
1147
  })
1044
- .eq("id", job.id);
1045
- await log(db, job.id, "wacht_op_engine_layout", reden);
1148
+ .eq("id", job.id)
1149
+ .neq("status", "geannuleerd");
1150
+ await log(db, job.id, "wacht_op_engine_layout", `${reden} (poging ${pogingen}/${MAX_WACHT_OP_LAYOUT})`);
1151
+ return "wachtend";
1046
1152
  }
1047
1153
  async function mislukt(db, job, reden, terugNaarWachtend) {
1048
1154
  const pogingen = Number(job.pogingen ?? 0);
@@ -1061,7 +1167,8 @@ async function mislukt(db, job, reden, terugNaarWachtend) {
1061
1167
  voltooid_op: opnieuw ? null : new Date().toISOString(),
1062
1168
  updated_at: new Date().toISOString(),
1063
1169
  })
1064
- .eq("id", job.id);
1170
+ .eq("id", job.id)
1171
+ .neq("status", "geannuleerd");
1065
1172
  await log(db, job.id, opnieuw ? "opnieuw_proberen" : "mislukt", reden);
1066
1173
  }
1067
1174
  function pdfBytes(base64) {
@@ -1082,6 +1189,11 @@ async function bewaarPdfArtefact(db, jobId, base64) {
1082
1189
  throw new PdfRenderFout("mislukt", `Het PDF-bestand kon niet veilig worden opgeslagen: ${error.message}`);
1083
1190
  return { pad, sha256, bytes: inhoud.byteLength };
1084
1191
  }
1192
+ /** Actuele annulering wint altijd van de status uit de eerder opgehaalde claim. */
1193
+ async function isJobGeannuleerd(db, jobId) {
1194
+ const { data } = await db.from("print_jobs").select("status").eq("id", jobId).maybeSingle();
1195
+ return data?.status === "geannuleerd";
1196
+ }
1085
1197
  /**
1086
1198
  * Kleine faseklok: meet per opdracht hoe lang claimen, layout kiezen, printer
1087
1199
  * kiezen, renderen en versturen duren. Eén logregel per opdracht, zodat de
@@ -1126,6 +1238,8 @@ async function metPool(items, max, fn) {
1126
1238
  async function verwerkJob(db, job) {
1127
1239
  const klok = new Faseklok(job);
1128
1240
  try {
1241
+ if (await isJobGeannuleerd(db, String(job.id)))
1242
+ return "wachtend";
1129
1243
  // Dubbel printen voorkomen: deze opdracht staat al bij de printer.
1130
1244
  if (job.extern_job_id) {
1131
1245
  await log(db, job.id, "overgeslagen", "Stond al bij de printer.");
@@ -1158,13 +1272,17 @@ async function verwerkJob(db, job) {
1158
1272
  const printerKeuze = await zoekPrinter(db, job, layout);
1159
1273
  klok.markeer("printer_resolved");
1160
1274
  const printer = printerKeuze.printer;
1161
- // Virtuele PDF-printer: geen PrintNode nodig; de PDF wordt bewaard.
1162
- const isPdfPrinter = String(printer?.afdrukmethode ?? "") === "pdf";
1163
- if (!printer || (!isPdfPrinter && !printer.extern_printer_id)) {
1275
+ if (!printer || !printer.extern_printer_id) {
1164
1276
  await mislukt(db, job, printerKeuze.reden ||
1165
1277
  `Printer '${printer?.naam ?? "onbekend"}' is niet aan PrintNode gekoppeld.`, true);
1166
1278
  return "mislukt";
1167
1279
  }
1280
+ // Een gebruiker kan annuleren terwijl deze opdracht wordt gerenderd. Lees
1281
+ // de actuele status vóór elke onomkeerbare aflevering, niet de oude claim.
1282
+ if (await isJobGeannuleerd(db, String(job.id))) {
1283
+ await log(db, job.id, "annulering_bevestigd", "Niet afgeleverd: opdracht was geannuleerd.");
1284
+ return "wachtend";
1285
+ }
1168
1286
  // Uitkomst van de opmaakkeuze; wordt na verzending vastgelegd naast de
1169
1287
  // aangeleverde invoer, zodat achteraf zichtbaar is wat er gekozen is.
1170
1288
  const uitkomst = { bron: "extern_pdf" };
@@ -1189,47 +1307,9 @@ async function verwerkJob(db, job) {
1189
1307
  });
1190
1308
  }
1191
1309
  klok.markeer("rendered");
1192
- // Virtuele PDF-printer: niets gaat naar een fysieke printer; de opgemaakte
1193
- // PDF wordt op de opdracht bewaard en is daarna te downloaden in de
1194
- // wachtrij. De opdracht is meteen voltooid.
1195
- if (isPdfPrinter) {
1196
- // Bewaar de PDF één keer als binair artefact. De wachtrij en Realtime
1197
- // vervoeren daarna alleen metadata; de werkplek downloadt precies dit bestand.
1198
- const artefact = await bewaarPdfArtefact(db, String(job.id), pdfBase64);
1199
- // De opdracht blijft bewust op 'bezig' staan: hij is pas voltooid als de
1200
- // werkplek het bestand echt in de PDF-map heeft gezet. De lokale service
1201
- // zet daarna 'opgeslagen' + 'voltooid', of 'mislukt' met een leesbare zin.
1202
- const { error: statusFout } = await db
1203
- .from("print_jobs")
1204
- .update({
1205
- printer_id: printer.id,
1206
- gebruikte_layout_id: uitkomst.bron === "extern_pdf" ? null : (layout?.id ?? null),
1207
- gebruikte_bron: uitkomst.bron,
1208
- status: "bezig",
1209
- verwerkingsfase: "pdf_gereed",
1210
- fase_voltooid_op: new Date().toISOString(),
1211
- foutcode: null,
1212
- lokale_pdf_status: "gereed",
1213
- lokale_pdf_print_plek_id: (await zoekPrintPlekId(db, job)) || null,
1214
- lokale_pdf_fout: null,
1215
- lokaal_opgeslagen_op: null,
1216
- foutmelding: null,
1217
- artefact_pad: artefact.pad,
1218
- artefact_sha256: artefact.sha256,
1219
- artefact_bytes: artefact.bytes,
1220
- artefact_gereed_op: new Date().toISOString(),
1221
- resultaat_pdf_base64: null,
1222
- volgende_poging: null,
1223
- updated_at: new Date().toISOString(),
1224
- })
1225
- .eq("id", job.id);
1226
- if (statusFout) {
1227
- await mislukt(db, job, `Opdracht afronden mislukt: ${statusFout.message}`, true);
1228
- return "mislukt";
1229
- }
1230
- klok.markeer("pdf_ready");
1231
- await log(db, job.id, "pdf_gereed", `PDF gereed voor lokale opslag via ${printer.naam ?? "PDF-printer"}. ${klok.samenvatting()}`);
1232
- return "verstuurd";
1310
+ if (await isJobGeannuleerd(db, String(job.id))) {
1311
+ await log(db, job.id, "annulering_bevestigd", "Niet afgeleverd: tijdens het opmaken geannuleerd.");
1312
+ return "wachtend";
1233
1313
  }
1234
1314
  const formaten = await zorgVoorFormaten(db, printer);
1235
1315
  const vfPapier = (job.payload ?? {})["voorbeeld_formaat"];
@@ -1243,7 +1323,13 @@ async function verwerkJob(db, job) {
1243
1323
  kopieen: Number(job.kopieen ?? 1),
1244
1324
  opties: { paper: papier.paper, rotate: papier.rotate, fitToPage: false },
1245
1325
  });
1246
- await db
1326
+ if (await isJobGeannuleerd(db, String(job.id))) {
1327
+ const { annuleerExternePrintOpdracht } = await import("./printService.server.js");
1328
+ await annuleerExternePrintOpdracht(externJobId).catch(() => undefined);
1329
+ await log(db, job.id, "annulering_bevestigd", "Externe printertaak na annulering verwijderd.");
1330
+ return "wachtend";
1331
+ }
1332
+ const { data: gekoppeldeJob } = await db
1247
1333
  .from("print_jobs")
1248
1334
  .update({
1249
1335
  printer_id: printer.id,
@@ -1259,15 +1345,23 @@ async function verwerkJob(db, job) {
1259
1345
  volgende_poging: null,
1260
1346
  updated_at: new Date().toISOString(),
1261
1347
  })
1262
- .eq("id", job.id);
1348
+ .eq("id", job.id)
1349
+ .neq("status", "geannuleerd")
1350
+ .select("id")
1351
+ .maybeSingle();
1352
+ if (!gekoppeldeJob) {
1353
+ const { annuleerExternePrintOpdracht } = await import("./printService.server.js");
1354
+ await annuleerExternePrintOpdracht(externJobId).catch(() => undefined);
1355
+ await log(db, job.id, "annulering_bevestigd", "Externe printertaak na gelijktijdige annulering verwijderd.");
1356
+ return "wachtend";
1357
+ }
1263
1358
  klok.markeer("submitted");
1264
1359
  await log(db, job.id, "verstuurd", `Naar printer ${printer.naam ?? ""}. ${papier.uitleg} ${klok.samenvatting()}`);
1265
1360
  return "verstuurd";
1266
1361
  }
1267
1362
  catch (e) {
1268
1363
  if (e instanceof WachtOpLayoutFout) {
1269
- await wachtOpLayout(db, job, e.message);
1270
- return "wachtend";
1364
+ return await wachtOpLayout(db, job, e.message);
1271
1365
  }
1272
1366
  // Ontbreekt er een document of layout? Dan heeft opnieuw proberen geen zin.
1273
1367
  const blijvend = e instanceof GeenDocumentFout;
@@ -1311,7 +1405,8 @@ async function synchroniseerStatussen(db) {
1311
1405
  foutmelding: status === "mislukt" ? "De printer meldde een fout." : null,
1312
1406
  updated_at: new Date().toISOString(),
1313
1407
  })
1314
- .eq("id", job.id);
1408
+ .eq("id", job.id)
1409
+ .neq("status", "geannuleerd");
1315
1410
  await log(db, job.id, status);
1316
1411
  return 1;
1317
1412
  }
@@ -1328,7 +1423,8 @@ async function synchroniseerStatussen(db) {
1328
1423
  foutmelding: `Status niet bevestigd: ${reden}`.slice(0, 500),
1329
1424
  updated_at: new Date().toISOString(),
1330
1425
  })
1331
- .eq("id", job.id);
1426
+ .eq("id", job.id)
1427
+ .neq("status", "geannuleerd");
1332
1428
  await log(db, job.id, "status_onbekend", reden);
1333
1429
  console.warn(`[printwachtrij] status ophalen mislukt voor job ${job.id}: ${reden}`);
1334
1430
  return 0;
@@ -1367,7 +1463,8 @@ async function vervalControle(db) {
1367
1463
  voltooid_op: new Date().toISOString(),
1368
1464
  updated_at: new Date().toISOString(),
1369
1465
  })
1370
- .eq("id", job.id);
1466
+ .eq("id", job.id)
1467
+ .neq("status", "geannuleerd");
1371
1468
  await log(db, job.id, "vervallen", reden);
1372
1469
  aantal++;
1373
1470
  }
@@ -1376,8 +1473,6 @@ async function vervalControle(db) {
1376
1473
  .select("id, pogingen")
1377
1474
  .eq("status", "bezig")
1378
1475
  .is("extern_job_id", null)
1379
- // PDF-opdrachten die op de werkplek wachten zijn niet vastgelopen.
1380
- .or("lokale_pdf_status.is.null,lokale_pdf_status.not.in.(wachtend,bezig,gereed)")
1381
1476
  .or(`opgepakt_op.lt.${grensClaim},and(opgepakt_op.is.null,updated_at.lt.${grensClaim})`)
1382
1477
  .limit(50);
1383
1478
  for (const job of arr(vastgelopen)) {
@@ -1395,7 +1490,8 @@ async function vervalControle(db) {
1395
1490
  voltooid_op: opnieuw ? null : new Date().toISOString(),
1396
1491
  updated_at: new Date().toISOString(),
1397
1492
  })
1398
- .eq("id", job.id);
1493
+ .eq("id", job.id)
1494
+ .neq("status", "geannuleerd");
1399
1495
  await log(db, job.id, opnieuw ? "opnieuw_proberen" : "mislukt", "Vastgelopen ronde hersteld.");
1400
1496
  aantal++;
1401
1497
  }