@flowselections/layout-maker 1.69.13 → 1.69.19

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 (37) hide show
  1. package/dist-lib/components/PrintQueuePage.d.ts.map +1 -1
  2. package/dist-lib/components/PrintQueuePage.js +2 -5
  3. package/dist-lib/integrations/supabase/types.d.ts +645 -2
  4. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  5. package/dist-lib/integrations/supabase/types.js +1 -0
  6. package/dist-lib/lib/codelijstCache.d.ts +11 -0
  7. package/dist-lib/lib/codelijstCache.d.ts.map +1 -0
  8. package/dist-lib/lib/codelijstCache.js +117 -0
  9. package/dist-lib/lib/documentTypes.d.ts +1 -1
  10. package/dist-lib/lib/documentTypes.d.ts.map +1 -1
  11. package/dist-lib/lib/documentTypes.js +2 -1
  12. package/dist-lib/lib/foutTekst.d.ts +3 -0
  13. package/dist-lib/lib/foutTekst.d.ts.map +1 -1
  14. package/dist-lib/lib/foutTekst.js +9 -0
  15. package/dist-lib/lib/kiesFormaat.d.ts +11 -0
  16. package/dist-lib/lib/kiesFormaat.d.ts.map +1 -0
  17. package/dist-lib/lib/kiesFormaat.js +32 -0
  18. package/dist-lib/lib/orderData.server.d.ts.map +1 -1
  19. package/dist-lib/lib/orderData.server.js +5 -14
  20. package/dist-lib/lib/printQueue.server.d.ts +5 -2
  21. package/dist-lib/lib/printQueue.server.d.ts.map +1 -1
  22. package/dist-lib/lib/printQueue.server.js +148 -47
  23. package/dist-lib/lib/printerKeuze.d.ts +15 -0
  24. package/dist-lib/lib/printerKeuze.d.ts.map +1 -0
  25. package/dist-lib/lib/printerKeuze.js +32 -0
  26. package/dist-lib/lib/queueTick.d.ts.map +1 -1
  27. package/dist-lib/lib/queueTick.js +9 -1
  28. package/dist-lib/lib/runnerRegistry.server.d.ts +10 -0
  29. package/dist-lib/lib/runnerRegistry.server.d.ts.map +1 -1
  30. package/dist-lib/lib/runnerRegistry.server.js +55 -0
  31. package/dist-lib/lib/useDocumentData.js +1 -1
  32. package/dist-lib/lib/versie.generated.d.ts +1 -1
  33. package/dist-lib/lib/versie.generated.js +1 -1
  34. package/package.json +1 -1
  35. package/sql/print-schema.aanvulling.sql +5 -0
  36. package/sql/print-schema.sql +4114 -2919
  37. package/sql/print-schema.verslag.md +402 -0
@@ -16,6 +16,8 @@ import { bouwDocumentHtml, voorbeeldContext } from "./layoutPreview.js";
16
16
  import { AfbeeldingInhoudFout, htmlNaarPdfServer, PdfRenderFout } from "./htmlNaarPdf.server.js";
17
17
  import { bepaalStickerMaat, floridayStickerMaat, STICKER_MAAT_ONBEKEND, stickerFormaatCode, stickerIdUitPayload, stickerInfoUitCache, stickerNietNodig, stickerTypeLabel, STICKER_NIET_NODIG, FASE_NIET_NODIG } from "./stickerFormaat.js";
18
18
  import { controlelijstHtml, EIGEN_STICKER_MAAT_ONBEKEND, VOORBEELD_TRAY_PAYLOAD, eigenStickerId, eigenStickerKlant, eigenStickerMaat, kiesTraystickerLogo, TRAYSTICKER_GEEN_INDELING, trayProducten, traystickerHtml } from "./eigenDocumenten.js";
19
+ import { neemCodelijstMeldingen, startCodelijstRonde, stopCodelijstRonde } from "./codelijstCache.js";
20
+ import { kiesFormaat, testprintHoudtFormaat } from "./kiesFormaat.js";
19
21
  import { OPMAAK_LIMIET_MS, OpmaakTeLangFout, metTijdslimiet } from "./opmaakLimiet.js";
20
22
  import { stuurPdfNaarPrinter, opdrachtStatusDetail, vervaltijdVoor } from "./printService.server.js";
21
23
  import { kiesPapier } from "./papierFormaten.js";
