@flowselections/layout-maker 1.38.4 → 1.45.9

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 (110) hide show
  1. package/dist-lib/components/AfleverbriefBuilderPage.d.ts +10 -0
  2. package/dist-lib/components/AfleverbriefBuilderPage.d.ts.map +1 -0
  3. package/dist-lib/components/AfleverbriefBuilderPage.js +101 -0
  4. package/dist-lib/components/BlokOntwerperPage.d.ts.map +1 -1
  5. package/dist-lib/components/BlokOntwerperPage.js +8 -3
  6. package/dist-lib/components/ComputersLijst.d.ts.map +1 -1
  7. package/dist-lib/components/ComputersLijst.js +1 -1
  8. package/dist-lib/components/FustKolomVolgorde.d.ts +11 -0
  9. package/dist-lib/components/FustKolomVolgorde.d.ts.map +1 -0
  10. package/dist-lib/components/FustKolomVolgorde.js +52 -0
  11. package/dist-lib/components/LayoutsPage.d.ts.map +1 -1
  12. package/dist-lib/components/LayoutsPage.js +10 -9
  13. package/dist-lib/components/PakbonBuilderPage.d.ts.map +1 -1
  14. package/dist-lib/components/PakbonBuilderPage.js +32 -14
  15. package/dist-lib/components/PakbonPreview.d.ts.map +1 -1
  16. package/dist-lib/components/PakbonPreview.js +21 -14
  17. package/dist-lib/components/PakbonSectiesPanel.d.ts +4 -2
  18. package/dist-lib/components/PakbonSectiesPanel.d.ts.map +1 -1
  19. package/dist-lib/components/PakbonSectiesPanel.js +5 -3
  20. package/dist-lib/components/PdfMapToestemmingBanner.d.ts +2 -1
  21. package/dist-lib/components/PdfMapToestemmingBanner.d.ts.map +1 -1
  22. package/dist-lib/components/PdfMapToestemmingBanner.js +124 -18
  23. package/dist-lib/components/PdfVoorbeeld.d.ts +20 -0
  24. package/dist-lib/components/PdfVoorbeeld.d.ts.map +1 -0
  25. package/dist-lib/components/PdfVoorbeeld.js +95 -0
  26. package/dist-lib/components/PrintProgrammaUitleg.d.ts.map +1 -1
  27. package/dist-lib/components/PrintProgrammaUitleg.js +1 -1
  28. package/dist-lib/components/PrintQueuePage.d.ts.map +1 -1
  29. package/dist-lib/components/PrintQueuePage.js +81 -43
  30. package/dist-lib/components/PrintplekDetailPage.d.ts.map +1 -1
  31. package/dist-lib/components/PrintplekDetailPage.js +2 -2
  32. package/dist-lib/components/PrintplekWizard.d.ts.map +1 -1
  33. package/dist-lib/components/PrintplekWizard.js +6 -2
  34. package/dist-lib/components/ProductiebonBuilderPage.d.ts.map +1 -1
  35. package/dist-lib/components/ProductiebonBuilderPage.js +13 -7
  36. package/dist-lib/index.d.ts +13 -5
  37. package/dist-lib/index.d.ts.map +1 -1
  38. package/dist-lib/index.js +10 -5
  39. package/dist-lib/integrations/supabase/auth-middleware.d.ts +2280 -198
  40. package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
  41. package/dist-lib/integrations/supabase/client.server.d.ts +2280 -198
  42. package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
  43. package/dist-lib/integrations/supabase/client.server.js +1 -1
  44. package/dist-lib/integrations/supabase/moduleClient.d.ts +2280 -198
  45. package/dist-lib/integrations/supabase/moduleClient.d.ts.map +1 -1
  46. package/dist-lib/integrations/supabase/types.d.ts +2306 -182
  47. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  48. package/dist-lib/integrations/supabase/types.js +1 -0
  49. package/dist-lib/lib/aiLayout.functions.d.ts +2280 -198
  50. package/dist-lib/lib/aiLayout.functions.d.ts.map +1 -1
  51. package/dist-lib/lib/documentVoorbeeld.functions.d.ts +23 -0
  52. package/dist-lib/lib/documentVoorbeeld.functions.d.ts.map +1 -0
  53. package/dist-lib/lib/documentVoorbeeld.functions.js +37 -0
  54. package/dist-lib/lib/fontMetrics.d.ts +15 -0
  55. package/dist-lib/lib/fontMetrics.d.ts.map +1 -0
  56. package/dist-lib/lib/fontMetrics.js +118 -0
  57. package/dist-lib/lib/htmlNaarPdf.server.d.ts +2 -2
  58. package/dist-lib/lib/htmlNaarPdf.server.d.ts.map +1 -1
  59. package/dist-lib/lib/htmlNaarPdf.server.js +2 -1
  60. package/dist-lib/lib/htmlPdf.server.d.ts +11 -0
  61. package/dist-lib/lib/htmlPdf.server.d.ts.map +1 -1
  62. package/dist-lib/lib/htmlPdf.server.js +118 -15
  63. package/dist-lib/lib/orderData.server.d.ts.map +1 -1
  64. package/dist-lib/lib/orderData.server.js +43 -16
  65. package/dist-lib/lib/pakbonBarcodeVerzoek.d.ts +39 -0
  66. package/dist-lib/lib/pakbonBarcodeVerzoek.d.ts.map +1 -0
  67. package/dist-lib/lib/pakbonBarcodeVerzoek.js +80 -0
  68. package/dist-lib/lib/pakbonBuilder.d.ts +62 -3
  69. package/dist-lib/lib/pakbonBuilder.d.ts.map +1 -1
  70. package/dist-lib/lib/pakbonBuilder.js +386 -50
  71. package/dist-lib/lib/pakbonContent.d.ts +41 -0
  72. package/dist-lib/lib/pakbonContent.d.ts.map +1 -0
  73. package/dist-lib/lib/pakbonContent.js +668 -0
  74. package/dist-lib/lib/pakbonDocument.d.ts +199 -0
  75. package/dist-lib/lib/pakbonDocument.d.ts.map +1 -0
  76. package/dist-lib/lib/pakbonDocument.js +735 -0
  77. package/dist-lib/lib/pakbonHtml.d.ts +7 -0
  78. package/dist-lib/lib/pakbonHtml.d.ts.map +1 -1
  79. package/dist-lib/lib/pakbonHtml.js +89 -73
  80. package/dist-lib/lib/pakbonPresentation.d.ts +8 -0
  81. package/dist-lib/lib/pakbonPresentation.d.ts.map +1 -0
  82. package/dist-lib/lib/pakbonPresentation.js +31 -0
  83. package/dist-lib/lib/pakbonScan.d.ts.map +1 -1
  84. package/dist-lib/lib/pakbonScan.js +20 -38
  85. package/dist-lib/lib/pdfJobSaver.d.ts +10 -6
  86. package/dist-lib/lib/pdfJobSaver.d.ts.map +1 -1
  87. package/dist-lib/lib/pdfJobSaver.js +302 -99
  88. package/dist-lib/lib/pdfMap.d.ts +37 -2
  89. package/dist-lib/lib/pdfMap.d.ts.map +1 -1
  90. package/dist-lib/lib/pdfMap.js +120 -7
  91. package/dist-lib/lib/pdfOpslagStatus.d.ts +23 -0
  92. package/dist-lib/lib/pdfOpslagStatus.d.ts.map +1 -0
  93. package/dist-lib/lib/pdfOpslagStatus.js +74 -0
  94. package/dist-lib/lib/printApi.d.ts +31 -0
  95. package/dist-lib/lib/printApi.d.ts.map +1 -1
  96. package/dist-lib/lib/printApi.js +62 -1
  97. package/dist-lib/lib/printQueue.server.d.ts +6 -0
  98. package/dist-lib/lib/printQueue.server.d.ts.map +1 -1
  99. package/dist-lib/lib/printQueue.server.js +450 -94
  100. package/dist-lib/lib/printService.functions.d.ts +8857 -529
  101. package/dist-lib/lib/printService.functions.d.ts.map +1 -1
  102. package/dist-lib/lib/profielHtml.d.ts.map +1 -1
  103. package/dist-lib/lib/profielHtml.js +8 -1
  104. package/dist-lib/lib/useDocumentData.d.ts +8 -0
  105. package/dist-lib/lib/useDocumentData.d.ts.map +1 -1
  106. package/dist-lib/lib/useDocumentData.js +1 -1
  107. package/dist-lib/lib/voorbeeldPrint.d.ts +3 -0
  108. package/dist-lib/lib/voorbeeldPrint.d.ts.map +1 -1
  109. package/dist-lib/lib/voorbeeldPrint.js +65 -17
  110. package/package.json +9 -2
