@flowselections/layout-maker 1.55.1 → 1.57.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 (65) hide show
  1. package/dist-lib/components/LayoutVerwijzingenKaart.d.ts +4 -0
  2. package/dist-lib/components/LayoutVerwijzingenKaart.d.ts.map +1 -0
  3. package/dist-lib/components/LayoutVerwijzingenKaart.js +59 -0
  4. package/dist-lib/components/LayoutsPage.d.ts.map +1 -1
  5. package/dist-lib/components/LayoutsPage.js +4 -13
  6. package/dist-lib/components/PrintQueuePage.d.ts.map +1 -1
  7. package/dist-lib/components/PrintQueuePage.js +16 -1
  8. package/dist-lib/components/PrintenInstellenWizard.d.ts +6 -0
  9. package/dist-lib/components/PrintenInstellenWizard.d.ts.map +1 -0
  10. package/dist-lib/components/PrintenInstellenWizard.js +89 -0
  11. package/dist-lib/components/PrintenPage.d.ts +3 -1
  12. package/dist-lib/components/PrintenPage.d.ts.map +1 -1
  13. package/dist-lib/components/PrintenPage.js +4 -2
  14. package/dist-lib/components/PrintplekDetailPage.d.ts.map +1 -1
  15. package/dist-lib/components/PrintplekDetailPage.js +2 -0
  16. package/dist-lib/components/TestprintDialog.js +2 -2
  17. package/dist-lib/components/WerkplekOmzettingStap.d.ts +10 -0
  18. package/dist-lib/components/WerkplekOmzettingStap.d.ts.map +1 -0
  19. package/dist-lib/components/WerkplekOmzettingStap.js +46 -0
  20. package/dist-lib/components/WerkplekVraag.d.ts +4 -0
  21. package/dist-lib/components/WerkplekVraag.d.ts.map +1 -0
  22. package/dist-lib/components/WerkplekVraag.js +26 -0
  23. package/dist-lib/components/WerkplekkenTab.d.ts +7 -0
  24. package/dist-lib/components/WerkplekkenTab.d.ts.map +1 -0
  25. package/dist-lib/components/WerkplekkenTab.js +143 -0
  26. package/dist-lib/index.d.ts +15 -0
  27. package/dist-lib/index.d.ts.map +1 -1
  28. package/dist-lib/index.js +12 -0
  29. package/dist-lib/integrations/supabase/types.d.ts +415 -0
  30. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  31. package/dist-lib/lib/beheerder.d.ts +2 -0
  32. package/dist-lib/lib/beheerder.d.ts.map +1 -0
  33. package/dist-lib/lib/beheerder.js +18 -0
  34. package/dist-lib/lib/etiketControle.d.ts +36 -0
  35. package/dist-lib/lib/etiketControle.d.ts.map +1 -0
  36. package/dist-lib/lib/etiketControle.js +89 -0
  37. package/dist-lib/lib/layoutVerwijzingen.d.ts +50 -0
  38. package/dist-lib/lib/layoutVerwijzingen.d.ts.map +1 -0
  39. package/dist-lib/lib/layoutVerwijzingen.js +101 -0
  40. package/dist-lib/lib/papierFormaten.d.ts +3 -3
  41. package/dist-lib/lib/papierFormaten.d.ts.map +1 -1
  42. package/dist-lib/lib/papierFormaten.js +6 -6
  43. package/dist-lib/lib/printQueue.server.d.ts +6 -0
  44. package/dist-lib/lib/printQueue.server.d.ts.map +1 -1
  45. package/dist-lib/lib/printQueue.server.js +149 -23
  46. package/dist-lib/lib/printRoutering.d.ts +14 -1
  47. package/dist-lib/lib/printRoutering.d.ts.map +1 -1
  48. package/dist-lib/lib/printRoutering.js +6 -0
  49. package/dist-lib/lib/printnodeStatusTekst.d.ts +2 -0
  50. package/dist-lib/lib/printnodeStatusTekst.d.ts.map +1 -0
  51. package/dist-lib/lib/printnodeStatusTekst.js +13 -0
  52. package/dist-lib/lib/productiebonHtml.js +1 -1
  53. package/dist-lib/lib/testprint.d.ts +6 -0
  54. package/dist-lib/lib/testprint.d.ts.map +1 -1
  55. package/dist-lib/lib/testprint.js +11 -7
  56. package/dist-lib/lib/waaromPrinter.d.ts +7 -0
  57. package/dist-lib/lib/waaromPrinter.d.ts.map +1 -0
  58. package/dist-lib/lib/waaromPrinter.js +35 -0
  59. package/dist-lib/lib/werkplekOmzetting.d.ts +22 -0
  60. package/dist-lib/lib/werkplekOmzetting.d.ts.map +1 -0
  61. package/dist-lib/lib/werkplekOmzetting.js +42 -0
  62. package/dist-lib/lib/werkplekken.d.ts +73 -0
  63. package/dist-lib/lib/werkplekken.d.ts.map +1 -0
  64. package/dist-lib/lib/werkplekken.js +156 -0
  65. package/package.json +1 -1
