@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.
- package/dist-lib/components/MailboxProposalsPage.d.ts.map +1 -1
- package/dist-lib/components/MailboxProposalsPage.js +89 -10
- package/dist-lib/integrations/supabase/types.d.ts +110 -0
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/lib/mailbox.functions.d.ts +30 -0
- package/dist-lib/lib/mailbox.functions.d.ts.map +1 -1
- package/dist-lib/lib/mailbox.functions.js +125 -82
- package/dist-lib/lib/verkoop-order.d.ts +14 -0
- package/dist-lib/lib/verkoop-order.d.ts.map +1 -0
- package/dist-lib/lib/verkoop-order.js +24 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MailboxProposalsPage.d.ts","sourceRoot":"","sources":["../../src/components/MailboxProposalsPage.tsx"],"names":[],"mappings":"
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
})()] }),
|
|
871
|
-
|
|
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)] })] }),
|
|
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;
|