@flowselections/floriday-afhandeling-module 1.0.50 → 1.0.54

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 (47) hide show
  1. package/dist-lib/components/afhandeling/ControlePage.js +1 -1
  2. package/dist-lib/components/afhandeling/DagDetailDialog.d.ts.map +1 -1
  3. package/dist-lib/components/afhandeling/DagDetailDialog.js +255 -89
  4. package/dist-lib/components/afhandeling/NuVersturenButton.d.ts +2 -2
  5. package/dist-lib/components/afhandeling/NuVersturenButton.d.ts.map +1 -1
  6. package/dist-lib/components/afhandeling/NuVersturenButton.js +22 -4
  7. package/dist-lib/components/afhandeling/ScanControleView.js +1 -1
  8. package/dist-lib/components/afhandeling/VervoerdersPage.js +1 -1
  9. package/dist-lib/components/afhandeling/VerzetOrderDialog.js +1 -1
  10. package/dist-lib/components/afhandeling/WeekAgendaView.d.ts.map +1 -1
  11. package/dist-lib/components/afhandeling/WeekAgendaView.js +4 -3
  12. package/dist-lib/components/afhandeling/WerkplekWaarschuwing.d.ts +9 -9
  13. package/dist-lib/components/afhandeling/WerkplekWaarschuwing.js +28 -28
  14. package/dist-lib/components/ui/responsive-dialog.js +1 -1
  15. package/dist-lib/hooks/useKnownOrders.d.ts +1 -0
  16. package/dist-lib/hooks/useKnownOrders.d.ts.map +1 -1
  17. package/dist-lib/hooks/useKnownOrders.js +20 -1
  18. package/dist-lib/hooks/useOrderBuffers.d.ts +6 -0
  19. package/dist-lib/hooks/useOrderBuffers.d.ts.map +1 -1
  20. package/dist-lib/hooks/useOrderBuffers.js +21 -0
  21. package/dist-lib/hooks/usePickAgenda.d.ts.map +1 -1
  22. package/dist-lib/hooks/usePickAgenda.js +7 -3
  23. package/dist-lib/hooks/usePickMode.d.ts +1 -1
  24. package/dist-lib/index.d.ts +0 -2
  25. package/dist-lib/index.d.ts.map +1 -1
  26. package/dist-lib/index.js +0 -2
  27. package/dist-lib/integrations/supabase/client.server.js +1 -1
  28. package/dist-lib/integrations/supabase/types.d.ts +14 -0
  29. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  30. package/dist-lib/lib/authGateway.server.d.ts.map +1 -1
  31. package/dist-lib/lib/authGateway.server.js +3 -2
  32. package/dist-lib/lib/cartAssignments.functions.d.ts +48 -0
  33. package/dist-lib/lib/cartAssignments.functions.d.ts.map +1 -1
  34. package/dist-lib/lib/cartAssignments.functions.js +111 -0
  35. package/dist-lib/lib/deliveryNotePrint.d.ts +33 -33
  36. package/dist-lib/lib/deliveryNotePrint.js +219 -219
  37. package/dist-lib/lib/floridayFulfillmentSend.server.d.ts +6 -1
  38. package/dist-lib/lib/floridayFulfillmentSend.server.d.ts.map +1 -1
  39. package/dist-lib/lib/floridayFulfillmentSend.server.js +23 -6
  40. package/dist-lib/lib/floridayLoading.functions.d.ts +2 -0
  41. package/dist-lib/lib/floridayLoading.functions.d.ts.map +1 -1
  42. package/dist-lib/lib/floridayLoading.functions.js +52 -24
  43. package/dist-lib/lib/logisticsEngine/capacity.js +1 -1
  44. package/dist-lib/lib/logisticsEngine/staffPlanning.js +1 -1
  45. package/dist-lib/lib/orderFulfilment.functions.d.ts.map +1 -1
  46. package/dist-lib/lib/orderFulfilment.functions.js +17 -0
  47. package/package.json +1 -1
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
2
2
  import { useEffect, useMemo, useRef, useState } from "react";
3
3
  import { Button, Card, Checkbox, Dialog, DialogDescription, DialogTitle, Input, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Textarea, Badge, toast, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@flowselections/core";
4
4
  import { Printer, CheckCircle2, AlertTriangle, History, ChevronLeft, ChevronRight, ScanLine } from "lucide-react";
5
- import { ResponsiveDialogBody, ResponsiveDialogContent, ResponsiveDialogFooter, ResponsiveDialogHeader, } from "../../components/ui/responsive-dialog";
5
+ import { ResponsiveDialogBody, ResponsiveDialogContent, ResponsiveDialogFooter, ResponsiveDialogHeader, } from "../ui/responsive-dialog.js";
6
6
  import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
7
7
  import { useServerFn } from "@tanstack/react-start";
8
8
  import { getTodaysControlCarts, getControlRescheduleProposals, submitControlSession, listControlHistory, getControlSessionDetail, getControlDraft, listDeliveredToday, saveControlDraft, } from "../../lib/cartControl.functions";
@@ -1 +1 @@
1
- {"version":3,"file":"DagDetailDialog.d.ts","sourceRoot":"","sources":["../../../src/components/afhandeling/DagDetailDialog.tsx"],"names":[],"mappings":"AAyBA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAkFjE,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,OAAO,CAAC;IACd,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC;IAClB,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;CACvC;AAQD,wBAAgB,eAAe,CAAC,KAAK,EAAE,oBAAoB,sCAG1D"}
1
+ {"version":3,"file":"DagDetailDialog.d.ts","sourceRoot":"","sources":["../../../src/components/afhandeling/DagDetailDialog.tsx"],"names":[],"mappings":"AAyBA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAmGjE,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,OAAO,CAAC;IACd,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC;IAClB,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;CACvC;AAQD,wBAAgB,eAAe,CAAC,KAAK,EAAE,oBAAoB,sCAG1D"}
@@ -1,14 +1,14 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { useState } from "react";
2
+ import { createContext, useContext, useState } from "react";
3
3
  import { useQuery, useQueryClient } from "@tanstack/react-query";
4
4
  import { useServerFn } from "@tanstack/react-start";
5
5
  import { Badge, Checkbox, Button, Dialog, DialogTitle, DialogDescription, Input, Tabs, TabsContent, TabsList, TabsTrigger, toast, } from "@flowselections/core";
6
6
  import { CalendarClock, CheckCircle2, ChevronDown, ChevronRight, Printer, Users } from "lucide-react";
7
- import { ResponsiveDialogBody, ResponsiveDialogContent, ResponsiveDialogFooter, ResponsiveDialogHeader, } from "../../components/ui/responsive-dialog";
7
+ import { ResponsiveDialogBody, ResponsiveDialogContent, ResponsiveDialogFooter, ResponsiveDialogHeader, } from "../ui/responsive-dialog.js";
8
8
  import { usePickAgenda } from "../../hooks/usePickAgenda";
9
- import { useOrderBuffers } from "../../hooks/useOrderBuffers";
9
+ import { useOrderBuffers, useBufferMembers } from "../../hooks/useOrderBuffers";
10
10
  import { isSameDay } from "../../lib/pickDate";
11
- import { getOrderLines, getDayCartContents, } from "../../lib/cartAssignments.functions";
11
+ import { getOrderLines, getDayCartContents, getOrderCartIds, getPakbonCartInfo, } from "../../lib/cartAssignments.functions";
12
12
  import { printLayoutDocument, printPdfDocument, printFoutMelding } from "../../lib/printing";
13
13
  import { AutoProcessStatus } from "./AutoProcessStatus";
14
14
  import { NuVersturenButton } from "./NuVersturenButton";
@@ -34,6 +34,16 @@ function voortgang(status) {
34
34
  }
35
35
  return { label: "Nog te picken", done: false, variant: "outline" };
36
36
  }
