@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
@@ -75,8 +75,134 @@ export const FUST_VELD_OPTIES = [
75
75
  { value: "locatie", label: "Locatie" },
76
76
  { value: "fustsoort", label: "Tray/fustsoort" },
77
77
  { value: "planten_per_fust", label: "Planten per fust" },
78
+ { value: "fust_aantal", label: "Aantal fusten" },
78
79
  { value: "aantal", label: "Totaal planten" },
79
80
  ];
81
+ /**
82
+ * Alle velden die op een fustregel van de afleverbrief kunnen staan. De
83
+ * afleverbrief beschrijft één kar, dus kar- en laagnummer horen er ook bij.
84
+ */
85
+ export const FUSTREGEL_VELD_OPTIES = [
86
+ { value: "fustsoort", label: "Fustsoort" },
87
+ { value: "fusthoogte", label: "Fusthoogte" },
88
+ { value: "belading", label: "Belading" },
89
+ { value: "karnummer", label: "Karnummer" },
90
+ { value: "laagnummer", label: "Laag" },
91
+ { value: "artikelnummer", label: "Artikelnummer" },
92
+ { value: "artikelnaam", label: "Omschrijving" },
93
+ { value: "potmaat", label: "Potmaat" },
94
+ { value: "kleur", label: "Kleur" },
95
+ { value: "productgroep", label: "Productgroep" },
96
+ { value: "locatie", label: "Locatie" },
97
+ { value: "planten_per_fust", label: "Planten per fust" },
98
+ { value: "fust_aantal", label: "Aantal fusten" },
99
+ { value: "aantal", label: "Totaal planten" },
100
+ { value: "gewicht", label: "Gewicht" },
101
+ ];
102
+ /** Labels van de fustregelvelden, gedeeld door scherm en afdruk. */
103
+ export const FUSTREGEL_LABELS = Object.fromEntries(FUSTREGEL_VELD_OPTIES.map((o) => [o.value, o.label]));
104
+ /** Velden die rechts uitgelijnd horen te staan. */
105
+ export const FUSTREGEL_RECHTS = new Set([
106
+ "planten_per_fust",
107
+ "fust_aantal",
108
+ "aantal",
109
+ "gewicht",
110
+ ]);
111
+ const tekst = (waarde) => String(waarde ?? "").trim();
112
+ /**
113
+ * Zet de karren van een levering om naar losse fustregels. Met `comprimeren`
114
+ * komen identieke fusten (zelfde fustsoort, artikel, potmaat, kleur en aantal
115
+ * planten per fust) op één regel te staan; het aantal fusten wordt opgeteld.
116
+ * Zonder `comprimeren` krijgt elke losse fust een eigen regel.
117
+ */
118
+ export function fustRegels(karren, opties = {}) {
119
+ const lijst = Array.isArray(karren) ? karren : [];
120
+ const regels = [];
121
+ for (const kar of lijst) {
122
+ const karnummer = tekst(kar?.karnummer ?? kar?.cart_label);
123
+ for (const laag of Array.isArray(kar?.lagen) ? kar.lagen : []) {
124
+ const fusten = Array.isArray(laag?.fusten) ? laag.fusten : [];
125
+ const fustInfo = (soort) => fusten.find((f) => tekst(f?.fusttype ?? f?.fustsoort).toLowerCase() === soort.toLowerCase());
126
+ for (const pr of Array.isArray(laag?.producten) ? laag.producten : []) {
127
+ const fustsoort = tekst(pr?.fustsoort) || "Onbekend";
128
+ const info = fustInfo(fustsoort);
129
+ regels.push({
130
+ fustsoort,
131
+ fusthoogte: tekst(info?.hoogte),
132
+ belading: tekst(pr?.belading ?? info?.belading ?? laag?.belading),
133
+ karnummer,
134
+ laagnummer: tekst(laag?.laagnummer),
135
+ artikelnummer: tekst(pr?.artikelnummer),
136
+ artikelnaam: tekst(pr?.artikelnaam),
137
+ potmaat: tekst(pr?.potmaat ?? laag?.potmaat),
138
+ kleur: tekst(pr?.kleur),
139
+ productgroep: tekst(pr?.productgroep),
140
+ locatie: tekst(pr?.locatie),
141
+ planten_per_fust: Number(pr?.planten_per_fust ?? 0) || 0,
142
+ fust_aantal: Number(pr?.fust_aantal ?? 0) || 0,
143
+ aantal: Number(pr?.aantal ?? 0) || 0,
144
+ gewicht: tekst(pr?.gewicht),
145
+ barcode: tekst(pr?.barcode ?? info?.barcode),
146
+ });
147
+ }
148
+ }
149
+ }
150
+ let uit = regels;
151
+ if (opties.comprimeren !== false) {
152
+ const map = new Map();
153
+ for (const r of regels) {
154
+ const sleutel = [
155
+ r.fustsoort,
156
+ r.fusthoogte,
157
+ r.artikelnummer,
158
+ r.artikelnaam,
159
+ r.potmaat,
160
+ r.kleur,
161
+ r.planten_per_fust,
162
+ ]
163
+ .join("|")
164
+ .toLowerCase();
165
+ const bestaand = map.get(sleutel);
166
+ if (bestaand) {
167
+ bestaand.fust_aantal += r.fust_aantal;
168
+ bestaand.aantal += r.aantal;
169
+ // Kar- en laagnummers van samengevoegde regels blijven zichtbaar.
170
+ const lagen = new Set([...bestaand.laagnummer.split(", "), r.laagnummer].filter(Boolean));
171
+ bestaand.laagnummer = [...lagen].join(", ");
172
+ if (!bestaand.locatie)
173
+ bestaand.locatie = r.locatie;
174
+ }
175
+ else {
176
+ map.set(sleutel, { ...r });
177
+ }
178
+ }
179
+ uit = [...map.values()];
180
+ }
181
+ else {
182
+ // Zonder samenvoegen krijgt elke losse fust een eigen regel, zodat de
183
+ // schakelaar zichtbaar effect heeft: 8 dezelfde fusten worden 8 regels
184
+ // van 1 fust in plaats van één regel met 8.
185
+ const los = [];
186
+ for (const r of regels) {
187
+ const n = r.fust_aantal;
188
+ if (n > 1 && n <= 500) {
189
+ const perFust = r.planten_per_fust > 0 ? r.planten_per_fust : Math.round(r.aantal / n);
190
+ for (let i = 0; i < n; i += 1) {
191
+ los.push({ ...r, fust_aantal: 1, aantal: perFust });
192
+ }
193
+ }
194
+ else {
195
+ los.push(r);
196
+ }
197
+ }
198
+ uit = los;
199
+ }
200
+ const sortering = opties.sortering ?? "fustsoort";
201
+ if (sortering !== "eigen") {
202
+ uit = [...uit].sort((a, b) => String(a[sortering] ?? "").localeCompare(String(b[sortering] ?? ""), "nl", { numeric: true }));
203
+ }
204
+ return uit;
205
+ }
80
206
  const groepering = {
81
207
  key: "groepering",
82
208
  label: "Groeperen op",
@@ -209,6 +335,52 @@ export const SECTIE_REGISTER = [
209
335
  },
210
336
  ],