@@ -0,0 +1,2 @@
1
+ export declare function useIsBeheerder(): boolean | null;
2
+ //# sourceMappingURL=beheerder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"beheerder.d.ts","sourceRoot":"","sources":["../../src/lib/beheerder.ts"],"names":[],"mappings":"AAIA,wBAAgB,cAAc,IAAI,OAAO,GAAG,IAAI,CAiB/C"}
@@ -0,0 +1,18 @@
1
+ // Dezelfde beheerderscontrole als de Shell: has_role admin (en superadmin).
2
+ import { useEffect, useState } from "react";
3
+ import { supabase } from "./supabaseClient.js";
4
+ export function useIsBeheerder() {
5
+ const [beheerder, setBeheerder] = useState(null);
6
+ useEffect(() => {
7
+ void (async () => {
8
+ const { data } = await supabase.auth.getUser();
9
+ const uid = data?.user?.id;
10
+ if (!uid)
11
+ return setBeheerder(false);
12
+ const rpc = supabase.rpc;
13
+ const r = await Promise.all(["admin", "superadmin"].map((rol) => rpc.call(supabase, "has_role", { _user_id: uid, _role: rol }).then((x) => x.data === true)));
14
+ setBeheerder(r.some(Boolean));
15
+ })();
16
+ }, []);
17
+ return beheerder;
18
+ }
@@ -0,0 +1,36 @@
1
+ export interface GeladenEtiket {
2
+ /** Naam zoals PrintNode het formaat kent (gaat mee als optie `paper`). */
3
+ naam: string;
4
+ breedte_mm: number;
5
+ hoogte_mm: number;
6
+ }
7
+ export type EtiketUitkomst = {
8
+ ok: true;
9
+ paper: string;
10
+ rotate: 0 | 90;
11
+ uitleg: string;
12
+ } | {
13
+ ok: false;
14
+ code: "FORMAAT_PAST_NIET" | "GEEN_GELADEN_ETIKET";
15
+ bericht: string;
16
+ };
17
+ export declare const maatTekst: (b: number, h: number) => string;
18
+ /** Leest een opgeslagen geladen etiket; ongeldig = null. */
19
+ export declare function leesGeladenEtiket(v: unknown): GeladenEtiket | null;
20
+ /** PrintNode-formaten (tienden mm) als keuzelijst in mm, zonder lege maten. */
21
+ export declare function etiketOpties(papers: unknown): GeladenEtiket[];
22
+ /**
23
+ * Past een document van breedte×hoogte (mm) op het geladen etiket? Draaien
24
+ * mag (liggend etiket, staande layout of andersom), schalen nooit.
25
+ */
26
+ export declare function etiketPast(doc: {
27
+ breedte_mm: number;
28
+ hoogte_mm: number;
29
+ soortLabel: string;
30
+ }, geladen: GeladenEtiket | null, printerNaam: string): EtiketUitkomst;
31
+ /** Leest de paginagrootte (mm) van de eerste pagina uit een PDF (MediaBox). */
32
+ export declare function pdfPaginaMm(pdfBase64: string): {
33
+ breedte_mm: number;
34
+ hoogte_mm: number;
35
+ } | null;
36
+ //# sourceMappingURL=etiketControle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"etiketControle.d.ts","sourceRoot":"","sources":["../../src/lib/etiketControle.ts"],"names":[],"mappings":"AASA,MAAM,WAAW,aAAa;IAC5B,0EAA0E;IAC1E,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,MAAM,cAAc,GACtB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC3D;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,mBAAmB,GAAG,qBAAqB,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAKtF,eAAO,MAAM,SAAS,GAAI,GAAG,MAAM,EAAE,GAAG,MAAM,WAA2B,CAAC;AAE1E,4DAA4D;AAC5D,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,OAAO,GAAG,aAAa,GAAG,IAAI,CAQlE;AAED,+EAA+E;AAC/E,wBAAgB,YAAY,CAAC,MAAM,EAAE,OAAO,GAAG,aAAa,EAAE,CAW7D;AAED;;;GAGG;AACH,wBAAgB,UAAU,CACxB,GAAG,EAAE;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,EAClE,OAAO,EAAE,aAAa,GAAG,IAAI,EAC7B,WAAW,EAAE,MAAM,GAClB,cAAc,CAgChB;AAED,+EAA+E;AAC/E,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAa/F"}
@@ -0,0 +1,89 @@
1
+ // ============================================================================
2
+ // src/lib/etiketControle.ts — past het document op het geladen etiket?
3
+ //
4
+ // PrintNode meldt per printer alle formaten die de driver kent
5
+ // (capabilities.papers, in tienden van een millimeter). Dat zegt niets over de
6
+ // rol die er écht in zit. Daarom kiest de beheerder per labelprinter het
7
+ // "geladen etiket" en controleren we daartegen. Nooit stil schalen.
8
+ // ============================================================================
9
+ const MARGE_MM = 2;
10
+ const mm = (n) => Math.round(n * 10) / 10;
11
+ export const maatTekst = (b, h) => `${mm(b)}×${mm(h)} mm`;
12
+ /** Leest een opgeslagen geladen etiket; ongeldig = null. */
13
+ export function leesGeladenEtiket(v) {
14
+ if (!v || typeof v !== "object")
15
+ return null;
16
+ const r = v;
17
+ const b = Number(r.breedte_mm);
18
+ const h = Number(r.hoogte_mm);
19
+ const naam = String(r.naam ?? "").trim();
20
+ if (!naam || !(b > 0) || !(h > 0))
21
+ return null;
22
+ return { naam, breedte_mm: b, hoogte_mm: h };
23
+ }
24
+ /** PrintNode-formaten (tienden mm) als keuzelijst in mm, zonder lege maten. */
25
+ export function etiketOpties(papers) {
26
+ if (!papers || typeof papers !== "object" || Array.isArray(papers))
27
+ return [];
28
+ const uit = [];
29
+ for (const [naam, maat] of Object.entries(papers)) {
30
+ if (!Array.isArray(maat) || maat.length < 2)
31
+ continue;
32
+ const b = Number(maat[0]);
33
+ const h = Number(maat[1]);
34
+ if (!(b > 0) || !(h > 0))
35
+ continue;
36
+ uit.push({ naam: naam.trim() ? naam : naam, breedte_mm: b / 10, hoogte_mm: h / 10 });
37
+ }
38
+ return uit.sort((a, b) => a.breedte_mm * a.hoogte_mm - b.breedte_mm * b.hoogte_mm);
39
+ }
40
+ /**
41
+ * Past een document van breedte×hoogte (mm) op het geladen etiket? Draaien
42
+ * mag (liggend etiket, staande layout of andersom), schalen nooit.
43
+ */
44
+ export function etiketPast(doc, geladen, printerNaam) {
45
+ if (!geladen) {
46
+ return {
47
+ ok: false,
48
+ code: "GEEN_GELADEN_ETIKET",
49
+ bericht: `Stel eerst het geladen etiket in voor labelprinter ${printerNaam}.`,
50
+ };
51
+ }
52
+ const { breedte_mm: b, hoogte_mm: h } = doc;
53
+ const recht = b <= geladen.breedte_mm + MARGE_MM && h <= geladen.hoogte_mm + MARGE_MM;
54
+ const gedraaid = b <= geladen.hoogte_mm + MARGE_MM && h <= geladen.breedte_mm + MARGE_MM;
55
+ if (!recht && !gedraaid) {
56
+ return {
57
+ ok: false,
58
+ code: "FORMAAT_PAST_NIET",
59
+ bericht: `${doc.soortLabel} ${maatTekst(b, h)} past niet op het geladen etiket ${maatTekst(geladen.breedte_mm, geladen.hoogte_mm)} van labelprinter ${printerNaam}.`,
60
+ };
61
+ }
62
+ const docLiggend = b > h;
63
+ const etiketLiggend = geladen.breedte_mm > geladen.hoogte_mm;
64
+ const rotate = !recht || docLiggend !== etiketLiggend ? 90 : 0;
65
+ return {
66
+ ok: true,
67
+ paper: geladen.naam,
68
+ rotate,
69
+ uitleg: `geladen etiket ${geladen.naam.trim()} (${maatTekst(geladen.breedte_mm, geladen.hoogte_mm)})${rotate ? ", gedraaid" : ""}`,
70
+ };
71
+ }
72
+ /** Leest de paginagrootte (mm) van de eerste pagina uit een PDF (MediaBox). */
73
+ export function pdfPaginaMm(pdfBase64) {
74
+ try {
75
+ const bin = atob(pdfBase64.slice(0, 400000));
76
+ const m = /\/MediaBox\s*\[\s*([-\d.]+)\s+([-\d.]+)\s+([-\d.]+)\s+([-\d.]+)\s*\]/.exec(bin);
77
+ if (!m)
78
+ return null;
79
+ const w = Math.abs(Number(m[3]) - Number(m[1]));
80
+ const h = Math.abs(Number(m[4]) - Number(m[2]));
81
+ if (!(w > 0) || !(h > 0))
82
+ return null;
83
+ const pt = 25.4 / 72;
84
+ return { breedte_mm: w * pt, hoogte_mm: h * pt };
85
+ }
86
+ catch {
87
+ return null;
88
+ }
89
+ }
@@ -0,0 +1,50 @@
1
+ export interface LayoutGebruik {
2
+ id: string;
3
+ naam: string;
4
+ documenttype: string;
5
+ bron: string;
6
+ actief: boolean;
7
+ is_standaard: boolean;
8
+ gebruik: Record<string, number>;
9
+ }
10
+ export interface LayoutVerwijzing {
11
+ tabel: string;
12
+ kolom: string;
13
+ pad: string[];
14
+ rij_id: string;
15
+ rij_naam: string | null;
16
+ rij_actief: boolean | null;
17
+ layout_id: string;
18
+ }
19
+ /** Tabellen die bij deze module horen: alleen die mag je hier omzetten. */
20
+ export declare const EIGEN_TABELLEN: readonly ["print_rules", "printer_mappings", "document_bronregels"];
21
+ export declare const isEigenVerwijzing: (v: Pick<LayoutVerwijzing, "tabel">) => boolean;
22
+ export interface VerwijzingPlek {
23
+ module: string;
24
+ scherm: string;
25
+ /** Relatieve link binnen de Shell; geen vast adres. */
26
+ link: string | null;
27
+ bekend: boolean;
28
+ }
29
+ export declare function plekVanVerwijzing(v: Pick<LayoutVerwijzing, "tabel" | "kolom" | "pad">): VerwijzingPlek;
30
+ export interface SoortOverzicht {
31
+ documenttype: string;
32
+ layouts: (LayoutGebruik & {
33
+ verwijzingen: LayoutVerwijzing[];
34
+ totaal30: number;
35
+ })[];
36
+ /** Er zijn meerdere actieve layouts, of instellingen wijzen naar verschillende layouts. */
37
+ conflict: boolean;
38
+ }
39
+ /** Bouwt per documentsoort het overzicht. Kiest nooit zelf een standaard. */
40
+ export declare function bouwOverzicht(layoutsIn: unknown, verwijzingenIn: unknown): SoortOverzicht[];
41
+ export declare function useLayoutVerwijzingen(): {
42
+ overzicht: SoortOverzicht[];
43
+ loading: boolean;
44
+ fout: string | null;
45
+ reload: () => Promise<void>;
46
+ };
47
+ /** Zet één verwijzing van deze module om. De database weigert andere modules. */
48
+ export declare function zetVerwijzingOm(v: LayoutVerwijzing, nieuwLayoutId: string): Promise<void>;
49
+ export declare const BRON_LABEL: Record<string, string>;
50
+ //# sourceMappingURL=layoutVerwijzingen.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layoutVerwijzingen.d.ts","sourceRoot":"","sources":["../../src/lib/layoutVerwijzingen.ts"],"names":[],"mappings":"AAaA,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,OAAO,CAAC;IAChB,YAAY,EAAE,OAAO,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACjC;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,EAAE,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,UAAU,EAAE,OAAO,GAAG,IAAI,CAAC;IAC3B,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,2EAA2E;AAC3E,eAAO,MAAM,cAAc,qEAAsE,CAAC;AAClG,eAAO,MAAM,iBAAiB,GAAI,GAAG,IAAI,CAAC,gBAAgB,EAAE,OAAO,CAAC,YACX,CAAC;AAyB1D,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,uDAAuD;IACvD,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,IAAI,CAAC,gBAAgB,EAAE,OAAO,GAAG,OAAO,GAAG,KAAK,CAAC,GAAG,cAAc,CAUtG;AAED,MAAM,WAAW,cAAc;IAC7B,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,CAAC,aAAa,GAAG;QAAE,YAAY,EAAE,gBAAgB,EAAE,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,EAAE,CAAC;IACpF,2FAA2F;IAC3F,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,6EAA6E;AAC7E,wBAAgB,aAAa,CAAC,SAAS,EAAE,OAAO,EAAE,cAAc,EAAE,OAAO,GAAG,cAAc,EAAE,CAoB3F;AAED,wBAAgB,qBAAqB;;;;;EAiBpC;AAED,iFAAiF;AACjF,wBAAsB,eAAe,CAAC,CAAC,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAM/F;AAED,eAAO,MAAM,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAM7C,CAAC"}
@@ -0,0 +1,101 @@
1
+ // ============================================================================
2
+ // src/lib/layoutVerwijzingen.ts — welke instellingen (op id) naar welke layout
3
+ // wijzen, en hoe vaak elke layout de laatste 30 dagen is gebruikt. Alleen
4
+ // verwijzingen van Layouts en printers zelf mogen hier worden omgezet;
5
+ // instellingen van andere modules worden alleen getoond met een link.
6
+ // ============================================================================
7
+ import { useCallback, useEffect, useState } from "react";
8
+ import { supabase } from "./supabaseClient.js";
9
+ const db = supabase;
10
+ const arr = (v) => (Array.isArray(v) ? v : []);
11
+ /** Tabellen die bij deze module horen: alleen die mag je hier omzetten. */
12
+ export const EIGEN_TABELLEN = ["print_rules", "printer_mappings", "document_bronregels"];
13
+ export const isEigenVerwijzing = (v) => EIGEN_TABELLEN.includes(v.tabel);
14
+ /** Eigenaar per tabelvoorvoegsel. Alleen structuur, geen gegevens van een Shell. */
15
+ const MODULES = [
16
+ { voorvoegsel: "logistics_", info: { module: "Logistiek Engine", moduleId: "logistiek-engine" } },
17
+ { voorvoegsel: "pick_station", info: { module: "Pickstations", moduleId: "pickstations" } },
18
+ { voorvoegsel: "print_", info: { module: "Layouts en printers", moduleId: "layouts-printers" } },
19
+ { voorvoegsel: "printer_", info: { module: "Layouts en printers", moduleId: "layouts-printers" } },
20
+ { voorvoegsel: "document_", info: { module: "Layouts en printers", moduleId: "layouts-printers" } },
21
+ ];
22
+ /** Bekende schermnamen per JSON-pad. Onbekend → tabel en veld tonen. */
23
+ const SCHERMEN = {
24
+ "logistics_rule_configs:pickstations.fields.shipping_note_layout": "Werkwijze › Pickstations › Verzendbrief voor BVO orders",
25
+ "logistics_rule_configs:pickstations.fields.packing_slip_layout": "Werkwijze › Pickstations › Pakbon",
26
+ "logistics_rule_configs:pickstations.fields.packing_slip_partial_layout": "Werkwijze › Pickstations › Restpakbon",
27
+ "logistics_rule_configs:pickstations.fields.production_note_layout": "Werkwijze › Pickstations › Productiebon",
28
+ "logistics_rule_configs:pickstations.fields.sticker_layout": "Werkwijze › Pickstations › Sticker",
29
+ "printer_mappings:layout_id": "Printen › Standaardregels",
30
+ "print_rules:layout_id": "Printen › Printregels",
31
+ "document_bronregels:layout_id": "Documenten › Bronregels",
32
+ };
33
+ export function plekVanVerwijzing(v) {
34
+ const info = MODULES.find((m) => v.tabel.startsWith(m.voorvoegsel))?.info ?? { module: "Andere module", moduleId: null };
35
+ const sleutel = `${v.tabel}:${v.pad.length ? v.pad.join(".") : v.kolom}`;
36
+ const scherm = SCHERMEN[sleutel];
37
+ return {
38
+ module: info.module,
39
+ scherm: scherm ?? `${v.tabel} › ${[v.kolom, ...v.pad].join(".")}`,
40
+ link: info.moduleId ? `/${info.moduleId}` : null,
41
+ bekend: Boolean(scherm),
42
+ };
43
+ }
44
+ /** Bouwt per documentsoort het overzicht. Kiest nooit zelf een standaard. */
45
+ export function bouwOverzicht(layoutsIn, verwijzingenIn) {
46
+ const layouts = arr(layoutsIn);
47
+ // Verwijzingen van de module zelf naar de standaardregels zijn geen keuze
48
+ // van een andere module; ze tellen wel mee in de lijst, niet als conflict.
49
+ const verw = arr(verwijzingenIn).map((v) => ({ ...v, pad: arr(v.pad) }));
50
+ const perSoort = new Map();
51
+ for (const l of layouts) {
52
+ const s = perSoort.get(l.documenttype) ?? { documenttype: l.documenttype, layouts: [], conflict: false };
53
+ const eigen = verw.filter((v) => v.layout_id === l.id);
54
+ const gebruik = l.gebruik && typeof l.gebruik === "object" ? l.gebruik : {};
55
+ s.layouts.push({ ...l, gebruik, verwijzingen: eigen, totaal30: Object.values(gebruik).reduce((a, b) => a + Number(b || 0), 0) });
56
+ perSoort.set(l.documenttype, s);
57
+ }
58
+ for (const s of perSoort.values()) {
59
+ const actief = s.layouts.filter((l) => l.actief);
60
+ const metModuleVerwijzing = s.layouts.filter((l) => l.verwijzingen.some((v) => !isEigenVerwijzing(v) && v.rij_actief !== false));
61
+ s.conflict = actief.length > 1 || metModuleVerwijzing.length > 1;
62
+ s.layouts.sort((a, b) => Number(b.actief) - Number(a.actief) || b.totaal30 - a.totaal30 || a.naam.localeCompare(b.naam));
63
+ }
64
+ return [...perSoort.values()].filter((s) => s.conflict).sort((a, b) => a.documenttype.localeCompare(b.documenttype));
65
+ }
66
+ export function useLayoutVerwijzingen() {
67
+ const [overzicht, setOverzicht] = useState([]);
68
+ const [loading, setLoading] = useState(true);
69
+ const [fout, setFout] = useState(null);
70
+ const reload = useCallback(async () => {
71
+ setLoading(true);
72
+ const { data, error } = await db.rpc("layout_verwijzingen");
73
+ if (error)
74
+ setFout(error.message);
75
+ else {
76
+ setFout(null);
77
+ const d = (data ?? {});
78
+ setOverzicht(bouwOverzicht(d.layouts, d.verwijzingen));
79
+ }
80
+ setLoading(false);
81
+ }, []);
82
+ useEffect(() => { void reload(); }, [reload]);
83
+ return { overzicht, loading, fout, reload };
84
+ }
85
+ /** Zet één verwijzing van deze module om. De database weigert andere modules. */
86
+ export async function zetVerwijzingOm(v, nieuwLayoutId) {
87
+ if (!isEigenVerwijzing(v))
88
+ throw new Error("Deze instelling hoort bij een andere module en wordt hier niet gewijzigd.");
89
+ const { error } = await db.rpc("layout_verwijzing_omzetten", {
90
+ _tabel: v.tabel, _rij_id: v.rij_id, _oud: v.layout_id, _nieuw: nieuwLayoutId,
91
+ });
92
+ if (error)
93
+ throw new Error(error.message);
94
+ }
95
+ export const BRON_LABEL = {
96
+ eigen_layout: "eigen layout",
97
+ extern_pdf: "Floriday-document",
98
+ render_html: "aangeleverde opmaak",
99
+ klantlayout: "klantlayout",
100
+ onbekend: "onbekend",
101
+ };
@@ -1,4 +1,4 @@
1
- /** Papierformaten van een printer: naam -> [breedte, hoogte] in 0,01 mm. */
1
+ /** Papierformaten van een printer: naam -> [breedte, hoogte] in 0,1 mm. */
2
2
  export type PrinterFormaten = Record<string, [number, number] | number[]>;