37
+ const ShipStateCtx = createContext(new Map());
38
+ /** Voortgang met voorrang voor de echte verzendstatus: eerst Floriday, dan de scan. */
39
+ function useVoortgang(ref, status) {
40
+ const st = useContext(ShipStateCtx).get(refKey(ref));
41
+ if (st?.floridaySent)
42
+ return { label: "Verzonden naar Floriday", done: true, variant: "default" };
43
+ if (st?.approvedAll)
44
+ return { label: "Klaar voor verzending", done: false, variant: "secondary" };
45
+ return voortgang(status);
46
+ }
37
47
  const NL_DATE = new Intl.DateTimeFormat("nl-NL", { dateStyle: "full" });
38
48
  function refKey(value) {
39
49
  return (value ?? "").trim().toUpperCase();
@@ -99,8 +109,11 @@ function DagDetailContent({ open, date, planned, onOpenChange, }) {
99
109
  contentsByRef.set(k, list);
100
110
  }
101
111
  const coverageByRef = new Map();
102
- for (const o of contentsQuery.data?.orders ?? [])
112
+ const shipState = new Map();
113
+ for (const o of contentsQuery.data?.orders ?? []) {
103
114
  coverageByRef.set(refKey(o.orderRef), o.label);
115
+ shipState.set(refKey(o.orderRef), { approvedAll: o.approvedAll, floridaySent: o.floridaySent });
116
+ }
104
117
  // Eén rij per kar die op deze dag valt (Engine-dekking is leidend, ook voor bufferkarren).
105
118
  const cartRows = [];
106
119
  for (const o of ordersOnDay) {
@@ -111,6 +124,7 @@ function DagDetailContent({ open, date, planned, onOpenChange, }) {
111
124
  const nr = rc?.cartNumber ?? idx + 1;
112
125
  cartRows.push({
113
126
  key: `${o.ref}-${idx}`,
127
+ cartId: rc?.cartId ?? null,
114
128
  orderRef: o.ref,
115
129
  customerName: o.planned?.order.customerName ?? "—",
116
130
  cartIndex: idx,
@@ -130,7 +144,7 @@ function DagDetailContent({ open, date, planned, onOpenChange, }) {
130
144
  });
131
145
  }
132
146
  }
133
- return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(ResponsiveDialogContent, { size: "xl", children: [_jsxs(ResponsiveDialogHeader, { children: [_jsxs(DialogTitle, { children: ["Dagdetails \u2014 ", NL_DATE.format(date)] }), _jsxs(DialogDescription, { children: [ordersOnDay.length, " bestellingen \u00B7 ", cartRows.length, " karren \u00B7", " ", ordersOnDay.filter((o) => voortgang(o.status).done).length, " van ", ordersOnDay.length, " ", "bestellingen afgehandeld \u00B7 ", cartRows.filter((c) => voortgang(c.status).done).length, " van", " ", cartRows.length, " karren afgehandeld"] })] }), _jsxs(ResponsiveDialogBody, { children: [_jsx(DayCapacityWarning, { date: date }), _jsx(BezettingCard, { date: date, cartsToday: cartRows.length }), _jsxs(Tabs, { defaultValue: "bestellingen", children: [_jsxs(TabsList, { children: [_jsxs(TabsTrigger, { value: "bestellingen", children: ["Bestellingen (", ordersOnDay.length, ")"] }), _jsxs(TabsTrigger, { value: "karren", children: ["Karren (", cartRows.length, ")"] })] }), _jsx(TabsContent, { value: "bestellingen", className: "mt-4", children: _jsx(BestellingenTab, { orders: ordersOnDay, isLoading: agenda.isLoading }) }), _jsx(TabsContent, { value: "karren", className: "mt-4", children: _jsx(KarrenTab, { carts: cartRows }) })] })] }), _jsx(ResponsiveDialogFooter, { children: _jsx(Button, { variant: "outline", onClick: () => onOpenChange(false), children: "Sluiten" }) })] }) }));
147
+ return (_jsx(ShipStateCtx.Provider, { value: shipState, children: _jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(ResponsiveDialogContent, { size: "xl", children: [_jsxs(ResponsiveDialogHeader, { children: [_jsxs(DialogTitle, { children: ["Dagdetails \u2014 ", NL_DATE.format(date)] }), _jsxs(DialogDescription, { children: [ordersOnDay.length, " bestellingen \u00B7 ", cartRows.length, " karren \u00B7", " ", ordersOnDay.filter((o) => voortgang(o.status).done).length, " van ", ordersOnDay.length, " ", "bestellingen afgehandeld \u00B7 ", cartRows.filter((c) => voortgang(c.status).done).length, " van", " ", cartRows.length, " karren afgehandeld"] })] }), _jsxs(ResponsiveDialogBody, { children: [_jsx(DayCapacityWarning, { date: date }), _jsx(BezettingCard, { date: date, cartsToday: cartRows.length }), _jsxs(Tabs, { defaultValue: "bestellingen", children: [_jsxs(TabsList, { children: [_jsxs(TabsTrigger, { value: "bestellingen", children: ["Bestellingen (", ordersOnDay.length, ")"] }), _jsxs(TabsTrigger, { value: "karren", children: ["Karren (", cartRows.length, ")"] })] }), _jsx(TabsContent, { value: "bestellingen", className: "mt-4", children: _jsx(BestellingenTab, { orders: ordersOnDay, isLoading: agenda.isLoading }) }), _jsx(TabsContent, { value: "karren", className: "mt-4", children: _jsx(KarrenTab, { carts: cartRows }) })] })] }), _jsx(ResponsiveDialogFooter, { children: _jsx(Button, { variant: "outline", onClick: () => onOpenChange(false), children: "Sluiten" }) })] }) }) }));
134
148
  }