@@ -48,7 +50,10 @@ export async function controleerClient(db) {
48
50
  /** Fase met tijd en details in het logboek, zodat een vastloper nooit stil is. */
49
51
  async function faseLog(db, jobId, fase, detail) {
50
52
  try {
51
- await log(db, String(jobId), "fase", JSON.stringify({ fase, op: new Date().toISOString(), ...detail }));
53
+ const op = new Date().toISOString();
54
+ await log(db, String(jobId), "fase", JSON.stringify({ fase, op, ...detail }));
55
+ // Vrijgave per fase (layouts_vrijgeven_vastgelopen) meet vanaf de laatste fase.
56
+ await db.from("print_jobs").update({ fase_gestart_op: op }).eq("id", String(jobId)).eq("status", "bezig");
52
57
  }
53
58
  catch {
54
59
  /* loggen mag het printen nooit blokkeren */
@@ -271,7 +276,7 @@ function isEngineOpdracht(job) {
271
276
  return bron.includes("logistic") || bron.includes("engine");
272
277
  }
273
278
  /** Stickers: altijd kant-en-klare PDF op een labelprinter. */
274
- const ETIKET_PDF_SOORTEN = new Set(["sticker", "traysticker"]);
279
+ const ETIKET_PDF_SOORTEN = new Set(["sticker", "traysticker", "eigen_sticker"]);
275
280
  /** Testprint: alleen de eigen testvlag telt (geen kar, geen barcode). */
276
281
  export function isTestprintJob(job) {
277
282
  const p = (job.payload ?? {});
@@ -460,7 +465,21 @@ async function zoekLayout(db, job) {
460
465
  .order("is_standaard", { ascending: false })
461
466
  .order("updated_at", { ascending: false })
462
467
  .limit(1);
463
- const gevonden = (arr(data)[0] ?? null);
468
+ let gevonden = (arr(data)[0] ?? null);
469
+ if (!gevonden && documenttype === "eigen_sticker") {
470
+ // Engine 1.0.194: soort eigen_sticker. Bestaande eigen-stickerlayout
471
+ // (soort sticker + fs.eigen_sticker) blijft werken; niets gekopieerd.
472
+ const { data: oud } = await db
473
+ .from("document_layouts")
474
+ .select("*")
475
+ .eq("documenttype", "sticker")
476
+ .eq("actief", true)
477
+ .not("config->eigen_sticker", "is", null)
478
+ .order("is_standaard", { ascending: false })
479
+ .order("updated_at", { ascending: false })
480
+ .limit(1);
481
+ gevonden = (arr(oud)[0] ?? null);
482
+ }
464
483
  if (!gevonden && !job.render_html) {
465
484
  throw new GeenDocumentFout(`Geen layout gevonden voor documenttype '${documenttype}'. ` +
466
485
  "Maak een layout aan en zet die op actief.");
@@ -758,7 +777,14 @@ async function zoekPrinter(db, job, layout) {
758
777
  // Alleen een printer die nog echt aan de plek gekoppeld én actief is telt.
759
778
  const plekPrinter = async (plek) => {
760
779
  const { data } = await db.rpc("printplek_printer", { _plek: plek, _documenttype: type });
761
- return typeof data === "string" ? data : null;
780
+ if (typeof data === "string")
781
+ return data;
782
+ // Eigen sticker: dezelfde printerkeuze als de traysticker.
783
+ if (type === "eigen_sticker") {
784
+ const { data: tray } = await db.rpc("printplek_printer", { _plek: plek, _documenttype: "traysticker" });
785
+ return typeof tray === "string" ? tray : null;
786
+ }
787
+ return null;
762
788
  };
763
789
  if (explicietePlek) {
764
790
  printer = await pak(await plekPrinter(explicietePlek));
@@ -788,11 +814,24 @@ async function zoekPrinter(db, job, layout) {
788
814
  _papierformaat: layout?.papierformaat ?? null,
789
815
  ...(leesWerkplekId(job) ? { _werkplek_id: leesWerkplekId(job) } : {}),
790
816
  });
791
- printer = await pak(route?.printer_id);
817
+ let routeRij = route;
818
+ if (!routeRij?.printer_id && type === "eigen_sticker") {
819
+ // Eigen sticker: dezelfde printerkeuze als de traysticker.
820
+ const { data: tray } = await db.rpc("printer_routering", {
821
+ _documenttype: "traysticker",
822
+ _print_plek_id: plekId || null,
823
+ _werkplek: werkplek || null,
824
+ _layout_id: layout?.id ?? null,
825
+ _papierformaat: layout?.papierformaat ?? null,
826
+ ...(leesWerkplekId(job) ? { _werkplek_id: leesWerkplekId(job) } : {}),
827
+ });
828
+ routeRij = tray;
829
+ }
830
+ printer = await pak(routeRij?.printer_id);
792
831
  if (printer) {
793
832
  try {
794
833
  const plekNaam = plekId ? await printplekNaamVoorJob(db, job) : null;
795
- const r = (route ?? {});
834
+ const r = (routeRij ?? {});
796
835
  await log(db, job.id, "waarom", waaromDezePrinter({ bron: r.bron, papierformaat: r.papierformaat ?? null, werkplek_naam: r.werkplek_naam, printer_rol: r.printer_rol, werkplek: r.werkplek }, String(printer.weergavenaam || printer.naam || "Printer"), plekNaam));
797
836
  }
798
837
  catch { /* uitleg is informatief */ }
@@ -1058,6 +1097,7 @@ export async function bouwHtml(db, job, keuze, uitkomst) {
1058
1097
  ? ((await laadPakbonData(db, { scope: "cart", id: cartId })) ??
1059
1098
  (await laadOrderVanuitDatabase(db, p)))
1060
1099
  : await laadOrderVanuitDatabase(db, p);
1100
+ await faseLog(db, job.id, "gegevens_geladen", { uit_database: !!uitDb });
1061
1101
  const bron = uitDb ??
1062
1102
  (p.voorbeeld === true && !heeftOrderInhoud(orderBron)
1063
1103
  ? voorbeeldPakbonOrder()
@@ -1128,6 +1168,7 @@ export async function bouwHtml(db, job, keuze, uitkomst) {
1128
1168
  ? ((await laadPakbonData(db, { scope: "cart", id: engineCartId })) ??
1129
1169
  (await laadOrderVanuitDatabase(db, p)))
1130
1170
  : await laadOrderVanuitDatabase(db, p);
1171
+ await faseLog(db, job.id, "gegevens_geladen", { uit_database: !!uitDb });
1131
1172
  // Een voorbeeldafdruk zonder ordergegevens krijgt een voorbeeldorder, zodat
1132
1173
  // je ziet hoe het document er in de praktijk uitziet.
1133
1174
  const bron = uitDb ??
@@ -1722,7 +1763,10 @@ async function verwerkJob(db, job) {
1722
1763
  const opmaakStart = Date.now();
1723
1764
  await faseLog(db, job.id, "opmaken_gestart", { layout_id: layout?.id ?? null });
1724
1765
  pdfBase64 = await metTijdslimiet(OPMAAK_LIMIET_MS, async () => {
1766
+ await faseLog(db, job.id, "gegevens_laden_gestart", {});
1725
1767
  const html = await bouwHtml(db, job, keuze, uitkomst);
1768
+ for (const m of neemCodelijstMeldingen())
1769
+ await faseLog(db, job.id, String(m["fase"] ?? "codelijst"), m);
1726
1770
  await faseLog(db, job.id, "html_klaar", { ms: Date.now() - opmaakStart, tekens: html.length });
1727
1771
  if (!heeftZichtbareInhoud(html)) {
1728
1772
  throw new GeenDocumentFout("Het document was leeg: er zijn geen gegevens gevonden om af te drukken. " +
@@ -1732,12 +1776,12 @@ async function verwerkJob(db, job) {
1732
1776
  // ontwerp is leidend), maar geeft wél het papierformaat van de bewerkte
1733
1777
  // layout mee. Zonder dit zou het voorbeeld altijd op A4 uitkomen.
1734
1778
  const vf = (job.payload ?? {})["voorbeeld_formaat"];
1735
- const liggend = String(layout?.orientatie ?? vf?.["orientatie"] ?? "portrait") === "landscape";
1779
+ const liggend = String(kiesFormaat(job, layout).orientatie ?? "portrait") === "landscape";
1736
1780
  const pdf = await htmlNaarPdfServer({
1737
1781
  html,
1738
- breedteMm: Number(layout?.breedte_mm ?? vf?.["breedte_mm"] ?? 210),
1739
- hoogteMm: Number(layout?.hoogte_mm ?? vf?.["hoogte_mm"] ?? 297),
1740
- margeMm: Number(layout?.marge_boven ?? vf?.["marge_boven"] ?? 10),
1782
+ breedteMm: kiesFormaat(job, layout).breedte_mm ?? 210,
1783
+ hoogteMm: kiesFormaat(job, layout).hoogte_mm ?? 297,
1784
+ margeMm: Number(testprintHoudtFormaat(job) ? (vf?.["marge_boven"] ?? 10) : (layout?.marge_boven ?? vf?.["marge_boven"] ?? 10)),
1741
1785
  landschap: liggend,
1742
1786
  bijOvergeslagen: (src, reden) => {
1743
1787
  void faseLog(db, job.id, "afbeelding_weggelaten", { bron: src.split("?")[0]?.slice(0, 200), reden });
@@ -1754,8 +1798,9 @@ async function verwerkJob(db, job) {
1754
1798
  }
1755
1799
  const formaten = await zorgVoorFormaten(db, printer);
1756
1800
  const vfPapier = (job.payload ?? {})["voorbeeld_formaat"];
1757
- const breedteMm = Number(layout?.breedte_mm ?? vfPapier?.["breedte_mm"] ?? standaardMaat(printer).breedte);
1758
- const hoogteMm = Number(layout?.hoogte_mm ?? vfPapier?.["hoogte_mm"] ?? standaardMaat(printer).hoogte);
1801
+ void vfPapier;
1802
+ const breedteMm = kiesFormaat(job, layout).breedte_mm ?? standaardMaat(printer).breedte;
1803
+ const hoogteMm = kiesFormaat(job, layout).hoogte_mm ?? standaardMaat(printer).hoogte;
1759
1804
  let doelPrinter = printer;
1760
1805
  let uitwijk = null;
1761
1806
  const fitToPage = false;
@@ -1838,6 +1883,7 @@ async function verwerkJob(db, job) {
1838
1883
  gebruikte_layout_id: uitkomst.bron === "extern_pdf" ? null : (layout?.id ?? null),
1839
1884
  gebruikte_bron: uitkomst.bron,
1840
1885
  extern_job_id: externJobId,
1886
+ printer_keuze: await printerKeuzeVoor(db, job, doelPrinter, uitwijk),
1841
1887
  printnode_status: "new",
1842
1888
  printnode_status_op: new Date().toISOString(),
1843
1889
  verloopt_na_s: verloopt,
@@ -1985,7 +2031,7 @@ async function synchroniseerStatussen(db) {
1985
2031
  * runner draait niet meer) -> na 10 minuten terug naar 'wachtend'.
1986
2032
  * Beide draaien in elke ronde, ook als er niets te claimen viel.
1987
2033
  */
1988
- async function vervalControle(db) {
2034
+ async function vervalControle(db, houder = null) {
1989
2035
  const nu = Date.now();
1990
2036
  const grensVerstuurd = new Date(nu - VERVAL_BEZIG_MIN * 60000).toISOString();
1991
2037
  const grensClaim = new Date(nu - VERVAL_CLAIM_MIN * 60000).toISOString();
@@ -2019,6 +2065,12 @@ async function vervalControle(db) {
2019
2065
  await log(db, job.id, "vervallen", reden);
2020
2066
  aantal++;
2021
2067
  }
2068
+ // Vrijgave per fase (standaard 90 s), alleen als geen levende andere ronde
2069
+ // de lease houdt. Lukt de functie niet, dan het oude vangnet van 10 minuten.
2070
+ const { data: vrij, error: vrijFout } = await db.rpc("layouts_vrijgeven_vastgelopen", { p_houder: houder });
2071
+ if (!vrijFout)
2072
+ return aantal + Number(vrij ?? 0);
2073
+ console.warn(`[printwachtrij] vrijgeven per fase mislukt: ${vrijFout.message}`);
2022
2074
  const { data: vastgelopen } = await db
2023
2075
  .from("print_jobs")
2024
2076
  .select("id, pogingen")
@@ -2049,7 +2101,10 @@ async function vervalControle(db) {
2049
2101
  return aantal;
2050
2102
  }
2051
2103
  /** Hoeveel opdrachten tegelijk worden gerenderd binnen één ronde. */
2052
- const JOB_PARALLEL = 3;
2104
+ const JOB_PARALLEL = 1;
2105
+ /** Na deze looptijd pakt een ronde geen nieuwe opdracht meer (lopende wordt afgemaakt). */
2106
+ export const RONDE_BUDGET_MS = 20000;
2107
+ void JOB_PARALLEL;
2053
2108
  /** Zo lang mag één ronde het slot houden; daarna is hij vanzelf herstelbaar. */
2054
2109
  const LEASE_SECONDEN = 120;
2055
2110
  const LEASE_NAAM = "print-queue-worker";
@@ -2059,7 +2114,7 @@ const VERVOLG_COOLDOWN_MS = 1500;
2059
2114
  * Wekt de wachtrij opnieuw, maar uitsluitend wanneer er aantoonbaar wachtend
2060
2115
  * werk overblijft. Het adres van de runner staat in de database, nooit in code.
2061
2116
  */
2062
- async function wekVervolgRonde(db) {
2117
+ async function wekVervolgRonde(db, origin = null) {
2063
2118
  const { count } = await db
2064
2119
  .from("print_jobs")
2065
2120
  .select("id", { count: "exact", head: true })
@@ -2067,13 +2122,32 @@ async function wekVervolgRonde(db) {
2067
2122
  .or(`volgende_poging.is.null,volgende_poging.lte.${new Date().toISOString()}`);
2068
2123
  if (!count)
2069
2124
  return;
2070
- await new Promise((r) => setTimeout(r, VERVOLG_COOLDOWN_MS));
2125
+ void VERVOLG_COOLDOWN_MS;
2126
+ // Eerst direct vanaf de server (geen pg_net-wachtrij); pg_net is het vangnet.
2127
+ const { wekEigenRonde } = await import("./runnerRegistry.server.js");
2128
+ const direct = await wekEigenRonde(db, origin);
2129
+ if (direct.ok)
2130
+ return;
2131
+ console.warn(`[printwachtrij] direct wekken niet gelukt (${direct.reden}); vangnet wake_print_queue.`);
2071
2132
  const { error } = await db.rpc("wake_print_queue", { _min_interval_ms: 1000 });
2072
2133
  if (error)
2073
2134
  console.warn(`[printwachtrij] vervolgronde wekken mislukt: ${error.message}`);
2074
2135
  }
2075
2136
  /** Eén ronde: vastgelopen opdrachten opruimen, afdrukken en statussen bijwerken. */
2076
- export async function verwerkWachtrij(db, max = 5) {
2137
+ async function claimEen(db) {
2138
+ const { data: pdf, error: pdfError } = await db.rpc("claim_pdf_print_jobs", { _max: 1 });
2139
+ if (pdfError)
2140
+ throw new Error(pdfError.message);
2141
+ const p = arr(pdf)[0];
2142
+ if (p)
2143
+ return p;
2144
+ const { data, error } = await db.rpc("claim_cloud_print_jobs", { _max: 1 });
2145
+ if (error)
2146
+ throw new Error(error.message);
2147
+ return arr(data)[0] ?? null;
2148
+ }
2149
+ export async function verwerkWachtrij(db, max = 5, opties = {}) {
2150
+ const rondeStart = Date.now();
2077
2151
  const leeg = {
2078
2152
  opgepakt: 0,
2079
2153
  verstuurd: 0,
@@ -2094,10 +2168,11 @@ export async function verwerkWachtrij(db, max = 5) {
2094
2168
  if (slot !== true)
2095
2169
  return { ...leeg, overgeslagen: "Er draait al een printronde." };
2096
2170
  let vervolgNodig = false;
2171
+ startCodelijstRonde();
2097
2172
  try {
2098
2173
  // Eerst het vangnet: dit draait ook als er niets te claimen valt.
2099
2174
  try {
2100
- await vervalControle(db);
2175
+ await vervalControle(db, houder);
2101
2176
  }
2102
2177
  catch (e) {
2103
2178
  console.warn(`[printwachtrij] vervalcontrole mislukt: ${e instanceof Error ? e.message : "onbekend"}`);
@@ -2110,47 +2185,48 @@ export async function verwerkWachtrij(db, max = 5) {
2110
2185
  catch (e) {
2111
2186
  console.warn(`[printwachtrij] webhook koppelen mislukt: ${e instanceof Error ? e.message : "onbekend"}`);
2112
2187
  }
2113
- // PDF-opdrachten hebben een eigen claimroute. Daardoor kunnen oudere
2114
- // gepubliceerde moduleversies, die een PDF-printer nog als PrintNode-printer
2115
- // behandelen, deze opdrachten niet meer vóór de actuele runner oppakken.
2116
- const { data: pdfData, error: pdfError } = await db.rpc("claim_pdf_print_jobs", { _max: max });
2117
- if (pdfError)
2118
- throw new Error(pdfError.message);
2119
- const pdfJobs = arr(pdfData);
2120
- const resterend = Math.max(0, max - pdfJobs.length);
2121
- let gewoneJobs = [];
2122
- if (resterend > 0) {
2123
- const { data, error } = await db.rpc("claim_cloud_print_jobs", { _max: resterend });
2124
- if (error)
2125
- throw new Error(error.message);
2126
- gewoneJobs = arr(data);
2127
- }
2128
- // Begrensd parallel: meerdere pakbonnen van één order lopen gelijktijdig,
2129
- // maar één mislukte opdracht annuleert de andere nooit.
2130
- const jobs = [...pdfJobs, ...gewoneJobs];
2131
- // Vastleggen welke installatie (en versie) deze opdrachten verwerkt.
2132
- if (jobs.length > 0) {
2188
+ // Eén opdracht tegelijk (JOB_PARALLEL = 1): claim er één, verwerk hem en
2189
+ // herhaal zolang de ronde jonger is dan RONDE_BUDGET_MS. Nooit vooraf
2190
+ // claimen, zodat wachtende opdrachten 'wachtend' blijven.
2191
+ // PDF-opdrachten hebben een eigen claimroute (eerst), daarna de cloud-route.
2192
+ const uitkomsten = [];
2193
+ let stopreden = "leeg";
2194
+ let opgepakt = 0;
2195
+ while (true) {
2196
+ if (opgepakt >= max) {
2197
+ stopreden = "max";
2198
+ break;
2199
+ }
2200
+ if (Date.now() - rondeStart >= RONDE_BUDGET_MS) {
2201
+ stopreden = "tijd";
2202
+ break;
2203
+ }
2204
+ const job = await claimEen(db);
2205
+ if (!job) {
2206
+ stopreden = "leeg";
2207
+ break;
2208
+ }
2209
+ opgepakt++;
2133
2210
  try {
2134
2211
  const { eigenBasisUrl } = await import("./runnerRegistry.server.js");
2135
2212
  const { MODULE_VERSIE } = await import("./shellPoort.js");
2136
2213
  await db
2137
2214
  .from("print_jobs")
2138
2215
  .update({ verwerkt_door_runner: eigenBasisUrl(), verwerkt_met_versie: MODULE_VERSIE })
2139
- .in("id", jobs.map((j) => j.id));
2216
+ .eq("id", job.id);
2140
2217
  }
2141
2218
  catch {
2142
2219
  /* vastleggen mag het printen nooit blokkeren */
2143
2220
  }
2144
- }
2145
- const uitkomsten = await metPool(jobs, JOB_PARALLEL, async (job) => {
2146
2221
  try {
2147
- return await verwerkJob(db, job);
2222
+ uitkomsten.push(await verwerkJob(db, job));
2148
2223
  }
2149
2224
  catch (e) {
2150
2225
  console.error(`[printwachtrij] opdracht ${job.id} viel onverwacht uit: ${e instanceof Error ? e.message : "onbekend"}`);
2151
- return "mislukt";
2226
+ uitkomsten.push("mislukt");
2152
2227
  }
2153
- });
2228
+ }
2229
+ const jobs = { length: opgepakt };
2154
2230
  const resultaat = {
2155
2231
  opgepakt: jobs.length,
2156
2232
  verstuurd: uitkomsten.filter((u) => u === "verstuurd").length,
@@ -2160,10 +2236,12 @@ export async function verwerkWachtrij(db, max = 5) {
2160
2236
  bijgewerkt: await synchroniseerStatussen(db),
2161
2237
  };
2162
2238
  // Alleen wanneer de batchlimiet vol zat kan er nog werk klaarstaan.
2163
- vervolgNodig = jobs.length >= max;
2239
+ vervolgNodig = stopreden !== "leeg";
2240
+ console.info(`[printwachtrij] ronde_klaar ${JSON.stringify({ opdrachten: opgepakt, ms: Date.now() - rondeStart, stopreden, eind: new Date().toISOString() })}`);
2164
2241
  return resultaat;
2165
2242
  }
2166
2243
  finally {
2244
+ stopCodelijstRonde();
2167
2245
  const { error } = await db.rpc("release_print_queue_lease", {
2168
2246
  _naam: LEASE_NAAM,
2169
2247
  _houder: houder,
@@ -2172,7 +2250,7 @@ export async function verwerkWachtrij(db, max = 5) {
2172
2250
  console.warn(`[printwachtrij] slot vrijgeven mislukt: ${error.message}`);
2173
2251
  if (vervolgNodig) {
2174
2252
  try {
2175
- await wekVervolgRonde(db);
2253
+ await wekVervolgRonde(db, opties.origin ?? null);
2176
2254
  }
2177
2255
  catch (fout) {
2178
2256
  const melding = fout instanceof Error ? fout.message : "onbekend";
@@ -2219,7 +2297,7 @@ export function stickerSoortVoor(documenttype, layout, floriday, klaar) {
2219
2297
  const cfg = (layout?.config ?? {});
2220
2298
  if (cfg.eigen_traysticker)
2221
2299
  return "eigen_traysticker";
2222
- if (cfg.eigen_sticker)
2300
+ if (cfg.eigen_sticker || String(documenttype) === "eigen_sticker")
2223
2301
  return "eigen_sticker";
2224
2302
  const tray = String(documenttype) === "traysticker";
2225
2303
  if (floriday || (klaar && tray))
@@ -2299,6 +2377,29 @@ function printerDpi(printer) {
2299
2377
  const d = Number(f?.dpi ?? (Array.isArray(f?.dpis) ? String(f.dpis[0]).split("x")[0] : NaN));
2300
2378
  return d > 0 ? d : null;
2301
2379
  }
2380
+ /**
2381
+ * Waarom deze printer, vastgelegd in dezelfde update als extern_job_id/status
2382
+ * (print_jobs.printer_keuze). Zo spreken status en uitleg elkaar nooit tegen.
2383
+ */
2384
+ async function printerKeuzeVoor(db, job, printer, uitwijk) {
2385
+ let regels = [];
2386
+ try {
2387
+ const { data } = await db
2388
+ .from("print_job_logs")
2389
+ .select("detail")
2390
+ .eq("job_id", String(job.id))
2391
+ .in("gebeurtenis", ["waarom", "etiket", "route"])
2392
+ .order("created_at", { ascending: true });
2393
+ regels = arr(data).map((r) => String(r.detail ?? "")).filter(Boolean);
2394
+ }
2395
+ catch { /* uitleg is informatief */ }
2396
+ const naam = String(printer.weergavenaam || printer.naam || "Printer");
2397
+ if (uitwijk?.reden)
2398
+ regels.push(uitwijk.reden);
2399
+ if (!regels.length)
2400
+ regels.push(`Verstuurd naar ${naam}.`);
2401
+ return { printer_id: printer.id ?? null, printer_naam: naam, regels, vastgelegd_op: new Date().toISOString() };
2402
+ }
2302
2403
  /** Legt vast wat er met de sticker gebeurd is (veldnamen voor Pickschermen 1.0.61). */
2303
2404
  async function legUitwijkVast(db, job, u) {
2304
2405
  // Pickschermen 1.0.61 leest uitwijkroute/printerNaam/schaal/reden; de
@@ -0,0 +1,15 @@
1
+ export type PrinterKeuze = {
2
+ printer_id: string | null;
3
+ printer_naam: string | null;
4
+ regels: string[];
5
+ vastgelegd_op: string;
6
+ };
7
+ export declare function leesPrinterKeuze(v: unknown): PrinterKeuze | null;
8
+ /** Een opdracht die al bij de printer is (of klaar is), is nooit "nog niet bepaald". */
9
+ export declare function waaromTekst(job: {
10
+ status?: string | null;
11
+ extern_job_id?: unknown;
12
+ foutmelding?: string | null;
13
+ printer_keuze?: unknown;
14
+ }, printerNaam: string | null, logRegels: string[]): string;
15
+ //# sourceMappingURL=printerKeuze.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"printerKeuze.d.ts","sourceRoot":"","sources":["../../src/lib/printerKeuze.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,YAAY,GAAG;IACzB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,OAAO,GAAG,YAAY,GAAG,IAAI,CAUhE;AAED,wFAAwF;AACxF,wBAAgB,WAAW,CACzB,GAAG,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,aAAa,CAAC,EAAE,OAAO,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,aAAa,CAAC,EAAE,OAAO,CAAA;CAAE,EAC9G,WAAW,EAAE,MAAM,GAAG,IAAI,EAC1B,SAAS,EAAE,MAAM,EAAE,GAClB,MAAM,CASR"}
@@ -0,0 +1,32 @@
1
+ // ============================================================================
2
+ // src/lib/printerKeuze.ts — "Waarom deze printer?" uit één bron: de rij zelf.
3
+ // De wachtrij schrijft print_jobs.printer_keuze in dezelfde update als de
4
+ // status/extern_job_id, dus status en uitleg kunnen elkaar niet tegenspreken.
5
+ // ============================================================================
6
+ export function leesPrinterKeuze(v) {
7
+ if (!v || typeof v !== "object")
8
+ return null;
9
+ const o = v;
10
+ const regels = Array.isArray(o["regels"]) ? o["regels"].filter((r) => typeof r === "string" && !!r) : [];
11
+ return {
12
+ printer_id: typeof o["printer_id"] === "string" ? o["printer_id"] : null,
13
+ printer_naam: typeof o["printer_naam"] === "string" ? o["printer_naam"] : null,
14
+ regels,
15
+ vastgelegd_op: typeof o["vastgelegd_op"] === "string" ? o["vastgelegd_op"] : "",
16
+ };
17
+ }
18
+ /** Een opdracht die al bij de printer is (of klaar is), is nooit "nog niet bepaald". */
19
+ export function waaromTekst(job, printerNaam, logRegels) {
20
+ const k = leesPrinterKeuze(job.printer_keuze);
21
+ if (k && k.regels.length)
22
+ return k.regels.join("\n");
23
+ if (logRegels.length)
24
+ return logRegels.join("\n");
25
+ const afgeleverd = job.status === "voltooid" || !!job.extern_job_id;
26
+ const naam = k?.printer_naam ?? printerNaam;
27
+ if (afgeleverd)
28
+ return naam ? `Verstuurd naar ${naam}.` : "Verstuurd naar de printer; de uitleg is bij deze oudere opdracht niet bewaard.";
29
+ if (job.status === "mislukt")
30
+ return job.foutmelding ?? "Er is niets geprint.";
31
+ return "Nog niet bepaald: de opdracht is nog niet verwerkt.";
32
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"queueTick.d.ts","sourceRoot":"","sources":["../../src/lib/queueTick.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAE5D,KAAK,EAAE,GAAG,cAAc,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;AAiBxC;;;;;;;;GAQG;AACH,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,GAAG,OAAO,CAAC,QAAQ,CAAC,CAmGzF"}
1
+ {"version":3,"file":"queueTick.d.ts","sourceRoot":"","sources":["../../src/lib/queueTick.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAE5D,KAAK,EAAE,GAAG,cAAc,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;AAiBxC;;;;;;;;GAQG;AACH,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,GAAG,OAAO,CAAC,QAAQ,CAAC,CAsGzF"}
@@ -100,7 +100,15 @@ export async function behandelQueueTick(request, supabase) {
100
100
  console.warn("[printwachtrij] standaardlayouts aanmaken mislukt:", e);
101
101
  }
102
102
  try {
103
- const resultaat = await verwerkWachtrij(supabase, 5);
103
+ // Eén opdracht tegelijk, doorwerken tot RONDE_BUDGET_MS; max is alleen een bovengrens.
104
+ let origin = null;
105
+ try {
106
+ origin = new URL(request.url).origin;
107
+ }
108
+ catch {
109
+ origin = null;
110
+ }
111
+ const resultaat = await verwerkWachtrij(supabase, 20, { origin });
104
112
  return Response.json({ ok: true, ...resultaat, ontbrekend });
105
113
  }
106
114
  catch (e) {
@@ -13,4 +13,14 @@ export declare function meldRunnerAan(db: PrintDb): Promise<void>;
13
13
  export declare function controleerShellPoort(db: PrintDb): Promise<import("./shellPoort.js").PoortUitkomst>;
14
14
  /** Controleert of de meegegeven sleutel bij een geregistreerde shell hoort. */
15
15
  export declare function isRunnerSleutel(db: PrintDb, sleutel: string): Promise<boolean>;
16
+ /**
17
+ * Wekt direct een nieuwe ronde op de eigen route, met de eigen origin van de
18
+ * aanvraag en de bestaande runner-sleutel (print_queue_runners.tick_sleutel).
19
+ * Geen host in code. Lukt het niet, dan meldt hij waarom; de aanroeper valt
20
+ * dan terug op wake_print_queue (pg_net).
21
+ */
22
+ export declare function wekEigenRonde(db: PrintDb, origin: string | null): Promise<{
23
+ ok: boolean;
24
+ reden: string;
25
+ }>;
16
26
  //# sourceMappingURL=runnerRegistry.server.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"runnerRegistry.server.d.ts","sourceRoot":"","sources":["../../src/lib/runnerRegistry.server.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAMnD,8DAA8D;AAC9D,wBAAgB,aAAa,IAAI,MAAM,GAAG,IAAI,CAS7C;AAED;;;GAGG;AACH,wBAAsB,aAAa,CAAC,EAAE,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAmC9D;AAED;;;GAGG;AACH,wBAAsB,oBAAoB,CAAC,EAAE,EAAE,OAAO,iDAcrD;AAED,+EAA+E;AAC/E,wBAAsB,eAAe,CAAC,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAapF"}
1
+ {"version":3,"file":"runnerRegistry.server.d.ts","sourceRoot":"","sources":["../../src/lib/runnerRegistry.server.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAMnD,8DAA8D;AAC9D,wBAAgB,aAAa,IAAI,MAAM,GAAG,IAAI,CAS7C;AAED;;;GAGG;AACH,wBAAsB,aAAa,CAAC,EAAE,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAmC9D;AAED;;;GAGG;AACH,wBAAsB,oBAAoB,CAAC,EAAE,EAAE,OAAO,iDAcrD;AAED,+EAA+E;AAC/E,wBAAsB,eAAe,CAAC,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAapF;AAaD;;;;;GAKG;AACH,wBAAsB,aAAa,CAAC,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAkC/G"}
@@ -96,3 +96,58 @@ export async function isRunnerSleutel(db, sleutel) {
96
96
  return false;
97
97
  }
98
98
  }
99
+ /** waitUntil van de Worker-runtime, als die er is (anders null). */
100
+ async function zoekWaitUntil() {
101
+ try {
102
+ const naam = "cloudflare:workers";
103
+ const m = (await import(/* @vite-ignore */ naam));
104
+ return typeof m?.waitUntil === "function" ? m.waitUntil : null;
105
+ }
106
+ catch {
107
+ return null;
108
+ }
109
+ }
110
+ /**
111
+ * Wekt direct een nieuwe ronde op de eigen route, met de eigen origin van de
112
+ * aanvraag en de bestaande runner-sleutel (print_queue_runners.tick_sleutel).
113
+ * Geen host in code. Lukt het niet, dan meldt hij waarom; de aanroeper valt
114
+ * dan terug op wake_print_queue (pg_net).
115
+ */
116
+ export async function wekEigenRonde(db, origin) {
117
+ const basis = (origin ?? eigenBasisUrl() ?? "").replace(/\/+$/, "");
118
+ if (!basis)
119
+ return { ok: false, reden: "eigen adres onbekend" };
120
+ const norm = (u) => u.trim().toLowerCase().replace(/\/+$/, "");
121
+ let sleutel = "";
122
+ try {
123
+ const { data } = await db.from("print_queue_runners").select("basis_url, tick_sleutel").eq("actief", true);
124
+ const rij = (Array.isArray(data) ? data : []).find((r) => norm(String(r.basis_url ?? "")) === norm(basis));
125
+ sleutel = String(rij?.tick_sleutel ?? "");
126
+ }
127
+ catch {
128
+ sleutel = "";
129
+ }
130
+ if (!sleutel)
131
+ return { ok: false, reden: "geen runner-sleutel voor dit adres" };
132
+ const waitUntil = await zoekWaitUntil();
133
+ // Zonder waitUntil zou de aanroep afbreken zodra deze ronde antwoordt (en
134
+ // daarmee ook de gewekte ronde). Dan liever het vangnet.
135
+ if (!waitUntil)
136
+ return { ok: false, reden: "runtime zonder waitUntil" };
137
+ const start = Date.now();
138
+ waitUntil(fetch(`${basis}/api/public/print/queue-tick`, {
139
+ method: "POST",
140
+ headers: { "Content-Type": "application/json", apikey: sleutel },
141
+ body: JSON.stringify({ bron: "vervolgronde" }),
142
+ })
143
+ .then(async (r) => {
144
+ console.info(`[printwachtrij] vervolgronde direct: HTTP ${r.status} na ${Date.now() - start} ms`);
145
+ if (!r.ok)
146
+ await db.rpc("wake_print_queue", { _min_interval_ms: 1000 });
147
+ })
148
+ .catch(async (e) => {
149
+ console.warn(`[printwachtrij] vervolgronde direct mislukt: ${e instanceof Error ? e.message : "onbekend"}; vangnet wake_print_queue.`);
150
+ await db.rpc("wake_print_queue", { _min_interval_ms: 1000 });
151
+ }));
152
+ return { ok: true, reden: "" };
153
+ }
@@ -332,7 +332,7 @@ export function usePrintJobs() {
332
332
  setLoading(true);
333
333
  const { data } = await supabase
334
334
  .from("print_jobs")
335
- .select("id, layout_id, printer_id, documenttype, documentnaam, status, paginas, kopieen, foutmelding, is_herprint, gebruiker_naam, created_at, voltooid_op, extern_job_id, gebruikte_layout_id, gebruikte_bron, payload, lokale_pdf_status, lokaal_opgeslagen_op, lokale_pdf_fout, lokale_pdf_print_plek_id, artefact_pad, artefact_sha256, artefact_bytes, verwerkingsfase, foutcode, printnode_status, printnode_bericht, printnode_status_op, uitwijk")
335
+ .select("id, layout_id, printer_id, documenttype, documentnaam, status, paginas, kopieen, foutmelding, is_herprint, gebruiker_naam, created_at, voltooid_op, extern_job_id, gebruikte_layout_id, gebruikte_bron, payload, lokale_pdf_status, lokaal_opgeslagen_op, lokale_pdf_fout, lokale_pdf_print_plek_id, artefact_pad, artefact_sha256, artefact_bytes, verwerkingsfase, foutcode, printnode_status, printnode_bericht, printnode_status_op, uitwijk, printer_keuze")
336
336
  .order("created_at", { ascending: false })
337
337
  .limit(200);
338
338
  setJobs(arr(data));
@@ -1,2 +1,2 @@
1
- export declare const PAKKET_VERSIE = "1.69.13";
1
+ export declare const PAKKET_VERSIE = "1.69.19";
2
2
  //# sourceMappingURL=versie.generated.d.ts.map
@@ -1,2 +1,2 @@
1
1
  // Gegenereerd door scripts/schrijf-versie.cjs uit package.json. Niet met de hand wijzigen.
2
- export const PAKKET_VERSIE = "1.69.13";
2
+ export const PAKKET_VERSIE = "1.69.19";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flowselections/layout-maker",
3
- "version": "1.69.13",
3
+ "version": "1.69.19",
4
4
  "flowselections": {
5
5
  "moduleId": "layout-maker",
6
6
  "pages": [
@@ -0,0 +1,5 @@
1
+ -- Objecten die in zeta bestaan maar in geen enkele migratie van deze module
2
+ -- staan (gevonden bij de vergelijking van 1.69.16). Alleen additief.
3
+ ALTER TABLE public.print_agents ADD COLUMN IF NOT EXISTS pairing_code_hash text;
4
+ ALTER TABLE public.print_agents ADD COLUMN IF NOT EXISTS pairing_expires_at timestamptz;
5
+ ALTER TABLE public.print_agents ADD COLUMN IF NOT EXISTS pairing_used_at timestamptz;