211
337
  },
338
+ {
339
+ type: "fustregels",
340
+ label: "Fustregels",
341
+ omschrijving: "Alle fusten van deze kar als regels, met de fustgegevens en het aantal fusten per regel.",
342
+ opties: [
343
+ {
344
+ key: "velden",
345
+ label: "Zichtbare fustgegevens",
346
+ type: "meerkeuze",
347
+ standaard: [
348
+ "fustsoort",
349
+ "artikelnummer",
350
+ "artikelnaam",
351
+ "potmaat",
352
+ "planten_per_fust",
353
+ "fust_aantal",
354
+ "aantal",
355
+ ],
356
+ opties: FUSTREGEL_VELD_OPTIES,
357
+ },
358
+ {
359
+ key: "comprimeren",
360
+ label: "Identieke fusten samenvoegen tot één regel",
361
+ type: "schakelaar",
362
+ standaard: true,
363
+ hint: "Twee dezelfde fusten op de kar komen dan op één regel te staan, met 2 bij het aantal fusten.",
364
+ },
365
+ { key: "toonKoppen", label: "Kolomkoppen tonen", type: "schakelaar", standaard: true },
366
+ { key: "zebra", label: "Om-en-om arcering", type: "schakelaar", standaard: false },
367
+ { key: "randen", label: "Randen om cellen", type: "schakelaar", standaard: true },
368
+ { key: "toonTotaal", label: "Totaalregel tonen", type: "schakelaar", standaard: true },
369
+ {
370
+ key: "sortering",
371
+ label: "Sorteren op",
372
+ type: "keuze",
373
+ standaard: "fustsoort",
374
+ opties: [
375
+ { value: "eigen", label: "Eigen volgorde" },
376
+ { value: "fustsoort", label: "Fustsoort" },
377
+ { value: "artikelnummer", label: "Artikelnummer" },
378
+ { value: "artikelnaam", label: "Omschrijving" },
379
+ { value: "locatie", label: "Locatie" },
380
+ ],
381
+ },
382
+ ],
383
+ },
212
384
  {
213
385
  type: "pickmethode",
214
386
  label: "Pickmethode",
@@ -244,6 +416,7 @@ export const SECTIE_REGISTER = [
244
416
  "locatie",
245
417
  "fustsoort",
246
418
  "planten_per_fust",
419
+ "fust_aantal",
247
420
  "aantal",
248
421
  ],
249
422
  opties: FUST_VELD_OPTIES,
@@ -483,6 +656,13 @@ export const SECTIE_REGISTER = [
483
656
  { key: "label", label: "Bijschrift", type: "tekst", standaard: "Voor ontvangst getekend" },
484
657
  { key: "toonNaam", label: "Naamregel", type: "schakelaar", standaard: true },
485
658
  { key: "toonDatum", label: "Datumregel", type: "schakelaar", standaard: true },
659
+ {
660
+ key: "toonBarcode",
661
+ label: "Barcode onderaan tonen",
662
+ type: "schakelaar",
663
+ standaard: true,
664
+ hint: "De barcode wordt uitsluitend overgenomen uit de Engine-opdracht.",
665
+ },
486
666
  ],
487
667
  },