135
149
  function BezettingCard({ date, cartsToday }) {
136
150
  const queryClient = useQueryClient();
@@ -228,63 +242,195 @@ function BestellingenTab({ orders, isLoading }) {
228
242
  if (orders.length === 0) {
229
243
  return (_jsx("p", { className: "text-sm text-muted-foreground", children: isLoading ? "Bestellingen laden…" : "Geen bestellingen op deze dag." }));
230
244
  }
231
- return (_jsx("div", { className: "space-y-3", children: groups.map((g) => g.bufferId && g.orders.length > 1 ? (_jsx(BufferGroup, { orders: g.orders }, g.key)) : (g.orders.map((o) => o.planned ? (_jsx(OrderRow, { planned: o.planned, cartsToday: o.carts, engineStatus: o.status }, o.ref)) : (_jsx(EngineOrderRow, { order: o }, o.ref))))) }));
245
+ return (_jsx("div", { className: "space-y-3", children: groups.map((g) => g.bufferId && g.orders.length > 1 ? (_jsx(BufferGroup, { bufferId: g.bufferId, orders: g.orders }, g.key)) : (g.orders.map((o) => o.planned ? (_jsx(OrderRow, { planned: o.planned, cartsToday: o.carts, engineStatus: o.status }, o.ref)) : (_jsx(EngineOrderRow, { order: o }, o.ref))))) }));
246
+ }
247
+ /** Al aangevraagd in de wachtrij = de bestaande herprint-regel; niet opnieuw aanmaken. */
248
+ function isDubbeleAanvraag(err) {
249
+ return err?.name === "DubbeleAanvraagFout";
250
+ }
251
+ function tijdLabel(iso) {
252
+ const d = new Date(iso);
253
+ return Number.isNaN(d.getTime()) ? iso : d.toLocaleString("nl-NL", { day: "numeric", month: "short", hour: "2-digit", minute: "2-digit" });
254
+ }
255
+ /** Concreet zoals op het pickstation, met direct een knop "Opnieuw printen" (als herprint). */
256
+ function meldOverslagen(s) {
257
+ const t = toast;
258
+ t(`Pakbon ${s.label} al verstuurd vanuit ${s.herkomst}${s.printer ? ` naar ${s.printer}` : ""} om ${tijdLabel(s.tijd)}`, {
259
+ duration: 15000,
260
+ action: {
261
+ label: "Opnieuw printen",
262
+ onClick: () => {
263
+ s.herprint().then(() => toast.success(`Pakbon ${s.label} opnieuw in de Printwachtrij gezet (herprint).`), (err) => toast.error(printFoutMelding(err)));
264
+ },
265
+ },
266
+ });
267
+ }
268
+ /** Toont de uitkomst direct; de achtergrondopdracht meldt zich later apart. */
269
+ function meldPrintUitkomst(r) {
270
+ if (r.printed > 0) {
271
+ toast.success(`${r.printed} opdracht${r.printed === 1 ? "" : "en"} in de Printwachtrij gezet.`);
272
+ }
273
+ for (const s of r.skipped)
274
+ meldOverslagen(s);
275
+ if (r.printed === 0 && r.skipped.length === 0 && !r.achtergrond) {
276
+ toast.error(`Er is niets in de Printwachtrij gezet. ${r.reasons[0] ?? ""}`.trim());
277
+ }
278
+ else if (r.failed > 0) {
279
+ toast.error(`${r.failed} document${r.failed === 1 ? "" : "en"} niet in de wachtrij: ${r.reasons[0] ?? ""}`);
280
+ }
281
+ }
282
+ async function meldAchtergrond(r) {
283
+ if (!r.achtergrond)
284
+ return;
285
+ const a = await r.achtergrond;
286
+ if (a.printed > 0)
287
+ toast.success(`Verzendbrief in de Printwachtrij gezet.`);
288
+ if (a.failed > 0 || (a.printed === 0 && a.reasons.length > 0)) {
289
+ toast.error(`Verzendbrief niet gemaakt: ${a.reasons[0] ?? "onbekende reden"}`);
290
+ }
291
+ }
292
+ /** Payload in dezelfde vorm als het pickstation; Pickschermen zoekt op documenttype pakbon + payload.cart_id. */
293
+ function pakbonPayload(args) {
294
+ return {
295
+ source: "afhandeling",
296
+ cart_id: args.cartId,
297
+ barcode: args.barcode,
298
+ cart: { id: args.cartId, barcode: args.barcode, number: args.cartNumber },
299
+ order_id: args.order.id,
300
+ order_number: args.order.orderNumber,
301
+ order: {
302
+ ...args.order,
303
+ pickDate: args.pickDate,
304
+ cartIndex: args.cartIndex,
305
+ cartTotal: args.cartTotal,
306
+ cartNumber: args.cartNumber,
307
+ barcode: args.barcode,
308
+ lines: args.lines,
309
+ },
310
+ };
232
311
  }
233
- /** Print alle pakbonnen van één bestelling; geeft aantallen terug. */
234
- async function printPakbonnenVoorOrder(p, fetchLines) {
312
+ /**
313
+ * Print alle documentsoorten van één bestelling: per echte Engine-kar een pakbon
314
+ * (cart_id verplicht) en de verzendbrief/afleverbrief. Pakbonnen gaan direct de
315
+ * wachtrij in (zonder op de printer te wachten); een kar met al een pakbon wordt
316
+ * overgeslagen met herkomst/printer/tijd en een herprintknop. De verzendbrief
317
+ * wordt op de achtergrond gemaakt en meldt zich apart.
318
+ */
319
+ async function printDocumentenVoorOrder(p, fetchLines, fetchCarts, fetchDeliveryNote, fetchShippingNoteLayout, fetchPakbonInfo) {
235
320
  const source = p.order.source;
236
- let lines = [];
321
+ const reasons = [];
322
+ const [linesRes, cartsRes] = await Promise.allSettled([
323
+ fetchLines({ data: { orderId: p.order.id, orderSource: source } }),
324
+ fetchCarts({ data: { orderId: p.order.id } }),
325
+ ]);
326
+ const lines = linesRes.status === "fulfilled" ? linesRes.value.lines : [];
327
+ let engineCarts = [];
328
+ if (cartsRes.status === "fulfilled")
329
+ engineCarts = cartsRes.value.carts;
330
+ else
331
+ reasons.push(cartsRes.reason instanceof Error ? cartsRes.reason.message : "Karindeling van de Engine niet opgehaald.");
332
+ if (engineCarts.length === 0) {
333
+ if (!reasons.length)
334
+ reasons.push(`De Engine heeft voor ${p.order.orderNumber} nog geen karren ingedeeld.`);
335
+ return { printed: 0, skipped: [], failed: 1, reasons, achtergrond: null };
336
+ }
337
+ let info;
237
338
  try {
238
- const res = await fetchLines({ data: { orderId: p.order.id, orderSource: source } });
239
- lines = res.lines;
339
+ info = (await fetchPakbonInfo({ data: { cartIds: engineCarts.map((c) => c.cartId) } })).carts;
240
340
  }
241
- catch {
242
- /* doorgaan met lege regels */
341
+ catch (err) {
342
+ return { printed: 0, skipped: [], failed: engineCarts.length, reasons: [err instanceof Error ? err.message : "Kar-barcodes niet opgehaald."], achtergrond: null };
243
343
  }
244
- const cartTotal = Math.max(1, (p.planning?.pickAssignments ?? []).reduce((sum, a) => sum + a.cartCount, 0));
245
- const pickDateIso = p.planning?.pickAssignments?.[0]?.date?.toISOString?.().slice(0, 10) ??
246
- new Date().toISOString().slice(0, 10);
247
- const pakbonLines = lines.map((l) => ({
248
- description: l.article,
249
- crates: l.crates,
250
- unit: null,
251
- }));
344
+ const infoById = new Map(info.map((i) => [i.cartId, i]));
345
+ const cartTotal = engineCarts.length;
346
+ const pickDate = p.planning?.pickAssignments?.[0]?.date?.toISOString?.().slice(0, 10) ?? new Date().toISOString().slice(0, 10);
347
+ const orderInfo = {
348
+ id: p.order.id,
349
+ orderNumber: p.order.orderNumber,
350
+ customerName: p.order.customerName,
351
+ country: p.order.country ?? null,
352
+ deliveryDate: p.order.deliveryDate,
353
+ };
354
+ const pakbonLines = lines.map((l) => ({ description: l.article, crates: l.crates, unit: null }));
252
355
  let printed = 0;
253
356
  let failed = 0;
254
- for (let idx = 0; idx < cartTotal; idx++) {
255
- const cartNumber = `${p.order.orderNumber}-${String(idx + 1).padStart(2, "0")}`;
357
+ const skipped = [];
358
+ await Promise.all(engineCarts.map(async (engineCart, idx) => {
359
+ const nr = String(engineCart.cartNumber).padStart(2, "0");
360
+ const cartNumber = `${p.order.orderNumber}-${nr}`;
361
+ const ci = infoById.get(engineCart.cartId);
362
+ if (!ci) {
363
+ failed++;
364
+ reasons.push(`Pakbon ${cartNumber}: geen kar-barcode van de Engine.`);
365
+ return;
366
+ }
367
+ const opdracht = (isHerprint) => printLayoutDocument({
368
+ documenttype: "pakbon",
369
+ documentnaam: `PAK-${p.order.orderNumber}-${nr}`,
370
+ isHerprint,
371
+ volgStatus: false,
372
+ payload: pakbonPayload({ cartId: ci.cartId, barcode: ci.barcode, cartNumber, cartIndex: idx + 1, cartTotal, order: orderInfo, pickDate, lines: pakbonLines }),
373
+ });
374
+ if (ci.bestaand) {
375
+ skipped.push({ label: cartNumber, herkomst: ci.bestaand.herkomst, printer: ci.bestaand.printer, tijd: ci.bestaand.tijd, herprint: () => opdracht(true) });
376
+ return;
377
+ }
256
378
  try {
257
- // Centrale pakbonlayout uit de Layout maker; de wachtrij kiest
258
- // zelf de actieve layout en de bijbehorende printer.
259
- await printLayoutDocument({
260
- documenttype: "pakbon",
261
- documentnaam: `PAK-${p.order.orderNumber}-${String(idx + 1).padStart(2, "0")}`,
262
- payload: {
263
- order: {
264
- id: p.order.id,
265
- orderNumber: p.order.orderNumber,
266
- customerName: p.order.customerName,
267
- country: p.order.country ?? null,
268
- deliveryDate: p.order.deliveryDate,
269
- pickDate: pickDateIso,
270
- cartIndex: idx + 1,
271
- cartTotal,
272
- cartNumber,
273
- lines: pakbonLines,
274
- },
275
- },
276
- });
277
- printed++;
379
+ const jobId = await opdracht(false);
380
+ if (jobId)
381
+ printed++;
382
+ else {
383
+ failed++;
384
+ reasons.push(`Pakbon ${cartNumber}: de Printwachtrij gaf geen opdracht-id terug.`);
385
+ }
278
386
  }
279
387
  catch (err) {
280
388
  failed++;
389
+ reasons.push(`Pakbon ${cartNumber}: ${printFoutMelding(err)}`);
281
390
  console.warn("[print-all] pakbon mislukt", cartNumber, err);
282
391
  }
283
- }
284
- return { printed, failed };
392
+ }));
393
+ // Verzendbrief/afleverbrief op de achtergrond: eerst de Engine-layout, anders de Floriday-brief.
394
+ const achtergrond = (async () => {
395
+ try {
396
+ const keuze = await fetchShippingNoteLayout({});
397
+ let jobId = null;
398
+ if (keuze.layoutId) {
399
+ jobId = await printLayoutDocument({
400
+ documenttype: keuze.documenttype ?? "afleverbrief",
401
+ documentnaam: `AFL-${p.order.orderNumber}`,
402
+ layoutId: keuze.layoutId,
403
+ volgStatus: false,
404
+ payload: { order: { ...orderInfo, cartIndex: 1, cartTotal, cartNumber: p.order.orderNumber } },
405
+ });
406
+ }
407
+ else {
408
+ const { pdf } = await fetchDeliveryNote({ data: { orderRef: p.order.orderNumber } });
409
+ if (!pdf)
410
+ return { printed: 0, failed: 1, reasons: [`geen Engine-layout en geen Floriday-brief beschikbaar voor ${p.order.orderNumber}.`] };
411
+ jobId = await printPdfDocument({ documenttype: "afleverbrief", documentnaam: `AFL-${p.order.orderNumber}`, pdfBase64: pdf.base64, volgStatus: false });
412
+ }
413
+ return jobId ? { printed: 1, failed: 0, reasons: [] } : { printed: 0, failed: 1, reasons: ["de Printwachtrij gaf geen opdracht-id terug."] };
414
+ }
415
+ catch (err) {
416
+ if (isDubbeleAanvraag(err))
417
+ return { printed: 0, failed: 0, reasons: [] };
418
+ console.warn("[print-all] verzendbrief mislukt", p.order.orderNumber, err);
419
+ return { printed: 0, failed: 1, reasons: [printFoutMelding(err)] };
420
+ }
421
+ })();
422
+ return { printed, skipped, failed, reasons, achtergrond };
285
423
  }
286
424
  /** Meerdere orders die samen in één buffer zitten; één regel in de agenda. */
287
- function BufferGroup({ orders }) {
425
+ function BufferGroup({ bufferId, orders: dayOrders }) {
426
+ // Alle orders van de buffer (koppeling op buffer-id), ook als ze op een andere dag staan.
427
+ const members = useBufferMembers(bufferId);
428
+ const orders = [...dayOrders];
429
+ for (const m of members.data ?? []) {
430
+ if (!orders.some((o) => refKey(o.ref) === refKey(m.orderNumber))) {
431
+ orders.push({ ref: m.orderNumber, carts: 0, plants: 0, status: m.status ?? "", planned: null });
432
+ }
433
+ }
288
434
  const [open, setOpen] = useState(false);
289
435
  const [verzetOpen, setVerzetOpen] = useState(false);
290
436
  const [printing, setPrinting] = useState(false);
@@ -292,10 +438,20 @@ function BufferGroup({ orders }) {
292
438
  const queryClient = useQueryClient();
293
439
  const mark = useServerFn(markOrderShipped);
294
440
  const fetchLines = useServerFn(getOrderLines);
441
+ const fetchCarts = useServerFn(getOrderCartIds);
442
+ const fetchDeliveryNote = useServerFn(getDeliveryNotePdf);
443
+ const fetchShippingNoteLayout = useServerFn(getShippingNoteLayout);
444
+ const fetchPakbonInfo = useServerFn(getPakbonCartInfo);
445
+ const shipState = useContext(ShipStateCtx);
295
446
  const carts = orders.reduce((s, o) => s + (o.carts || 0), 0);
296
447
  const klant = orders.find((o) => o.planned?.order.customerName)?.planned?.order.customerName ?? null;
297
- const alleAf = orders.every((o) => voortgang(o.status).done);
448
+ const alleAf = orders.every((o) => shipState.get(refKey(o.ref))?.floridaySent === true);
298
449
  const refs = orders.map((o) => o.ref).filter(Boolean);
450
+ // Alleen Floriday-orders (op Floriday-id); de bron-kolom is hier niet betrouwbaar.
451
+ const bekend = orders.filter((o) => o.planned);
452
+ const alleFloriday = bekend.length > 0 && bekend.every((o) => !!o.planned?.order.salesOrderId);
453
+ // Pickstatus uit de Engine: goedgekeurde scan of door de Engine afgemeld.
454
+ const engineKlaar = (o) => shipState.get(refKey(o.ref))?.approvedAll === true || voortgang(o.status).done;
299
455
  const verzetOrders = orders.map((o) => ({
300
456
  ref: o.ref,
301
457
  customerName: o.planned?.order.customerName ?? "—",
@@ -311,14 +467,16 @@ function BufferGroup({ orders }) {
311
467
  await mark({ data: { orderRef: ref } });
312
468
  ok++;
313
469
  }
314
- catch {
315
- failed.push(ref);
470
+ catch (err) {
471
+ failed.push(err instanceof Error ? err.message : `${ref}: afhandelen mislukt.`);
316
472
  }
317
473
  }
318
474
  if (ok > 0)
319
- toast.success(`${ok} bestelling${ok === 1 ? "" : "en"} uit deze buffer afgehandeld (verzonden).`);
475
+ toast.success(`${ok} van ${refs.length} bestelling${refs.length === 1 ? "" : "en"} uit deze buffer afgehandeld (verzonden).`);
320
476
  if (failed.length > 0)
321
- toast.error(`Niet gelukt voor: ${failed.join(", ")}`);
477
+ toast.error(`Niet afgehandeld: ${failed.join(" · ")}`);
478
+ if (ok === 0 && failed.length === 0)
479
+ toast.error("Er is niets afgehandeld: de buffer heeft geen bestellingen.");
322
480
  for (const key of [
323
481
  ["logistics-pick-agenda"],
324
482
  ["engine"],
@@ -335,23 +493,20 @@ function BufferGroup({ orders }) {
335
493
  if (printing)
336
494
  return;
337
495
  setPrinting(true);
338
- let printed = 0;
339
- let failed = 0;
340
496
  try {
341
- for (const o of orders) {
342
- if (!o.planned) {
343
- failed++;
344
- continue;
345
- }
346
- const res = await printPakbonnenVoorOrder(o.planned, fetchLines);
347
- printed += res.printed;
348
- failed += res.failed;
349
- }
350
- if (printed > 0) {
351
- toast.success(`Documenten geprint — ${printed} pakbon${printed === 1 ? "" : "nen"}.`);
352
- }
353
- if (failed > 0)
354
- toast.error(`${failed} pakbon kon niet worden geprint.`);
497
+ const uitkomsten = await Promise.all(orders.map(async (o) => o.planned
498
+ ? printDocumentenVoorOrder(o.planned, fetchLines, fetchCarts, fetchDeliveryNote, fetchShippingNoteLayout, fetchPakbonInfo)
499
+ : { printed: 0, skipped: [], failed: 1, reasons: [`${o.ref}: ordergegevens niet geladen op deze dag.`], achtergrond: null }));
500
+ const samen = {
501
+ printed: uitkomsten.reduce((t, u) => t + u.printed, 0),
502
+ skipped: uitkomsten.flatMap((u) => u.skipped),
503
+ failed: uitkomsten.reduce((t, u) => t + u.failed, 0),
504
+ reasons: uitkomsten.flatMap((u) => u.reasons),
505
+ achtergrond: null,
506
+ };
507
+ samen.achtergrond = uitkomsten.some((u) => u.achtergrond) ? Promise.resolve({ printed: 0, failed: 0, reasons: [] }) : null;
508
+ meldPrintUitkomst(samen);
509
+ await Promise.all(uitkomsten.map(meldAchtergrond));
355
510
  }
356
511
  catch (err) {
357
512
  toast.error(printFoutMelding(err));
@@ -360,7 +515,7 @@ function BufferGroup({ orders }) {
360
515
  setPrinting(false);
361
516
  }
362
517
  };
363
- return (_jsxs("div", { className: "rounded-lg border border-primary/40 bg-primary/5", children: [_jsxs("div", { className: "flex flex-col gap-3 p-3", children: [_jsxs("button", { type: "button", onClick: () => setOpen((o) => !o), className: "-m-1 flex items-start gap-3 rounded-md p-1 text-left hover:bg-primary/10", children: [open ? (_jsx(ChevronDown, { className: "mt-1 h-4 w-4 shrink-0" })) : (_jsx(ChevronRight, { className: "mt-1 h-4 w-4 shrink-0" })), _jsxs("div", { className: "min-w-0 flex-1 space-y-2", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2 text-sm", children: [_jsx(Badge, { variant: "secondary", children: "Buffer" }), _jsxs("span", { className: "font-medium", children: [orders.length, " bestellingen samen"] }), klant ? _jsx("span", { className: "text-xs text-muted-foreground", children: klant }) : null] }), _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsxs(Badge, { variant: "secondary", children: [carts, " ", carts === 1 ? "kar" : "karren", " vandaag"] }), _jsx(Badge, { variant: alleAf ? "default" : "outline", children: alleAf ? "Afgehandeld" : "Nog te picken" }), _jsx("span", { className: "text-xs text-muted-foreground", children: refs.join(", ") })] })] })] }), _jsxs("div", { className: "flex flex-wrap items-center gap-2 border-t pt-3", children: [_jsxs(Button, { size: "sm", variant: "outline", onClick: afhandelen, disabled: busy || alleAf, title: "Alle bestellingen in deze buffer in \u00E9\u00E9n keer afhandelen", children: [_jsx(CheckCircle2, { className: "mr-1 h-3.5 w-3.5" }), alleAf ? "Afgehandeld" : busy ? "Bezig…" : "Buffer afhandelen (verzonden)"] }), _jsxs(Button, { size: "sm", variant: "outline", onClick: printAlles, disabled: printing, title: "Alle pakbonnen van deze buffer afdrukken", children: [_jsx(Printer, { className: "mr-1 h-3.5 w-3.5" }), printing ? "Bezig…" : "Documenten afdrukken"] }), _jsxs(Button, { size: "sm", variant: "outline", onClick: () => setVerzetOpen(true), title: "Alle bestellingen van deze buffer verzetten", children: [_jsx(CalendarClock, { className: "mr-1 h-3.5 w-3.5" }), "Verzetten"] })] })] }), open && (_jsx("div", { className: "space-y-2 border-t border-primary/30 px-2 py-2", children: orders.map((o) => o.planned ? (_jsx(OrderRow, { planned: o.planned, cartsToday: o.carts, engineStatus: o.status }, o.ref)) : (_jsx(EngineOrderRow, { order: o }, o.ref))) })), _jsx(BulkVerzetDialog, { open: verzetOpen, orders: verzetOrders, cartCount: carts, onOpenChange: setVerzetOpen, onDone: () => setVerzetOpen(false) })] }));
518
+ return (_jsxs("div", { className: "rounded-lg border border-primary/40 bg-primary/5", children: [_jsxs("div", { className: "flex flex-col gap-3 p-3", children: [_jsxs("button", { type: "button", onClick: () => setOpen((o) => !o), className: "-m-1 flex items-start gap-3 rounded-md p-1 text-left hover:bg-primary/10", children: [open ? (_jsx(ChevronDown, { className: "mt-1 h-4 w-4 shrink-0" })) : (_jsx(ChevronRight, { className: "mt-1 h-4 w-4 shrink-0" })), _jsxs("div", { className: "min-w-0 flex-1 space-y-2", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2 text-sm", children: [_jsx(Badge, { variant: "secondary", children: "Buffer" }), _jsxs("span", { className: "font-medium", children: [orders.length, " bestellingen samen"] }), klant ? _jsx("span", { className: "text-xs text-muted-foreground", children: klant }) : null] }), _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsxs(Badge, { variant: "secondary", children: [carts, " ", carts === 1 ? "kar" : "karren", " vandaag"] }), _jsx(Badge, { variant: alleAf ? "default" : "outline", children: alleAf ? "Verzonden naar Floriday" : orders.every(engineKlaar) ? "Klaar voor verzending" : orders.some(engineKlaar) ? "Deels gescand" : "Nog te picken" }), _jsx("span", { className: "text-xs text-muted-foreground", children: refs.join(", ") })] })] })] }), _jsxs("div", { className: "flex flex-wrap items-center gap-2 border-t pt-3", children: [_jsxs(Button, { size: "sm", variant: "outline", onClick: afhandelen, disabled: busy || alleAf, title: "Alle bestellingen in deze buffer in \u00E9\u00E9n keer afhandelen", children: [_jsx(CheckCircle2, { className: "mr-1 h-3.5 w-3.5" }), alleAf ? "Afgehandeld" : busy ? "Bezig…" : "Buffer afhandelen (verzonden)"] }), refs[0] && alleFloriday ? _jsx(NuVersturenButton, { orderRef: refs[0], label: "Buffer naar Floriday versturen" }) : null, _jsxs(Button, { size: "sm", variant: "outline", onClick: printAlles, disabled: printing, title: "Alle pakbonnen van deze buffer afdrukken", children: [_jsx(Printer, { className: "mr-1 h-3.5 w-3.5" }), printing ? "In de Printwachtrij…" : "Documenten afdrukken"] }), _jsxs(Button, { size: "sm", variant: "outline", onClick: () => setVerzetOpen(true), title: "Alle bestellingen van deze buffer verzetten", children: [_jsx(CalendarClock, { className: "mr-1 h-3.5 w-3.5" }), "Verzetten"] })] })] }), open && (_jsx("div", { className: "space-y-2 border-t border-primary/30 px-2 py-2", children: orders.map((o) => o.planned ? (_jsx(OrderRow, { planned: o.planned, cartsToday: o.carts, engineStatus: o.status }, o.ref)) : (_jsx(EngineOrderRow, { order: o }, o.ref))) })), _jsx(BulkVerzetDialog, { open: verzetOpen, orders: verzetOrders, cartCount: carts, onOpenChange: setVerzetOpen, onDone: () => setVerzetOpen(false) })] }));
364
519
  }
365
520
  /** Bestelling die alleen de Engine kent; verdere ordergegevens ontbreken. */
366
521
  /** Knop "Afhandelen (verzonden)" — zet de bestelling in de orderadministratie op Verzonden. */
@@ -400,7 +555,8 @@ function AfhandelenButton({ orderRef, done }) {
400
555
  return (_jsxs(Button, { size: "sm", variant: "outline", onClick: handle, disabled: busy || done, title: "Deze bestelling afhandelen en op verzonden zetten", children: [_jsx(CheckCircle2, { className: "mr-1 h-3.5 w-3.5" }), done ? "Afgehandeld" : busy ? "Bezig…" : "Afhandelen (verzonden)"] }));
401
556
  }
402
557
  function EngineOrderRow({ order }) {
403
- return (_jsx("div", { className: "rounded-lg border bg-card px-4 py-3", children: _jsxs("div", { className: "grid grid-cols-2 gap-3 text-sm md:grid-cols-4", children: [_jsxs("div", { children: [_jsx("div", { className: "font-medium", children: order.ref }), _jsx("div", { className: "text-xs text-muted-foreground", children: "Alleen bekend bij de Engine" })] }), _jsxs("div", { children: [_jsx("div", { className: "text-xs text-muted-foreground", children: "Karren vandaag" }), _jsx("div", { children: order.carts })] }), _jsxs("div", { children: [_jsx("div", { className: "text-xs text-muted-foreground", children: "Planten" }), _jsx("div", { children: order.plants })] }), _jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [_jsx(Badge, { variant: voortgang(order.status).variant, children: voortgang(order.status).label }), _jsx(AfhandelenButton, { orderRef: order.ref, done: voortgang(order.status).done })] })] }) }));
558
+ const vg = useVoortgang(order.ref, order.status);
559
+ return (_jsx("div", { className: "rounded-lg border bg-card px-4 py-3", children: _jsxs("div", { className: "grid grid-cols-2 gap-3 text-sm md:grid-cols-4", children: [_jsxs("div", { children: [_jsx("div", { className: "font-medium", children: order.ref }), _jsx("div", { className: "text-xs text-muted-foreground", children: "Alleen bekend bij de Engine" })] }), _jsxs("div", { children: [_jsx("div", { className: "text-xs text-muted-foreground", children: "Karren vandaag" }), _jsx("div", { children: order.carts })] }), _jsxs("div", { children: [_jsx("div", { className: "text-xs text-muted-foreground", children: "Planten" }), _jsx("div", { children: order.plants })] }), _jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [_jsx(Badge, { variant: vg.variant, children: vg.label }), _jsx(AfhandelenButton, { orderRef: order.ref, done: vg.done })] })] }) }));
404
560
  }
405
561
  function OrderRow({ planned: p, cartsToday, engineStatus, }) {
406
562
  const [open, setOpen] = useState(false);
@@ -408,6 +564,11 @@ function OrderRow({ planned: p, cartsToday, engineStatus, }) {
408
564
  const [verzetOpen, setVerzetOpen] = useState(false);
409
565
  const source = p.order.source;
410
566
  const fetchLines = useServerFn(getOrderLines);
567
+ const fetchCarts = useServerFn(getOrderCartIds);
568
+ const fetchDeliveryNote = useServerFn(getDeliveryNotePdf);
569
+ const fetchShippingNoteLayout = useServerFn(getShippingNoteLayout);
570
+ const fetchPakbonInfo = useServerFn(getPakbonCartInfo);
571
+ const vg = useVoortgang(p.order.orderNumber, engineStatus ?? p.order.status);
411
572
  const linesQuery = useQuery({
412
573
  queryKey: ["order-lines", source, p.order.id],
413
574
  queryFn: () => fetchLines({ data: { orderId: p.order.id, orderSource: source } }),
@@ -419,13 +580,9 @@ function OrderRow({ planned: p, cartsToday, engineStatus, }) {
419
580
  return;
420
581
  setPrinting(true);
421
582
  try {
422
- const { printed, failed } = await printPakbonnenVoorOrder(p, fetchLines);
423
- if (printed > 0) {
424
- toast.success(`Documenten geprint — ${printed} pakbon${printed === 1 ? "" : "nen"}.`);
425
- }
426
- if (failed > 0) {
427
- toast.error(`${failed} pakbon kon niet worden geprint.`);
428
- }
583
+ const r = await printDocumentenVoorOrder(p, fetchLines, fetchCarts, fetchDeliveryNote, fetchShippingNoteLayout, fetchPakbonInfo);
584
+ meldPrintUitkomst(r);
585
+ await meldAchtergrond(r);
429
586
  }
430
587
  catch (err) {
431
588
  toast.error(printFoutMelding(err));
@@ -437,7 +594,7 @@ function OrderRow({ planned: p, cartsToday, engineStatus, }) {
437
594
  };
438
595
  return (_jsxs("div", { className: "rounded-lg border bg-card", children: [_jsxs("div", { className: "flex flex-col gap-3 p-3", children: [_jsxs("button", { type: "button", onClick: () => setOpen((o) => !o), className: "-m-1 flex items-start gap-3 rounded-md p-1 text-left hover:bg-muted/50", children: [open ? (_jsx(ChevronDown, { className: "mt-1 h-4 w-4 shrink-0" })) : (_jsx(ChevronRight, { className: "mt-1 h-4 w-4 shrink-0" })), _jsxs("div", { className: "min-w-0 flex-1 space-y-2", children: [_jsxs("div", { className: "flex flex-wrap items-baseline gap-x-6 gap-y-1 text-sm", children: [_jsxs("div", { className: "min-w-0", children: [_jsx("div", { className: "font-medium", children: p.order.orderNumber }), _jsx("div", { className: "truncate text-xs text-muted-foreground", children: p.order.customerName })] }), _jsxs("div", { className: "text-xs text-muted-foreground", children: ["Type ", _jsx("span", { className: "text-foreground", children: p.order.deliveryType })] }), _jsxs("div", { className: "text-xs text-muted-foreground", children: ["Leverdatum", " ", _jsx("span", { className: "text-foreground", children: p.order.deliveryDate
439
596
  ? new Date(p.order.deliveryDate).toLocaleDateString("nl-NL")
440
- : "—" })] })] }), _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsxs(Badge, { variant: "secondary", children: [cartsToday, " karren vandaag"] }), _jsx(Badge, { variant: voortgang(engineStatus ?? p.order.status).variant, children: voortgang(engineStatus ?? p.order.status).label }), _jsx(Badge, { variant: "outline", children: p.order.status }), (() => {
597
+ : "—" })] })] }), _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsxs(Badge, { variant: "secondary", children: [cartsToday, " karren vandaag"] }), _jsx(Badge, { variant: vg.variant, children: vg.label }), _jsx(Badge, { variant: "outline", children: p.order.status }), (() => {
441
598
  const src = p.order.source === "floriday" ? "floriday" : "manual";
442
599
  const soId = p.order.salesOrderId ?? (src === "floriday" ? p.order.id : null);
443
600
  return soId ? (_jsx(AutoProcessStatus, { salesOrderId: soId, compact: true, allowManualTrigger: false })) : null;
@@ -445,7 +602,7 @@ function OrderRow({ planned: p, cartsToday, engineStatus, }) {
445
602
  const src = p.order.source === "floriday" ? "floriday" : "manual";
446
603
  const soId = p.order.salesOrderId ?? (src === "floriday" ? p.order.id : null);
447
604
  return soId ? (_jsx(AutoProcessStatus, { salesOrderId: soId, orderRef: p.order.orderNumber })) : null;
448
- })(), p.order.orderNumber ? _jsx(NuVersturenButton, { orderRef: p.order.orderNumber }) : null, _jsx(AfhandelenButton, { orderRef: p.order.orderNumber, done: voortgang(p.order.status).done }), _jsxs(Button, { size: "sm", variant: "outline", onClick: handlePrintAll, disabled: printing, title: "Alle pakbonnen van deze bestelling afdrukken", children: [_jsx(Printer, { className: "mr-1 h-3.5 w-3.5" }), printing ? "Bezig…" : "Alle documenten afdrukken"] }), _jsxs(Button, { size: "sm", variant: "outline", onClick: (e) => {
605
+ })(), p.order.orderNumber && p.order.salesOrderId ? _jsx(NuVersturenButton, { orderRef: p.order.orderNumber }) : null, _jsx(AfhandelenButton, { orderRef: p.order.orderNumber, done: vg.done }), _jsxs(Button, { size: "sm", variant: "outline", onClick: handlePrintAll, disabled: printing, title: "Alle documenten van deze bestelling afdrukken (pakbonnen per kar en de verzendbrief)", children: [_jsx(Printer, { className: "mr-1 h-3.5 w-3.5" }), printing ? "In de Printwachtrij…" : "Alle documenten afdrukken"] }), _jsxs(Button, { size: "sm", variant: "outline", onClick: (e) => {
449
606
  e.stopPropagation();
450
607
  setVerzetOpen(true);
451
608
  }, title: "Ophaaldag of ophaaltijd van deze order wijzigen", children: [_jsx(CalendarClock, { className: "mr-1 h-3.5 w-3.5" }), "Verzetten"] })] })] }), _jsx(VerzetOrderDialog, { open: verzetOpen, orderRef: p.order.orderNumber, carrierTarget: {
@@ -526,6 +683,7 @@ function KarRow({ cart: c, cartTotal, selected, onToggle, }) {
526
683
  const fetchLines = useServerFn(getOrderLines);
527
684
  const fetchDeliveryNote = useServerFn(getDeliveryNotePdf);
528
685
  const fetchShippingNoteLayout = useServerFn(getShippingNoteLayout);
686
+ const fetchPakbonInfo = useServerFn(getPakbonCartInfo);
529
687
  const printPakbon = async () => {
530
688
  if (bezig)
531
689
  return;
@@ -548,27 +706,35 @@ function KarRow({ cart: c, cartTotal, selected, onToggle, }) {
548
706
  /* pakbon zonder regels blijft mogelijk */
549
707
  }
550
708
  }
551
- const nr = String(c.cartIndex).padStart(2, "0");
709
+ if (!c.cartId)
710
+ throw new Error(`Pakbon ${c.cartNumber}: de Engine heeft voor deze kar nog geen kar-id.`);
711
+ const [ci] = (await fetchPakbonInfo({ data: { cartIds: [c.cartId] } })).carts;
712
+ if (!ci)
713
+ throw new Error(`Pakbon ${c.cartNumber}: geen kar-barcode van de Engine.`);
714
+ const nr = String(c.cartIndex + 1).padStart(2, "0");
552
715
  await printLayoutDocument({
553
716
  documenttype: "pakbon",
554
717
  documentnaam: `PAK-${c.orderRef}-${nr}`,
555
718
  isHerprint: true,
556
- payload: {
719
+ volgStatus: false,
720
+ payload: pakbonPayload({
721
+ cartId: ci.cartId,
722
+ barcode: ci.barcode,
723
+ cartNumber: c.cartNumber,
724
+ cartIndex: c.cartIndex + 1,
725
+ cartTotal,
557
726
  order: {
558
727
  id: p?.order.id ?? c.orderRef,
559
728
  orderNumber: c.orderRef,
560
729
  customerName: c.customerName,
561
730
  country: p?.order.country ?? null,
562
731
  deliveryDate: p?.order.deliveryDate ?? null,
563
- pickDate: new Date().toISOString().slice(0, 10),
564
- cartIndex: c.cartIndex,
565
- cartTotal,
566
- cartNumber: c.cartNumber,
567
- lines: pakbonLines,
568
732
  },
569
- },
733
+ pickDate: new Date().toISOString().slice(0, 10),
734
+ lines: pakbonLines,
735
+ }),
570
736
  });
571
- toast.success(`Pakbon ${c.cartNumber} is naar de printer gestuurd.`);
737
+ toast.success(`Pakbon ${c.cartNumber} als herprint in de Printwachtrij gezet.`);
572
738
  }
573
739
  catch (err) {
574
740
  toast.error(printFoutMelding(err));
@@ -1,5 +1,5 @@
1
- /** "Nu versturen": toont eerst een samenvatting met controles en vraagt bevestiging. */
2
- export declare function NuVersturenButton({ orderRef }: {
1
+ export declare function NuVersturenButton({ orderRef, label }: {
3
2
  orderRef: string;
3
+ label?: string;
4
4
  }): import("react").JSX.Element;
5
5
  //# sourceMappingURL=NuVersturenButton.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"NuVersturenButton.d.ts","sourceRoot":"","sources":["../../../src/components/afhandeling/NuVersturenButton.tsx"],"names":[],"mappings":"AAYA,wFAAwF;AACxF,wBAAgB,iBAAiB,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,+BAkGnE"}
1
+ {"version":3,"file":"NuVersturenButton.d.ts","sourceRoot":"","sources":["../../../src/components/afhandeling/NuVersturenButton.tsx"],"names":[],"mappings":"AAoBA,wBAAgB,iBAAiB,CAAC,EAAE,QAAQ,EAAE,KAAsB,EAAE,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,+BAmH3G"}
@@ -5,20 +5,34 @@ import { Button, toast, AlertDialog, AlertDialogCancel, AlertDialogContent, Aler
5
5
  import { Send, Loader2, CheckCircle2, XCircle } from "lucide-react";
6
6
  import { prepareFloridayLoading, sendFloridayLoading, } from "../../lib/floridayLoading.functions";
7
7
  /** "Nu versturen": toont eerst een samenvatting met controles en vraagt bevestiging. */
8
- export function NuVersturenButton({ orderRef }) {
8
+ const PICK_LABEL = {
9
+ geen_pickrij: "geen pickrij bij de Engine",
10
+ open: "nog te picken",
11
+ bezig: "wordt gepickt",
12
+ gepickt: "gepickt",
13
+ };
14
+ export function NuVersturenButton({ orderRef, label = "Nu versturen" }) {
9
15
  const prepare = useServerFn(prepareFloridayLoading);
10
16
  const send = useServerFn(sendFloridayLoading);
11
17
  const [loading, setLoading] = useState(false);
12
18
  const [sending, setSending] = useState(false);
13
19
  const [preview, setPreview] = useState(null);
14
20
  const load = async () => setPreview(await prepare({ data: { orderRef } }));
21
+ const meldFout = (e, standaard) => {
22
+ const t = e instanceof Error ? e.message : standaard;
23
+ const m = /^(.*?)\s*\[([^\]]+)\]$/s.exec(t);
24
+ if (m)
25
+ toast.error(m[1], { description: m[2] });
26
+ else
27
+ toast.error(t);
28
+ };
15
29
  const open = async () => {
16
30
  setLoading(true);
17
31
  try {
18
32
  await load();
19
33
  }
20
34
  catch (e) {
21
- toast.error(e instanceof Error ? e.message : "Belading samenstellen mislukt");
35
+ meldFout(e, "Belading samenstellen mislukt");
22
36
  }
23
37
  finally {
24
38
  setLoading(false);
@@ -44,7 +58,11 @@ export function NuVersturenButton({ orderRef }) {
44
58
  setSending(false);
45
59
  }
46
60
  };
47
- return (_jsxs(_Fragment, { children: [_jsxs(Button, { size: "sm", variant: "outline", onClick: open, disabled: loading, children: [loading ? _jsx(Loader2, { className: "h-3 w-3 mr-1 animate-spin" }) : _jsx(Send, { className: "h-3 w-3 mr-1" }), "Nu versturen"] }), _jsx(AlertDialog, { open: !!preview, onOpenChange: (o) => !o && !sending && setPreview(null), children: _jsxs(AlertDialogContent, { children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: "Belading naar Floriday versturen?" }), _jsx(AlertDialogDescription, { children: "Een geaccepteerde opdracht start de verzendbrief, vooraankondiging, fust-administratie en afrekening, en is op productie niet terug te draaien." })] }), preview && (_jsxs("div", { className: "space-y-2 text-sm", children: [_jsxs("div", { children: [_jsx("span", { className: "text-muted-foreground", children: "Orders:" }), " ", preview.orderRefs.join(", ")] }), _jsxs("div", { children: [_jsx("span", { className: "text-muted-foreground", children: "Leveringen:" }), " ", preview.shipments.length] }), _jsxs("div", { children: [_jsx("span", { className: "text-muted-foreground", children: "Karren:" }), " ", preview.cartCount, preview.carriers.some((c) => c.shared) ? " (waarvan gedeeld)" : ""] }), _jsxs("div", { children: [_jsx("span", { className: "text-muted-foreground", children: "Regels:" }), " ", preview.lineCount] }), _jsx("ul", { className: "space-y-1", children: preview.orders.map((o) => (_jsxs("li", { className: "flex items-center gap-2", children: [o.controlled
61
+ return (_jsxs(_Fragment, { children: [_jsxs(Button, { size: "sm", variant: "outline", onClick: open, disabled: loading, children: [loading ? _jsx(Loader2, { className: "h-3 w-3 mr-1 animate-spin" }) : _jsx(Send, { className: "h-3 w-3 mr-1" }), label] }), _jsx(AlertDialog, { open: !!preview, onOpenChange: (o) => !o && !sending && setPreview(null), children: _jsxs(AlertDialogContent, { children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: "Belading naar Floriday versturen?" }), _jsx(AlertDialogDescription, { children: "Een geaccepteerde opdracht start de verzendbrief, vooraankondiging, fust-administratie en afrekening, en is op productie niet terug te draaien." })] }), preview && (_jsxs("div", { className: "space-y-2 text-sm", children: [_jsxs("div", { children: [_jsx("span", { className: "text-muted-foreground", children: "Orders:" }), " ", preview.orderRefs.join(", ")] }), _jsxs("div", { children: [_jsx("span", { className: "text-muted-foreground", children: "Leveringen:" }), " ", preview.shipments.length] }), _jsxs("div", { children: [_jsx("span", { className: "text-muted-foreground", children: "Karren:" }), " ", preview.cartCount, preview.carriers.some((c) => c.shared) ? " (waarvan gedeeld)" : ""] }), _jsxs("div", { children: [_jsx("span", { className: "text-muted-foreground", children: "Regels:" }), " ", preview.lineCount] }), _jsx("ul", { className: "space-y-1", children: preview.orders.map((o) => (_jsxs("li", { className: "flex items-center gap-2", children: [o.controlled
48
62
  ? _jsx(CheckCircle2, { className: "h-3.5 w-3.5 text-primary" })
49
- : _jsx(XCircle, { className: "h-3.5 w-3.5 text-destructive" }), _jsxs("span", { children: [o.orderRef, ": ", o.cartsControlled, "/", o.cartsTotal, " karren goedgekeurd op Scan-controle \u00B7 ", o.pickStatus ?? "geen pickrij", o.autoStatus ? ` · auto-verwerking: ${o.autoStatus}` : ""] })] }, o.orderRef))) }), preview.shipments.filter((sh) => sh.floridayStatus || preview.lastAttempts.some((a) => a.fulfillmentOrderId === sh.fulfillmentOrderId && (a.code === "created" || a.code === "already_fulfilled"))).map((sh) => (_jsxs("div", { className: "rounded border border-border p-2", children: ["Belading verstuurd", sh.floridayStatus ? ` (Floriday: ${sh.floridayStatus})` : "", " \u00B7", " ", sh.deliveryNoteReady === true ? "verzendbrief klaar" : "verzendbrief volgt via de Engine"] }, sh.fulfillmentOrderId))), preview.lastAttempts.length > 0 && (_jsxs("div", { className: "rounded border border-border p-2", children: [_jsx("div", { className: "text-muted-foreground mb-1", children: "Laatste pogingen" }), preview.lastAttempts.slice(0, 4).map((a) => (_jsxs("div", { children: [new Date(a.createdAt).toLocaleString("nl-NL"), " \u00B7 versturen \u00B7 ", a.code] }, a.createdAt + a.step)))] })), preview.blockers.length > 0 && (_jsx("ul", { className: "list-disc pl-5 text-destructive", children: preview.blockers.map((b) => _jsx("li", { children: b }, b)) }))] })), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { disabled: sending, children: "Annuleren" }), _jsxs(Button, { onClick: doSend, disabled: !preview?.canSend || sending, children: [sending ? _jsx(Loader2, { className: "h-3 w-3 mr-1 animate-spin" }) : null, "Versturen"] })] })] }) })] }));
63
+ : _jsx(XCircle, { className: "h-3.5 w-3.5 text-destructive" }), _jsxs("span", { children: [o.orderRef, ": ", o.cartsControlled, " van ", o.cartsTotal, " karren goedgekeurd op de Scan-controle", o.controlled ? " — klaar voor verzending" : "", o.pickStatus ? ` · pickstatus: ${PICK_LABEL[o.pickStatus] ?? o.pickStatus}` : "", o.autoStatus ? ` · auto-verwerking: ${o.autoStatus}` : ""] })] }, o.orderRef))) }), preview.shipments.filter((sh) => sh.floridayStatus || preview.lastAttempts.some((a) => a.fulfillmentOrderId === sh.fulfillmentOrderId && (a.code === "created" || a.code === "already_fulfilled"))).map((sh) => (_jsxs("div", { className: "rounded border border-border p-2", children: ["Verzonden naar Floriday", sh.floridayStatus ? ` (Floriday: ${sh.floridayStatus})` : "", " \u00B7", " ", sh.deliveryNoteReady === true ? "verzendbrief klaar" : "verzendbrief volgt via de Engine"] }, sh.fulfillmentOrderId))), preview.lastAttempts.length > 0 && (_jsxs("div", { className: "rounded border border-border p-2", children: [_jsx("div", { className: "text-muted-foreground mb-1", children: "Laatste pogingen" }), preview.lastAttempts.slice(0, 4).map((a) => (_jsxs("div", { children: [new Date(a.createdAt).toLocaleString("nl-NL"), " \u00B7 versturen \u00B7 ", a.code] }, a.createdAt + a.step)))] })), preview.blockers.length > 0 && (_jsx("ul", { className: "list-disc pl-5 text-destructive", children: preview.blockers.map((b) => {
64
+ // Begrijpelijke zin, technische code klein eronder.
65
+ const m = /^(.*?)\s*\[([^\]]+)\]$/s.exec(b);
66
+ return (_jsxs("li", { children: [m ? m[1] : b, m ? _jsx("div", { className: "text-xs text-muted-foreground", children: m[2] }) : null] }, b));
67
+ }) }))] })), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { disabled: sending, children: "Annuleren" }), _jsxs(Button, { onClick: doSend, disabled: !preview?.canSend || sending, children: [sending ? _jsx(Loader2, { className: "h-3 w-3 mr-1 animate-spin" }) : null, "Versturen"] })] })] }) })] }));
50
68
  }
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useEffect, useMemo, useRef, useState } from "react";
3
- import { supabase } from "../../integrations/supabase/moduleClient";
3
+ import { supabase } from "../../integrations/supabase/moduleClient.js";
4
4
  import { Button, Badge, Card, Input, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, toast, } from "@flowselections/core";
5
5
  import { AlertTriangle, CheckCircle2, RefreshCw, ScanLine } from "lucide-react";
6
6
  import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";