@flowselections/pick-schermen-kwekers 1.0.2 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/dist-lib/components/PickPublicPage.d.ts +7 -0
  2. package/dist-lib/components/PickPublicPage.d.ts.map +1 -0
  3. package/dist-lib/components/PickPublicPage.js +19 -0
  4. package/dist-lib/components/PickstationsPage.d.ts.map +1 -1
  5. package/dist-lib/components/PickstationsPage.js +57 -14
  6. package/dist-lib/components/pick/LanguageTiles.d.ts +8 -0
  7. package/dist-lib/components/pick/LanguageTiles.d.ts.map +1 -0
  8. package/dist-lib/components/pick/LanguageTiles.js +11 -0
  9. package/dist-lib/components/pick/PickScherm.d.ts +6 -4
  10. package/dist-lib/components/pick/PickScherm.d.ts.map +1 -1
  11. package/dist-lib/components/pick/PickScherm.js +145 -61
  12. package/dist-lib/components/pick/PublicPickRoute.d.ts +6 -0
  13. package/dist-lib/components/pick/PublicPickRoute.d.ts.map +1 -0
  14. package/dist-lib/components/pick/PublicPickRoute.js +11 -0
  15. package/dist-lib/components/pick/QueueTable.d.ts +4 -1
  16. package/dist-lib/components/pick/QueueTable.d.ts.map +1 -1
  17. package/dist-lib/components/pick/QueueTable.js +11 -10
  18. package/dist-lib/hooks/usePickQueue.d.ts +7 -1
  19. package/dist-lib/hooks/usePickQueue.d.ts.map +1 -1
  20. package/dist-lib/hooks/usePickQueue.js +43 -6
  21. package/dist-lib/index.d.ts +6 -6
  22. package/dist-lib/index.d.ts.map +1 -1
  23. package/dist-lib/index.js +7 -12
  24. package/dist-lib/integrations/supabase/auth-middleware.d.ts +9221 -5284
  25. package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
  26. package/dist-lib/integrations/supabase/client.d.ts +9221 -5284
  27. package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
  28. package/dist-lib/integrations/supabase/client.server.d.ts +9221 -5284
  29. package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
  30. package/dist-lib/integrations/supabase/types.d.ts +9394 -5387
  31. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  32. package/dist-lib/lib/layoutApi.d.ts +23 -0
  33. package/dist-lib/lib/layoutApi.d.ts.map +1 -0
  34. package/dist-lib/lib/layoutApi.js +94 -0
  35. package/dist-lib/lib/layoutMaker/documentProfiles.d.ts +50 -0
  36. package/dist-lib/lib/layoutMaker/documentProfiles.d.ts.map +1 -0
  37. package/dist-lib/lib/layoutMaker/documentProfiles.js +458 -0
  38. package/dist-lib/lib/layoutMaker/documentTypes.d.ts +123 -0
  39. package/dist-lib/lib/layoutMaker/documentTypes.d.ts.map +1 -0
  40. package/dist-lib/lib/layoutMaker/documentTypes.js +74 -0
  41. package/dist-lib/lib/layoutMaker/logoLibrary.d.ts +13 -0
  42. package/dist-lib/lib/layoutMaker/logoLibrary.d.ts.map +1 -0
  43. package/dist-lib/lib/layoutMaker/logoLibrary.js +54 -0
  44. package/dist-lib/lib/layoutMaker/pakbonBuilder.d.ts +196 -0
  45. package/dist-lib/lib/layoutMaker/pakbonBuilder.d.ts.map +1 -0
  46. package/dist-lib/lib/layoutMaker/pakbonBuilder.js +813 -0
  47. package/dist-lib/lib/layoutMaker/pakbonHtml.d.ts +48 -0
  48. package/dist-lib/lib/layoutMaker/pakbonHtml.d.ts.map +1 -0
  49. package/dist-lib/lib/layoutMaker/pakbonHtml.js +703 -0
  50. package/dist-lib/lib/layoutRenderer.d.ts +7 -0
  51. package/dist-lib/lib/layoutRenderer.d.ts.map +1 -0
  52. package/dist-lib/lib/layoutRenderer.js +233 -0
  53. package/dist-lib/lib/layoutTypes.d.ts +84 -0
  54. package/dist-lib/lib/layoutTypes.d.ts.map +1 -0
  55. package/dist-lib/lib/layoutTypes.js +25 -0
  56. package/dist-lib/lib/logisticsApi.d.ts +54 -0
  57. package/dist-lib/lib/logisticsApi.d.ts.map +1 -0
  58. package/dist-lib/lib/logisticsApi.js +72 -0
  59. package/dist-lib/lib/logisticsTypes.d.ts +34 -0
  60. package/dist-lib/lib/logisticsTypes.d.ts.map +1 -0
  61. package/dist-lib/lib/logisticsTypes.js +36 -0
  62. package/dist-lib/lib/orderContext.d.ts +50 -0
  63. package/dist-lib/lib/orderContext.d.ts.map +1 -0
  64. package/dist-lib/lib/orderContext.js +162 -0
  65. package/dist-lib/lib/pakbonOrderInput.d.ts +8 -0
  66. package/dist-lib/lib/pakbonOrderInput.d.ts.map +1 -0
  67. package/dist-lib/lib/pakbonOrderInput.js +150 -0
  68. package/dist-lib/lib/pickApi.d.ts +24 -1
  69. package/dist-lib/lib/pickApi.d.ts.map +1 -1
  70. package/dist-lib/lib/pickApi.js +46 -5
  71. package/dist-lib/lib/pickI18n.d.ts +14 -0
  72. package/dist-lib/lib/pickI18n.d.ts.map +1 -0
  73. package/dist-lib/lib/pickI18n.js +500 -0
  74. package/dist-lib/lib/pickPrint.d.ts +18 -0
  75. package/dist-lib/lib/pickPrint.d.ts.map +1 -0
  76. package/dist-lib/lib/pickPrint.js +184 -0
  77. package/dist-lib/lib/pickTypes.d.ts +1 -0
  78. package/dist-lib/lib/pickTypes.d.ts.map +1 -1
  79. package/package.json +7 -20
  80. package/dist-lib/components/PickBeheerPage.d.ts +0 -2
  81. package/dist-lib/components/PickBeheerPage.d.ts.map +0 -1
  82. package/dist-lib/components/PickBeheerPage.js +0 -84
  83. package/dist-lib/components/PickDashboardPage.d.ts +0 -2
  84. package/dist-lib/components/PickDashboardPage.d.ts.map +0 -1
  85. package/dist-lib/components/PickDashboardPage.js +0 -54
  86. package/dist-lib/components/PickKioskPage.d.ts +0 -7
  87. package/dist-lib/components/PickKioskPage.d.ts.map +0 -1
  88. package/dist-lib/components/PickKioskPage.js +0 -18
  89. package/dist-lib/components/PickstationDetailPage.d.ts +0 -6
  90. package/dist-lib/components/PickstationDetailPage.d.ts.map +0 -1
  91. package/dist-lib/components/PickstationDetailPage.js +0 -25
  92. package/dist-lib/components/pick/ConfigCrud.d.ts +0 -27
  93. package/dist-lib/components/pick/ConfigCrud.d.ts.map +0 -1
  94. package/dist-lib/components/pick/ConfigCrud.js +0 -64
  95. package/dist-lib/components/pick/CountryTiles.d.ts +0 -9
  96. package/dist-lib/components/pick/CountryTiles.d.ts.map +0 -1
  97. package/dist-lib/components/pick/CountryTiles.js +0 -13