@@ -0,0 +1,668 @@
1
+ // ============================================================================
2
+ // src/lib/pakbonContent.ts — het inhoudsmodel van de pakbon.
3
+ // ----------------------------------------------------------------------------
4
+ // Vertaalt de sectieconfiguratie + ordergegevens naar één neutrale reeks
5
+ // inhoudsblokken (Flow[]). Hier staat WAT er op de pakbon komt; de
6
+ // layout-engine (pakbonDocument.ts) bepaalt WAAR het terechtkomt. Er is geen
7
+ // tweede plek meer waar pakboninhoud wordt samengesteld.
8
+ // ============================================================================
9
+ import { beperkTotDeelkar, conditieContext, deelkarVoorbeeldOrder, fustVerzamellijst, groepeerIdentiekeKarren, groepeerIdentiekeLagen, karBundelTekst, laagBundelTekst, leesPakbonConfig, pakbonLayoutVariant, pickMethode, productVeldBreedtes, sectieOnderdruktVoorVariant, sectieZichtbaar, sorteerProducten, totalen, vulVeldcodes, } from "./pakbonBuilder.js";
10
+ import { karMetadata } from "./pakbonPresentation.js";
11
+ import { KLEUR_GRIJS } from "./pakbonDocument.js";
12
+ const arr = (v) => (Array.isArray(v) ? v : []);
13
+ const opt = (s, key, fallback) => s.opties?.[key] ?? fallback;
14
+ const optS = (s, key, fallback) => String(s.opties?.[key] ?? fallback);
15
+ const lijst = (s, key) => Array.isArray(s.opties?.[key]) ? s.opties[key].map(String) : [];
16
+ const cel = (p, veld) => {
17
+ if (veld === "foto" || veld === "qrcode")
18
+ return "";
19
+ return String(p[veld] ?? "");
20
+ };
21
+ const PRODUCT_LABELS = {
22
+ artikelnummer: "Artikelnr",
23
+ artikelnaam: "Artikel",
24
+ potmaat: "Potmaat",
25
+ aantal: "Aantal",
26
+ belading: "Belading",
27
+ barcode: "Barcode",
28
+ opmerking: "Opmerking",
29
+ locatie: "Locatie",
30
+ gewicht: "Gewicht",
31
+ productgroep: "Productgroep",
32
+ kleur: "Kleur",
33
+ fust_aantal: "Aantal fusten",
34
+ fustsoort: "Fustsoort",
35
+ planten_per_fust: "Planten per fust",
36
+ };
37
+ const FUST_KOLOM_LABELS = {
38
+ artikelnummer: "Artikel",
39
+ artikelnaam: "Omschrijving",
40
+ potmaat: "Potmaat",
41
+ kleur: "Kleur",
42
+ productgroep: "Productgroep",
43
+ locatie: "Locatie",
44
+ fustsoort: "Tray/fust",
45
+ planten_per_fust: "Pl./fust",
46
+ fust_aantal: "Fusten",
47
+ aantal: "Planten",
48
+ };
49
+ const FUST_RECHTS = new Set(["planten_per_fust", "fust_aantal", "aantal"]);
50
+ const RECHTS_VELDEN = new Set(["aantal", "gewicht", "fust_aantal", "planten_per_fust"]);
51
+ function sectitel(tekst, basis) {
52
+ if (!tekst)
53
+ return [];
54
+ return [
55
+ {
56
+ kind: "tekst",
57
+ tekst: tekst.toUpperCase(),
58
+ groottePt: basis * 0.9,
59
+ vet: true,
60
+ kleur: KLEUR_GRIJS,
61
+ margeOnderMm: 0.6,
62
+ },
63
+ ];
64
+ }
65
+ function productTabel(velden, producten, toonKop, basis) {
66
+ if (!velden.length || !producten.length)
67
+ return null;
68
+ const breedtes = productVeldBreedtes(velden);
69
+ const kolommen = velden.map((v, i) => ({
70
+ label: PRODUCT_LABELS[v] ?? v,
71
+ breedte: breedtes[i] ?? 100 / velden.length,
72
+ uitlijning: (RECHTS_VELDEN.has(v) ? "right" : "left"),
73
+ }));
74
+ return {
75
+ kind: "tabel",
76
+ kolommen,
77
+ rijen: producten.map((p) => velden.map((v) => cel(p, v))),
78
+ toonKop,
79
+ groottePt: basis,
80
+ randen: "onder",
81
+ kopHerhalen: true,
82
+ };
83
+ }
84
+ function fustTabel(velden, fusten, titel, basis) {
85
+ if (!velden.length || !fusten.length)
86
+ return [];
87
+ const labels = {
88
+ fusttype: "Fustsoort",
89
+ aantal: "Aantal",
90
+ hoogte: "Hoogte",
91
+ belading: "Belading",
92
+ barcode: "Barcode",
93
+ };
94
+ return [
95
+ ...sectitel(titel, basis),
96
+ {
97
+ kind: "tabel",
98
+ kolommen: velden.map((v) => ({
99
+ label: labels[v] ?? v,
100
+ breedte: 100 / velden.length,
101
+ uitlijning: (v === "aantal" ? "right" : "left"),
102
+ })),
103
+ rijen: fusten.map((f) => velden.map((v) => String(f[v] ?? ""))),
104
+ toonKop: true,
105
+ groottePt: basis,
106
+ randen: "onder",
107
+ kopHerhalen: true,
108
+ },
109
+ ];
110
+ }
111
+ function laagKopFlow(s, laag, velden, bundel, basis) {
112
+ const tekst = optS(s, "koptekst", "Laag {laag}").replace(/\{laag\}/g, String(laag.laagnummer));
113
+ const info = [];
114
+ if (velden.includes("belading") && laag.belading)
115
+ info.push(`Belading ${laag.belading}`);
116
+ if (velden.includes("potmaat") && laag.potmaat)
117
+ info.push(laag.potmaat);
118
+ if (velden.includes("producten"))
119
+ info.push(`${laag.producten.reduce((n, p) => n + p.aantal, 0)} producten`);
120
+ if (velden.includes("fusten"))
121
+ info.push(`${laag.fusten.reduce((n, f) => n + f.aantal, 0)} fusten`);
122
+ if (velden.includes("gewicht") && laag.gewicht)
123
+ info.push(laag.gewicht);
124
+ if (velden.includes("opmerking") && laag.opmerking)
125
+ info.push(laag.opmerking);
126
+ const titel = [velden.includes("laagnummer") ? tekst : "", bundel].filter(Boolean).join(" ");
127
+ if (!titel && !info.length)
128
+ return [];
129
+ return [
130
+ {
131
+ kind: "tussen",
132
+ links: titel,
133
+ rechts: info.join(" · "),
134
+ vet: true,
135
+ lijnOnder: true,
136
+ groottePt: basis,
137
+ },
138
+ ];
139
+ }
140
+ function karKopFlow(s, kar, velden, bundel, basis) {
141
+ const scheiding = optS(s, "scheiding", "kop");
142
+ const tekst = optS(s, "koptekst", "KAR {kar}")
143
+ .replace(/\{kar\}/g, String(kar.karnummer))
144
+ .replace(/\{route\}/g, kar.route)
145
+ .replace(/\{chauffeur\}/g, kar.chauffeur)
146
+ .replace(/\{lagen\}/g, String(kar.lagen.length))
147
+ .replace(/\{producten\}/g, String(kar.lagen.reduce((n, l) => n + l.producten.length, 0)));
148
+ const info = karMetadata(kar, velden);
149
+ const toonNummer = velden.includes("karnummer");
150
+ if (!toonNummer && !info.length && !bundel)
151
+ return [];
152
+ const groot = scheiding === "kop";
153
+ const gekleurd = scheiding === "kleur" || groot;
154
+ return [
155
+ {
156
+ kind: "band",
157
+ titel: [toonNummer ? tekst : "", bundel].filter(Boolean).join(" "),
158
+ meta: info.join(" · "),
159
+ groottePt: groot ? basis * 1.17 : basis,
160
+ achtergrond: gekleurd ? optS(s, "kleur", "#e8eef7") : undefined,
161
+ margeOnderMm: 1,
162
+ },
163
+ ];
164
+ }
165
+ function karBlokFlow(s, kar, lagen, zoek, omkaderd, aantal, basis) {
166
+ const bundelt = opt(s, "bundel_identiek", true) !== false && aantal > 1;
167
+ const velden = lijst(s, "velden").filter((v) => !(bundelt && (v === "karnummer" || v === "barcode" || v === "qrcode")));
168
+ const lagenSectie = zoek("lagen");
169
+ const productSectie = zoek("producten");
170
+ const fustSectie = zoek("fusten");
171
+ const lagenModus = lagenSectie ? optS(lagenSectie, "modus", "apart") : "geen";
172
+ const kinderen = [...karKopFlow(s, kar, velden, karBundelTekst(aantal), basis)];
173
+ // De Engine-cartregels zijn selectie-/controlegegevens en verschijnen nooit
174
+ // als tweede productlijst. De barcode hoort bij deze kar en blijft erbij.
175
+ if (kar.barcode)
176
+ kinderen.push({
177
+ kind: "barcode",
178
+ waarde: String(kar.barcode),
179
+ breedteMm: 45,
180
+ hoogteMm: Number(opt(s, "barcodeHoogte", 15)) || 15,
181
+ uitlijning: "left",
182
+ });
183
+ if (lagen.length && lagenSectie && lagenModus !== "geen") {
184
+ const lVelden = lijst(lagenSectie, "velden");
185
+ const toonProducten = lagenModus !== "nummer" && lagenModus !== "regel";
186
+ const bundelLagen = lagenModus === "gebundeld";
187
+ groepeerIdentiekeLagen(lagen, bundelLagen).forEach(({ laag, aantal: laagAantal }, index) => {
188
+ kinderen.push(...laagKopFlow(lagenSectie, laag, lVelden, laagAantal > 1 ? laagBundelTekst(laagAantal) : "", basis));
189
+ if (toonProducten && productSectie) {
190
+ const velden2 = lijst(productSectie, "velden");
191
+ let producten = sorteerProducten(laag.producten, optS(productSectie, "sortering", "eigen"));
192
+ if (opt(productSectie, "groeperen", false)) {
193
+ const map = new Map();
194
+ for (const p of producten) {
195
+ const b = map.get(p.artikelnummer);
196
+ if (b) {
197
+ b.aantal += p.aantal;
198
+ b.fust_aantal += p.fust_aantal;
199
+ }
200
+ else
201
+ map.set(p.artikelnummer, { ...p });
202
+ }
203
+ producten = [...map.values()];
204
+ }
205
+ const toonKop = opt(productSectie, "toonKoppen", true) !== false &&
206
+ (index === 0 || opt(productSectie, "kop_eenmalig", true) === false);
207
+ const tabel = productTabel(velden2, producten, toonKop, basis);
208
+ if (tabel)
209
+ kinderen.push(tabel);
210
+ }
211
+ if (fustSectie && ["laag", "product"].includes(optS(fustSectie, "modus", "totaal")))
212
+ kinderen.push(...fustTabel(lijst(fustSectie, "velden"), laag.fusten, `Fusten laag ${laag.laagnummer}`, basis));
213
+ kinderen.push({ kind: "ruimte", hoogteMm: 1.5 });
214
+ });
215
+ }
216
+ if (fustSectie && optS(fustSectie, "modus", "totaal") === "kar")
217
+ kinderen.push(...fustTabel(lijst(fustSectie, "velden"), lagen.flatMap((l) => l.fusten), `Fusten kar ${kar.karnummer}`, basis));
218
+ return { kind: "groep", kinderen, kader: omkaderd, margeOnderMm: 2 };
219
+ }
220
+ function blokKader(s, kinderen) {
221
+ if (!kinderen.length)
222
+ return [];
223
+ const o = s.opties ?? {};
224
+ const breedte = Math.min(Math.max(Number(o["blok_breedte"] ?? 100), 5), 100);
225
+ const uitlijning = String(o["blok_uitlijning"] ?? "left");
226
+ const marge = Number(o["blok_marge"] ?? 0);
227
+ const padding = Number(o["blok_padding"] ?? 0);
228
+ return [
229
+ ...(marge ? [{ kind: "ruimte", hoogteMm: marge }] : []),
230
+ {
231
+ kind: "groep",
232
+ kinderen,
233
+ breedtePercent: breedte,
234
+ uitlijning,
235
+ paddingMm: padding,
236
+ kader: !!o["blok_rand"],
237
+ achtergrond: o["blok_achtergrond"] ? String(o["blok_achtergrond"]) : undefined,
238
+ margeOnderMm: marge + 2,
239
+ },
240
+ ];
241
+ }
242
+ function fustVerzamelFlow(order, s, basis) {
243
+ const groepen = fustVerzamellijst(order.karren);
244
+ if (!groepen.length)
245
+ return [];
246
+ const gekozen = s ? lijst(s, "velden") : [];
247
+ const kolommen = (gekozen.length ? gekozen : Object.keys(FUST_KOLOM_LABELS)).filter((k) => FUST_KOLOM_LABELS[k]);
248
+ const kopTekst = s
249
+ ? String(s.opties?.["kop"] ?? "Te verzamelen fusten — karindeling zelf bepalen")
250
+ : "Te verzamelen fusten — karindeling zelf bepalen";
251
+ const toonHoogte = s ? opt(s, "toonFusthoogte", true) !== false : true;
252
+ const toonTotaal = s ? opt(s, "toonTotaal", true) !== false : true;
253
+ const totaal = groepen.reduce((n, f) => n + f.aantal, 0);
254
+ const uit = [...sectitel(kopTekst, basis)];
255
+ for (const f of groepen) {
256
+ uit.push({
257
+ kind: "tussen",
258
+ links: [f.fusttype, toonHoogte ? f.hoogte : "", toonHoogte ? f.belading : ""].filter(Boolean).join(" · "),
259
+ rechts: `${f.aantal}× fust`,
260
+ vet: true,
261
+ lijnOnder: true,
262
+ groottePt: basis,
263
+ });
264
+ if (kolommen.length)
265
+ uit.push({
266
+ kind: "tabel",
267
+ kolommen: kolommen.map((k) => ({
268
+ label: FUST_KOLOM_LABELS[k] ?? k,
269
+ breedte: 100 / kolommen.length,
270
+ uitlijning: (FUST_RECHTS.has(k) ? "right" : "left"),
271
+ })),
272
+ rijen: f.producten.map((pr) => kolommen.map((k) => String(pr[k] ?? ""))),
273
+ toonKop: true,
274
+ groottePt: basis,
275
+ randen: "onder",
276
+ kopHerhalen: true,
277
+ });
278
+ }
279
+ if (toonTotaal)
280
+ uit.push({ kind: "tussen", links: "Totaal fusten", rechts: String(totaal), vet: true, groottePt: basis });
281
+ return uit;
282
+ }
283
+ function pakbonBarcodeWaarde(order) {
284
+ const direct = String(order.barcode ?? order.cart?.barcode ?? "").trim();
285
+ if (direct)
286
+ return direct;
287
+ const karren = Array.isArray(order.karren) ? order.karren : [];
288
+ if (karren.length !== 1)
289
+ return "";
290
+ return String(karren[0]?.barcode ?? "").trim();
291
+ }
292
+ function sectieFlow(s, order, zoek, input) {
293
+ const basis = input.basisGroottePt;
294
+ switch (s.type) {
295
+ case "logo": {
296
+ const logoId = typeof s.opties?.["logoId"] === "string" ? String(s.opties["logoId"]) : null;
297
+ if (!logoId)
298
+ return [];
299
+ const logo = input.logos.find((l) => l.id === logoId);
300
+ if (!logo?.viewUrl)
301
+ return [];
302
+ return [
303
+ {
304
+ kind: "afbeelding",
305
+ src: logo.viewUrl,
306
+ hoogteMm: Number(opt(s, "hoogte", 18)),
307
+ uitlijning: optS(s, "uitlijning", "left"),
308
+ },
309
+ ];
310
+ }
311
+ case "bedrijfsgegevens":
312
+ return [
313
+ ...sectitel(s.titel, basis),
314
+ ...(opt(s, "toonNaam", true)
315
+ ? [{ kind: "tekst", tekst: order.bedrijf.naam, vet: true, groottePt: basis }]
316
+ : []),
317
+ ...(opt(s, "toonAdres", true)
318
+ ? [
319
+ {
320
+ kind: "tekst",
321
+ tekst: [order.bedrijf.adres, order.bedrijf.plaats].filter(Boolean).join(" · "),
322
+ groottePt: basis,
323
+ },
324
+ ]
325
+ : []),
326
+ ...(opt(s, "toonContact", true)
327
+ ? [
328
+ {
329
+ kind: "tekst",
330
+ tekst: [order.bedrijf.telefoon, order.bedrijf.email].filter(Boolean).join(" · "),
331
+ groottePt: basis,
332
+ },
333
+ ]
334
+ : []),
335
+ ...(opt(s, "toonKvk", false)
336
+ ? [
337
+ {
338
+ kind: "tekst",
339
+ tekst: [order.bedrijf.kvk, order.bedrijf.btw].filter(Boolean).join(" · "),
340
+ groottePt: basis,
341
+ },
342
+ ]
343
+ : []),
344
+ ].filter((f) => f.kind !== "tekst" || f.tekst);
345
+ case "klantgegevens":
346
+ return [
347
+ ...sectitel(s.titel, basis),
348
+ { kind: "tekst", tekst: order.klant.naam, vet: true, groottePt: basis },
349
+ ...kvOf(opt(s, "toonKlantnummer", true), "Klantnummer", order.klant.nummer, basis),
350
+ ...kvOf(opt(s, "toonContact", true), "Contact", order.klant.contact, basis),
351
+ ...kvOf(opt(s, "toonReferentie", true), "Referentie", order.klant.referentie, basis),
352
+ ];
353
+ case "afleveradres":
354
+ case "factuuradres": {
355
+ const a = s.type === "afleveradres" ? order.afleveradres : order.factuuradres;
356
+ return [
357
+ ...sectitel(s.titel, basis),
358
+ { kind: "tekst", tekst: a.naam, vet: true, groottePt: basis },
359
+ { kind: "tekst", tekst: a.adres, groottePt: basis },
360
+ { kind: "tekst", tekst: a.plaats, groottePt: basis },
361
+ ...(opt(s, "toonLand", true) && a.land
362
+ ? [{ kind: "tekst", tekst: a.land, groottePt: basis }]
363
+ : []),
364
+ ].filter((f) => f.kind !== "tekst" || f.tekst);
365
+ }
366
+ case "orderinformatie": {
367
+ const items = [
368
+ ...itemOf(opt(s, "toonOrdernummer", true), "Ordernummer", order.order.nummer),
369
+ ...itemOf(opt(s, "toonDatum", true), "Orderdatum", order.order.datum),
370
+ ...itemOf(opt(s, "toonLeverdatum", true), "Leverdatum", order.order.leverdatum),
371
+ ...itemOf(opt(s, "toonReferentie", true), "Referentie", order.order.referentie),
372
+ ];
373
+ if (!items.length)
374
+ return [];
375
+ return [...sectitel(s.titel, basis), { kind: "kvgrid", items, kolommen: 2, groottePt: basis }];
376
+ }
377
+ case "transportinformatie": {
378
+ const items = [
379
+ ...itemOf(opt(s, "toonTransporteur", true), "Transporteur", order.transport.transporteur),
380
+ ...itemOf(opt(s, "toonRoute", true), "Route", order.transport.route),
381
+ ...itemOf(opt(s, "toonChauffeur", false), "Chauffeur", order.transport.chauffeur),
382
+ ...itemOf(opt(s, "toonTrack", false), "Track & trace", order.transport.track),
383
+ ];
384
+ if (!items.length)
385
+ return [];
386
+ return [...sectitel(s.titel, basis), { kind: "kvgrid", items, kolommen: 2, groottePt: basis }];
387
+ }
388
+ case "opmerkingen":
389
+ if (!order.order.opmerking)
390
+ return [];
391
+ return [
392
+ {
393
+ kind: "groep",
394
+ kader: !!opt(s, "kader", true),
395
+ kinderen: [{ kind: "tekst", tekst: `Opmerkingen: ${order.order.opmerking}`, groottePt: basis }],
396
+ },
397
+ ];
398
+ case "orderregels": {
399
+ const kolommen = arr(s.kolommen);
400
+ if (!kolommen.length)
401
+ return [];
402
+ const groepen = groepeerRegels(order, optS(s, "groepering", "order"), optS(s, "sortering", "eigen"));
403
+ const uit = [...sectitel(s.titel, basis)];
404
+ for (const g of groepen) {
405
+ if (!g.producten.length)
406
+ continue;
407
+ if (g.titel !== "—")
408
+ uit.push({ kind: "tekst", tekst: g.titel, vet: true, groottePt: basis });
409
+ uit.push({
410
+ kind: "tabel",
411
+ kolommen: kolommen.map((k) => ({
412
+ label: k.label,
413
+ breedte: Number(k.breedte) || 0,
414
+ uitlijning: (k.uitlijning ?? "left"),
415
+ vet: k.vet,
416
+ groottePt: k.fontgrootte ?? basis,
417
+ })),
418
+ rijen: g.producten.map((p) => kolommen.map((k) => cel(p, k.key))),
419
+ toonKop: opt(s, "toonKoppen", true) !== false,
420
+ groottePt: basis,
421
+ zebra: !!opt(s, "zebra", false),
422
+ randen: opt(s, "randen", true) !== false ? "rand" : "onder",
423
+ kopHerhalen: true,
424
+ });
425
+ }
426
+ return uit;
427
+ }
428
+ case "pickmethode":
429
+ if (optS(s, "methode", "kar") === "kar")
430
+ return [];
431
+ return fustVerzamelFlow(order, s, basis);
432
+ case "karren":
433
+ case "lagen":
434
+ case "producten":
435
+ return [];
436
+ case "fusten": {
437
+ if (optS(s, "modus", "totaal") !== "totaal")
438
+ return [];
439
+ const alle = order.karren.flatMap((k) => k.lagen.flatMap((l) => l.fusten));
440
+ return fustTabel(lijst(s, "velden"), alle, s.titel, basis);
441
+ }
442
+ case "totalen": {
443
+ const t = totalen(order);
444
+ const perFust = optS(zoek("pickmethode") ?? s, "methode", "kar") === "fusten";
445
+ const velden = lijst(s, "velden").filter((v) => !(perFust && (v === "karren" || v === "lagen")));
446
+ if (!velden.length)
447
+ return [];
448
+ const rechts = optS(s, "uitlijning", "right") === "right";
449
+ return [
450
+ {
451
+ kind: "tabel",
452
+ kolommen: velden.map(() => ({
453
+ label: "",
454
+ breedte: 100 / velden.length,
455
+ uitlijning: (rechts ? "right" : "left"),
456
+ vet: true,
457
+ })),
458
+ rijen: [velden.map((v) => t[v] ?? v)],
459
+ toonKop: false,
460
+ groottePt: basis,
461
+ randen: "geen",
462
+ },
463
+ ];
464
+ }
465
+ case "handtekening":
466
+ return [
467
+ ...sectitel(String(opt(s, "label", "")), basis),
468
+ ...(opt(s, "toonNaam", true) ? [{ kind: "lijnveld", label: "Naam", hoogteMm: 7 }] : []),
469
+ ...(opt(s, "toonDatum", true) ? [{ kind: "lijnveld", label: "Datum", hoogteMm: 7 }] : []),
470
+ { kind: "lijnveld", label: "", hoogteMm: 12 },
471
+ ];
472
+ case "tekstblok": {
473
+ const tekst = vulVeldcodes(optS(s, "tekst", ""), order);
474
+ if (!tekst)
475
+ return [];
476
+ const blok = { kind: "tekst", tekst, groottePt: Number(opt(s, "fontgrootte", 9)) };
477
+ return opt(s, "kader", false) ? [{ kind: "groep", kader: true, kinderen: [blok] }] : [blok];
478
+ }
479
+ case "qrcode": {
480
+ const grootte = Number(opt(s, "grootte", 25));
481
+ return [
482
+ { kind: "groep", kader: true, kinderen: [{ kind: "ruimte", hoogteMm: grootte }], breedtePercent: 100 },
483
+ { kind: "tekst", tekst: vulVeldcodes(optS(s, "waarde", ""), order), groottePt: basis * 0.85, kleur: KLEUR_GRIJS },
484
+ ];
485
+ }
486
+ case "barcode": {
487
+ const waarde = pakbonBarcodeWaarde(order);
488
+ if (!waarde)
489
+ return [];
490
+ return [
491
+ {
492
+ kind: "barcode",
493
+ waarde,
494
+ breedteMm: 45,
495
+ hoogteMm: Number(opt(s, "hoogte", 15)) || 15,
496
+ uitlijning: "center",
497
+ },
498
+ ];
499
+ }
500
+ case "afbeelding": {
501
+ const url = optS(s, "url", "");
502
+ return url ? [{ kind: "afbeelding", src: url, hoogteMm: Number(opt(s, "hoogte", 25)) }] : [];
503
+ }
504
+ default:
505
+ return [];
506
+ }
507
+ }
508
+ function kvOf(toon, label, waarde, basis) {
509
+ return toon && waarde ? [{ kind: "kv", label, waarde, groottePt: basis }] : [];
510
+ }
511
+ function itemOf(toon, label, waarde) {
512
+ return toon && waarde ? [{ label, waarde }] : [];
513
+ }
514
+ function groepeerRegels(order, groepering, sortering) {
515
+ const alle = order.karren.flatMap((k) => k.lagen.flatMap((l) => l.producten.map((p) => ({ p, kar: k, laag: l }))));
516
+ const maak = (titel, items) => ({ titel, producten: sorteerProducten(items, sortering) });
517
+ if (groepering === "kar")
518
+ return order.karren.map((k) => maak(`Kar ${k.karnummer}`, k.lagen.flatMap((l) => l.producten)));
519
+ if (groepering === "laag") {
520
+ const map = new Map();
521
+ for (const r of alle) {
522
+ const t = `Kar ${r.kar.karnummer} · laag ${r.laag.laagnummer}`;
523
+ map.set(t, [...(map.get(t) ?? []), r.p]);
524
+ }
525
+ return [...map.entries()].map(([t, items]) => maak(t, items));
526
+ }
527
+ if (groepering === "productgroep") {
528
+ const map = new Map();
529
+ for (const r of alle)
530
+ map.set(r.p.productgroep, [...(map.get(r.p.productgroep) ?? []), r.p]);
531
+ return [...map.entries()].map(([t, items]) => maak(t, items));
532
+ }
533
+ if (groepering === "afleveradres")
534
+ return [maak(order.afleveradres.naam, alle.map((r) => r.p))];
535
+ if (groepering === "klant")
536
+ return [maak(order.klant.naam, alle.map((r) => r.p))];
537
+ return [maak("—", alle.map((r) => r.p))];
538
+ }
539
+ function karrenFlow(s, order, zoek, basis) {
540
+ const modus = optS(s, "modus", "pagina");
541
+ if (modus === "geen")
542
+ return [];
543
+ const scheiding = optS(s, "scheiding", "kop");
544
+ const lagenSectie = zoek("lagen");
545
+ const lagenModus = lagenSectie ? optS(lagenSectie, "modus", "apart") : "geen";
546
+ const perPagina = modus === "pagina" || scheiding === "pagina";
547
+ const perLaagPagina = modus !== "nummer" && lagenModus === "pagina";
548
+ const bundelKarren = opt(s, "bundel_identiek", true) !== false;
549
+ const groepen = groepeerIdentiekeKarren(order.karren, bundelKarren);
550
+ const uit = [];
551
+ groepen.forEach((groep, i) => {
552
+ const kar = groep.kar;
553
+ const lagen = modus === "nummer" ? [] : kar.lagen;
554
+ if (perLaagPagina && lagen.length) {
555
+ groepeerIdentiekeLagen(lagen, false).forEach((lg, j) => {
556
+ if (i > 0 || j > 0)
557
+ uit.push({ kind: "paginabreuk" });
558
+ uit.push(...blokKader(s, [karBlokFlow(s, kar, [lg.laag], zoek, modus === "blok" || modus === "passend", groep.aantal, basis)]));
559
+ });
560
+ return;
561
+ }
562
+ if (i > 0 && perPagina)
563
+ uit.push({ kind: "paginabreuk" });
564
+ else if (i > 0 && scheiding === "lijn")
565
+ uit.push({ kind: "lijn" });
566
+ else if (i > 0 && scheiding === "balk")
567
+ uit.push({ kind: "lijn", dikteMm: 1, kleur: "#374151" });
568
+ const blok = karBlokFlow(s, kar, lagen, zoek, modus === "blok" || modus === "passend", groep.aantal, basis);
569
+ if (modus === "passend")
570
+ blok.bijElkaar = true;
571
+ uit.push(...blokKader(s, [blok]));
572
+ });
573
+ return uit;
574
+ }
575
+ /** Bouwt het complete inhoudsmodel van één pakbon. */
576
+ export function bouwPakbonInhoud(input) {
577
+ const basis = input.basisGroottePt;
578
+ const cfg = leesPakbonConfig(input.config);
579
+ const variant = pakbonLayoutVariant(input.layoutVariant);
580
+ // Restpakbon: uitsluitend de meegestuurde restregels, nooit de hele kar.
581
+ const order = variant === "partial_order" ? beperkTotDeelkar(input.order) : input.order;
582
+ const ctx = conditieContext(order);
583
+ const alle = arr(cfg.secties);
584
+ const methode = pickMethode(alle);
585
+ const secties = alle.filter((s) => sectieZichtbaar(s, ctx) && !sectieOnderdruktVoorVariant(s.type, methode, variant));
586
+ const zoek = (t) => secties.find((s) => s.type === t);
587
+ const heeftKarBarcodes = secties.some((s) => s.type === "karren") &&
588
+ (Array.isArray(order.karren) ? order.karren : []).some((k) => Boolean(String(k.barcode ?? "").trim()));
589
+ const paginaKop = variant
590
+ ? [
591
+ {
592
+ kind: "band",
593
+ titel: variant === "full_cart" ? "HELE KAR" : "DEELKAR",
594
+ groottePt: 15,
595
+ uitlijning: "center",
596
+ lijnBovenOnder: true,
597
+ margeOnderMm: 2,
598
+ },
599
+ ]
600
+ : [];
601
+ const documentLogo = cfg.logo?.logoId
602
+ ? input.logos.find((l) => l.id === cfg.logo?.logoId)
603
+ : undefined;
604
+ const flows = [];
605
+ if (documentLogo?.viewUrl)
606
+ flows.push({
607
+ kind: "afbeelding",
608
+ src: documentLogo.viewUrl,
609
+ hoogteMm: Number(cfg.logo?.hoogte ?? 18),
610
+ uitlijning: (cfg.logo?.uitlijning ?? "left"),
611
+ });
612
+ const totalenSectie = zoek("totalen");
613
+ const totalenElkePagina = !!totalenSectie && opt(totalenSectie, "elke_pagina", false) === true;
614
+ for (const s of secties) {
615
+ if (s.type === "totalen" && totalenElkePagina)
616
+ continue;
617
+ if (s.type === "barcode" && heeftKarBarcodes)
618
+ continue;
619
+ if (s.type === "karren") {
620
+ flows.push(...karrenFlow(s, order, zoek, basis));
621
+ continue;
622
+ }
623
+ flows.push(...blokKader(s, sectieFlow(s, order, zoek, input)));
624
+ }
625
+ const pickSectie = alle.find((s) => s.type === "pickmethode") ?? alle[0];
626
+ const automatischeBarcode = !heeftKarBarcodes && !secties.some((s) => s.type === "barcode");
627
+ const waarde = automatischeBarcode ? pakbonBarcodeWaarde(order) : "";
628
+ if (waarde)
629
+ flows.push({
630
+ kind: "barcode",
631
+ waarde,
632
+ breedteMm: 45,
633
+ hoogteMm: Number(pickSectie ? opt(pickSectie, "barcodeHoogte", 15) : 15) || 15,
634
+ uitlijning: "left",
635
+ });
636
+ const paginaVoet = totalenElkePagina && totalenSectie && !input.zonderPaginavoet
637
+ ? [
638
+ { kind: "lijn", dikteMm: 0.35, kleur: "#9ca3af", margeMm: 0.4 },
639
+ ...sectieFlow(totalenSectie, order, zoek, input),
640
+ ]
641
+ : [];
642
+ const paginanummers = !!totalenSectie && opt(totalenSectie, "toon_paginanummer", false) === true;
643
+ return { flows, paginaKop, paginaVoet, paginanummers };
644
+ }
645
+ /**
646
+ * Inhoud voor "Voorbeeld printen": eerst de hele karren, daarna één
647
+ * deelkarpagina — exact zoals het gecombineerde voorbeeld op het scherm.
648
+ */
649
+ export function bouwGecombineerdeInhoud(input) {
650
+ const heel = bouwPakbonInhoud({ ...input, layoutVariant: "full_cart" });
651
+ const deel = bouwPakbonInhoud({
652
+ ...input,
653
+ order: deelkarVoorbeeldOrder(input.order),
654
+ layoutVariant: "partial_order",
655
+ zonderPaginavoet: true,
656
+ });
657
+ return {
658
+ flows: [
659
+ ...heel.flows,
660
+ { kind: "paginabreuk" },
661
+ ...(deel.paginaKop ?? []),
662
+ ...deel.flows,
663
+ ],
664
+ paginaKop: heel.paginaKop,
665
+ paginaVoet: heel.paginaVoet,
666
+ paginanummers: heel.paginanummers,
667
+ };
668
+ }