488
668
  {
@@ -564,20 +744,77 @@ export function standaardPakbonConfig() {
564
744
  export function leesPakbonConfig(config) {
565
745
  // Accepteert zowel de layout-config ({ pakbon: {...} }) als een kale
566
746
  // PakbonConfig. Zonder deze tolerantie viel de afdruk terug op de
567
- // standaardsecties en verdween o.a. het documentlogo.
747
+ // standaardsecties en verdween o.a. het documentlogo. De sleutel
748
+ // "afleverbrief" wordt meegelezen zodat de printpijplijn ook
749
+ // afleverbrief-layouts met hun eigen secties afdrukt.
568
750
  const raw = config;
569
751
  const c = Array.isArray(raw?.secties)
570
752
  ? raw
571
- : raw?.pakbon;
753
+ : (raw?.pakbon ?? raw?.afleverbrief);
572
754
  const secties = Array.isArray(c?.secties) ? c.secties : null;
573
755
  const logo = c?.logo ?? null;
574
756
  const basis = secties && secties.length ? { secties } : standaardPakbonConfig();
575
757
  return { ...basis, secties: migreerPickmethode(basis.secties), logo };
576
758
  }
759
+ /** Standaardindeling van een afleverbrief: geen kar-, laag- of fustblokken. */
760
+ export function standaardAfleverbriefConfig() {
761
+ return {
762
+ secties: [
763
+ "logo",
764
+ "bedrijfsgegevens",
765
+ "klantgegevens",
766
+ "afleveradres",
767
+ "orderinformatie",
768
+ "transportinformatie",
769
+ "fustregels",
770
+ "totalen",
771
+ "opmerkingen",
772
+ "handtekening",
773
+ ].map(nieuweSectie),
774
+ };
775
+ }
776
+ /**
777
+ * Blokken die op een afleverbrief thuishoren. Een afleverbrief beschrijft
778
+ * altijd precies één kar, dus kar-, laag- en pickmethodeblokken vervallen.
779
+ */
780
+ export const AFLEVERBRIEF_SECTIE_TYPES = [
781
+ "logo",
782
+ "bedrijfsgegevens",
783
+ "klantgegevens",
784
+ "afleveradres",
785
+ "factuuradres",
786
+ "orderinformatie",
787
+ "transportinformatie",
788
+ "fustregels",
789
+ "totalen",
790
+ "opmerkingen",
791
+ "handtekening",
792
+ "tekstblok",
793
+ "qrcode",
794
+ "barcode",
795
+ "afbeelding",
796
+ ];
797
+ /** Leest de sectie-opbouw van een afleverbrief-layout. */
798
+ export function leesAfleverbriefConfig(config) {
799
+ // Eerst de eigen sleutel; oudere afleverbrief-layouts zijn per abuis via de
800
+ // pakbon-opbouw onder "pakbon" opgeslagen en worden hier meegelezen.
801
+ const raw = config;
802
+ const c = Array.isArray(raw?.secties)
803
+ ? raw
804
+ : (raw?.afleverbrief ?? raw?.pakbon);
805
+ const secties = Array.isArray(c?.secties) ? c.secties : null;
806
+ const logo = c?.logo ?? null;
807
+ const basis = secties && secties.length ? { secties } : standaardAfleverbriefConfig();
808
+ return { ...basis, secties: basis.secties, logo };
809
+ }
577
810
  /** Zet de oude karren-optie "pickmethode" om naar een eigen pickmethode-blok. */
578
811
  function migreerPickmethode(secties) {
812
+ // Alleen relevant voor pakbonnen met een karren-blok; een afleverbrief
813
+ // krijgt nooit een pickmethode-kop.
579
814
  if (secties.some((s) => s.type === "pickmethode"))
580
815
  return secties;
816
+ if (!secties.some((s) => s.type === "karren"))
817
+ return secties;
581
818
  const karIndex = secties.findIndex((s) => s.type === "karren");
582
819
  const oud = karIndex >= 0 ? String(secties[karIndex].opties?.["pickmethode"] ?? "kar") : "kar";
583
820
  const blok = nieuweSectie("pickmethode");
@@ -902,71 +1139,93 @@ export function karBundelTekst(aantal) {
902
1139
  export function laagBundelTekst(aantal) {
903
1140
  return `Deze laag ${aantal}×`;
904
1141
  }
1142
+ const fustSleutel = (waarde) => String(waarde ?? "")
1143
+ .trim()
1144
+ .replace(/\s+/g, " ")
1145
+ .toLowerCase();
905
1146
  /**
906
1147
  * Verzamellijst met alle fusten van een order, opgeteld per fusttype,
907
1148
  * inclusief de producten die in dat fusttype gepickt moeten worden.
908
- * Gebruikt wanneer er per fust gepickt wordt en de gebruiker zelf de
909
- * karindeling maakt.
1149
+ * Een product hoort uitsluitend bij zijn eigen fustsoort — nooit bij alle
1150
+ * fustgroepen van de laag.
910
1151
  */
911
1152
  export function fustVerzamellijst(karren) {
912
1153
  const lijst = Array.isArray(karren) ? karren : [];
913
1154
  const map = new Map();
1155
+ /** Expliciet door de Engine opgegeven fustaantallen per fusttype. */
1156
+ const opgegevenAantallen = new Map();
1157
+ const zoekGroep = (type, f) => {
1158
+ const sleutel = fustSleutel(type) || "onbekend";
1159
+ let groep = map.get(sleutel);
1160
+ if (!groep) {
1161
+ groep = {
1162
+ fusttype: String(type ?? "").trim() || "Onbekend",
1163
+ aantal: 0,
1164
+ hoogte: String(f?.["hoogte"] ?? ""),
1165
+ belading: String(f?.["belading"] ?? ""),
1166
+ producten: [],
1167
+ };
1168
+ map.set(sleutel, groep);
1169
+ }
1170
+ else if (f) {
1171
+ if (!groep.hoogte && f["hoogte"])
1172
+ groep.hoogte = String(f["hoogte"]);
1173
+ if (!groep.belading && f["belading"])
1174
+ groep.belading = String(f["belading"]);
1175
+ }
1176
+ return groep;
1177
+ };
914
1178
  for (const kar of lijst) {
915
1179
  for (const laag of Array.isArray(kar?.lagen) ? kar.lagen : []) {
916
1180
  const producten = Array.isArray(laag?.producten) ? laag.producten : [];
917
- // Zonder aparte fustlijst: fusten afleiden uit de producten, zodat een
918
- // module die alleen orderregels aanlevert toch een verzamellijst krijgt.
919
1181
  const opgegeven = Array.isArray(laag?.fusten) ? laag.fusten : [];
920
- const afgeleid = new Map();
921
- if (!opgegeven.length) {
922
- for (const pr of producten) {
923
- const soort = String(pr?.fustsoort ?? "").trim() || "Onbekend";
924
- const aantal = Number(pr?.fust_aantal ?? 0) || 0;
925
- if (aantal > 0)
926
- afgeleid.set(soort, (afgeleid.get(soort) ?? 0) + aantal);
927
- }
928
- }
929
- const fusten = opgegeven.length
930
- ? opgegeven
931
- : Array.from(afgeleid.entries()).map(([fusttype, aantal]) => ({ fusttype, aantal }));
932
- for (const f of fusten) {
1182
+ // Fustmetadata (hoogte/belading) en expliciete aantallen vastleggen.
1183
+ for (const f of opgegeven) {
933
1184
  const type = String(f?.fusttype ?? f?.fustsoort ?? "Onbekend");
934
- let groep = map.get(type);
935
- if (!groep) {
936
- groep = {
937
- fusttype: type,
938
- aantal: 0,
939
- hoogte: String(f?.hoogte ?? ""),
940
- belading: String(f?.belading ?? ""),
941
- producten: [],
942
- };
943
- map.set(type, groep);
1185
+ zoekGroep(type, f);
1186
+ const sleutel = fustSleutel(type) || "onbekend";
1187
+ opgegevenAantallen.set(sleutel, (opgegevenAantallen.get(sleutel) ?? 0) + (Number(f?.aantal ?? 0) || 0));
1188
+ }
1189
+ // Producten bij hun eigen fustsoort plaatsen.
1190
+ for (const pr of producten) {
1191
+ const type = String(pr?.fustsoort ?? "").trim() || "Onbekend";
1192
+ const groep = zoekGroep(type);
1193
+ const nr = String(pr?.artikelnummer ?? "");
1194
+ const naam = String(pr?.artikelnaam ?? "");
1195
+ const bestaand = groep.producten.find((x) => x.artikelnummer === nr && x.artikelnaam === naam);
1196
+ const aantal = Number(pr?.aantal ?? 0) || 0;
1197
+ const fustAantal = Number(pr?.fust_aantal ?? 0) || 0;
1198
+ if (bestaand) {
1199
+ bestaand.aantal += aantal;
1200
+ bestaand.fust_aantal += fustAantal;
944
1201
  }
945
- groep.aantal += Number(f?.aantal ?? 0);
946
- for (const pr of producten) {
947
- const nr = String(pr?.artikelnummer ?? "");
948
- const bestaand = groep.producten.find((x) => x.artikelnummer === nr);
949
- if (bestaand) {
950
- bestaand.aantal += Number(pr?.aantal ?? 0);
951
- }
952
- else {
953
- groep.producten.push({
954
- artikelnummer: nr,
955
- artikelnaam: String(pr?.artikelnaam ?? ""),
956
- potmaat: String(pr?.potmaat ?? ""),
957
- kleur: String(pr?.kleur ?? ""),
958
- productgroep: String(pr?.productgroep ?? ""),
959
- locatie: String(pr?.locatie ?? ""),
960
- fustsoort: String(pr?.fustsoort ?? ""),
961
- planten_per_fust: Number(pr?.planten_per_fust ?? 0),
962
- aantal: Number(pr?.aantal ?? 0),
963
- });
964
- }
1202
+ else {
1203
+ groep.producten.push({
1204
+ artikelnummer: nr,
1205
+ artikelnaam: naam,
1206
+ potmaat: String(pr?.potmaat ?? ""),
1207
+ kleur: String(pr?.kleur ?? ""),
1208
+ productgroep: String(pr?.productgroep ?? ""),
1209
+ locatie: String(pr?.locatie ?? ""),
1210
+ fustsoort: String(pr?.fustsoort ?? ""),
1211
+ planten_per_fust: Number(pr?.planten_per_fust ?? 0) || 0,
1212
+ fust_aantal: fustAantal,
1213
+ aantal,
1214
+ });
965
1215
  }
966
1216
  }
967
1217
  }
968
1218
  }
969
- return [...map.values()].sort((a, b) => a.fusttype.localeCompare(b.fusttype));
1219
+ const groepen = [...map.entries()].map(([sleutel, groep]) => {
1220
+ const uitProducten = groep.producten.reduce((n, p) => n + p.fust_aantal, 0);
1221
+ const expliciet = opgegevenAantallen.get(sleutel) ?? 0;
1222
+ // Engine-opgave is leidend; anders tellen we de fusten van de producten op.
1223
+ groep.aantal = expliciet > 0 ? expliciet : uitProducten;
1224
+ return groep;
1225
+ });
1226
+ return groepen
1227
+ .filter((g) => g.producten.length > 0 || g.aantal > 0)
1228
+ .sort((a, b) => a.fusttype.localeCompare(b.fusttype));
970
1229
  }
971
1230
  /**
972
1231
  * Voorbeeld van een deelkar (niet-volle kar): alleen een deel van de fusten,
@@ -984,8 +1243,85 @@ export function deelkarVoorbeeldOrder(order = DEMO_ORDER) {
984
1243
  {
985
1244
  ...eerste,
986
1245
  gedeeld: true,
1246
+ barcode: "VOORBEELD-DEELKAR",
987
1247
  lagen: [{ ...laag, producten: laag.producten.slice(0, 1), fusten: laag.fusten?.slice(0, 1) ?? [] }],
988
1248
  },
989
1249
  ],
990
1250
  };
991
1251
  }
1252
+ // --- restpakbon (deelkar) ---------------------------------------------------
1253
+ /** Restregels zoals de Logistics Engine ze meestuurt onder `cart.cart_items`. */
1254
+ export function deelkarRegels(order) {
1255
+ const o = (order && typeof order === "object" ? order : {});
1256
+ const cart = (o["cart"] && typeof o["cart"] === "object" ? o["cart"] : {});
1257
+ const lijst = cart["cart_items"] ?? cart["items"];
1258
+ return Array.isArray(lijst) ? lijst : [];
1259
+ }
1260
+ const tekstWaarde = (waarde) => waarde === undefined || waarde === null ? "" : String(waarde).trim();
1261
+ /**
1262
+ * Beperkt een order tot de restregels van één deelkar. Alleen wat de Engine
1263
+ * als `cart.cart_items` meestuurt komt op de restpakbon te staan; nooit de
1264
+ * volledige karinhoud. Wordt gedeeld door voorbeeld, PDF en print, zodat de
1265
+ * drie weergaven niet uiteen kunnen lopen.
1266
+ */
1267
+ export function beperkTotDeelkar(order) {
1268
+ const regels = deelkarRegels(order);
1269
+ if (!regels.length)
1270
+ return order;
1271
+ const o = (order && typeof order === "object" ? order : {});
1272
+ const cart = (o["cart"] && typeof o["cart"] === "object" ? o["cart"] : {});
1273
+ const karren = Array.isArray(o["karren"]) ? o["karren"] : [];
1274
+ const cartId = tekstWaarde(cart["cart_id"]);
1275
+ const basisKar = (cartId ? karren.find((k) => tekstWaarde(k["cart_id"]) === cartId) : undefined) ??
1276
+ karren[0] ??
1277
+ {};
1278
+ const producten = regels.map((raw) => {
1279
+ const item = raw;
1280
+ return {
1281
+ artikelnummer: tekstWaarde(item["artikelnummer"] ?? item["artikelcode"]),
1282
+ artikelnaam: tekstWaarde(item["artikelnaam"] ?? item["product_name"] ?? item["omschrijving"]),
1283
+ potmaat: tekstWaarde(item["potmaat"]),
1284
+ aantal: Number(item["aantal"] ?? item["quantity"] ?? 0) || 0,
1285
+ belading: tekstWaarde(item["belading"]),
1286
+ barcode: tekstWaarde(item["barcode"]),
1287
+ opmerking: tekstWaarde(item["opmerking"]),
1288
+ locatie: tekstWaarde(item["locatie"]),
1289
+ gewicht: tekstWaarde(item["gewicht"]),
1290
+ productgroep: tekstWaarde(item["productgroep"]),
1291
+ kleur: tekstWaarde(item["kleur"]),
1292
+ fust_aantal: Number(item["fust_aantal"] ?? item["aantal_fusten"] ?? 0) || 0,
1293
+ fustsoort: tekstWaarde(item["fustsoort"] ?? item["fusttype"]),
1294
+ planten_per_fust: Number(item["planten_per_fust"] ?? 0) || 0,
1295
+ };
1296
+ });
1297
+ // Fusten per soort optellen uit dezelfde restregels, zodat de verzamellijst
1298
+ // en de totalen nooit meer tonen dan er nog verzameld moet worden.
1299
+ const fustMap = new Map();
1300
+ for (const pr of producten) {
1301
+ const sleutel = pr.fustsoort.toLowerCase() || "onbekend";
1302
+ const bestaand = fustMap.get(sleutel);
1303
+ if (bestaand)
1304
+ bestaand.aantal += pr.fust_aantal;
1305
+ else
1306
+ fustMap.set(sleutel, {
1307
+ fusttype: pr.fustsoort || "Onbekend",
1308
+ aantal: pr.fust_aantal,
1309
+ hoogte: "",
1310
+ belading: pr.belading,
1311
+ barcode: "",
1312
+ });
1313
+ }
1314
+ const laag = {
1315
+ laagnummer: 1,
1316
+ belading: "",
1317
+ potmaat: "",
1318
+ gewicht: "",
1319
+ opmerking: "",
1320
+ producten,
1321
+ fusten: [...fustMap.values()].filter((f) => f.aantal > 0),
1322
+ };
1323
+ return {
1324
+ ...o,
1325
+ karren: [{ ...basisKar, lagen: [laag] }],
1326
+ };
1327
+ }
@@ -0,0 +1,41 @@
1
+ import { type DemoOrder, type PakbonConfig } from "./pakbonBuilder.js";
2
+ import { type Flow } from "./pakbonDocument.js";
3
+ export interface PakbonLogo {
4
+ id: string;
5
+ naam?: string;
6
+ viewUrl?: string | null;
7
+ }
8
+ /** Alles wat één renderactie nodig heeft; overal identiek samengesteld. */
9
+ export interface PakbonRenderInput {
10
+ config: PakbonConfig | unknown;
11
+ order: PakbonOrderVorm;
12
+ breedteMm: number;
13
+ hoogteMm: number;
14
+ margeMm: number;
15
+ basisGroottePt: number;
16
+ logos: PakbonLogo[];
17
+ layoutVariant: string | null;
18
+ /** Alleen voor "Voorbeeld printen": hele karren gevolgd door een deelkar. */
19
+ beideVarianten?: boolean;
20
+ zonderPaginavoet?: boolean;
21
+ }
22
+ export type PakbonOrderVorm = DemoOrder & {
23
+ barcode?: string;
24
+ cart?: {
25
+ barcode?: string;
26
+ };
27
+ };
28
+ export interface PakbonInhoud {
29
+ flows: Flow[];
30
+ paginaKop: Flow[];
31
+ paginaVoet: Flow[];
32
+ paginanummers: boolean;
33
+ }
34
+ /** Bouwt het complete inhoudsmodel van één pakbon. */
35
+ export declare function bouwPakbonInhoud(input: PakbonRenderInput): PakbonInhoud;
36
+ /**
37
+ * Inhoud voor "Voorbeeld printen": eerst de hele karren, daarna één
38
+ * deelkarpagina — exact zoals het gecombineerde voorbeeld op het scherm.
39
+ */
40
+ export declare function bouwGecombineerdeInhoud(input: PakbonRenderInput): PakbonInhoud;
41
+ //# sourceMappingURL=pakbonContent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pakbonContent.d.ts","sourceRoot":"","sources":["../../src/lib/pakbonContent.ts"],"names":[],"mappings":"AASA,OAAO,EAoBL,KAAK,SAAS,EAEd,KAAK,YAAY,EAGlB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAe,KAAK,IAAI,EAAmC,MAAM,kBAAkB,CAAC;AAE3F,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED,2EAA2E;AAC3E,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC;IAC/B,KAAK,EAAE,eAAe,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;IACvB,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,6EAA6E;IAC7E,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,MAAM,MAAM,eAAe,GAAG,SAAS,GAAG;IACxC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAC7B,CAAC;AAomBF,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,SAAS,EAAE,IAAI,EAAE,CAAC;IAClB,UAAU,EAAE,IAAI,EAAE,CAAC;IACnB,aAAa,EAAE,OAAO,CAAC;CACxB;AAED,sDAAsD;AACtD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,iBAAiB,GAAG,YAAY,CA+EvE;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,iBAAiB,GAAG,YAAY,CAmB9E"}