@@ -0,0 +1,703 @@
1
+ // ============================================================================
2
+ // src/lib/pakbonHtml.ts — canonieke print-HTML van een pakbon
3
+ // ----------------------------------------------------------------------------
4
+ // Dit is de ENIGE plek waar een pakbon naar printbare HTML wordt vertaald.
5
+ // Andere modules (bijv. het pickstation) mogen GEEN eigen pakbon-sjabloon meer
6
+ // bevatten: zij roepen `renderPakbonDocument()` of `printPakbon()` aan met de
7
+ // layoutconfiguratie en de echte ordergegevens. Zo tonen het live voorbeeld en
8
+ // de afdruk altijd exact dezelfde secties — inclusief de orderregels.
9
+ // ============================================================================
10
+ import { DEMO_ORDER, conditieContext, groepeerIdentiekeKarren, groepeerIdentiekeLagen, karBundelTekst, fustVerzamellijst, laagBundelTekst, leesPakbonConfig, sectieZichtbaar, sorteerProducten, totalen, vulVeldcodes, } from "./pakbonBuilder";
11
+ import { haalPrintLogos } from "./logoLibrary";
12
+ // --- kleine hulpjes ---------------------------------------------------------
13
+ const arr = (v) => (Array.isArray(v) ? v : []);
14
+ const opt = (s, key, fallback) => s.opties?.[key] ?? fallback;
15
+ const optS = (s, key, fallback) => String(s.opties?.[key] ?? fallback);
16
+ const lijst = (s, key) => Array.isArray(s.opties?.[key]) ? s.opties[key].map(String) : [];
17
+ export function esc(v) {
18
+ return String(v ?? "").replace(/[&<>"]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" })[c]);
19
+ }
20
+ const cel = (p, veld) => {
21
+ if (veld === "foto")
22
+ return "";
23
+ if (veld === "qrcode")
24
+ return "";
25
+ return String(p[veld] ?? "");
26
+ };
27
+ /**
28
+ * Vult ontbrekende onderdelen aan met veilige lege waarden, zodat een module
29
+ * die maar een deel van de gegevens heeft nooit een lege of stukke pakbon
30
+ * afdrukt.
31
+ */
32
+ export function maakPakbonOrder(input) {
33
+ const bron = (input && typeof input === "object" ? input : {});
34
+ const groep = (naam, velden) => {
35
+ const b = (bron[naam] && typeof bron[naam] === "object" ? bron[naam] : {});
36
+ const out = {};
37
+ for (const v of velden)
38
+ out[v] = b[v] === undefined || b[v] === null ? "" : String(b[v]);
39
+ return out;
40
+ };
41
+ const karren = arr(bron["karren"]).map((k, i) => ({
42
+ karnummer: Number(k["karnummer"] ?? i + 1),
43
+ route: String(k["route"] ?? ""),
44
+ chauffeur: String(k["chauffeur"] ?? ""),
45
+ gewicht: String(k["gewicht"] ?? ""),
46
+ lagen: arr(k["lagen"]).map((l, j) => ({
47
+ laagnummer: Number(l["laagnummer"] ?? j + 1),
48
+ belading: String(l["belading"] ?? ""),
49
+ potmaat: String(l["potmaat"] ?? ""),
50
+ gewicht: String(l["gewicht"] ?? ""),
51
+ opmerking: String(l["opmerking"] ?? ""),
52
+ producten: arr(l["producten"]).map(normProduct),
53
+ fusten: arr(l["fusten"]).map((f) => ({
54
+ fusttype: String(f["fusttype"] ?? f["fustsoort"] ?? ""),
55
+ aantal: Number(f["aantal"] ?? 0) || 0,
56
+ hoogte: String(f["hoogte"] ?? ""),
57
+ belading: String(f["belading"] ?? ""),
58
+ barcode: String(f["barcode"] ?? ""),
59
+ })),
60
+ })),
61
+ }));
62
+ // Losse orderregels zonder karren: in één kar/laag zetten zodat elke
63
+ // sectie-instelling (orderregels, producten) gewoon werkt.
64
+ const losseRegels = arr(bron["producten"] ?? bron["regels"]).map(normProduct);
65
+ if (!karren.length && losseRegels.length) {
66
+ karren.push({
67
+ karnummer: 1,
68
+ route: "",
69
+ chauffeur: "",
70
+ gewicht: "",
71
+ lagen: [
72
+ {
73
+ laagnummer: 1,
74
+ belading: "",
75
+ potmaat: "",
76
+ gewicht: "",
77
+ opmerking: "",
78
+ producten: losseRegels,
79
+ fusten: [],
80
+ },
81
+ ],
82
+ });
83
+ }
84
+ return {
85
+ order: groep("order", ["nummer", "datum", "leverdatum", "referentie", "opmerking"]),
86
+ bedrijf: groep("bedrijf", ["naam", "adres", "plaats", "telefoon", "email", "kvk", "btw"]),
87
+ klant: groep("klant", ["naam", "nummer", "contact", "referentie"]),
88
+ afleveradres: groep("afleveradres", ["naam", "adres", "plaats", "land"]),
89
+ factuuradres: groep("factuuradres", ["naam", "adres", "plaats", "land"]),
90
+ transport: groep("transport", ["transporteur", "route", "chauffeur", "track"]),
91
+ karren,
92
+ };
93
+ }
94
+ function normProduct(p) {
95
+ const aantal = Number(p["aantal"] ?? 0) || 0;
96
+ return {
97
+ artikelnummer: String(p["artikelnummer"] ?? p["artikelcode"] ?? ""),
98
+ artikelnaam: String(p["artikelnaam"] ?? p["naam"] ?? p["product_name"] ?? ""),
99
+ potmaat: String(p["potmaat"] ?? ""),
100
+ aantal,
101
+ belading: String(p["belading"] ?? ""),
102
+ barcode: String(p["barcode"] ?? ""),
103
+ opmerking: String(p["opmerking"] ?? ""),
104
+ locatie: String(p["locatie"] ?? ""),
105
+ gewicht: String(p["gewicht"] ?? ""),
106
+ productgroep: String(p["productgroep"] ?? ""),
107
+ kleur: String(p["kleur"] ?? ""),
108
+ fust_aantal: Number(p["fust_aantal"] ?? 0) || 0,
109
+ fustsoort: String(p["fustsoort"] ?? ""),
110
+ planten_per_fust: Number(p["planten_per_fust"] ?? 0) || 0,
111
+ };
112
+ }
113
+ // --- secties ----------------------------------------------------------------
114
+ function blokFrame(s, inhoud) {
115
+ if (!inhoud)
116
+ return "";
117
+ const o = s.opties ?? {};
118
+ const breedte = Math.min(Math.max(Number(o["blok_breedte"] ?? 100), 5), 100);
119
+ const uitlijning = String(o["blok_uitlijning"] ?? "left");
120
+ const marge = Number(o["blok_marge"] ?? 0);
121
+ const padding = Number(o["blok_padding"] ?? 0);
122
+ const rand = !!o["blok_rand"];
123
+ const radius = Number(o["blok_radius"] ?? 0);
124
+ const schaal = Number(o["blok_tekstschaal"] ?? 100);
125
+ const stijl = [
126
+ "display:flow-root",
127
+ `width:${breedte}%`,
128
+ uitlijning === "right" || uitlijning === "center" ? "margin-left:auto" : "",
129
+ uitlijning === "left" || uitlijning === "center" ? "margin-right:auto" : "",
130
+ `margin-top:${marge}mm`,
131
+ `margin-bottom:${marge}mm`,
132
+ padding ? `padding:${padding}mm` : "",
133
+ o["blok_achtergrond"] ? `background:${String(o["blok_achtergrond"])}` : "",
134
+ rand ? `border:0.3mm solid ${String(o["blok_randkleur"] ?? "#e5e7eb")}` : "",
135
+ radius ? `border-radius:${radius}mm` : "",
136
+ schaal !== 100 ? `font-size:${schaal}%` : "",
137
+ ]
138
+ .filter(Boolean)
139
+ .join(";");
140
+ return `<div class="blok" style="${stijl}">${inhoud}</div>`;
141
+ }
142
+ function titelHtml(s) {
143
+ return s.titel ? `<div class="sectitel">${esc(s.titel)}</div>` : "";
144
+ }
145
+ function rij(label, waarde) {
146
+ if (waarde === null || waarde === undefined || waarde === "")
147
+ return "";
148
+ return `<div class="kv"><span class="k">${esc(label)}</span><span class="v">${esc(waarde)}</span></div>`;
149
+ }
150
+ function regelWeergave(producten, kolommen, weergave, toonKoppen, zebra, randen) {
151
+ const kols = kolommen.length ? kolommen : [];
152
+ if (!kols.length || !producten.length)
153
+ return "";
154
+ const kstijl = (k) => [
155
+ k.breedte ? `width:${k.breedte}%` : "",
156
+ `text-align:${k.uitlijning ?? "left"}`,
157
+ k.fontfamilie ? `font-family:${k.fontfamilie}` : "",
158
+ k.fontgrootte ? `font-size:${k.fontgrootte}pt` : "",
159
+ k.kleur ? `color:${k.kleur}` : "",
160
+ k.vet ? "font-weight:700" : "",
161
+ k.achtergrond ? `background:${k.achtergrond}` : "",
162
+ ]
163
+ .filter(Boolean)
164
+ .join(";");
165
+ if (weergave === "tabel" || weergave === "compact") {
166
+ // Vaste kolombreedtes zodat elke laag/kar exact onder dezelfde koppen uitlijnt,
167
+ // ook wanneer de koppen maar één keer worden getoond.
168
+ const zonder = kols.filter((k) => !k.breedte).length;
169
+ const gebruikt = kols.reduce((som, k) => som + (Number(k.breedte) || 0), 0);
170
+ const rest = zonder > 0 ? Math.max(0, 100 - gebruikt) / zonder : 0;
171
+ const colgroup = `<colgroup>${kols
172
+ .map((k) => `<col style="width:${Number(k.breedte) || rest || 100 / kols.length}%" />`)
173
+ .join("")}</colgroup>`;
174
+ const kop = toonKoppen
175
+ ? `<thead><tr>${kols
176
+ .map((k) => `<th style="${kstijl(k)}" class="${randen ? "rand" : "onder"}">${esc(k.label)}</th>`)
177
+ .join("")}</tr></thead>`
178
+ : "";
179
+ const body = producten
180
+ .map((p, i) => `<tr${zebra && i % 2 === 1 ? ' class="zebra"' : ""}>${kols
181
+ .map((k) => `<td style="${kstijl(k)}" class="${randen ? "rand" : "onder"}">${esc(cel(p, k.key))}</td>`)
182
+ .join("")}</tr>`)
183
+ .join("");
184
+ return `<table class="regels ${weergave === "compact" ? "compact" : ""}">${colgroup}${kop}<tbody>${body}</tbody></table>`;
185
+ }
186
+ const items = producten
187
+ .map((p) => `<div class="regelrij">${kols
188
+ .map((k) => `<span style="${kstijl(k)}">${esc(cel(p, k.key))}</span>`)
189
+ .join("")}</div>`)
190
+ .join("");
191
+ return weergave === "naast" ? `<div class="tweekolom">${items}</div>` : `<div>${items}</div>`;
192
+ }
193
+ function groepeer(order, groepering, sortering) {
194
+ const alle = order.karren.flatMap((k) => k.lagen.flatMap((l) => l.producten.map((p) => ({ p, kar: k, laag: l }))));
195
+ const maak = (titel, items) => ({
196
+ titel,
197
+ producten: sorteerProducten(items, sortering),
198
+ });
199
+ if (groepering === "kar")
200
+ return order.karren.map((k) => maak(`Kar ${k.karnummer}`, k.lagen.flatMap((l) => l.producten)));
201
+ if (groepering === "laag") {
202
+ const map = new Map();
203
+ for (const r of alle) {
204
+ const t = `Kar ${r.kar.karnummer} · laag ${r.laag.laagnummer}`;
205
+ map.set(t, [...(map.get(t) ?? []), r.p]);
206
+ }
207
+ return [...map.entries()].map(([t, items]) => maak(t, items));
208
+ }
209
+ if (groepering === "productgroep") {
210
+ const map = new Map();
211
+ for (const r of alle)
212
+ map.set(r.p.productgroep, [...(map.get(r.p.productgroep) ?? []), r.p]);
213
+ return [...map.entries()].map(([t, items]) => maak(t, items));
214
+ }
215
+ if (groepering === "afleveradres")
216
+ return [maak(order.afleveradres.naam, alle.map((r) => r.p))];
217
+ if (groepering === "klant")
218
+ return [maak(order.klant.naam, alle.map((r) => r.p))];
219
+ return [maak("—", alle.map((r) => r.p))];
220
+ }
221
+ function fustTabel(velden, fusten, titel) {
222
+ if (!velden.length || !fusten.length)
223
+ return "";
224
+ const label = {
225
+ fusttype: "Fustsoort",
226
+ aantal: "Aantal",
227
+ hoogte: "Hoogte",
228
+ belading: "Belading",
229
+ barcode: "Barcode",
230
+ };
231
+ const kop = `<tr>${velden.map((v) => `<th class="onder">${esc(label[v] ?? v)}</th>`).join("")}</tr>`;
232
+ const rijen = fusten
233
+ .map((f) => `<tr>${velden
234
+ .map((v) => `<td class="onder">${esc(f[v] ?? "")}</td>`)
235
+ .join("")}</tr>`)
236
+ .join("");
237
+ return `${titel ? `<div class="sectitel">${esc(titel)}</div>` : ""}<table class="regels">${kop}${rijen}</table>`;
238
+ }
239
+ function productenHtml(sectie, laag, toonKop = true) {
240
+ const velden = lijst(sectie, "velden");
241
+ if (!velden.length)
242
+ return "";
243
+ let producten = sorteerProducten(laag.producten, optS(sectie, "sortering", "eigen"));
244
+ if (opt(sectie, "groeperen", false)) {
245
+ const map = new Map();
246
+ for (const p of producten) {
247
+ const b = map.get(p.artikelnummer);
248
+ if (b) {
249
+ b.aantal += p.aantal;
250
+ b.fust_aantal += p.fust_aantal;
251
+ }
252
+ else
253
+ map.set(p.artikelnummer, { ...p });
254
+ }
255
+ producten = [...map.values()];
256
+ }
257
+ if (!producten.length)
258
+ return "";
259
+ const label = (v) => v === "foto" ? "Foto" : v === "qrcode" ? "QR-code" : PRODUCT_LABELS[v] ?? v;
260
+ const weergave = optS(sectie, "weergave", "tabel");
261
+ if (weergave === "lijst") {
262
+ return `<ul class="plijst">${producten
263
+ .map((p) => `<li>${velden.map((v) => esc(cel(p, v))).filter(Boolean).join(" · ")}</li>`)
264
+ .join("")}</ul>`;
265
+ }
266
+ const kop = toonKop && opt(sectie, "toonKoppen", true) !== false
267
+ ? `<tr>${velden.map((v) => `<th class="onder">${esc(label(v))}</th>`).join("")}</tr>`
268
+ : "";
269
+ const rijen = producten
270
+ .map((p) => `<tr>${velden.map((v) => `<td class="onder">${esc(cel(p, v))}</td>`).join("")}</tr>`)
271
+ .join("");
272
+ return `<table class="regels">${kop}${rijen}</table>`;
273
+ }
274
+ const PRODUCT_LABELS = {
275
+ artikelnummer: "Artikelnr",
276
+ artikelnaam: "Artikel",
277
+ potmaat: "Potmaat",
278
+ aantal: "Aantal",
279
+ belading: "Belading",
280
+ barcode: "Barcode",
281
+ opmerking: "Opmerking",
282
+ locatie: "Locatie",
283
+ gewicht: "Gewicht",
284
+ productgroep: "Productgroep",
285
+ kleur: "Kleur",
286
+ fust_aantal: "Aantal fusten",
287
+ fustsoort: "Fustsoort",
288
+ planten_per_fust: "Planten per fust",
289
+ };
290
+ function karKop(s, kar, velden, bundelTekst = "") {
291
+ const scheiding = optS(s, "scheiding", "kop");
292
+ const tekst = optS(s, "koptekst", "KAR {kar}")
293
+ .replace(/\{kar\}/g, String(kar.karnummer))
294
+ .replace(/\{route\}/g, kar.route)
295
+ .replace(/\{chauffeur\}/g, kar.chauffeur)
296
+ .replace(/\{lagen\}/g, String(kar.lagen.length))
297
+ .replace(/\{producten\}/g, String(kar.lagen.reduce((n, l) => n + l.producten.length, 0)));
298
+ const info = [];
299
+ if (velden.includes("lagen"))
300
+ info.push(`${kar.lagen.length} lagen`);
301
+ if (velden.includes("producten"))
302
+ info.push(`${kar.lagen.reduce((n, l) => n + l.producten.reduce((m, p) => m + p.aantal, 0), 0)} producten`);
303
+ if (velden.includes("gewicht") && kar.gewicht)
304
+ info.push(kar.gewicht);
305
+ if (velden.includes("chauffeur") && kar.chauffeur)
306
+ info.push(kar.chauffeur);
307
+ if (velden.includes("route") && kar.route)
308
+ info.push(kar.route);
309
+ const toonNummer = velden.includes("karnummer");
310
+ if (!toonNummer && !info.length && !bundelTekst)
311
+ return "";
312
+ const groot = scheiding === "kop";
313
+ const gekleurd = scheiding === "kleur" || groot;
314
+ const stijl = [
315
+ gekleurd ? `background:${optS(s, "kleur", "#e8eef7")}` : "",
316
+ groot ? "font-size:117%;font-weight:700" : "font-weight:600",
317
+ ]
318
+ .filter(Boolean)
319
+ .join(";");
320
+ const links = [
321
+ toonNummer ? esc(tekst) : "",
322
+ bundelTekst ? `<span class="bundel">${esc(bundelTekst)}</span>` : "",
323
+ ]
324
+ .filter(Boolean)
325
+ .join(" ");
326
+ return `<div class="karkop" style="${stijl}"><span>${links}</span><span class="meta">${info
327
+ .map((t) => `<span>${esc(t)}</span>`)
328
+ .join("")}</span></div>`;
329
+ }
330
+ function laagKop(s, laag, velden, compact, bundelTekst = "") {
331
+ const tekst = optS(s, "koptekst", "Laag {laag}").replace(/\{laag\}/g, String(laag.laagnummer));
332
+ const info = [];
333
+ if (velden.includes("belading") && laag.belading)
334
+ info.push(`Belading ${laag.belading}`);
335
+ if (velden.includes("potmaat") && laag.potmaat)
336
+ info.push(laag.potmaat);
337
+ if (velden.includes("producten"))
338
+ info.push(`${laag.producten.reduce((n, p) => n + p.aantal, 0)} producten`);
339
+ if (velden.includes("fusten"))
340
+ info.push(`${laag.fusten.reduce((n, f) => n + f.aantal, 0)} fusten`);
341
+ if (velden.includes("gewicht") && laag.gewicht)
342
+ info.push(laag.gewicht);
343
+ if (velden.includes("opmerking") && laag.opmerking)
344
+ info.push(laag.opmerking);
345
+ if (!velden.includes("laagnummer") && !info.length && !bundelTekst)
346
+ return "";
347
+ return `<div class="laagkop${compact ? " compact" : ""}"><span>${velden.includes("laagnummer") ? esc(tekst) : ""}${bundelTekst ? ` <span class="bundel">${esc(bundelTekst)}</span>` : ""}</span><span class="meta">${info.map((t) => `<span>${esc(t)}</span>`).join("")}</span></div>`;
348
+ }
349
+ function bundelMelding(tekst) {
350
+ return `<div class="bundel">${esc(tekst)}</div>`;
351
+ }
352
+ function karBlok(s, kar, lagen, zoek, omkaderd, aantal = 1) {
353
+ // Bij samenvoegen van identieke karren vervalt het unieke karnummer.
354
+ const bundelt = opt(s, "bundel_identiek", true) !== false;
355
+ const velden = lijst(s, "velden").filter((v) => !(bundelt && (v === "karnummer" || v === "barcode" || v === "qrcode")));
356
+ const lagenSectie = zoek("lagen");
357
+ const productSectie = zoek("producten");
358
+ const fustSectie = zoek("fusten");
359
+ const lagenModus = lagenSectie ? optS(lagenSectie, "modus", "apart") : "geen";
360
+ let inhoud = karKop(s, kar, velden, karBundelTekst(aantal));
361
+ if (lagen.length && lagenSectie && lagenModus !== "geen") {
362
+ const lVelden = lijst(lagenSectie, "velden");
363
+ const toonProducten = lagenModus !== "nummer" && lagenModus !== "regel";
364
+ const bundelLagen = lagenModus === "gebundeld";
365
+ inhoud += groepeerIdentiekeLagen(lagen, bundelLagen)
366
+ .map(({ laag, aantal: laagAantal }, index) => {
367
+ let l = laagKop(lagenSectie, laag, lVelden, lagenModus === "regel", laagAantal > 1 ? laagBundelTekst(laagAantal) : "");
368
+ if (toonProducten && productSectie)
369
+ l += productenHtml(productSectie, laag, index === 0 || opt(productSectie, "kop_eenmalig", true) === false);
370
+ if (fustSectie && ["laag", "product"].includes(optS(fustSectie, "modus", "totaal")))
371
+ l += fustTabel(lijst(fustSectie, "velden"), laag.fusten, `Fusten laag ${laag.laagnummer}`);
372
+ return `<div class="laag">${l}</div>`;
373
+ })
374
+ .join("");
375
+ }
376
+ if (fustSectie && optS(fustSectie, "modus", "totaal") === "kar")
377
+ inhoud += fustTabel(lijst(fustSectie, "velden"), lagen.flatMap((l) => l.fusten), `Fusten kar ${kar.karnummer}`);
378
+ return `<div class="karblok${omkaderd ? " omkaderd" : ""}">${inhoud}</div>`;
379
+ }
380
+ /** Verzamellijst met alle te picken fusten per fusttype. */
381
+ function fustVerzamellijstHtml(order) {
382
+ const lijst = fustVerzamellijst(order?.karren);
383
+ if (!lijst.length)
384
+ return "";
385
+ const totaal = lijst.reduce((n, f) => n + f.aantal, 0);
386
+ const blokken = lijst
387
+ .map((f) => {
388
+ const kop = `<div class="sectitel" style="display:flex;justify-content:space-between"><span>${esc([f.fusttype, f.hoogte, f.belading].filter(Boolean).join(" · "))}</span><span>${f.aantal}× fust</span></div>`;
389
+ const rijen = f.producten
390
+ .map((pr) => `<tr><td class="onder">${esc(pr.artikelnummer)}</td><td class="onder">${esc(pr.artikelnaam)}</td><td class="onder">${esc(pr.potmaat)}</td><td class="onder">${esc(pr.kleur)}</td><td class="onder">${esc(pr.locatie)}</td><td class="onder">${esc(pr.fustsoort)}</td><td class="onder" style="text-align:right">${pr.planten_per_fust}</td><td class="onder" style="text-align:right">${pr.aantal}</td></tr>`)
391
+ .join("");
392
+ return `${kop}<table class="regels"><tr><th class="onder">Artikel</th><th class="onder">Omschrijving</th><th class="onder">Potmaat</th><th class="onder">Kleur</th><th class="onder">Locatie</th><th class="onder">Tray/fust</th><th class="onder" style="text-align:right">Pl./fust</th><th class="onder" style="text-align:right">Planten</th></tr>${rijen}</table>`;
393
+ })
394
+ .join("");
395
+ return `<div class="sectitel">Te verzamelen fusten — karindeling zelf bepalen</div>${blokken}<table class="regels"><tr><td style="font-weight:700">Totaal fusten</td><td style="font-weight:700;text-align:right">${totaal}</td></tr></table>`;
396
+ }
397
+ function karrenHtml(s, order, zoek) {
398
+ const modus = optS(s, "modus", "pagina");
399
+ if (modus === "geen")
400
+ return "";
401
+ if (optS(s, "pickmethode", "kar") === "fusten")
402
+ return blokFrame(s, fustVerzamellijstHtml(order));
403
+ const scheiding = optS(s, "scheiding", "kop");
404
+ const lagenSectie = zoek("lagen");
405
+ const lagenModus = lagenSectie ? optS(lagenSectie, "modus", "apart") : "geen";
406
+ const perPagina = modus === "pagina" || scheiding === "pagina";
407
+ const perLaagPagina = modus !== "nummer" && lagenModus === "pagina";
408
+ const bundelKarren = opt(s, "bundel_identiek", true) !== false;
409
+ const karGroepen = groepeerIdentiekeKarren(order.karren, bundelKarren);
410
+ if (modus === "naast") {
411
+ return blokFrame(s, `<div class="tweekolom">${karGroepen
412
+ .map((g) => karBlok(s, g.kar, g.kar.lagen, zoek, false, g.aantal))
413
+ .join("")}</div>`);
414
+ }
415
+ const delen = [];
416
+ karGroepen.forEach((groep, i) => {
417
+ const kar = groep.kar;
418
+ const lagen = modus === "nummer" ? [] : kar.lagen;
419
+ if (perLaagPagina && lagen.length) {
420
+ const laagGroepen = groepeerIdentiekeLagen(lagen, String(lagenModus) === "gebundeld");
421
+ laagGroepen.forEach((lg, j) => {
422
+ const breek = i > 0 || j > 0;
423
+ delen.push(`<div class="${breek ? "breekvoor" : ""}">${blokFrame(s, karBlok(s, kar, [lg.laag], zoek, modus === "blok" || modus === "passend", groep.aantal))}</div>`);
424
+ });
425
+ return;
426
+ }
427
+ const breek = i > 0 && perPagina;
428
+ const scheider = i > 0 && !perPagina ? scheidingHtml(scheiding) : "";
429
+ delen.push(`<div class="${breek ? "breekvoor" : ""} ${modus === "passend" ? "heelhouden" : ""}">${scheider}${blokFrame(s, karBlok(s, kar, lagen, zoek, modus === "blok" || modus === "passend", groep.aantal))}</div>`);
430
+ });
431
+ return delen.join("");
432
+ }
433
+ function scheidingHtml(modus) {
434
+ if (modus === "lijn")
435
+ return `<div class="lijn"></div>`;
436
+ if (modus === "balk")
437
+ return `<div class="balk"></div>`;
438
+ return "";
439
+ }
440
+ function sectieHtml(s, order, zoek, opties) {
441
+ switch (s.type) {
442
+ case "logo": {
443
+ const logoId = typeof s.opties?.["logoId"] === "string" ? String(s.opties["logoId"]) : null;
444
+ // Geen eigen logo gekozen? Dan niets tonen — het documentlogo staat al bovenaan.
445
+ if (!logoId)
446
+ return "";
447
+ const logos = arr(opties.logos);
448
+ const logo = logos.find((l) => l.id === logoId);
449
+ if (!logo?.viewUrl)
450
+ return "";
451
+ const align = optS(s, "uitlijning", "left");
452
+ return `<div style="text-align:${esc(align)}"><img src="${esc(logo.viewUrl)}" alt="${esc(logo.naam ?? "")}" style="height:${Number(opt(s, "hoogte", 18))}mm;object-fit:contain" /></div>`;
453
+ }
454
+ case "bedrijfsgegevens":
455
+ return (titelHtml(s) +
456
+ [
457
+ opt(s, "toonNaam", true) ? `<div class="vet">${esc(order.bedrijf.naam)}</div>` : "",
458
+ opt(s, "toonAdres", true)
459
+ ? `<div>${esc([order.bedrijf.adres, order.bedrijf.plaats].filter(Boolean).join(" · "))}</div>`
460
+ : "",
461
+ opt(s, "toonContact", true)
462
+ ? `<div>${esc([order.bedrijf.telefoon, order.bedrijf.email].filter(Boolean).join(" · "))}</div>`
463
+ : "",
464
+ opt(s, "toonKvk", false)
465
+ ? `<div>${esc([order.bedrijf.kvk, order.bedrijf.btw].filter(Boolean).join(" · "))}</div>`
466
+ : "",
467
+ ].join(""));
468
+ case "klantgegevens":
469
+ return (titelHtml(s) +
470
+ `<div class="vet">${esc(order.klant.naam)}</div>` +
471
+ (opt(s, "toonKlantnummer", true) ? rij("Klantnummer", order.klant.nummer) : "") +
472
+ (opt(s, "toonContact", true) ? rij("Contact", order.klant.contact) : "") +
473
+ (opt(s, "toonReferentie", true) ? rij("Referentie", order.klant.referentie) : ""));
474
+ case "afleveradres":
475
+ case "factuuradres": {
476
+ const a = s.type === "afleveradres" ? order.afleveradres : order.factuuradres;
477
+ return (titelHtml(s) +
478
+ `<div class="vet">${esc(a.naam)}</div><div>${esc(a.adres)}</div><div>${esc(a.plaats)}</div>` +
479
+ (opt(s, "toonLand", true) ? `<div>${esc(a.land)}</div>` : ""));
480
+ }
481
+ case "orderinformatie":
482
+ return (titelHtml(s) +
483
+ `<div class="grid2">${[
484
+ opt(s, "toonOrdernummer", true) ? rij("Ordernummer", order.order.nummer) : "",
485
+ opt(s, "toonDatum", true) ? rij("Orderdatum", order.order.datum) : "",
486
+ opt(s, "toonLeverdatum", true) ? rij("Leverdatum", order.order.leverdatum) : "",
487
+ opt(s, "toonReferentie", true) ? rij("Referentie", order.order.referentie) : "",
488
+ ].join("")}</div>`);
489
+ case "transportinformatie":
490
+ return (titelHtml(s) +
491
+ `<div class="grid2">${[
492
+ opt(s, "toonTransporteur", true) ? rij("Transporteur", order.transport.transporteur) : "",
493
+ opt(s, "toonRoute", true) ? rij("Route", order.transport.route) : "",
494
+ opt(s, "toonChauffeur", false) ? rij("Chauffeur", order.transport.chauffeur) : "",
495
+ opt(s, "toonTrack", false) ? rij("Track & trace", order.transport.track) : "",
496
+ ].join("")}</div>`);
497
+ case "opmerkingen":
498
+ if (!order.order.opmerking)
499
+ return "";
500
+ return `<div class="${opt(s, "kader", true) ? "kader" : ""}"><span class="vet">Opmerkingen: </span>${esc(order.order.opmerking)}</div>`;
501
+ case "orderregels": {
502
+ const kolommen = arr(s.kolommen);
503
+ const groepen = groepeer(order, optS(s, "groepering", "order"), optS(s, "sortering", "eigen"));
504
+ const body = groepen
505
+ .map((g) => {
506
+ const tabel = regelWeergave(g.producten, kolommen, optS(s, "weergave", "tabel"), opt(s, "toonKoppen", true) !== false, !!opt(s, "zebra", false), opt(s, "randen", true) !== false);
507
+ if (!tabel)
508
+ return "";
509
+ return `<div class="groep">${g.titel !== "—" ? `<div class="vet">${esc(g.titel)}</div>` : ""}${tabel}</div>`;
510
+ })
511
+ .join("");
512
+ return titelHtml(s) + body;
513
+ }
514
+ case "karren":
515
+ case "lagen":
516
+ case "producten":
517
+ return ""; // apart afgehandeld
518
+ case "fusten": {
519
+ if (optS(s, "modus", "totaal") !== "totaal")
520
+ return "";
521
+ const alle = order.karren.flatMap((k) => k.lagen.flatMap((l) => l.fusten));
522
+ return fustTabel(lijst(s, "velden"), alle, s.titel);
523
+ }
524
+ case "totalen": {
525
+ const t = totalen(order);
526
+ const velden = lijst(s, "velden");
527
+ if (!velden.length)
528
+ return "";
529
+ const rechts = optS(s, "uitlijning", "right") === "right";
530
+ return `<div class="totalen" style="justify-content:${rechts ? "flex-end" : "flex-start"}">${velden
531
+ .map((v) => `<span>${esc(t[v] ?? v)}</span>`)
532
+ .join("")}</div>`;
533
+ }
534
+ case "handtekening":
535
+ return (`<div class="handtekening">${opt(s, "label", "") ? `<div class="sectitel">${esc(opt(s, "label", ""))}</div>` : ""}` +
536
+ (opt(s, "toonNaam", true) ? `<div class="lijnveld">Naam</div>` : "") +
537
+ (opt(s, "toonDatum", true) ? `<div class="lijnveld">Datum</div>` : "") +
538
+ `<div class="lijnveld hoog"></div></div>`);
539
+ case "tekstblok":
540
+ return `<div class="${opt(s, "kader", false) ? "kader" : ""}" style="font-size:${Number(opt(s, "fontgrootte", 9))}pt">${esc(vulVeldcodes(optS(s, "tekst", ""), order))}</div>`;
541
+ case "qrcode":
542
+ return `<div><div class="qr" style="width:${Number(opt(s, "grootte", 25))}mm;height:${Number(opt(s, "grootte", 25))}mm"></div><div class="klein">${esc(vulVeldcodes(optS(s, "waarde", ""), order))}</div></div>`;
543
+ case "barcode":
544
+ return `<div><div class="streepjes" style="height:${Number(opt(s, "hoogte", 15))}mm"></div><div>${esc(vulVeldcodes(optS(s, "waarde", ""), order))}</div></div>`;
545
+ case "afbeelding": {
546
+ const url = optS(s, "url", "");
547
+ return url
548
+ ? `<img src="${esc(url)}" alt="${esc(s.titel)}" style="height:${Number(opt(s, "hoogte", 25))}mm" />`
549
+ : "";
550
+ }
551
+ default:
552
+ return "";
553
+ }
554
+ }
555
+ // --- publieke API -----------------------------------------------------------
556
+ /** Bouwt alleen de inhoud (zonder <html>-wrapper) van de pakbon. */
557
+ export function renderPakbonBody(config, order, opties = {}) {
558
+ const cfg = leesPakbonConfig(config);
559
+ const ctx = conditieContext(order);
560
+ const secties = arr(cfg.secties).filter((s) => sectieZichtbaar(s, ctx));
561
+ const zoek = (t) => secties.find((s) => s.type === t);
562
+ const opts = { ...opties, logoId: opties.logoId ?? cfg.logo?.logoId ?? null };
563
+ const kop = documentLogoHtml(cfg.logo, opts);
564
+ const totalenSectie = zoek("totalen");
565
+ const totalenElkePagina = !!totalenSectie && opt(totalenSectie, "elke_pagina", false) === true;
566
+ const inhoud = secties
567
+ .filter((s) => !(s.type === "totalen" && totalenElkePagina))
568
+ .map((s) => s.type === "karren" ? karrenHtml(s, order, zoek) : blokFrame(s, sectieHtml(s, order, zoek, opts)))
569
+ .join("");
570
+ const voet = totalenElkePagina && totalenSectie
571
+ ? `<div class="paginavoet">${sectieHtml(totalenSectie, order, zoek, opts)}</div>`
572
+ : "";
573
+ return kop + inhoud + voet;
574
+ }
575
+ /** Het documentlogo bovenaan de pakbon. */
576
+ function documentLogoHtml(logoCfg, opties) {
577
+ if (!logoCfg?.logoId)
578
+ return "";
579
+ const logos = arr(opties.logos);
580
+ const logo = logos.find((l) => l.id === logoCfg.logoId);
581
+ if (!logo?.viewUrl)
582
+ return "";
583
+ return `<div class="blok" style="text-align:${esc(logoCfg.uitlijning ?? "left")}"><img src="${esc(logo.viewUrl)}" alt="${esc(logo.naam ?? "")}" style="height:${Number(logoCfg.hoogte ?? 18)}mm;object-fit:contain" /></div>`;
584
+ }
585
+ /** Bouwt een volledig printbaar HTML-document van de pakbon. */
586
+ export function renderPakbonDocument(config, order, opties = {}) {
587
+ const b = opties.breedteMm ?? 210;
588
+ const h = opties.hoogteMm ?? 297;
589
+ const marge = opties.margeMm ?? 12;
590
+ const font = opties.lettertype ?? 'Arial, Helvetica, "Segoe UI", sans-serif';
591
+ const grootte = opties.lettergrootte ?? 8.5;
592
+ const body = renderPakbonBody(config, order, opties);
593
+ return `<!doctype html><html lang="nl"><head><meta charset="utf-8" />
594
+ <title>${esc(opties.titel ?? "Pakbon")}</title>
595
+ <style>
596
+ @page { size: ${b}mm ${h}mm; margin: ${marge}mm; }
597
+ * { box-sizing: border-box; }
598
+ html, body { margin: 0; padding: 0; }
599
+ body { font-family: ${font}; font-size: ${grootte}pt; color: #111827; line-height: 1.35; }
600
+ .blok { margin-bottom: 2mm; }
601
+ .sectitel { font-size: 90%; font-weight: 600; text-transform: uppercase; color: #6b7280; margin-bottom: 0.6mm; }
602
+ .vet { font-weight: 600; }
603
+ .kv { display: flex; justify-content: space-between; gap: 3mm; }
604
+ .kv .k { color: #6b7280; }
605
+ .kv .v { font-weight: 500; }
606
+ .grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 0 6mm; }
607
+ .kader { border: 0.25mm solid #d1d5db; border-radius: 1mm; padding: 1.5mm; }
608
+ table.regels { width: 100%; border-collapse: collapse; table-layout: fixed; }
609
+ table.regels th, table.regels td { overflow-wrap: anywhere; }
610
+ table.regels th, table.regels td { padding: 0.7mm 1mm; vertical-align: top; }
611
+ table.regels th { font-size: 90%; text-transform: uppercase; color: #6b7280; font-weight: 600; text-align: left; }
612
+ .rand { border: 0.2mm solid #d1d5db; }
613
+ .onder { border-bottom: 0.2mm solid #d1d5db; }
614
+ table.regels.compact th, table.regels.compact td { padding: 0.3mm 0.8mm; }
615
+ tr.zebra td { background: #f3f4f6; }
616
+ .plijst { margin: 0; padding-left: 4mm; }
617
+ .groep { margin-bottom: 1.5mm; }
618
+ .regelrij { display: flex; gap: 2mm; }
619
+ .tweekolom { display: grid; grid-template-columns: 1fr 1fr; gap: 3mm; }
620
+ .karblok { margin-bottom: 2mm; }
621
+ .karblok.omkaderd { border: 0.25mm solid #d1d5db; border-radius: 1mm; padding: 1.5mm; }
622
+ .karkop { display: flex; justify-content: space-between; align-items: center; padding: 0.8mm 1.5mm; border-radius: 1mm; margin-bottom: 1mm; }
623
+ .bundel { display: inline-block; border: 0.3mm dashed #6b7280; border-radius: 1mm; padding: 0.4mm 1.5mm; margin-bottom: 1mm; font-weight: 700; text-transform: uppercase; letter-spacing: 0.2mm; font-size: 90%; }
624
+ .laag { margin-bottom: 1.5mm; }
625
+ .laagkop { display: flex; justify-content: space-between; font-weight: 500; border-bottom: 0.2mm solid #d1d5db; padding-bottom: 0.4mm; margin-bottom: 0.6mm; }
626
+ .laagkop.compact { border-bottom: none; }
627
+ .meta { display: flex; gap: 3mm; font-size: 88%; font-weight: 400; color: #4b5563; }
628
+ .totalen { display: flex; flex-wrap: wrap; gap: 5mm; border-top: 0.25mm solid #d1d5db; padding-top: 1.2mm; font-weight: 500; }
629
+ .paginavoet { position: fixed; bottom: 0; left: 0; right: 0; background: #fff; }
630
+ .handtekening { margin-top: 3mm; }
631
+ .lijnveld { margin-top: 4mm; border-bottom: 0.25mm solid #6b7280; padding-bottom: 3mm; }
632
+ .lijnveld.hoog { padding-bottom: 10mm; }
633
+ .lijn { border-top: 0.25mm solid #d1d5db; margin: 1.5mm 0; }
634
+ .balk { height: 1mm; background: #374151; margin: 1.5mm 0; }
635
+ .qr { border: 0.25mm dashed #9ca3af; }
636
+ .streepjes { width: 45mm; background: repeating-linear-gradient(to right, #111827 0 2px, transparent 2px 5px); }
637
+ .klein { font-size: 85%; color: #6b7280; }
638
+ .breekvoor { break-before: page; page-break-before: always; }
639
+ .heelhouden { break-inside: avoid; page-break-inside: avoid; }
640
+ img { max-width: 100%; }
641
+ </style></head>
642
+ <body>${body}</body></html>`;
643
+ }
644
+ /** Handig voor testen: het document met de voorbeeldgegevens. */
645
+ export function voorbeeldPakbonOrder() {
646
+ return DEMO_ORDER;
647
+ }
648
+ /**
649
+ * Opent het printvenster met de pakbon. Andere modules hoeven hiervoor niets
650
+ * zelf te bouwen: layoutconfiguratie + ordergegevens erin, afdruk eruit.
651
+ */
652
+ export function printPakbon(config, order, opties = {}) {
653
+ if (typeof window === "undefined")
654
+ return;
655
+ const html = renderPakbonDocument(config, order, opties);
656
+ const frame = document.createElement("iframe");
657
+ frame.setAttribute("aria-hidden", "true");
658
+ frame.style.cssText = "position:fixed;right:0;bottom:0;width:0;height:0;border:0";
659
+ document.body.appendChild(frame);
660
+ const doc = frame.contentDocument;
661
+ if (!doc) {
662
+ frame.remove();
663
+ return;
664
+ }
665
+ doc.open();
666
+ doc.write(html);
667
+ doc.close();
668
+ const start = () => {
669
+ frame.contentWindow?.focus();
670
+ frame.contentWindow?.print();
671
+ window.setTimeout(() => frame.remove(), 60000);
672
+ };
673
+ // Wachten tot afbeeldingen (logo's) geladen zijn, anders drukt de browser
674
+ // een pakbon af zonder logo.
675
+ const wachtOpAfbeeldingen = async () => {
676
+ const imgs = Array.from(doc.images ?? []);
677
+ await Promise.all(imgs.map((img) => img.complete
678
+ ? Promise.resolve()
679
+ : new Promise((res) => {
680
+ img.onload = () => res();
681
+ img.onerror = () => res();
682
+ })));
683
+ start();
684
+ };
685
+ window.setTimeout(() => void wachtOpAfbeeldingen(), 200);
686
+ }
687
+ /**
688
+ * Zelfde als `printPakbon`, maar haalt de logo's zelf uit de centrale Logo
689
+ * Bibliotheek en bedt ze in. Andere modules hoeven zo niets over logo's te
690
+ * weten: het documentlogo verschijnt automatisch op de afdruk.
691
+ */
692
+ export async function printPakbonMetLogos(config, order, opties = {}) {
693
+ let logos = opties.logos;
694
+ if (!Array.isArray(logos) || logos.length === 0) {
695
+ try {
696
+ logos = await haalPrintLogos();
697
+ }
698
+ catch {
699
+ logos = [];
700
+ }
701
+ }
702
+ printPakbon(config, order, { ...opties, logos });
703
+ }