@flowselections/mailbox-orders 1.0.34 → 1.0.36

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.
@@ -1 +1 @@
1
- {"version":3,"file":"MailboxProposalsPage.d.ts","sourceRoot":"","sources":["../../src/components/MailboxProposalsPage.tsx"],"names":[],"mappings":"AA6GA,6EAA6E;AAC7E,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;CACtB;AA2CD,wBAAgB,oBAAoB,gCAubnC"}
1
+ {"version":3,"file":"MailboxProposalsPage.d.ts","sourceRoot":"","sources":["../../src/components/MailboxProposalsPage.tsx"],"names":[],"mappings":"AAgHA,6EAA6E;AAC7E,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;CACtB;AA2CD,wBAAgB,oBAAoB,gCAmcnC"}
@@ -4,7 +4,7 @@ import { useQuery, useQueryClient, useMutation } from "@tanstack/react-query";
4
4
  import { useServerFn } from "@tanstack/react-start";
5
5
  import { Button, Badge, Card, CardContent, CardHeader, CardTitle, Input, Label, Textarea, Switch, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Dialog, DialogContent, DialogHeader, DialogTitle, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, toast, } from "@flowselections/core";
6
6
  import { Check, X, Trash2, Plus, AlertTriangle, RefreshCw } from "lucide-react";
7
- import { listProposals, getProposalDetail, approveProposal, rejectProposal, listCustomersForPicker, createCustomerFromGroup, listProductsForPicker, listDeliveryLocationsForCustomer, listPickupLocations, listCarriersForPicker, getActiveOrdersMailbox, syncMailboxNow, refreshProposalsForAgentChange, reparseProposalNow, listBeladingOptionsForProducts, listAllowedLoadCarriers, getDeparturePreview, getAgreementPrices, countOutdatedProposals, } from "../lib/mailbox.functions.js";
7
+ import { listProposals, getProposalDetail, approveProposal, rejectProposal, getStickerDefaults, listCustomersForPicker, createCustomerFromGroup, listProductsForPicker, listDeliveryLocationsForCustomer, listPickupLocations, listCarriersForPicker, getActiveOrdersMailbox, syncMailboxNow, refreshProposalsForAgentChange, reparseProposalNow, listBeladingOptionsForProducts, listAllowedLoadCarriers, getDeparturePreview, getAgreementPrices, countOutdatedProposals, } from "../lib/mailbox.functions.js";
8
8
  import { normalizeUnitKind, toPlants, describeConversion, lineTotal, UNIT_KIND_LABEL, } from "../lib/mailbox-units.js";
9
9
  import { enabledLoadCarriers, normalizeAllowedLoadCarriers, EMPTY_LOAD_CARRIERS } from "../lib/load-carriers.js";
10
10
  import { listTemplateFields, listPlantTypesForPicker, listSuppliersForPicker, } from "../lib/mailbox-template.functions.js";
@@ -14,6 +14,7 @@ import { EmailPreview } from "./EmailPreview.js";
14
14
  import { TablePagination } from "./TablePagination.js";
15
15
  import { MailboxFilterBar, usePersistedState, normalizeText, matchesPeriod } from "./MailboxFilterBar.js";
16
16
  import { supabase } from "../integrations/supabase/client.js";
17
+ import { canOverrideLateSignup } from "../lib/verkoop-order.js";
17
18
  const beladingKey = (b) => `${b.plants_per_crate}x${b.crates_per_layer}x${b.layers_per_cart}|${b.crate_code ?? ""}|${b.carrier_code ?? ""}`;
18
19
  const beladingLabel = (b) => `${b.plants_per_crate} planten/fust · ${b.crates_per_layer} fusten/laag · ${b.layers_per_cart} lagen`;
19
20
  // Extra regel onder het label: fusten/kar + fusttype + kartype