3
3
  export interface FormaatKeuze {
4
4
  /** Naam zoals de printer die kent, of null als er geen match is. */
@@ -9,8 +9,8 @@ export interface FormaatKeuze {
9
9
  uitleg: string;
10
10
  }
11
11
  /**
12
- * Zoekt de papiernaam die het dichtst bij de gevraagde maat ligt.
13
- * Marge: 2 mm per zijde. Draaiing telt mee (liggend papier mag ook).
12
+ * Zoekt de papiernaam die exact bij de gevraagde maat past (afronding max 1 mm per zijde).
13
+ * Geen exacte match -> geen papiernaam. Draaiing telt mee (liggend papier mag ook).
14
14
  */
15
15
  export declare function kiesPapier(formaten: PrinterFormaten | null | undefined, breedteMm: number, hoogteMm: number, margeMm?: number): FormaatKeuze;
16
16
  export interface PrinterFormaatOptie {
@@ -1 +1 @@
1
- {"version":3,"file":"papierFormaten.d.ts","sourceRoot":"","sources":["../../src/lib/papierFormaten.ts"],"names":[],"mappings":"AAWA,4EAA4E;AAC5E,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,EAAE,CAAC,CAAC;AAE1E,MAAM,WAAW,YAAY;IAC3B,oEAAoE;IACpE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,iDAAiD;IACjD,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC;IACf,0DAA0D;IAC1D,MAAM,EAAE,MAAM,CAAC;CAChB;AAUD;;;GAGG;AACH,wBAAgB,UAAU,CACxB,QAAQ,EAAE,eAAe,GAAG,IAAI,GAAG,SAAS,EAC5C,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,OAAO,SAAI,GACV,YAAY,CAyCd;AAQD,MAAM,WAAW,mBAAmB;IAClC,sCAAsC;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,yEAAyE;IACzE,IAAI,EAAE,MAAM,CAAC;IACb,8CAA8C;IAC9C,KAAK,EAAE,MAAM,CAAC;CACf;AAED,oEAAoE;AACpE,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,SAAI,GAAG,MAAM,CAe5F;AAED,qEAAqE;AACrE,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,eAAe,GAAG,IAAI,GAAG,SAAS,GAC3C,mBAAmB,EAAE,CAuBvB"}
1
+ {"version":3,"file":"papierFormaten.d.ts","sourceRoot":"","sources":["../../src/lib/papierFormaten.ts"],"names":[],"mappings":"AAWA,2EAA2E;AAC3E,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,EAAE,CAAC,CAAC;AAE1E,MAAM,WAAW,YAAY;IAC3B,oEAAoE;IACpE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,iDAAiD;IACjD,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC;IACf,0DAA0D;IAC1D,MAAM,EAAE,MAAM,CAAC;CAChB;AAUD;;;GAGG;AACH,wBAAgB,UAAU,CACxB,QAAQ,EAAE,eAAe,GAAG,IAAI,GAAG,SAAS,EAC5C,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,OAAO,SAAI,GACV,YAAY,CAyCd;AAQD,MAAM,WAAW,mBAAmB;IAClC,sCAAsC;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,yEAAyE;IACzE,IAAI,EAAE,MAAM,CAAC;IACb,8CAA8C;IAC9C,KAAK,EAAE,MAAM,CAAC;CACf;AAED,oEAAoE;AACpE,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,SAAI,GAAG,MAAM,CAe5F;AAED,qEAAqE;AACrE,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,eAAe,GAAG,IAAI,GAAG,SAAS,GAC3C,mBAAmB,EAAE,CAuBvB"}
@@ -2,7 +2,7 @@
2
2
  // src/lib/papierFormaten.ts — het juiste papierformaat van de printer kiezen.
3
3
  //
4
4
  // De printdienst levert per printer een lijst met papierformaten die die
5
- // printer aankan, met naam en afmeting in honderdsten van een millimeter
5
+ // printer aankan, met naam en afmeting in tienden van een millimeter
6
6
  // (A4 = [2100, 2970]). Hier zoeken we de naam die past bij de maat van onze
7
7
  // layout, zodat de opdracht op het juiste papier uitkomt.
8
8
  // ============================================================================
@@ -10,13 +10,13 @@ import { PAPIERFORMATEN } from "./documentTypes.js";
10
10
  const arrGetal = (v) => Array.isArray(v) ? v.map((n) => Number(n)).filter((n) => Number.isFinite(n)) : [];
11
11
  /** Zet een waarde uit de printerlijst om naar millimeters. */
12
12
  function naarMm(waarde) {
13
- return waarde / 100;
13
+ return waarde / 10;
14
14
  }
15
15
  /**
16
- * Zoekt de papiernaam die het dichtst bij de gevraagde maat ligt.
17
- * Marge: 2 mm per zijde. Draaiing telt mee (liggend papier mag ook).
16
+ * Zoekt de papiernaam die exact bij de gevraagde maat past (afronding max 1 mm per zijde).
17
+ * Geen exacte match -> geen papiernaam. Draaiing telt mee (liggend papier mag ook).
18
18
  */
19
- export function kiesPapier(formaten, breedteMm, hoogteMm, margeMm = 2) {
19
+ export function kiesPapier(formaten, breedteMm, hoogteMm, margeMm = 1) {
20
20
  const liggend = breedteMm > hoogteMm;
21
21
  const rotate = liggend ? 90 : 0;
22
22
  if (!formaten || typeof formaten !== "object" || Array.isArray(formaten)) {
@@ -37,7 +37,7 @@ export function kiesPapier(formaten, breedteMm, hoogteMm, margeMm = 2) {
37
37
  const kort = Math.min(naarMm(m[0]), naarMm(m[1]));
38
38
  const lang = Math.max(naarMm(m[0]), naarMm(m[1]));
39
39
  const afwijking = Math.abs(kort - doelKort) + Math.abs(lang - doelLang);
40
- if (afwijking <= margeMm * 2 && (!beste || afwijking < beste.afwijking)) {
40
+ if (Math.abs(kort - doelKort) <= margeMm && Math.abs(lang - doelLang) <= margeMm && (!beste || afwijking < beste.afwijking)) {
41
41
  beste = { naam, afwijking };
42
42
  }
43
43
  }
@@ -40,6 +40,12 @@ export type BronUitkomst = {
40
40
  export declare function normaliseerDocumenttype(type: unknown): string;
41
41
  /** Het layout-id zoals de Engine het aanlevert (kolom of payload). */
42
42
  export declare function leesEngineLayoutId(job: Job): string;
43
+ /** Testprint: alleen de eigen testvlag telt (geen kar, geen barcode). */
44
+ export declare function isTestprintJob(job: {
45
+ payload?: unknown;
46
+ }): boolean;
47
+ /** Verwijdert elke barcode (order en karren), zodat het barcode-element leeg blijft. */
48
+ export declare function zonderBarcodes<T>(order: T): T;
43
49
  /**
44
50
  * Herkent een ondertekende downloadlink naar de EIGEN bestandsopslag (adres
45
51
  * uit de omgeving, niets vast in code) en geeft map + pad terug. Zo werkt een
@@ -1 +1 @@
1
- {"version":3,"file":"printQueue.server.d.ts","sourceRoot":"","sources":["../../src/lib/printQueue.server.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"printQueue.server.d.ts","sourceRoot":"","sources":["../../src/lib/printQueue.server.ts"],"names":[],"mappings":"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"}
@@ -14,6 +14,9 @@ import { bouwDocumentHtml, voorbeeldContext } from "./layoutPreview.js";
14
14
  import { htmlNaarPdfServer, PdfRenderFout } from "./htmlNaarPdf.server.js";
15
15
  import { stuurPdfNaarPrinter, opdrachtStatusDetail, vervaltijdVoor } from "./printService.server.js";
16
16
  import { kiesPapier } from "./papierFormaten.js";
17
+ import { waaromDezePrinter } from "./waaromPrinter.js";
18
+ import { etiketPast, leesGeladenEtiket, pdfPaginaMm } from "./etiketControle.js";
19
+ import { documentTypeLabel as documentTypeLabelVoor } from "./documentTypes.js";
17
20
  import { PAPIERFORMATEN } from "./documentTypes.js";
18
21
  const MAX_POGINGEN = 3;
19
22
  const arr = (v) => (Array.isArray(v) ? v : []);
@@ -167,6 +170,29 @@ function isEngineOpdracht(job) {
167
170
  const bron = String(p.bron_module ?? p.owner_module ?? p.source_module ?? "").toLowerCase();
168
171
  return bron.includes("logistic") || bron.includes("engine");
169
172
  }
173
+ /** Testprint: alleen de eigen testvlag telt (geen kar, geen barcode). */
174
+ export function isTestprintJob(job) {
175
+ const p = (job.payload ?? {});
176
+ return p["is_testprint"] === true || p["is_testprint"] === "true";
177
+ }
178
+ /** Verwijdert elke barcode (order en karren), zodat het barcode-element leeg blijft. */
179
+ export function zonderBarcodes(order) {
180
+ const o = { ...order };
181
+ delete o.barcode;
182
+ if (o.cart && typeof o.cart === "object") {
183
+ const { barcode: _b, ...rest } = o.cart;
184
+ o.cart = rest;
185
+ }
186
+ if (Array.isArray(o.karren)) {
187
+ o.karren = o.karren.map((k) => {
188
+ if (!k || typeof k !== "object")
189
+ return k;
190
+ const { barcode: _b, ...rest } = k;
191
+ return rest;
192
+ });
193
+ }
194
+ return o;
195
+ }
170
196
  /** Handmatig opnieuw printen? */
171
197
  function isHerprint(job) {
172
198
  const p = (job.payload ?? {});
@@ -607,7 +633,7 @@ async function zoekPrinter(db, job, layout) {
607
633
  return null;
608
634
  const { data } = await db
609
635
  .from("printers")
610
- .select("id, naam, extern_printer_id, actief, papierformaat, ondersteunde_formaten, ondersteunde_laden, formaten_bijgewerkt_op, afdrukmethode")
636
+ .select("id, naam, weergavenaam, extern_printer_id, actief, papierformaat, ondersteunde_formaten, ondersteunde_laden, formaten_bijgewerkt_op, afdrukmethode, geladen_etiket")
611
637
  .eq("id", id)
612
638
  .maybeSingle();
613
639
  return data && data.actief !== false ? data : null;
@@ -657,10 +683,18 @@ async function zoekPrinter(db, job, layout) {
657
683
  _werkplek: werkplek || null,
658
684
  _layout_id: layout?.id ?? null,
659
685
  _papierformaat: layout?.papierformaat ?? null,
686
+ ...(leesWerkplekId(job) ? { _werkplek_id: leesWerkplekId(job) } : {}),
660
687
  });
661
688
  printer = await pak(route?.printer_id);
662
- if (printer)
689
+ if (printer) {
690
+ try {
691
+ const plekNaam = plekId ? await printplekNaamVoorJob(db, job) : null;
692
+ const r = (route ?? {});
693
+ 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));
694
+ }
695
+ catch { /* uitleg is informatief */ }
663
696
  return { printer, reden: "" };
697
+ }
664
698
  if (plekId) {
665
699
  const naam = (await printplekNaamVoorJob(db, job)) ?? "onbekend";
666
700
  return {
@@ -819,9 +853,15 @@ export async function bouwHtml(db, job, keuze, uitkomst) {
819
853
  }
820
854
  // Barcode en kargegevens van de Engine mogen niet verloren gaan wanneer
821
855
  // de ordergegevens uit een subobject komen.
822
- const herprintBron = await zoekHerprintBarcodeBron(db, job);
823
- const engineBarcode = p.barcode ?? p.data?.cart?.barcode ?? p.cart?.barcode ?? herprintBron?.barcode ?? null;
824
- const ruweEngineCart = p.data?.cart ?? p.cart ?? herprintBron?.cart ?? null;
856
+ // Een testpakbon heeft geen kar en geen barcode: het barcode-element blijft
857
+ // leeg, er gaat geen barcodeverzoek naar de Engine en er wordt niets
858
+ // geregistreerd voor de scanner.
859
+ const isTestpakbon = isTestprintJob(job);
860
+ const herprintBron = isTestpakbon ? null : await zoekHerprintBarcodeBron(db, job);
861
+ const engineBarcode = isTestpakbon
862
+ ? null
863
+ : (p.barcode ?? p.data?.cart?.barcode ?? p.cart?.barcode ?? herprintBron?.barcode ?? null);
864
+ const ruweEngineCart = isTestpakbon ? null : (p.data?.cart ?? p.cart ?? herprintBron?.cart ?? null);
825
865
  const engineCart = ruweEngineCart && typeof ruweEngineCart === "object"
826
866
  ? {
827
867
  ...ruweEngineCart,
@@ -858,7 +898,9 @@ export async function bouwHtml(db, job, keuze, uitkomst) {
858
898
  ...(engineBarcode ? { barcode: engineBarcode } : {}),
859
899
  ...(engineCart ? { cart: engineCart } : {}),
860
900
  });
861
- if (isEngineOpdracht(job)) {
901
+ if (isTestpakbon)
902
+ order = zonderBarcodes(order);
903
+ if (isEngineOpdracht(job) && !isTestpakbon) {
862
904
  let barcode = String(engineBarcode ?? "").trim();
863
905
  if (!barcode) {
864
906
  // De Engine heeft (nog) geen barcode meegestuurd. Heeft zij er
@@ -979,13 +1021,15 @@ export async function bouwHtml(db, job, keuze, uitkomst) {
979
1021
  // De Engine bepaalt de barcode. De module registreert uitsluitend de
980
1022
  // gerenderde inhoud onder die bestaande waarde voor de scanner. Als de
981
1023
  // registratie tijdelijk niet kan, mag dat een afdruk nooit blokkeren.
982
- const scanInhoud = bouwPakbonScanInhoud(order, {
983
- barcode: order?.cart?.barcode ?? engineBarcode,
984
- layoutVariant: pakbonLayoutVariant(engineVariant),
985
- layoutId: String(layout.id ?? "") || null,
986
- printJobId: String(job.id ?? "") || null,
987
- isHerprint: isHerprint(job),
988
- });
1024
+ const scanInhoud = isTestpakbon
1025
+ ? null
1026
+ : bouwPakbonScanInhoud(order, {
1027
+ barcode: order?.cart?.barcode ?? engineBarcode,
1028
+ layoutVariant: pakbonLayoutVariant(engineVariant),
1029
+ layoutId: String(layout.id ?? "") || null,
1030
+ printJobId: String(job.id ?? "") || null,
1031
+ isHerprint: isHerprint(job),
1032
+ });
989
1033
  if (scanInhoud) {
990
1034
  try {
991
1035
  await bewaarPakbonScanInhoud(db, scanInhoud);
@@ -1292,6 +1336,20 @@ async function verwerkJob(db, job) {
1292
1336
  await log(db, job.id, "annulering_bevestigd", "Niet afgeleverd: opdracht was geannuleerd.");
1293
1337
  return "wachtend";
1294
1338
  }
1339
+ // Labelprinter: het geladen etiket is leidend (punt "geladen etiket").
1340
+ const label = await labelControleVoorbereiden(db, printer);
1341
+ const isTest = (job.payload ?? {})["is_testprint"] === true;
1342
+ if (label && isTest && label.etiket) {
1343
+ // Een testprint gebruikt het formaat dat echt in de printer zit.
1344
+ const p = (job.payload ?? {});
1345
+ p["voorbeeld_formaat"] = {
1346
+ breedte_mm: label.etiket.breedte_mm,
1347
+ hoogte_mm: label.etiket.hoogte_mm,
1348
+ orientatie: label.etiket.breedte_mm > label.etiket.hoogte_mm ? "landscape" : "portrait",
1349
+ marge_boven: 2,
1350
+ };
1351
+ job.payload = p;
1352
+ }
1295
1353
  // Uitkomst van de opmaakkeuze; wordt na verzending vastgelegd naast de
1296
1354
  // aangeleverde invoer, zodat achteraf zichtbaar is wat er gekozen is.
1297
1355
  const uitkomst = { bron: floriday ? "floriday_1op1" : "extern_pdf" };
@@ -1324,17 +1382,48 @@ async function verwerkJob(db, job) {
1324
1382
  const vfPapier = (job.payload ?? {})["voorbeeld_formaat"];
1325
1383
  const breedteMm = Number(layout?.breedte_mm ?? vfPapier?.["breedte_mm"] ?? standaardMaat(printer).breedte);
1326
1384
  const hoogteMm = Number(layout?.hoogte_mm ?? vfPapier?.["hoogte_mm"] ?? standaardMaat(printer).hoogte);
1327
- const papier = kiesPapier(formaten, breedteMm, hoogteMm);
1385
+ let papier = kiesPapier(formaten, breedteMm, hoogteMm);
1386
+ if (label) {
1387
+ const maat = (klaar ? pdfPaginaMm(pdfBase64) : null) ?? { breedte_mm: breedteMm, hoogte_mm: hoogteMm };
1388
+ const uit = etiketPast({ ...maat, soortLabel: documentTypeLabelVoor(String(job.documenttype ?? "Document")) }, label.etiket, label.naam);
1389
+ if (!uit.ok) {
1390
+ await mislukt(db, job, `${uit.code}: ${uit.bericht}`, true);
1391
+ return "mislukt";
1392
+ }
1393
+ papier = { paper: uit.paper, rotate: uit.rotate, uitleg: uit.uitleg };
1394
+ await log(db, job.id, "etiket", `Labelprinter ${label.naam}: ${uit.uitleg}.`);
1395
+ }
1328
1396
  const verloopt = vervaltijdVoor(String(job.documenttype ?? ""));
1329
- const externJobId = await stuurPdfNaarPrinter({
1330
- jobId: String(job.id),
1331
- expireAfter: verloopt,
1332
- externPrinterId: String(printer.extern_printer_id),
1333
- documentnaam: String(job.documentnaam ?? "Document"),
1334
- pdfBase64,
1335
- kopieen: Number(job.kopieen ?? 1),
1336
- opties: { paper: papier.paper, rotate: papier.rotate, fitToPage: false },
1337
- });
1397
+ // Eén fysieke printer via meerdere computers: probeer de routes op
1398
+ // volgorde; valt de eerste weg, dan automatisch de volgende.
1399
+ const routes = await printerRoutes(db, String(printer.id), String(printer.extern_printer_id));
1400
+ let externJobId = "";
1401
+ let laatsteFout = null;
1402
+ for (const [i, route] of routes.entries()) {
1403
+ try {
1404
+ externJobId = await stuurPdfNaarPrinter({
1405
+ jobId: String(job.id),
1406
+ expireAfter: verloopt,
1407
+ externPrinterId: route.extern,
1408
+ documentnaam: String(job.documentnaam ?? "Document"),
1409
+ pdfBase64,
1410
+ kopieen: Number(job.kopieen ?? 1),
1411
+ opties: { paper: papier.paper, rotate: papier.rotate, fitToPage: false },
1412
+ });
1413
+ if (i > 0) {
1414
+ await log(db, job.id, "route", `Verstuurd via computer ${route.computer ?? "andere route"}: de eerste computer was niet bereikbaar.`);
1415
+ }
1416
+ laatsteFout = null;
1417
+ break;
1418
+ }
1419
+ catch (e) {
1420
+ laatsteFout = e;
1421
+ if (!(e instanceof Error) || i === routes.length - 1 || e.name === "AlOntvangenFout")
1422
+ throw e;
1423
+ }
1424
+ }
1425
+ if (laatsteFout)
1426
+ throw laatsteFout;
1338
1427
  if (await isJobGeannuleerd(db, String(job.id))) {
1339
1428
  const { annuleerExternePrintOpdracht } = await import("./printService.server.js");
1340
1429
  await annuleerExternePrintOpdracht(externJobId).catch(() => undefined);
@@ -1699,3 +1788,40 @@ export async function bepaalDocumentKeuze(db, job) {
1699
1788
  : await zoekLayout(db, job);
1700
1789
  return { keuze, klaarPdf: klaar };
1701
1790
  }
1791
+ /**
1792
+ * Is dit een labelprinter? Ja als er een geladen etiket is ingesteld, of als
1793
+ * een werkplek hem als labelprinter gebruikt. Alleen dan geldt de controle,
1794
+ * zodat bestaande printers zonder werkplek precies blijven werken als nu.
1795
+ */
1796
+ async function labelControleVoorbereiden(db, printer) {
1797
+ const etiket = leesGeladenEtiket(printer?.geladen_etiket);
1798
+ const naam = String(printer?.weergavenaam || printer?.naam || "onbekend");
1799
+ if (etiket)
1800
+ return { naam, etiket };
1801
+ const { data } = await db
1802
+ .from("print_werkplekken")
1803
+ .select("id")
1804
+ .eq("label_printer_id", printer.id)
1805
+ .eq("actief", true)
1806
+ .limit(1);
1807
+ return Array.isArray(data) && data.length ? { naam, etiket: null } : null;
1808
+ }
1809
+ /** Routes van een printer, op prioriteit; zonder routes de printer zelf. */
1810
+ async function printerRoutes(db, printerId, eigen) {
1811
+ const { data } = await db
1812
+ .from("printer_routes")
1813
+ .select("extern_printer_id, computer_naam, prioriteit")
1814
+ .eq("printer_id", printerId)
1815
+ .order("prioriteit", { ascending: true });
1816
+ const lijst = (Array.isArray(data) ? data : [])
1817
+ .map((r) => ({ extern: String(r.extern_printer_id ?? ""), computer: r.computer_naam ?? null }))
1818
+ .filter((r) => r.extern);
1819
+ if (!lijst.some((r) => r.extern === eigen))
1820
+ lijst.unshift({ extern: eigen, computer: null });
1821
+ return lijst;
1822
+ }
1823
+ /** Werkplek uit het nieuwe model (op id), meegestuurd in de payload. */
1824
+ function leesWerkplekId(job) {
1825
+ const v = String((job.payload ?? {})["werkplek_id"] ?? "").trim();
1826
+ return /^[0-9a-f-]{36}$/i.test(v) ? v : null;
1827
+ }