@@ -316,6 +317,16 @@ export function MailboxProposalsPage() {
316
317
  } }, children: _jsx(DialogContent, { className: "max-w-[95vw] w-[95vw] h-[92vh] max-h-[92vh] p-0 flex flex-col overflow-hidden", children: !active ? (_jsxs("div", { className: "flex-1 flex flex-col items-center justify-center gap-2 text-sm text-muted-foreground", children: [_jsx(DialogTitle, { className: "text-base", children: "Bestelling" }), detailLoading ? "Bestelling laden…" : "Bestelling niet gevonden."] })) : (_jsxs(_Fragment, { children: [_jsxs(DialogHeader, { className: "px-6 pt-6 pb-3 shrink-0 border-b", children: [_jsxs("div", { className: "flex items-start justify-between gap-4", children: [_jsx(DialogTitle, { children: customerNameFor(activeSummary ?? active) }), _jsxs(Button, { type: "button", variant: "outline", size: "sm", className: "mr-8", disabled: reparseMutation.isPending, onClick: () => reparseMutation.mutate(active.id), children: [_jsx(RefreshCw, { className: `h-4 w-4 mr-2 ${reparseMutation.isPending ? "animate-spin" : ""}` }), reparseMutation.isPending ? "Analyseren…" : "Mail opnieuw analyseren"] })] }), reparseError && (_jsxs("div", { className: "mt-2 flex items-start gap-2 rounded-md border border-destructive/40 bg-destructive/10 px-3 py-2 text-sm text-destructive", children: [_jsx(AlertTriangle, { className: "h-4 w-4 mt-0.5 shrink-0" }), _jsx("span", { children: reparseError })] }))] }), _jsx("div", { className: "flex-1 min-h-0 overflow-hidden p-6", children: _jsx(ProposalEditor, { proposal: active, template: template, customers: customers, products: products, allowBackorder: allowBackorder, plantTypes: plantTypes, suppliers: suppliers, carriers: carriers, pickupLocations: pickupLocations, onApprove: async (payload) => {
317
318
  try {
318
319
  const res = await fetchApprove({ data: payload });
320
+ if (res?.ok === false) {
321
+ toast.error(res.message ?? "Verkoop heeft de bestelling geweigerd.");
322
+ return res;
323
+ }
324
+ if (res?.alreadyApproved) {
325
+ toast.info(`Er bestaat al een order voor dit voorstel (${res.orderNumber}).`);
326
+ qc.invalidateQueries({ queryKey: ["mailbox-proposals"] });
327
+ setActiveId(null);
328
+ return res;
329
+ }
319
330
  const shortages = Array.isArray(res?.shortages) ? res.shortages : [];
320
331
  toast.success("Bestelling aangemaakt — karindeling wordt automatisch aangemaakt in de Verkoop-module");
321
332
  if (shortages.length > 0) {
@@ -325,9 +336,11 @@ export function MailboxProposalsPage() {
325
336
  }
326
337
  qc.invalidateQueries({ queryKey: ["mailbox-proposals"] });
327
338
  setActiveId(null);
339
+ return res;
328
340
  }
329
341
  catch (e) {
330
342
  toast.error(e?.message ?? "Goedkeuren mislukt");
343
+ return { ok: false, reason: "other", message: e?.message ?? "Goedkeuren mislukt" };
331
344
  }
332
345
  }, onReject: async (reason) => {
333
346
  try {
@@ -598,7 +611,50 @@ function ProposalEditor({ proposal, template, customers, products, allowBackorde
598
611
  },
599
612
  });
600
613
  const agreements = agreementData?.prices ?? {};
601
- const [acceptNoPrice, setAcceptNoPrice] = useState(false);
614
+ // ─── Stickervinkje per regel ─────────────────────────────────────────────
615
+ const fetchStickers = useServerFn(getStickerDefaults);
616
+ const stickerProductIds = Array.from(new Set(lines.map((l) => l.product_id).filter(Boolean))).sort();
617
+ const { data: stickerData } = useQuery({
618
+ queryKey: ["mailbox-sticker-defaults", customerVal, stickerProductIds.join(",")],
619
+ queryFn: async () => {
620
+ try {
621
+ return await fetchStickers({ data: { customerId: isLocalCustomerId(customerVal) ? customerVal : null, productIds: stickerProductIds } });
622
+ }
623
+ catch {
624
+ return { available: false, defaults: {} };
625
+ }
626
+ },
627
+ });
628
+ const stickerAvailable = stickerData?.available === true;
629
+ const stickerDefaults = stickerData?.defaults ?? {};
630
+ useEffect(() => {
631
+ if (!stickerAvailable)
632
+ return;
633
+ let changed = false;
634
+ const next = lines.map((l) => {
635
+ if (l.product_id && (l.needs_sticker === null || l.needs_sticker === undefined) && l.product_id in stickerDefaults) {
636
+ changed = true;
637
+ return { ...l, needs_sticker: stickerDefaults[l.product_id] === true };
638
+ }
639
+ return l;
640
+ });
641
+ if (changed)
642
+ setLines(next);
643
+ // eslint-disable-next-line react-hooks/exhaustive-deps
644
+ }, [stickerData, lines]);
645
+ const stickerCustomerRef = useRef(customerVal);
646
+ useEffect(() => {
647
+ if (stickerCustomerRef.current === customerVal)
648
+ return;
649
+ stickerCustomerRef.current = customerVal;
650
+ setLines((prev) => prev.map((l) => ({ ...l, needs_sticker: undefined })));
651
+ }, [customerVal]);
652
+ const [lateReason, setLateReason] = useState("");
653
+ const [lateOpen, setLateOpen] = useState(false);
654
+ const [refusal, setRefusal] = useState(null);
655
+ const previewKind = preview?.chosen?.departed === true ? "departed" : "late_signup";
656
+ const lateOverride = lateOpen && canOverrideLateSignup(refusal?.kind ?? previewKind, lateReason);
657
+ useEffect(() => { setRefusal(null); setLateOpen(false); setLateReason(""); }, [deliveryVal, carrierVal, deliveryLocationVal]);
602
658
  const orderTotal = lines.reduce((sum, l) => sum + lineTotal(l.quantity ?? null, l.unit_price ?? null), 0);
603
659
  const linesWithoutPrice = lines.filter((l) => l.product_id && (l.unit_price === null || l.unit_price === undefined)).length;
604
660
  const euro = (n) => n.toLocaleString("nl-NL", { style: "currency", currency: "EUR" });
@@ -637,7 +693,7 @@ function ProposalEditor({ proposal, template, customers, products, allowBackorde
637
693
  toast.error("Kies een vervoerder.");
638
694
  return null;
639
695
  }
640
- if (!dateFeasible) {
696
+ if (!dateFeasible && !lateOverride) {
641
697
  toast.error(preview?.reason ? `Leverdatum niet haalbaar: ${preview.reason}` : "De leverdatum is nog niet gecontroleerd of niet haalbaar.");
642
698
  return null;
643
699
  }
@@ -651,6 +707,10 @@ function ProposalEditor({ proposal, template, customers, products, allowBackorde
651
707
  return null;
652
708
  }
653
709
  }
710
+ if (validLines.some((l) => l.unit_price === null || l.unit_price === undefined || l.unit_price <= 0)) {
711
+ toast.error("Vul bij elke regel een prijs per stuk in; Verkoop maakt geen order zonder prijs.");
712
+ return null;
713
+ }
654
714
  const cust = customers.find((c) => c.id === customerVal);
655
715
  return {
656
716
  proposalId: proposal.id,
@@ -668,13 +728,14 @@ function ProposalEditor({ proposal, template, customers, products, allowBackorde
668
728
  quantity: l.quantity,
669
729
  unit: "planten",
670
730
  unit_price: l.unit_price ?? null,
731
+ needs_sticker: stickerAvailable ? l.needs_sticker === true : undefined,
671
732
  belading: b ? { plants_per_crate: b.plants_per_crate, crates_per_layer: b.crates_per_layer, layers_per_cart: b.layers_per_cart } : null,
672
733
  };
673
734
  }),
674
735
  fieldValues: Object.entries(values).map(([field_key, value]) => ({ field_key, value })),
675
736
  customerReference: typeof values["reference"] === "string" ? values["reference"].trim() || null : null,
676
737
  momentKey: preview?.chosen?.momentKey ?? null,
677
- acceptMissingPrice: acceptNoPrice,
738
+ lateSignup: lateOverride ? { reason: lateReason.trim() } : null,
678
739
  };
679
740
  };
680
741
  // Stap 1: controleren → bevestigingsdialoog. Pas na bevestigen wordt de
@@ -690,8 +751,16 @@ function ProposalEditor({ proposal, template, customers, products, allowBackorde
690
751
  return;
691
752
  setSubmitting(true);
692
753
  try {
693
- await onApprove(payload);
754
+ const res = await onApprove(payload);
694
755
  setConfirmOpen(false);
756
+ setCheckedOk(false);
757
+ if (res?.ok === false) {
758
+ setRefusal({
759
+ kind: res.reason ?? "other",
760
+ message: String(res.message ?? "Verkoop heeft de bestelling geweigerd."),
761
+ nextDeliveryDate: res.nextDeliveryDate ?? null,
762
+ });
763
+ }
695
764
  }
696
765
  finally {
697
766
  setSubmitting(false);
@@ -748,8 +817,8 @@ function ProposalEditor({ proposal, template, customers, products, allowBackorde
748
817
  : "Kies vervoerder...", clearLabel: "\u2014 Geen \u2014", disabled: !deliveryLocationVal, options: carriers.map((c) => ({
749
818
  value: c.id,
750
819
  label: c.name + (c.is_default ? " (standaard)" : ""),
751
- })), onChange: (v) => setVal("carrier", v) }), selectedLocation?.carrier_id && carrierVal !== selectedLocation.carrier_id && (_jsx("div", { className: "text-xs text-muted-foreground", children: "Wijkt af van standaard vervoerder van deze locatie \u2014 geldt alleen voor deze bestelling." }))] }), _jsxs("div", { className: "space-y-1", children: [_jsxs(Label, { className: "flex items-center gap-2", children: ["Bezorgdatum ", _jsx("span", { className: "text-destructive", children: "*" })] }), _jsx(Input, { type: "date", value: deliveryVal ?? "", onChange: (e) => setVal("delivery_date", e.target.value || null) }), _jsx(ConfBadge, { meta: metaFor("delivery_date"), thresholds: thresholds }), !carrierVal || !deliveryLocationVal ? (_jsx("div", { className: "text-xs text-muted-foreground", children: "Kies een afleverlocatie en vervoerder; daarna controleren we of deze datum haalbaar is." })) : !deliveryVal ? (_jsx("div", { className: "text-xs text-muted-foreground", children: "De mail noemt geen datum. Kies zelf een datum." })) : previewLoading ? (_jsx("div", { className: "text-xs text-muted-foreground", children: "Haalbaarheid controleren\u2026" })) : previewError ? (_jsxs("div", { className: "text-xs text-destructive", children: ["De haalbaarheid kon niet worden gecontroleerd: ", previewError] })) : dateFeasible ? (_jsxs("div", { className: "rounded-md border border-emerald-200 bg-emerald-50 px-3 py-2 text-xs text-emerald-800", children: [fmtDate(deliveryVal), " is haalbaar.", preview?.chosen?.departureAt ? ` Ophaalmoment: ${fmtDateTime(preview.chosen.departureAt)}` : "", preview?.chosen?.signupDeadline ? ` (aanmelden tot ${fmtDateTime(preview.chosen.signupDeadline)}).` : "."] })) : (_jsxs("div", { className: "rounded-md border border-destructive/40 bg-destructive/5 px-3 py-2 text-xs space-y-2", children: [_jsxs("div", { className: "text-destructive", children: [fmtDate(deliveryVal), " is niet haalbaar", preview?.reason ? `: ${preview.reason}` : "."] }), preview?.firstFeasibleDeliveryDate && preview.firstFeasibleDeliveryDate !== deliveryVal && (_jsxs(Button, { size: "sm", variant: "outline", onClick: () => setVal("delivery_date", preview.firstFeasibleDeliveryDate), children: ["Gebruik ", fmtDate(preview.firstFeasibleDeliveryDate)] }))] }))] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { children: "Referentie klant" }), _jsx(Input, { value: typeof values["reference"] === "string" ? values["reference"] : "", placeholder: "Bijv. ordernummer van de klant", onChange: (e) => setVal("reference", e.target.value) }), _jsx(ConfBadge, { meta: metaFor("reference"), thresholds: thresholds })] }), otherFields.map(renderField)] }));
752
- })(), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Bestelregels" }), _jsxs("div", { className: "grid grid-cols-12 gap-2 text-xs text-muted-foreground px-1", children: [_jsx("div", { className: "col-span-4", children: "Product" }), _jsx("div", { className: "col-span-3", children: "Belading" }), _jsx("div", { className: "col-span-2", children: "Fusten" }), _jsx("div", { className: "col-span-2", children: "Prijs per stuk" }), _jsx("div", { className: "col-span-1" })] }), lines.map((line, idx) => {
820
+ })), onChange: (v) => setVal("carrier", v) }), selectedLocation?.carrier_id && carrierVal !== selectedLocation.carrier_id && (_jsx("div", { className: "text-xs text-muted-foreground", children: "Wijkt af van standaard vervoerder van deze locatie \u2014 geldt alleen voor deze bestelling." }))] }), _jsxs("div", { className: "space-y-1", children: [_jsxs(Label, { className: "flex items-center gap-2", children: ["Bezorgdatum ", _jsx("span", { className: "text-destructive", children: "*" })] }), _jsx(Input, { type: "date", value: deliveryVal ?? "", onChange: (e) => setVal("delivery_date", e.target.value || null) }), _jsx(ConfBadge, { meta: metaFor("delivery_date"), thresholds: thresholds }), !carrierVal || !deliveryLocationVal ? (_jsx("div", { className: "text-xs text-muted-foreground", children: "Kies een afleverlocatie en vervoerder; daarna controleren we of deze datum haalbaar is." })) : !deliveryVal ? (_jsx("div", { className: "text-xs text-muted-foreground", children: "De mail noemt geen datum. Kies zelf een datum." })) : previewLoading ? (_jsx("div", { className: "text-xs text-muted-foreground", children: "Haalbaarheid controleren\u2026" })) : previewError ? (_jsxs("div", { className: "text-xs text-destructive", children: ["De haalbaarheid kon niet worden gecontroleerd: ", previewError] })) : dateFeasible ? (_jsxs("div", { className: "rounded-md border border-emerald-200 bg-emerald-50 px-3 py-2 text-xs text-emerald-800", children: [fmtDate(deliveryVal), " is haalbaar.", preview?.chosen?.departureAt ? ` Ophaalmoment: ${fmtDateTime(preview.chosen.departureAt)}` : "", preview?.chosen?.signupDeadline ? ` (aanmelden tot ${fmtDateTime(preview.chosen.signupDeadline)}).` : "."] })) : (_jsxs("div", { className: "rounded-md border border-destructive/40 bg-destructive/5 px-3 py-2 text-xs space-y-2", children: [_jsxs("div", { className: "text-destructive", children: [fmtDate(deliveryVal), " is niet haalbaar", preview?.reason ? `: ${preview.reason}` : "."] }), preview?.firstFeasibleDeliveryDate && preview.firstFeasibleDeliveryDate !== deliveryVal && (_jsxs(Button, { size: "sm", variant: "outline", onClick: () => setVal("delivery_date", preview.firstFeasibleDeliveryDate), children: ["Gebruik ", fmtDate(preview.firstFeasibleDeliveryDate)] })), preview?.ok === true && previewKind === "late_signup" && (_jsx(LateSignupChoice, { open: lateOpen, setOpen: setLateOpen, reason: lateReason, setReason: setLateReason }))] }))] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { children: "Referentie klant" }), _jsx(Input, { value: typeof values["reference"] === "string" ? values["reference"] : "", placeholder: "Bijv. ordernummer van de klant", onChange: (e) => setVal("reference", e.target.value) }), _jsx(ConfBadge, { meta: metaFor("reference"), thresholds: thresholds })] }), otherFields.map(renderField)] }));
821
+ })(), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Bestelregels" }), _jsxs("div", { className: "grid grid-cols-12 gap-2 text-xs text-muted-foreground px-1", children: [_jsx("div", { className: "col-span-4", children: "Product" }), _jsx("div", { className: "col-span-3", children: "Belading" }), _jsx("div", { className: "col-span-2", children: "Fusten" }), _jsx("div", { className: "col-span-2", children: "Prijs per stuk" }), _jsx("div", { className: "col-span-1 text-center", children: stickerAvailable ? "Sticker" : "" })] }), lines.map((line, idx) => {
753
822
  const product = line.product_id ? products.find((p) => p.id === line.product_id) : null;
754
823
  const stdBelading = defaultBelading(product);
755
824
  const available = (line.product_id ? (Array.isArray(beladingByProduct[line.product_id]) ? beladingByProduct[line.product_id] : []) : []).map((b) => ({
@@ -867,14 +936,18 @@ function ProposalEditor({ proposal, template, customers, products, allowBackorde
867
936
  next[idx] = { ...next[idx], unit_price: ag.net_price, price_source: "afspraak" };
868
937
  setLines(next);
869
938
  }, children: "gebruiken" })] }))] }));
870
- })()] }), _jsx(Button, { variant: "ghost", size: "icon", className: "col-span-1", onClick: () => setLines(lines.filter((_, i) => i !== idx)), children: _jsx(Trash2, { className: "h-4 w-4" }) })] }, idx));
871
- }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => setLines([...lines, { product_id: null, raw_product_text: "", quantity: 1, unit: "stuks", belading: null }]), children: [_jsx(Plus, { className: "h-3 w-3 mr-1" }), " Regel toevoegen"] }), _jsxs("div", { className: "flex justify-between border-t pt-2 text-sm", children: [_jsx("span", { className: "text-muted-foreground", children: "Totaal" }), _jsx("span", { className: "font-medium", children: euro(orderTotal) })] }), linesWithoutPrice > 0 && (_jsxs("div", { className: "rounded-md border border-amber-200 bg-amber-50 px-3 py-2 text-xs text-amber-800", children: ["Geen prijs bekend voor ", linesWithoutPrice, " regel(s). Vul een prijs in of bevestig straks dat je zonder prijs doorgaat."] }))] }), _jsxs("div", { className: "flex gap-2 pt-2", children: [_jsxs(Button, { className: "flex-1", onClick: handleApprove, children: [_jsx(Check, { className: "h-4 w-4 mr-1" }), " Controleren & bevestigen"] }), _jsxs(Button, { variant: "destructive", onClick: () => onReject(notesVal || ""), children: [_jsx(X, { className: "h-4 w-4 mr-1" }), " Afwijzen"] })] }), _jsx(Dialog, { open: confirmOpen, onOpenChange: (o) => {
939
+ })()] }), _jsxs("div", { className: "col-span-1 flex flex-col items-center gap-1", children: [stickerAvailable && (_jsxs("label", { className: "flex flex-col items-center text-[11px] text-muted-foreground cursor-pointer", title: "Sticker nodig op deze orderregel", children: [_jsx("input", { type: "checkbox", "aria-label": "Sticker", checked: line.needs_sticker === true, onChange: (e) => {
940
+ const next = [...lines];
941
+ next[idx] = { ...next[idx], needs_sticker: e.target.checked };
942
+ setLines(next);
943
+ } }), "Sticker"] })), _jsx(Button, { variant: "ghost", size: "icon", onClick: () => setLines(lines.filter((_, i) => i !== idx)), children: _jsx(Trash2, { className: "h-4 w-4" }) })] })] }, idx));
944
+ }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => setLines([...lines, { product_id: null, raw_product_text: "", quantity: 1, unit: "stuks", belading: null }]), children: [_jsx(Plus, { className: "h-3 w-3 mr-1" }), " Regel toevoegen"] }), _jsxs("div", { className: "flex justify-between border-t pt-2 text-sm", children: [_jsx("span", { className: "text-muted-foreground", children: "Totaal" }), _jsx("span", { className: "font-medium", children: euro(orderTotal) })] }), linesWithoutPrice > 0 && (_jsxs("div", { className: "rounded-md border border-amber-200 bg-amber-50 px-3 py-2 text-xs text-amber-800", children: ["Geen prijs bekend voor ", linesWithoutPrice, " regel(s). Vul een prijs in; zonder prijs kan de bestelling niet worden aangemaakt."] }))] }), refusal && (_jsxs("div", { className: "rounded-md border border-destructive/40 bg-destructive/5 px-3 py-2 text-sm space-y-2", children: [_jsx("div", { className: "font-medium text-destructive", children: "Bestelling niet aangemaakt \u2014 het voorstel blijft open." }), _jsx("div", { className: "text-destructive", children: refusal.message }), _jsx("div", { className: "flex flex-wrap gap-2", children: refusal.nextDeliveryDate && refusal.nextDeliveryDate !== deliveryVal && (_jsxs(Button, { size: "sm", variant: "outline", onClick: () => setVal("delivery_date", refusal.nextDeliveryDate), children: ["Volgend haalbaar moment: ", fmtDate(refusal.nextDeliveryDate)] })) }), refusal.kind === "late_signup" && (_jsx(LateSignupChoice, { open: lateOpen, setOpen: setLateOpen, reason: lateReason, setReason: setLateReason }))] })), _jsxs("div", { className: "flex gap-2 pt-2", children: [_jsxs(Button, { className: "flex-1", onClick: handleApprove, children: [_jsx(Check, { className: "h-4 w-4 mr-1" }), " Controleren & bevestigen"] }), _jsxs(Button, { variant: "destructive", onClick: () => onReject(notesVal || ""), children: [_jsx(X, { className: "h-4 w-4 mr-1" }), " Afwijzen"] })] }), _jsx(Dialog, { open: confirmOpen, onOpenChange: (o) => {
872
945
  if (submitting)
873
946
  return;
874
947
  setConfirmOpen(o);
875
948
  if (!o)
876
949
  setCheckedOk(false);
877
- }, children: _jsxs(DialogContent, { className: "max-w-lg", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: "Bestelling bevestigen" }) }), _jsxs("div", { className: "space-y-3 text-sm", children: [_jsx("p", { className: "text-muted-foreground", children: "De bestelling wordt pas aangemaakt in Bestellingen nadat je hem hebt gecontroleerd en bevestigd." }), _jsxs("div", { className: "rounded-md border p-3 space-y-1", children: [_jsxs("div", { children: [_jsx("span", { className: "text-muted-foreground", children: "Klant:" }), " ", customers.find((c) => c.id === customerVal)?.company_name ?? "-"] }), _jsxs("div", { children: [_jsx("span", { className: "text-muted-foreground", children: "Bezorgdatum:" }), " ", deliveryVal || "-"] }), _jsxs("div", { children: [_jsx("span", { className: "text-muted-foreground", children: "Bestelregels:" }), " ", lines.filter((l) => l.product_id && (l.quantity ?? 0) > 0).length] }), _jsxs("div", { children: [_jsx("span", { className: "text-muted-foreground", children: "Referentie:" }), " ", values["reference"] || "-"] }), _jsxs("div", { children: [_jsx("span", { className: "text-muted-foreground", children: "Totaal:" }), " ", euro(orderTotal)] })] }), linesWithoutPrice > 0 && (_jsxs("label", { className: "flex items-start gap-2 cursor-pointer rounded-md border border-amber-200 bg-amber-50 p-2 text-amber-800", children: [_jsx("input", { type: "checkbox", className: "mt-1", checked: acceptNoPrice, onChange: (e) => setAcceptNoPrice(e.target.checked) }), _jsxs("span", { children: ["Geen prijs bekend voor ", linesWithoutPrice, " regel(s). Ik wil toch zonder prijs bevestigen."] })] })), _jsxs("label", { className: "flex items-start gap-2 cursor-pointer", children: [_jsx("input", { type: "checkbox", className: "mt-1", checked: checkedOk, onChange: (e) => setCheckedOk(e.target.checked) }), _jsx("span", { children: "Ik heb deze bestelling gecontroleerd en de gegevens kloppen." })] })] }), _jsxs("div", { className: "flex gap-2 pt-2", children: [_jsxs(Button, { className: "flex-1", disabled: !checkedOk || submitting || (linesWithoutPrice > 0 && !acceptNoPrice), onClick: handleConfirm, children: [_jsx(Check, { className: "h-4 w-4 mr-1" }), submitting ? "Bezig…" : "Bevestigen en bestelling aanmaken"] }), _jsx(Button, { variant: "outline", disabled: submitting, onClick: () => setConfirmOpen(false), children: "Annuleren" })] })] }) }), _jsx(NewCustomerDialog, { candidate: newCustomer, onClose: () => setNewCustomer(null), onCreated: (id) => { setNewCustomer(null); setVal("customer", id); } })] })] })] }));
950
+ }, children: _jsxs(DialogContent, { className: "max-w-lg", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: "Bestelling bevestigen" }) }), _jsxs("div", { className: "space-y-3 text-sm", children: [_jsx("p", { className: "text-muted-foreground", children: "De bestelling wordt pas aangemaakt in Bestellingen nadat je hem hebt gecontroleerd en bevestigd." }), _jsxs("div", { className: "rounded-md border p-3 space-y-1", children: [_jsxs("div", { children: [_jsx("span", { className: "text-muted-foreground", children: "Klant:" }), " ", customers.find((c) => c.id === customerVal)?.company_name ?? "-"] }), _jsxs("div", { children: [_jsx("span", { className: "text-muted-foreground", children: "Bezorgdatum:" }), " ", deliveryVal || "-"] }), _jsxs("div", { children: [_jsx("span", { className: "text-muted-foreground", children: "Bestelregels:" }), " ", lines.filter((l) => l.product_id && (l.quantity ?? 0) > 0).length] }), _jsxs("div", { children: [_jsx("span", { className: "text-muted-foreground", children: "Referentie:" }), " ", values["reference"] || "-"] }), _jsxs("div", { children: [_jsx("span", { className: "text-muted-foreground", children: "Totaal:" }), " ", euro(orderTotal)] })] }), lateOverride && (_jsxs("div", { className: "rounded-md border border-amber-200 bg-amber-50 p-2 text-amber-800", children: ["Je maakt de order toch aan na de aanmeldtijd. Reden: ", lateReason.trim()] })), _jsxs("label", { className: "flex items-start gap-2 cursor-pointer", children: [_jsx("input", { type: "checkbox", className: "mt-1", checked: checkedOk, onChange: (e) => setCheckedOk(e.target.checked) }), _jsx("span", { children: "Ik heb deze bestelling gecontroleerd en de gegevens kloppen." })] })] }), _jsxs("div", { className: "flex gap-2 pt-2", children: [_jsxs(Button, { className: "flex-1", disabled: !checkedOk || submitting, onClick: handleConfirm, children: [_jsx(Check, { className: "h-4 w-4 mr-1" }), submitting ? "Bezig…" : "Bevestigen en bestelling aanmaken"] }), _jsx(Button, { variant: "outline", disabled: submitting, onClick: () => setConfirmOpen(false), children: "Annuleren" })] })] }) }), _jsx(NewCustomerDialog, { candidate: newCustomer, onClose: () => setNewCustomer(null), onCreated: (id) => { setNewCustomer(null); setVal("customer", id); } })] })] })] }));
878
951
  }
879
952
  /** Verplichte bevestiging: externe klant eerst als lokale klant aanmaken. */
880
953
  function NewCustomerDialog({ candidate, onClose, onCreated, }) {
@@ -992,3 +1065,9 @@ function ConfBadge({ meta, thresholds, }) {
992
1065
  : "border-destructive/40 bg-destructive/5 text-destructive";
993
1066
  return (_jsxs("span", { className: `inline-flex items-center rounded border px-1.5 py-0.5 text-[11px] ${cls}`, children: [Math.round(c * 100), "% \u00B7 ", meta.source] }));
994
1067
  }
1068
+ function LateSignupChoice({ open, setOpen, reason, setReason }) {
1069
+ if (!open) {
1070
+ return (_jsx(Button, { size: "sm", variant: "outline", onClick: () => setOpen(true), children: "Toch aanmaken" }));
1071
+ }
1072
+ return (_jsxs("div", { className: "space-y-1", children: [_jsxs(Label, { className: "text-xs", children: ["Reden om toch aan te maken na de aanmeldtijd ", _jsx("span", { className: "text-destructive", children: "*" })] }), _jsx(Textarea, { rows: 2, value: reason, placeholder: "Bijv. afgestemd met de vervoerder", onChange: (e) => setReason(e.target.value) }), _jsx("div", { className: "text-[11px] text-muted-foreground", children: "Wie, wanneer en de reden worden op de order vastgelegd. Daarna \"Controleren & bevestigen\"." }), _jsx(Button, { size: "sm", variant: "ghost", onClick: () => { setOpen(false); setReason(""); }, children: "Annuleren" })] }));
1073
+ }
@@ -606,24 +606,36 @@ export type Database = {
606
606
  ai_modules: {
607
607
  Row: {
608
608
  ai_enabled: boolean;
609
+ ai_source: string;
609
610
  created_at: string;
611
+ declared_ai: boolean;
610
612
  id: string;
613
+ is_in_use: boolean;
614
+ last_declared_at: string | null;
611
615
  module_key: string;
612
616
  module_name: string;
613
617
  updated_at: string;
614
618
  };
615
619
  Insert: {
616
620
  ai_enabled?: boolean;
621
+ ai_source?: string;
617
622
  created_at?: string;
623
+ declared_ai?: boolean;
618
624
  id?: string;
625
+ is_in_use?: boolean;
626
+ last_declared_at?: string | null;
619
627
  module_key: string;
620
628
  module_name: string;
621
629
  updated_at?: string;
622
630
  };
623
631
  Update: {
624
632
  ai_enabled?: boolean;
633
+ ai_source?: string;
625
634
  created_at?: string;
635
+ declared_ai?: boolean;
626
636
  id?: string;
637
+ is_in_use?: boolean;
638
+ last_declared_at?: string | null;
627
639
  module_key?: string;
628
640
  module_name?: string;
629
641
  updated_at?: string;
@@ -24628,6 +24640,10 @@ export type Database = {
24628
24640
  superseded_at: string | null;
24629
24641
  updated_at: string;
24630
24642
  user_id: string;
24643
+ write_enabled: boolean;
24644
+ write_enabled_at: string | null;
24645
+ write_enabled_by: string | null;
24646
+ write_enabled_reason: string | null;
24631
24647
  };
24632
24648
  Insert: {
24633
24649
  account_key?: string | null;
@@ -24654,6 +24670,10 @@ export type Database = {
24654
24670
  superseded_at?: string | null;
24655
24671
  updated_at?: string;
24656
24672
  user_id: string;
24673
+ write_enabled?: boolean;
24674
+ write_enabled_at?: string | null;
24675
+ write_enabled_by?: string | null;
24676
+ write_enabled_reason?: string | null;
24657
24677
  };
24658
24678
  Update: {
24659
24679
  account_key?: string | null;
@@ -24680,6 +24700,10 @@ export type Database = {
24680
24700
  superseded_at?: string | null;
24681
24701
  updated_at?: string;
24682
24702
  user_id?: string;
24703
+ write_enabled?: boolean;
24704
+ write_enabled_at?: string | null;
24705
+ write_enabled_by?: string | null;
24706
+ write_enabled_reason?: string | null;
24683
24707
  };
24684
24708
  Relationships: [];
24685
24709
  };
@@ -25568,6 +25592,21 @@ export type Database = {
25568
25592
  };
25569
25593
  Relationships: [];
25570
25594
  };
25595
+ twinfield_schema_upgrades: {
25596
+ Row: {
25597
+ applied_at: string;
25598
+ key: string;
25599
+ };
25600
+ Insert: {
25601
+ applied_at?: string;
25602
+ key: string;
25603
+ };
25604
+ Update: {
25605
+ applied_at?: string;
25606
+ key?: string;
25607
+ };
25608
+ Relationships: [];
25609
+ };
25571
25610
  twinfield_session_cache: {
25572
25611
  Row: {
25573
25612
  cluster: string;
@@ -29684,6 +29723,26 @@ export type Database = {
29684
29723
  Args: never;
29685
29724
  Returns: Json;
29686
29725
  };
29726
+ ai_is_admin: {
29727
+ Args: {
29728
+ _uid: string;
29729
+ };
29730
+ Returns: boolean;
29731
+ };
29732
+ ai_register_consumers: {
29733
+ Args: {
29734
+ _full_list?: boolean;
29735
+ _modules: Json;
29736
+ _source?: string;
29737
+ };
29738
+ Returns: Json;
29739
+ };
29740
+ ai_register_runtime_consumer: {
29741
+ Args: {
29742
+ _module_key: string;
29743
+ };
29744
+ Returns: boolean;
29745
+ };
29687
29746
  ai_usage_by_module: {
29688
29747
  Args: {
29689
29748
  _from: string;
@@ -31839,10 +31898,25 @@ export type Database = {
31839
31898
  };
31840
31899
  Returns: boolean;
31841
31900
  };
31901
+ klanten_auto_promote_status: {
31902
+ Args: never;
31903
+ Returns: Json;
31904
+ };
31842
31905
  klanten_install_check: {
31843
31906
  Args: never;
31844
31907
  Returns: Json;
31845
31908
  };
31909
+ klanten_promote_round: {
31910
+ Args: never;
31911
+ Returns: Json;
31912
+ };
31913
+ klanten_set_auto_promote: {
31914
+ Args: {
31915
+ p_kind: string;
31916
+ p_on: boolean;
31917
+ };
31918
+ Returns: Json;
31919
+ };
31846
31920
  layout_print_document: {
31847
31921
  Args: {
31848
31922
  _documentnaam: string;
@@ -37864,6 +37938,12 @@ export type Database = {
37864
37938
  };
37865
37939
  Returns: boolean;
37866
37940
  };
37941
+ twinfield_cancel_queue_item: {
37942
+ Args: {
37943
+ p_id: string;
37944
+ };
37945
+ Returns: boolean;
37946
+ };
37867
37947
  twinfield_connection_offices: {
37868
37948
  Args: never;
37869
37949
  Returns: {
@@ -37893,6 +37973,19 @@ export type Database = {
37893
37973
  };
37894
37974
  Returns: Json;
37895
37975
  };
37976
+ twinfield_gated_queue: {
37977
+ Args: {
37978
+ p_connection_id: string;
37979
+ };
37980
+ Returns: {
37981
+ action: string;
37982
+ created_at: string;
37983
+ customer_id: string;
37984
+ id: string;
37985
+ payload: Json;
37986
+ status: string;
37987
+ }[];
37988
+ };
37896
37989
  twinfield_handle_administration_change: {
37897
37990
  Args: {
37898
37991
  _administration_code: string;
@@ -37900,6 +37993,10 @@ export type Database = {
37900
37993
  };
37901
37994
  Returns: undefined;
37902
37995
  };
37996
+ twinfield_is_admin: {
37997
+ Args: never;
37998
+ Returns: boolean;
37999
+ };
37903
38000
  twinfield_prune_push_queue: {
37904
38001
  Args: never;
37905
38002
  Returns: number;
@@ -37911,6 +38008,13 @@ export type Database = {
37911
38008
  };
37912
38009
  Returns: Json;
37913
38010
  };
38011
+ twinfield_set_write_enabled: {
38012
+ Args: {
38013
+ p_connection_id: string;
38014
+ p_enabled: boolean;
38015
+ };
38016
+ Returns: Json;
38017
+ };
37914
38018
  twinfield_sync_apply: {
37915
38019
  Args: {
37916
38020
  _base: Json;
@@ -37947,6 +38051,12 @@ export type Database = {
37947
38051
  };
37948
38052
  Returns: Json;
37949
38053
  };
38054
+ twinfield_write_allowed: {
38055
+ Args: {
38056
+ p_connection_id: string;
38057
+ };
38058
+ Returns: boolean;
38059
+ };
37950
38060
  upsert_auto_fulfillment_log: {
37951
38061
  Args: {
37952
38062
  p_connection_id: string;