@flowselections/floriday-klanten-module 1.0.49 → 1.0.50
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/klanten/DeliveryLocationsTab.js +1 -1
- package/dist-lib/components/klanten/DiscountsTab.js +1 -1
- package/dist-lib/components/klanten/ExternalCacheBanner.d.ts.map +1 -1
- package/dist-lib/components/klanten/ExternalCacheBanner.js +2 -5
- package/dist-lib/components/klanten/FloridayNetworkStatus.d.ts.map +1 -1
- package/dist-lib/components/klanten/FloridayNetworkStatus.js +3 -1
- package/dist-lib/components/klanten/KlantDetail.d.ts.map +1 -1
- package/dist-lib/components/klanten/KlantDetail.js +15 -3
- package/dist-lib/components/klanten/KlantDetailLayout.d.ts.map +1 -1
- package/dist-lib/components/klanten/KlantDetailLayout.js +3 -1
- package/dist-lib/components/klanten/KlantenOverzicht.js +2 -2
- package/dist-lib/components/klanten/SyncStatusChip.d.ts.map +1 -1
- package/dist-lib/components/klanten/SyncStatusChip.js +5 -4
- package/dist-lib/components/klanten/pricegroups/PriceGroupFormPage.js +2 -2
- package/dist-lib/components/klanten/pricegroups/UnknownFloridayMembers.js +2 -2
- package/dist-lib/integrations/supabase/types.d.ts +104 -0
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/lib/floriday-network-status.functions.d.ts +2 -0
- package/dist-lib/lib/floriday-network-status.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-network-status.functions.js +38 -17
- package/dist-lib/lib/friendly-twinfield-error.d.ts +8 -0
- package/dist-lib/lib/friendly-twinfield-error.d.ts.map +1 -0
- package/dist-lib/lib/friendly-twinfield-error.js +16 -0
- package/package.json +1 -1
|
@@ -60,7 +60,7 @@ export function DeliveryLocationsTab({ customerId, groupKey, externalProvider, e
|
|
|
60
60
|
mutationFn: () => importFn({ data: { preview: true } }),
|
|
61
61
|
onSuccess: (res) => {
|
|
62
62
|
if (res.cacheEmpty) {
|
|
63
|
-
toast.info("
|
|
63
|
+
toast.info("Er zijn nog geen afleverlocaties uit Floriday opgehaald.");
|
|
64
64
|
return;
|
|
65
65
|
}
|
|
66
66
|
setImportPreview(res);
|
|
@@ -77,7 +77,7 @@ export function DiscountsTab({ buyerOrganizationId }) {
|
|
|
77
77
|
}
|
|
78
78
|
const s = data?.summary;
|
|
79
79
|
const rows = data?.rows ?? [];
|
|
80
|
-
return (_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "grid gap-3 md:grid-cols-4", children: [_jsx(SummaryCard, { label: "Contracten", value: s?.agreements ?? 0, icon: _jsx(FileText, { className: "h-4 w-4" }) }), _jsx(SummaryCard, { label: "Prijsregels", value: s?.sales_strategies ?? 0, icon: _jsx(Percent, { className: "h-4 w-4" }) }), _jsx(SummaryCard, { label: "Prijsgroep-regels", value: s?.price_groups ?? 0, icon: _jsx(Users, { className: "h-4 w-4" }) }), _jsx(SummaryCard, { label: "Laatst gesynchroniseerd", value: formatDateTime(s?.last_fetched_at), icon: _jsx(Info, { className: "h-4 w-4" }), small: true })] }), rows.length === 0 ? (_jsxs("div", { className: "space-y-3", children: [_jsx(ExternalCacheBanner, { lastFetchedAt: s?.last_fetched_at ?? null, providers: [], title: "Floriday-kortingen", description: "De beschikbare kortings- en prijsgegevens worden automatisch bijgewerkt vanuit Floriday." }), _jsx(EmptyState, { message: "Voor deze klant zijn nog geen kortingen of prijsafspraken beschikbaar." })] })) : (_jsx(Card, { children: _jsxs(CardContent, { className: "pt-6 space-y-4", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx(Input, { placeholder: "Zoek op artikel of ID\u2026", value: search, onChange: (e) => setSearch(e.target.value), className: "max-w-xs" }), _jsxs(Select, { value: source, onValueChange: (v) => setSource(v), children: [_jsx(SelectTrigger, { className: "w-44", children: _jsx(SelectValue, { placeholder: "Bron" }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "all", children: "Alle bronnen" }), _jsx(SelectItem, { value: "agreement", children: "Contract" }), _jsx(SelectItem, { value: "sales_strategy", children: "Sales Strategy" }), _jsx(SelectItem, { value: "price_group", children: "Prijsgroep" })] })] }), _jsxs("label", { className: "flex items-center gap-2 text-sm text-muted-foreground ml-auto", children: [_jsx("input", { type: "checkbox", checked: onlyActive, onChange: (e) => setOnlyActive(e.target.checked) }), "Alleen actief vandaag"] })] }), _jsx("div", { className: "rounded-md border overflow-x-auto", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Artikel / Groep" }), _jsx(TableHead, { children: "Bron" }), _jsx(TableHead, { className: "text-right", children: "Basisprijs" }), _jsx(TableHead, { className: "text-right", children: "Korting" }), _jsx(TableHead, { className: "text-right", children: "Netto" }), _jsx(TableHead, { children: "Geldig" }), _jsx(TableHead, { className: "text-right", children: "Min. hvh" })] }) }), _jsx(TableBody, { children: filtered.length === 0 ? (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 7, className: "text-center text-muted-foreground py-6", children: "Geen kortingen die aan de filters voldoen." }) })) : filtered.map((r, i) => (_jsxs(TableRow, { children: [_jsxs(TableCell, { children: [_jsx("div", { className: "font-medium", children: r.trade_item_name ?? r.trade_item_id ?? r.product_group_id ?? "—" }), r.trade_item_name && (_jsx("div", { className: "text-xs text-muted-foreground font-mono truncate max-w-[280px]", children: r.trade_item_id }))] }), _jsx(TableCell, { children: _jsxs(Badge, { variant: "secondary", className: "gap-1", children: [SOURCE_ICON[r.source], SOURCE_LABEL[r.source]] }) }), _jsx(TableCell, { className: "text-right tabular-nums", children: fmtMoney(r.base_price, r.currency) }), _jsx(TableCell, { className: "text-right tabular-nums", children: fmtDiscount(r) }), _jsx(TableCell, { className: "text-right tabular-nums font-medium", children: fmtMoney(r.net_price, r.currency) }), _jsxs(TableCell, { className: "text-sm text-muted-foreground", children: [fmtDate(r.valid_from), " \u2013 ", fmtDate(r.valid_until)] }), _jsx(TableCell, { className: "text-right tabular-nums", children: r.min_quantity || "—" })] }, `${r.source_id}-${r.trade_item_id}-${i}`))) })] }) }), _jsx("p", { className: "text-xs text-muted-foreground", children: "
|
|
80
|
+
return (_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "grid gap-3 md:grid-cols-4", children: [_jsx(SummaryCard, { label: "Contracten", value: s?.agreements ?? 0, icon: _jsx(FileText, { className: "h-4 w-4" }) }), _jsx(SummaryCard, { label: "Prijsregels", value: s?.sales_strategies ?? 0, icon: _jsx(Percent, { className: "h-4 w-4" }) }), _jsx(SummaryCard, { label: "Prijsgroep-regels", value: s?.price_groups ?? 0, icon: _jsx(Users, { className: "h-4 w-4" }) }), _jsx(SummaryCard, { label: "Laatst gesynchroniseerd", value: formatDateTime(s?.last_fetched_at), icon: _jsx(Info, { className: "h-4 w-4" }), small: true })] }), rows.length === 0 ? (_jsxs("div", { className: "space-y-3", children: [_jsx(ExternalCacheBanner, { lastFetchedAt: s?.last_fetched_at ?? null, providers: [], title: "Floriday-kortingen", description: "De beschikbare kortings- en prijsgegevens worden automatisch bijgewerkt vanuit Floriday." }), _jsx(EmptyState, { message: "Voor deze klant zijn nog geen kortingen of prijsafspraken beschikbaar." })] })) : (_jsx(Card, { children: _jsxs(CardContent, { className: "pt-6 space-y-4", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx(Input, { placeholder: "Zoek op artikel of ID\u2026", value: search, onChange: (e) => setSearch(e.target.value), className: "max-w-xs" }), _jsxs(Select, { value: source, onValueChange: (v) => setSource(v), children: [_jsx(SelectTrigger, { className: "w-44", children: _jsx(SelectValue, { placeholder: "Bron" }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "all", children: "Alle bronnen" }), _jsx(SelectItem, { value: "agreement", children: "Contract" }), _jsx(SelectItem, { value: "sales_strategy", children: "Sales Strategy" }), _jsx(SelectItem, { value: "price_group", children: "Prijsgroep" })] })] }), _jsxs("label", { className: "flex items-center gap-2 text-sm text-muted-foreground ml-auto", children: [_jsx("input", { type: "checkbox", checked: onlyActive, onChange: (e) => setOnlyActive(e.target.checked) }), "Alleen actief vandaag"] })] }), _jsx("div", { className: "rounded-md border overflow-x-auto", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Artikel / Groep" }), _jsx(TableHead, { children: "Bron" }), _jsx(TableHead, { className: "text-right", children: "Basisprijs" }), _jsx(TableHead, { className: "text-right", children: "Korting" }), _jsx(TableHead, { className: "text-right", children: "Netto" }), _jsx(TableHead, { children: "Geldig" }), _jsx(TableHead, { className: "text-right", children: "Min. hvh" })] }) }), _jsx(TableBody, { children: filtered.length === 0 ? (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 7, className: "text-center text-muted-foreground py-6", children: "Geen kortingen die aan de filters voldoen." }) })) : filtered.map((r, i) => (_jsxs(TableRow, { children: [_jsxs(TableCell, { children: [_jsx("div", { className: "font-medium", children: r.trade_item_name ?? r.trade_item_id ?? r.product_group_id ?? "—" }), r.trade_item_name && (_jsx("div", { className: "text-xs text-muted-foreground font-mono truncate max-w-[280px]", children: r.trade_item_id }))] }), _jsx(TableCell, { children: _jsxs(Badge, { variant: "secondary", className: "gap-1", children: [SOURCE_ICON[r.source], SOURCE_LABEL[r.source]] }) }), _jsx(TableCell, { className: "text-right tabular-nums", children: fmtMoney(r.base_price, r.currency) }), _jsx(TableCell, { className: "text-right tabular-nums", children: fmtDiscount(r) }), _jsx(TableCell, { className: "text-right tabular-nums font-medium", children: fmtMoney(r.net_price, r.currency) }), _jsxs(TableCell, { className: "text-sm text-muted-foreground", children: [fmtDate(r.valid_from), " \u2013 ", fmtDate(r.valid_until)] }), _jsx(TableCell, { className: "text-right tabular-nums", children: r.min_quantity || "—" })] }, `${r.source_id}-${r.trade_item_id}-${i}`))) })] }) }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Kortingen pas je aan in Floriday; hier zie je ze alleen." })] }) }))] }));
|
|
81
81
|
}
|
|
82
82
|
function SummaryCard({ label, value, icon, small, }) {
|
|
83
83
|
return (_jsx(Card, { children: _jsxs(CardContent, { className: "p-4", children: [_jsxs("div", { className: "flex items-center gap-2 text-xs uppercase text-muted-foreground", children: [icon, label] }), _jsx("div", { className: small ? "text-sm mt-1" : "text-2xl font-semibold mt-1", children: value })] }) }));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExternalCacheBanner.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/ExternalCacheBanner.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wCAAwC,CAAC;AAa/E,wBAAgB,mBAAmB,CAAC,EAClC,aAAa,EACb,SAAS,EACT,KAA2C,EAC3C,WAAW,GACZ,EAAE;IACD,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,SAAS,EAAE,gBAAgB,EAAE,CAAC;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,+
|
|
1
|
+
{"version":3,"file":"ExternalCacheBanner.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/ExternalCacheBanner.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wCAAwC,CAAC;AAa/E,wBAAgB,mBAAmB,CAAC,EAClC,aAAa,EACb,SAAS,EACT,KAA2C,EAC3C,WAAW,GACZ,EAAE;IACD,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,SAAS,EAAE,gBAAgB,EAAE,CAAC;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,+BAmBA"}
|
|
@@ -14,9 +14,6 @@ function relativeTime(iso) {
|
|
|
14
14
|
return `${day} dag${day === 1 ? "" : "en"} geleden`;
|
|
15
15
|
}
|
|
16
16
|
export function ExternalCacheBanner({ lastFetchedAt, providers, title = "Externe klantgegevens (read-only)", description, }) {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
.map((p) => p.label)
|
|
20
|
-
.join(", ");
|
|
21
|
-
return (_jsxs("div", { className: "flex items-start gap-3 rounded-lg border border-border bg-muted/40 px-4 py-3 text-sm", children: [_jsx(Cloud, { className: "h-4 w-4 mt-0.5 text-muted-foreground shrink-0" }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("div", { className: "font-medium", children: title }), _jsx("div", { className: "text-xs text-muted-foreground mt-0.5", children: description ?? (_jsxs(_Fragment, { children: ["Data wordt automatisch verfrist vanuit de gekoppelde bronnen", labels ? `: ${labels}` : "", "."] })) })] }), _jsxs("div", { className: "flex items-center gap-1.5 text-xs text-muted-foreground shrink-0", children: [_jsx(Clock, { className: "h-3.5 w-3.5" }), lastFetchedAt ? `Laatste cache: ${relativeTime(lastFetchedAt)}` : "Nog geen cache"] })] }));
|
|
17
|
+
void providers;
|
|
18
|
+
return (_jsxs("div", { className: "flex items-start gap-3 rounded-lg border border-border bg-muted/40 px-4 py-3 text-sm", children: [_jsx(Cloud, { className: "h-4 w-4 mt-0.5 text-muted-foreground shrink-0" }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("div", { className: "font-medium", children: title }), _jsx("div", { className: "text-xs text-muted-foreground mt-0.5", children: description ?? (_jsx(_Fragment, { children: "Gegevens worden automatisch bijgewerkt uit Floriday en Twinfield." })) })] }), _jsxs("div", { className: "flex items-center gap-1.5 text-xs text-muted-foreground shrink-0", children: [_jsx(Clock, { className: "h-3.5 w-3.5" }), lastFetchedAt ? `Laatst bijgewerkt: ${relativeTime(lastFetchedAt)}` : "Nog niet opgehaald"] })] }));
|
|
22
19
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FloridayNetworkStatus.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/FloridayNetworkStatus.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"FloridayNetworkStatus.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/FloridayNetworkStatus.tsx"],"names":[],"mappings":"AAMA,iGAAiG;AACjG,wBAAgB,qBAAqB,CAAC,EAAE,cAAc,EAAE,EAAE;IAAE,cAAc,EAAE,MAAM,CAAA;CAAE,sCAiBnF"}
|
|
@@ -3,9 +3,11 @@ import { useQuery } from "@tanstack/react-query";
|
|
|
3
3
|
import { useServerFn } from "@tanstack/react-start";
|
|
4
4
|
import { getFloridayNetworkStatus } from "../../lib/floriday-network-status.functions";
|
|
5
5
|
import { formatDate } from "../../lib/format-date";
|
|
6
|
+
import { useCustomerDefaults } from "./useCustomerDefaults";
|
|
6
7
|
/** Alleen weergave: staat deze Floriday-organisatie in je Floriday-netwerk? Geen klantstatus. */
|
|
7
8
|
export function FloridayNetworkStatus({ organizationId }) {
|
|
8
9
|
const fn = useServerFn(getFloridayNetworkStatus);
|
|
10
|
+
const { isAdmin } = useCustomerDefaults();
|
|
9
11
|
const { data } = useQuery({
|
|
10
12
|
queryKey: ["floriday-network-status", organizationId],
|
|
11
13
|
queryFn: () => fn({ data: { organizationId } }),
|
|
@@ -14,5 +16,5 @@ export function FloridayNetworkStatus({ organizationId }) {
|
|
|
14
16
|
if (!data)
|
|
15
17
|
return null;
|
|
16
18
|
const label = data.inNetwork === null ? "onbekend" : data.inNetwork ? "ja" : "nee";
|
|
17
|
-
return (_jsxs("div", { className: "mt-2 text-xs text-muted-foreground space-y-0.5", children: [_jsxs("p", { children: ["In je Floriday-netwerk: ", _jsx("span", { className: "font-medium text-foreground", children: label })] }), data.endDate && _jsxs("p", { children: ["Einddatum in Floriday: ", formatDate(data.endDate)] })] }));
|
|
19
|
+
return (_jsxs("div", { className: "mt-2 text-xs text-muted-foreground space-y-0.5", children: [_jsxs("p", { children: ["In je Floriday-netwerk: ", _jsx("span", { className: "font-medium text-foreground", children: label })] }), isAdmin === true && data.reason && _jsxs("p", { children: ["Details voor beheer: ", data.reason] }), data.endDate && _jsxs("p", { children: ["Einddatum in Floriday: ", formatDate(data.endDate)] })] }));
|
|
18
20
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"KlantDetail.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/KlantDetail.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"KlantDetail.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/KlantDetail.tsx"],"names":[],"mappings":"AA6CA,wBAAgB,WAAW,CAAC,EAAE,EAAE,EAAE,EAAE;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,+BAuXjD"}
|
|
@@ -4,8 +4,8 @@ import { REFRESH_MS } from "../../lib/refresh-intervals.js";
|
|
|
4
4
|
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
|
5
5
|
import { useServerFn } from "@tanstack/react-start";
|
|
6
6
|
import { useEffect, useState } from "react";
|
|
7
|
-
import { Card, CardContent, CardHeader, CardTitle, Badge, Button, toast, Tabs, TabsContent, TabsList, TabsTrigger, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle,
|
|
8
|
-
import { Trash2, Mail, Phone, Power, Pencil, } from "lucide-react";
|
|
7
|
+
import { Card, CardContent, CardHeader, CardTitle, Badge, Button, toast, Tabs, TabsContent, TabsList, TabsTrigger, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, } from "@flowselections/core";
|
|
8
|
+
import { Trash2, Mail, Phone, Power, Pencil, MoreHorizontal, } from "lucide-react";
|
|
9
9
|
import { getCustomer, deleteCustomer, updateCustomer } from "../../lib/customers.functions.js";
|
|
10
10
|
import { ProfileCard, KpiStrip, BadgeList, EmptyState, ActivityItem, adaptLocalToProfile, } from "./KlantDetailLayout.js";
|
|
11
11
|
import { DeliveryLocationsTab } from "./DeliveryLocationsTab.js";
|
|
@@ -39,6 +39,7 @@ export function KlantDetail({ id }) {
|
|
|
39
39
|
const saveNote = useServerFn(upsertCustomerNote);
|
|
40
40
|
const [tab, setTab] = useState("overview");
|
|
41
41
|
const [editOpen, setEditOpen] = useState(false);
|
|
42
|
+
const [confirm, setConfirm] = useState(null);
|
|
42
43
|
const { data, isLoading } = useQuery({
|
|
43
44
|
queryKey: ["customer", id],
|
|
44
45
|
queryFn: () => get({ data: { id } }).then(decodeDeep),
|
|
@@ -103,9 +104,20 @@ export function KlantDetail({ id }) {
|
|
|
103
104
|
if (!data)
|
|
104
105
|
return _jsx("main", { className: "p-6", children: "Klant niet gevonden" });
|
|
105
106
|
const c = data.customer;
|
|
107
|
+
const linkedToTwinfield = !!(c.twinfield_code ?? c.twinfield_debtor_code);
|
|
106
108
|
const missingTwinfield = getMissingTwinfieldFields(c);
|
|
107
109
|
const priceGroups = data.price_groups ?? c.price_groups ?? [];
|
|
108
|
-
return (_jsxs("main", { className: "p-6 space-y-4", children: [_jsx(BackLink, {}), missingTwinfield.length > 0 && (_jsxs("div", { className: "rounded-xl border border-warning/40 bg-warning/10 px-4 py-3 text-sm", children: [_jsx("p", { className: "font-medium", children: "Onvolledig voor Twinfield" }), _jsxs("p", { className: "text-muted-foreground mt-0.5", children: ["Deze klant kan pas naar Twinfield worden gestuurd zodra de volgende gegevens zijn aangevuld: ", missingTwinfield.map((f) => f.label).join(", "), "."] })] })), _jsx("div", { className: "flex items-center justify-end", children: _jsxs("div", { className: "flex gap-2 items-center", children: [_jsx(BlockedBadge, { blocked: c.blocked }), _jsx(SyncStatusChip, { customerId: id }), _jsx(FloridayDeeplinkButton, { organizationId: c.floriday_organization_id }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => setEditOpen(true), children: [_jsx(Pencil, { className: "h-4 w-4 mr-2" }), " Bewerken"] }), _jsxs(Button, { variant: "outline", size: "sm",
|
|
110
|
+
return (_jsxs("main", { className: "p-6 space-y-4", children: [_jsx(BackLink, {}), missingTwinfield.length > 0 && (_jsxs("div", { className: "rounded-xl border border-warning/40 bg-warning/10 px-4 py-3 text-sm", children: [_jsx("p", { className: "font-medium", children: "Onvolledig voor Twinfield" }), _jsxs("p", { className: "text-muted-foreground mt-0.5", children: ["Deze klant kan pas naar Twinfield worden gestuurd zodra de volgende gegevens zijn aangevuld: ", missingTwinfield.map((f) => f.label).join(", "), "."] })] })), _jsx("div", { className: "flex items-center justify-end", children: _jsxs("div", { className: "flex gap-2 items-center", children: [_jsx(BlockedBadge, { blocked: c.blocked }), _jsx(SyncStatusChip, { customerId: id }), _jsx(FloridayDeeplinkButton, { organizationId: c.floriday_organization_id }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => setEditOpen(true), children: [_jsx(Pencil, { className: "h-4 w-4 mr-2" }), " Bewerken"] }), _jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsxs(Button, { variant: "outline", size: "sm", children: [_jsx(MoreHorizontal, { className: "h-4 w-4 mr-2" }), " Meer"] }) }), _jsxs(DropdownMenuContent, { align: "end", children: [_jsxs(DropdownMenuItem, { onSelect: () => setConfirm("active"), children: [_jsx(Power, { className: "h-4 w-4 mr-2" }), c.is_active ? "Op inactief zetten" : "Activeren"] }), _jsx(DropdownMenuSeparator, {}), _jsxs(DropdownMenuItem, { className: "text-destructive", onSelect: () => setConfirm("delete"), children: [_jsx(Trash2, { className: "h-4 w-4 mr-2" }), " Verwijderen"] })] })] }), _jsx(AlertDialog, { open: confirm !== null, onOpenChange: (o) => !o && setConfirm(null), children: _jsxs(AlertDialogContent, { children: [confirm === "delete" ? (_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: "Klant verwijderen?" }), _jsxs(AlertDialogDescription, { children: [_jsx("strong", { children: c.company_name }), " wordt alleen hier verwijderd. In de boekhouding (Twinfield) blijft de klant bestaan, en in Floriday verandert niets. Dit kun je niet ongedaan maken."] })] })) : c.is_active ? (_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: "Klant op inactief zetten?" }), _jsxs(AlertDialogDescription, { children: [_jsx("strong", { children: c.company_name }), " wordt hier inactief.", " ", linkedToTwinfield
|
|
111
|
+
? "In de boekhouding (Twinfield) wordt de klant verborgen; de gegevens blijven daar bewaard."
|
|
112
|
+
: "Deze klant staat niet in de boekhouding, dus daar verandert niets.", " ", "In Floriday verandert niets. Je kunt de klant later weer activeren."] })] })) : (_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: "Klant activeren?" }), _jsxs(AlertDialogDescription, { children: [_jsx("strong", { children: c.company_name }), " wordt hier weer actief.", " ", linkedToTwinfield
|
|
113
|
+
? "In de boekhouding (Twinfield) wordt de klant weer zichtbaar."
|
|
114
|
+
: "Deze klant staat niet in de boekhouding, dus daar verandert niets.", " ", "In Floriday verandert niets."] })] })), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { children: "Annuleren" }), _jsx(AlertDialogAction, { onClick: () => {
|
|
115
|
+
if (confirm === "delete")
|
|
116
|
+
delMut.mutate();
|
|
117
|
+
else
|
|
118
|
+
toggleActive.mutate(!c.is_active);
|
|
119
|
+
setConfirm(null);
|
|
120
|
+
}, children: confirm === "delete" ? "Verwijderen" : c.is_active ? "Op inactief zetten" : "Activeren" })] })] }) })] }) }), _jsxs("div", { className: "grid gap-6 lg:grid-cols-[340px_1fr]", children: [_jsx(ProfileCard, { profile: adaptLocalToProfile(c, data.contacts), notes: noteData?.note ?? null, onSaveNotes: (n) => noteMut.mutate(n), isSaving: noteMut.isPending || noteLoading }), _jsxs("div", { className: "space-y-4 min-w-0", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-4", children: [keyData?.customerKey ? (_jsx(CustomerPriceGroupSelect, { customerKey: keyData.customerKey })) : null, priceGroups.length > 0 && (_jsxs("div", { className: "flex items-center gap-2 text-sm", children: [_jsx("span", { className: "text-muted-foreground", children: "Floriday-prijscategorie:" }), _jsx(PriceGroupBadges, { groups: priceGroups, max: priceGroups.length })] }))] }), _jsx(KpiStrip, { counts: {
|
|
109
121
|
contacts: data.contacts.length,
|
|
110
122
|
locations: data.delivery_locations.length,
|
|
111
123
|
assortment: null,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"KlantDetailLayout.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/KlantDetailLayout.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"KlantDetailLayout.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/KlantDetailLayout.tsx"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useCustomerDefaults } from "./useCustomerDefaults";
|
|
2
3
|
import { useState, useEffect } from "react";
|
|
3
4
|
import { formatDateTime } from "../../lib/format-date.js";
|
|
4
5
|
import { Card, CardContent, Badge, Separator, Textarea, Button, } from "@flowselections/core";
|
|
@@ -152,6 +153,7 @@ export function ProfileCard({ profile: p, notes, onSaveNotes, isSaving = false,
|
|
|
152
153
|
const countryDisplay = countryFlag ? `${countryFlag} ${p.country}` : p.country;
|
|
153
154
|
const fullAddress = [address, cityLine, countryDisplay].filter(Boolean).join(", ");
|
|
154
155
|
const [showTech, setShowTech] = useState(false);
|
|
156
|
+
const { isAdmin } = useCustomerDefaults();
|
|
155
157
|
const hasTech = !!(p.gln || p.rfh_id || p.organization_type || p.rfh_company_account_id || p.plantion_number);
|
|
156
158
|
const editable = !!onSaveNotes;
|
|
157
159
|
useEffect(() => {
|
|
@@ -162,7 +164,7 @@ export function ProfileCard({ profile: p, notes, onSaveNotes, isSaving = false,
|
|
|
162
164
|
await onSaveNotes?.(draft);
|
|
163
165
|
setIsEditing(false);
|
|
164
166
|
};
|
|
165
|
-
return (_jsx(Card, { className: "lg:sticky lg:top-6 self-start", children: _jsxs(CardContent, { className: "pt-6 space-y-5", children: [_jsxs("div", { className: "flex flex-col items-center text-center", children: [_jsx(CustomerAvatar, { logoUrl: p.logo_url, name: p.company_name, size: "lg", className: "h-24 w-24 text-2xl" }), _jsx("h1", { className: "mt-3 text-xl font-bold leading-tight", children: p.company_name }), p.commercial_name && p.commercial_name !== p.company_name && (_jsx("p", { className: "text-sm text-muted-foreground", children: p.commercial_name })), _jsxs("div", { className: "mt-3 flex flex-wrap items-center justify-center gap-1.5", children: [p.is_active ? _jsx(Badge, { children: "Actief" }) : _jsx(Badge, { variant: "destructive", children: "Inactief" }), p.class_badge && _jsx(Badge, { variant: "secondary", children: p.class_badge })] }), p.floriday_organization_id && _jsx(FloridayNetworkStatus, { organizationId: p.floriday_organization_id })] }), (p.email || p.phone || p.website) && (_jsxs(_Fragment, { children: [_jsx(Separator, {}), _jsxs("div", { children: [_jsx(SectionLabel, { children: "Snelle acties" }), _jsxs("div", { className: "grid grid-cols-3 gap-2", children: [_jsx(QuickAction, { icon: _jsx(Mail, { className: "h-4 w-4" }), label: "E-mail", href: p.email ? `mailto:${p.email}` : undefined }), _jsx(QuickAction, { icon: _jsx(Phone, { className: "h-4 w-4" }), label: "Bellen", href: p.phone ? `tel:${p.phone}` : undefined }), _jsx(QuickAction, { icon: _jsx(Globe, { className: "h-4 w-4" }), label: "Website", href: p.website ?? undefined, external: true })] })] })] })), _jsx(Separator, {}), _jsxs("div", { className: "space-y-3", children: [_jsx(SectionLabel, { children: "Contact" }), _jsx(InfoRow, { icon: _jsx(MapPin, { className: "h-4 w-4" }), value: fullAddress || null }), _jsx(InfoRow, { icon: _jsx(Mail, { className: "h-4 w-4" }), value: p.email, href: p.email ? `mailto:${p.email}` : undefined, hint: p.email_from_contact ? `via ${p.primary_contact_name ?? "contactpersoon"}` : undefined }), _jsx(InfoRow, { icon: _jsx(Phone, { className: "h-4 w-4" }), value: p.phone, href: p.phone ? `tel:${p.phone}` : undefined, hint: p.phone_from_contact ? `via ${p.primary_contact_name ?? "contactpersoon"}` : undefined }), _jsx(InfoRow, { icon: _jsx(Globe, { className: "h-4 w-4" }), value: p.website, href: p.website ?? undefined, external: true }), !p.email && !p.phone && !p.has_contacts && (_jsxs("p", { className: "text-xs text-muted-foreground leading-relaxed", children: ["Voeg e-mail of telefoon toe via ", _jsx("strong", { children: "Bewerken" }), "."] }))] }), (p.payment_providers?.length || p.mailing_address) ? (_jsxs(_Fragment, { children: [_jsx(Separator, {}), _jsxs("div", { className: "space-y-3", children: [p.payment_providers && p.payment_providers.length > 0 && (_jsx(InfoRow, { icon: _jsx(Hash, { className: "h-4 w-4" }), label: "Financi\u00EBle afhandeling", value: p.payment_providers.join(", ") })), p.mailing_address && (_jsx(InfoRow, { icon: _jsx(MapPin, { className: "h-4 w-4" }), label: "Postadres", value: p.mailing_address }))] })] })) : null, p.vat_number && (_jsxs(_Fragment, { children: [_jsx(Separator, {}), _jsxs("div", { className: "space-y-3", children: [_jsx(SectionLabel, { children: "Identificatie" }), _jsx(InfoRow, { icon: _jsx(Hash, { className: "h-4 w-4" }), label: "BTW", value: p.vat_number, mono: true })] })] })), hasTech && (_jsxs(_Fragment, { children: [_jsx(Separator, {}), _jsxs("button", { type: "button", onClick: () => setShowTech((v) => !v), className: "flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground", children: [showTech ? _jsx(ChevronDown, { className: "h-3 w-3" }) : _jsx(ChevronRight, { className: "h-3 w-3" }), "
|
|
167
|
+
return (_jsx(Card, { className: "lg:sticky lg:top-6 self-start", children: _jsxs(CardContent, { className: "pt-6 space-y-5", children: [_jsxs("div", { className: "flex flex-col items-center text-center", children: [_jsx(CustomerAvatar, { logoUrl: p.logo_url, name: p.company_name, size: "lg", className: "h-24 w-24 text-2xl" }), _jsx("h1", { className: "mt-3 text-xl font-bold leading-tight", children: p.company_name }), p.commercial_name && p.commercial_name !== p.company_name && (_jsx("p", { className: "text-sm text-muted-foreground", children: p.commercial_name })), _jsxs("div", { className: "mt-3 flex flex-wrap items-center justify-center gap-1.5", children: [p.is_active ? _jsx(Badge, { children: "Actief" }) : _jsx(Badge, { variant: "destructive", children: "Inactief" }), p.class_badge && _jsx(Badge, { variant: "secondary", children: p.class_badge })] }), p.floriday_organization_id && _jsx(FloridayNetworkStatus, { organizationId: p.floriday_organization_id })] }), (p.email || p.phone || p.website) && (_jsxs(_Fragment, { children: [_jsx(Separator, {}), _jsxs("div", { children: [_jsx(SectionLabel, { children: "Snelle acties" }), _jsxs("div", { className: "grid grid-cols-3 gap-2", children: [_jsx(QuickAction, { icon: _jsx(Mail, { className: "h-4 w-4" }), label: "E-mail", href: p.email ? `mailto:${p.email}` : undefined }), _jsx(QuickAction, { icon: _jsx(Phone, { className: "h-4 w-4" }), label: "Bellen", href: p.phone ? `tel:${p.phone}` : undefined }), _jsx(QuickAction, { icon: _jsx(Globe, { className: "h-4 w-4" }), label: "Website", href: p.website ?? undefined, external: true })] })] })] })), _jsx(Separator, {}), _jsxs("div", { className: "space-y-3", children: [_jsx(SectionLabel, { children: "Contact" }), _jsx(InfoRow, { icon: _jsx(MapPin, { className: "h-4 w-4" }), value: fullAddress || null }), _jsx(InfoRow, { icon: _jsx(Mail, { className: "h-4 w-4" }), value: p.email, href: p.email ? `mailto:${p.email}` : undefined, hint: p.email_from_contact ? `via ${p.primary_contact_name ?? "contactpersoon"}` : undefined }), _jsx(InfoRow, { icon: _jsx(Phone, { className: "h-4 w-4" }), value: p.phone, href: p.phone ? `tel:${p.phone}` : undefined, hint: p.phone_from_contact ? `via ${p.primary_contact_name ?? "contactpersoon"}` : undefined }), _jsx(InfoRow, { icon: _jsx(Globe, { className: "h-4 w-4" }), value: p.website, href: p.website ?? undefined, external: true }), !p.email && !p.phone && !p.has_contacts && (_jsxs("p", { className: "text-xs text-muted-foreground leading-relaxed", children: ["Voeg e-mail of telefoon toe via ", _jsx("strong", { children: "Bewerken" }), "."] }))] }), (p.payment_providers?.length || p.mailing_address) ? (_jsxs(_Fragment, { children: [_jsx(Separator, {}), _jsxs("div", { className: "space-y-3", children: [p.payment_providers && p.payment_providers.length > 0 && (_jsx(InfoRow, { icon: _jsx(Hash, { className: "h-4 w-4" }), label: "Financi\u00EBle afhandeling", value: p.payment_providers.join(", ") })), p.mailing_address && (_jsx(InfoRow, { icon: _jsx(MapPin, { className: "h-4 w-4" }), label: "Postadres", value: p.mailing_address }))] })] })) : null, p.vat_number && (_jsxs(_Fragment, { children: [_jsx(Separator, {}), _jsxs("div", { className: "space-y-3", children: [_jsx(SectionLabel, { children: "Identificatie" }), _jsx(InfoRow, { icon: _jsx(Hash, { className: "h-4 w-4" }), label: "BTW", value: p.vat_number, mono: true })] })] })), hasTech && isAdmin === true && (_jsxs(_Fragment, { children: [_jsx(Separator, {}), _jsxs("button", { type: "button", onClick: () => setShowTech((v) => !v), className: "flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground", children: [showTech ? _jsx(ChevronDown, { className: "h-3 w-3" }) : _jsx(ChevronRight, { className: "h-3 w-3" }), "Details voor beheer"] }), showTech && (_jsxs("div", { className: "space-y-3", children: [p.rfh_id && _jsx(InfoRow, { icon: _jsx(Hash, { className: "h-4 w-4" }), label: "RFH-relatienr.", value: p.rfh_id, mono: true }), p.rfh_company_account_id && _jsx(InfoRow, { icon: _jsx(Hash, { className: "h-4 w-4" }), label: "RFH-bedrijfsaccount", value: p.rfh_company_account_id, mono: true }), p.plantion_number && _jsx(InfoRow, { icon: _jsx(Hash, { className: "h-4 w-4" }), label: "Plantion-registratienr.", value: p.plantion_number, mono: true }), _jsx(InfoRow, { icon: _jsx(Hash, { className: "h-4 w-4" }), label: "GLN", value: p.gln, mono: true }), _jsx(InfoRow, { icon: _jsx(Building2, { className: "h-4 w-4" }), label: "Type", value: formatOrgType(p.organization_type) })] }))] })), _jsx(Separator, {}), _jsxs("div", { className: "space-y-3", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx(SectionLabel, { children: "Notitie" }), editable && !isEditing && (_jsxs("button", { type: "button", onClick: () => setIsEditing(true), className: "flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground", "aria-label": "Notitie bewerken", children: [_jsx(Pencil, { className: "h-3 w-3" }), "Bewerken"] }))] }), isEditing ? (_jsxs("div", { className: "space-y-2", children: [_jsx(Textarea, { value: draft, onChange: (e) => setDraft(e.target.value), rows: 4, placeholder: "Voeg een korte notitie over deze klant toe...", disabled: isSaving, className: "text-sm resize-none" }), _jsxs("div", { className: "flex items-center justify-end gap-2", children: [_jsxs(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => {
|
|
166
168
|
setDraft(notes ?? "");
|
|
167
169
|
setIsEditing(false);
|
|
168
170
|
}, disabled: isSaving, children: [_jsx(X, { className: "h-4 w-4 mr-1" }), " Annuleren"] }), _jsx(Button, { type: "button", size: "sm", onClick: handleSave, disabled: isSaving || draft === (notes ?? ""), children: isSaving ? (_jsxs("span", { className: "flex items-center gap-1", children: [_jsx("span", { className: "h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent" }), "Opslaan..."] })) : (_jsxs(_Fragment, { children: [_jsx(Check, { className: "h-4 w-4 mr-1" }), " Opslaan"] })) })] })] })) : notes ? (_jsx("p", { className: "text-sm whitespace-pre-wrap text-foreground", children: notes })) : editable ? (_jsxs("button", { type: "button", onClick: () => setIsEditing(true), className: "flex w-full items-center gap-2 rounded-md border border-dashed border-border bg-muted/40 px-3 py-2 text-sm text-muted-foreground transition-colors hover:bg-muted", children: [_jsx(StickyNote, { className: "h-4 w-4" }), "Notitie toevoegen"] })) : (_jsxs("div", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [_jsx(StickyNote, { className: "h-4 w-4" }), "Geen notitie"] }))] })] }) }));
|
|
@@ -482,7 +482,7 @@ export function KlantenOverzicht() {
|
|
|
482
482
|
queryClient.invalidateQueries({ queryKey: ["overdue-balances"] });
|
|
483
483
|
queryClient.invalidateQueries({ queryKey: ["customer-revenue-12m"] });
|
|
484
484
|
}, children: "Opnieuw proberen" })] })), _jsxs("div", { className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-3", children: [_jsx(StatCard, { icon: _jsx(Users, { className: "h-5 w-5" }), tint: "muted", label: "Alle klanten", value: totals.all, tooltip: "Totaal aantal klanten (lokaal + gekoppelde bronnen).", active: view === "all", onClick: () => { setView("all"); resetPage(); } }), _jsx(StatCard, { icon: _jsx(UserCheck, { className: "h-5 w-5" }), tint: "success", label: "Met omzet", value: totals.active, tooltip: revenueData
|
|
485
|
-
? `Klanten met minimaal één bestelling of factuur sinds ${formatDateNl(revenueData.since)}.\nTotale omzet in die periode: ${formatEuro(revenueData.totals.amount)} · ${revenueData.totals.transactions} transacties`
|
|
485
|
+
? `Klanten met minimaal één bestelling of factuur sinds ${formatDateNl(revenueData.since)}.\nTotale omzet in die periode: ${formatEuro(revenueData.totals.amount)} · ${revenueData.totals.transactions} ${revenueData.totals.transactions === 1 ? "transactie" : "transacties"}`
|
|
486
486
|
: "Klanten met minimaal één bestelling in de afgelopen 12 maanden.", active: view === "active", onClick: () => { setView("active"); resetPage(); } }), _jsx(StatCard, { icon: _jsx(UserX, { className: "h-5 w-5" }), tint: "warning", label: "Zonder omzet", value: totals.inactive, tooltip: revenueData
|
|
487
487
|
? `Klanten zonder enige bestelling of factuur sinds ${formatDateNl(revenueData.since)}.`
|
|
488
488
|
: "Klanten zonder omzet in de afgelopen 12 maanden.", active: view === "inactive", onClick: () => { setView("inactive"); resetPage(); } }), _jsx(StatCard, { icon: _jsx(AlertCircle, { className: "h-5 w-5" }), tint: "destructive", label: "Achterstallige betalingen", value: totals.overdue, tooltip: (overdueData?.totals
|
|
@@ -545,7 +545,7 @@ function SortHeader({ label, col, sort, onClick, }) {
|
|
|
545
545
|
}
|
|
546
546
|
function StatusPill({ active, missing }) {
|
|
547
547
|
if (missing) {
|
|
548
|
-
return (_jsxs("span", { className: "inline-flex items-center gap-1.5 rounded-full px-2.5 py-0.5 text-xs font-medium bg-destructive/10 text-destructive", title: "Deze
|
|
548
|
+
return (_jsxs("span", { className: "inline-flex items-center gap-1.5 rounded-full px-2.5 py-0.5 text-xs font-medium bg-destructive/10 text-destructive", title: "Deze klant staat niet meer in de boekhouding (Twinfield).", children: [_jsx("span", { className: "h-1.5 w-1.5 rounded-full bg-destructive" }), "Niet meer in Twinfield"] }));
|
|
549
549
|
}
|
|
550
550
|
return (_jsxs("span", { className: `inline-flex items-center gap-1.5 rounded-full px-2.5 py-0.5 text-xs font-medium ${active
|
|
551
551
|
? "bg-primary/10 text-primary"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SyncStatusChip.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/SyncStatusChip.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"SyncStatusChip.d.ts","sourceRoot":"","sources":["../../../src/components/klanten/SyncStatusChip.tsx"],"names":[],"mappings":"AAaA,wBAAgB,cAAc,CAAC,EAAE,UAAU,EAAE,EAAE;IAAE,UAAU,EAAE,MAAM,CAAA;CAAE,sCAsGpE"}
|
|
@@ -7,6 +7,7 @@ import { useServerFn } from "@tanstack/react-start";
|
|
|
7
7
|
import { Badge } from "@flowselections/core";
|
|
8
8
|
import { CheckCircle2, Clock, AlertTriangle, Loader2 } from "lucide-react";
|
|
9
9
|
import { getTwinfieldSyncStatus, retryTwinfieldPush, } from "../../lib/accounting/twinfield/worker.functions.js";
|
|
10
|
+
import { friendlyTwinfieldError, GENERIC_BOOKKEEPING_ERROR } from "../../lib/friendly-twinfield-error.js";
|
|
10
11
|
export function SyncStatusChip({ customerId }) {
|
|
11
12
|
const qc = useQueryClient();
|
|
12
13
|
const [retrying, setRetrying] = useState(false);
|
|
@@ -49,16 +50,16 @@ export function SyncStatusChip({ customerId }) {
|
|
|
49
50
|
const failedAt = latest?.updated_at ?? latest?.created_at ?? null;
|
|
50
51
|
const failedLongEnough = failedAt && Date.now() - new Date(failedAt).getTime() > FOUT_ZICHTBAAR_NA_MS;
|
|
51
52
|
if ((status === "failed" || status === "dead") && failedLongEnough) {
|
|
52
|
-
const reason = latest?.last_error ?? customer?.twinfield_sync_error;
|
|
53
|
-
return (_jsx("button", { type: "button", onClick: () => !retrying && retry.mutate(), className: "inline-flex max-w-full items-center gap-1.5 text-xs", title: reason ??
|
|
53
|
+
const reason = friendlyTwinfieldError(latest?.last_error ?? customer?.twinfield_sync_error);
|
|
54
|
+
return (_jsx("button", { type: "button", onClick: () => !retrying && retry.mutate(), className: "inline-flex max-w-full items-center gap-1.5 text-xs", title: reason ?? GENERIC_BOOKKEEPING_ERROR, children: _jsxs(Badge, { variant: "destructive", className: "gap-1 max-w-full", children: [retrying ? _jsx(Loader2, { className: "h-3 w-3 animate-spin" }) : _jsx(AlertTriangle, { className: "h-3 w-3" }), _jsx("span", { className: "truncate", children: "Niet bijgewerkt in de boekhouding \u2014 opnieuw proberen" })] }) }));
|
|
54
55
|
}
|
|
55
56
|
if (status === "pending" || status === "processing" || status === "failed" || status === "dead") {
|
|
56
|
-
const waiting = latest?.last_error
|
|
57
|
+
const waiting = friendlyTwinfieldError(latest?.last_error);
|
|
57
58
|
return (_jsxs(Badge, { variant: "secondary", className: "gap-1 max-w-full", title: waiting ?? undefined, children: [status === "processing" ? (_jsx(Loader2, { className: "h-3 w-3 animate-spin" })) : (_jsx(Clock, { className: "h-3 w-3" })), _jsx("span", { className: "truncate", children: "Wordt bijgewerkt" })] }));
|
|
58
59
|
}
|
|
59
60
|
if (synced) {
|
|
60
61
|
const when = lastPushAt ? formatDateTime(lastPushAt) : null;
|
|
61
|
-
const warning = customer?.twinfield_sync_error;
|
|
62
|
+
const warning = friendlyTwinfieldError(customer?.twinfield_sync_error);
|
|
62
63
|
return (_jsxs("span", { className: "inline-flex flex-wrap items-center gap-1.5", children: [_jsxs(Badge, { variant: "outline", className: "gap-1 text-success-soft-foreground border-success bg-success-soft", children: [_jsx(CheckCircle2, { className: "h-3 w-3" }), "Gesynchroniseerd", code ? ` · Twinfield ${code}` : "", when ? ` · laatst verstuurd ${when}` : ""] }), warning && (_jsxs(Badge, { variant: "secondary", className: "gap-1 max-w-full", title: warning, children: [_jsx(AlertTriangle, { className: "h-3 w-3" }), _jsx("span", { className: "truncate", children: warning })] }))] }));
|
|
63
64
|
}
|
|
64
65
|
return null;
|
|
@@ -257,7 +257,7 @@ export function PriceGroupFormPage({ editingId }) {
|
|
|
257
257
|
"De ingestelde transport- en afleverkosten in de levervoorwaarden worden in rekening gebracht",
|
|
258
258
|
] }))] }), _jsxs("div", { className: "pt-2", children: [_jsx("div", { className: "text-sm text-muted-foreground mb-2", children: "Diensten" }), _jsx(Separator, { className: "mb-3" }), _jsxs("label", { className: "flex items-center gap-2 cursor-pointer select-none", children: [_jsx(Checkbox, { checked: form.include_stickers, onCheckedChange: (v) => patch({ include_stickers: v === true }) }), _jsx("span", { className: "text-sm font-medium", children: "Inclusief stickeren" })] })] }), _jsxs("div", { className: "pt-2", children: [_jsx("div", { className: "text-sm text-muted-foreground mb-2", children: "Afname" }), _jsx(Separator, { className: "mb-3" }), _jsxs("div", { className: "max-w-sm", children: [_jsx(Label, { children: "Afname-eenheid" }), _jsxs(Select, { value: form.unit, onValueChange: (v) => patch({ unit: v }), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: "- Selecteer een eenheid -" }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "per_fust", children: "Per fust" }), _jsx(SelectItem, { value: "per_laag", children: "Per laag" }), _jsx(SelectItem, { value: "per_ladingdrager", children: "Per ladingdrager" })] })] })] }), _jsxs("div", { className: "text-xs text-muted-foreground mt-2 leading-relaxed", children: ["Afname-eenheid per aangeboden artikel.", _jsx("br", {}), "Deze instelling geldt voor alle klanten in deze prijsgroep, per aangeboden artikel, niet per bestelling."] })] }), _jsx("div", { className: "pt-2 flex justify-end", children: _jsxs(Button, { onClick: () => nextStep(1), children: ["Volgende stap ", _jsx(ArrowRight, { className: "h-4 w-4 ml-1" })] }) })] }) }), _jsx(StepSection, { index: 2, title: STEP_TITLES[2], subtitle: summaries[2], open: openStep === 2, completed: form.pricing_mode === "calculated" && maxReached >= 2 && openStep !== 2, disabled: maxReached < 2 || form.pricing_mode !== "calculated", onToggle: () => goToStep(2), children: _jsx("div", { className: "space-y-4", children: form.pricing_mode !== "calculated" ? (_jsx("div", { className: "text-sm text-muted-foreground p-4 border rounded-md bg-muted/30", children: "Deze prijsgroep gebruikt handmatige prijzen. Prijsberekening is niet van toepassing." })) : (_jsxs(_Fragment, { children: [_jsxs("div", { children: [_jsx(Label, { children: "Korting/opslag per stuk" }), _jsxs("div", { className: "grid grid-cols-1 sm:grid-cols-3 gap-3 mt-1.5", children: [_jsxs(Select, { value: form.calc_type ?? "discount", onValueChange: (v) => patch({ calc_type: v }), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "discount", children: "Korting" }), _jsx(SelectItem, { value: "surcharge", children: "Opslag" })] })] }), _jsxs(Select, { value: form.calc_value_type ?? "fixed", onValueChange: (v) => patch({ calc_value_type: v }), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "percentage", children: "Percentage" }), _jsx(SelectItem, { value: "fixed", children: "Vast bedrag" })] })] }), _jsxs("div", { className: "relative", children: [form.calc_value_type !== "percentage" && (_jsx("span", { className: "absolute left-3 top-1/2 -translate-y-1/2 text-sm text-muted-foreground pointer-events-none", children: "\u20AC" })), _jsx(Input, { type: "number", step: form.calc_value_type === "percentage" ? "1" : "0.001", value: form.calc_value ?? 0, onChange: (e) => patch({ calc_value: Number(e.target.value) }), className: form.calc_value_type === "percentage" ? "pr-8" : "pl-7" }), form.calc_value_type === "percentage" && (_jsx("span", { className: "absolute right-3 top-1/2 -translate-y-1/2 text-sm text-muted-foreground pointer-events-none", children: "%" }))] })] })] }), _jsxs("div", { className: "grid grid-cols-1 sm:grid-cols-2 gap-4", children: [_jsxs("div", { children: [_jsxs(Label, { className: "flex items-center gap-1.5", children: ["Afhandelingskosten (optioneel)", _jsx(Info, { className: "h-3.5 w-3.5 text-muted-foreground", "aria-label": "Extra kosten die bovenop de prijs komen. Kies hoe deze verdeeld worden." })] }), _jsxs("div", { className: "relative mt-1.5", children: [_jsx("span", { className: "absolute left-3 top-1/2 -translate-y-1/2 text-sm text-muted-foreground pointer-events-none", children: "\u20AC" }), _jsx(Input, { type: "number", step: "0.01", value: form.handling_fee ?? 0, onChange: (e) => patch({ handling_fee: Number(e.target.value) }), className: "pl-7" })] })] }), _jsxs("div", { children: [_jsx(Label, { children: "Verdeeld over het aantal planten of bloemen op een" }), _jsxs(Select, { value: form.distribute_over ?? "ladingdrager", onValueChange: (v) => patch({ distribute_over: v }), children: [_jsx(SelectTrigger, { className: "mt-1.5", children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "ladingdrager", children: "Ladingdrager" }), _jsx(SelectItem, { value: "laag", children: "Laag" }), _jsx(SelectItem, { value: "fust", children: "Fust" })] })] })] })] }), _jsxs("div", { className: "grid grid-cols-1 sm:grid-cols-2 gap-4", children: [_jsxs("div", { children: [_jsx(Label, { children: "Prijs afronden" }), _jsxs(Select, { value: form.rounding, onValueChange: (v) => patch({ rounding: v }), children: [_jsx(SelectTrigger, { className: "mt-1.5", children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "none", children: "Niet afronden" }), _jsx(SelectItem, { value: "up", children: "Omhoog" }), _jsx(SelectItem, { value: "down", children: "Omlaag" }), _jsx(SelectItem, { value: "nearest", children: "Dichtstbijzijnde" })] })] })] }), form.rounding !== "none" && (_jsxs("div", { children: [_jsx(Label, { children: "Op" }), _jsxs(Select, { value: String(form.rounding_step ?? 0.05), onValueChange: (v) => patch({ rounding_step: Number(v) }), children: [_jsx(SelectTrigger, { className: "mt-1.5", children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "0.01", children: "\u20AC 0,01" }), _jsx(SelectItem, { value: "0.05", children: "\u20AC 0,05" }), _jsx(SelectItem, { value: "0.1", children: "\u20AC 0,10" })] })] })] }))] }), _jsx("div", { className: "pt-2 flex justify-end", children: _jsxs(Button, { onClick: () => nextStep(2), children: ["Volgende stap ", _jsx(ArrowRight, { className: "h-4 w-4 ml-1" })] }) })] })) }) }), _jsxs(StepSection, { index: 3, title: STEP_TITLES[3], subtitle: summaries[3], open: openStep === 3, completed: form.group_type === "customer_specific" && maxReached >= 3 && openStep !== 3 && selectedCustomerKeys.length > 0, disabled: maxReached < 3 || form.group_type !== "customer_specific", onToggle: () => goToStep(3), children: [form.group_type !== "customer_specific" ? (_jsx("div", { className: "text-sm text-muted-foreground p-4 border rounded-md bg-muted/30", children: "Algemene prijsgroepen zijn zichtbaar voor alle klanten \u2014 een doelgroep is niet van toepassing." })) : selectedCustomerKeys.length === 0 ? (_jsxs("div", { className: "rounded-lg border bg-muted/20 p-8", children: [_jsxs("div", { className: "flex flex-col items-center text-center gap-3", children: [_jsxs("div", { className: "relative h-16 w-16 rounded-full bg-muted flex items-center justify-center", children: [_jsx(Users2, { className: "h-8 w-8 text-muted-foreground/60" }), _jsx("span", { className: "absolute -top-1 -right-1 h-6 w-6 rounded-full bg-background border flex items-center justify-center", children: _jsx(UserPlus, { className: "h-3.5 w-3.5 text-muted-foreground" }) })] }), _jsx("div", { children: _jsx("div", { className: "font-medium text-sm", children: "Nog geen klanten toegevoegd" }) })] }), _jsx("div", { className: "flex justify-end mt-4", children: _jsxs(Button, { variant: "outline", onClick: () => setCustomerPickerOpen(true), children: [_jsx(UserPlus, { className: "h-4 w-4 mr-1.5" }), " Organisaties wijzigen"] }) })] })) : (_jsxs("div", { className: "space-y-3", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "text-sm text-muted-foreground", children: [_jsx("strong", { className: "text-foreground", children: selectedCustomerKeys.length }), " ", "klant", selectedCustomerKeys.length === 1 ? "" : "en", " gekoppeld"] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => setCustomerPickerOpen(true), children: [_jsx(UserPlus, { className: "h-4 w-4 mr-1.5" }), " Organisaties wijzigen"] })] }), _jsx("div", { className: "max-h-[320px] overflow-y-auto border rounded-md divide-y", children: selectedCustomerKeys.map((k) => {
|
|
259
259
|
const c = customersByKey.get(k);
|
|
260
|
-
return (_jsxs("div", { className: "flex items-center gap-3 p-2.5", children: [_jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("div", { className: "text-sm font-medium truncate", children: c?.display_name ?? k }), _jsx("div", { className: "text-xs text-muted-foreground truncate", children: c ? (c.city || c.country ? (_jsxs("span", { className: "inline-flex items-center gap-1", children: [c.city && _jsx("span", { children: c.city }), c.city && c.country && _jsx("span", { children: "\u00B7" }), c.country && (_jsxs(_Fragment, { children: [_jsx("span", { className: "text-base", "aria-hidden": "true", children: countryNameToFlag(c.country) ?? "🌐" }), _jsx("span", { children: c.country })] }))] })) : "—") : "
|
|
260
|
+
return (_jsxs("div", { className: "flex items-center gap-3 p-2.5", children: [_jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("div", { className: "text-sm font-medium truncate", children: c?.display_name ?? k }), _jsx("div", { className: "text-xs text-muted-foreground truncate", children: c ? (c.city || c.country ? (_jsxs("span", { className: "inline-flex items-center gap-1", children: [c.city && _jsx("span", { children: c.city }), c.city && c.country && _jsx("span", { children: "\u00B7" }), c.country && (_jsxs(_Fragment, { children: [_jsx("span", { className: "text-base", "aria-hidden": "true", children: countryNameToFlag(c.country) ?? "🌐" }), _jsx("span", { children: c.country })] }))] })) : "—") : "Nog niet opgehaald uit Floriday" })] }), _jsx("div", { className: "flex gap-1", children: (c?.providers ?? []).slice(0, 3).map((p) => (_jsx(Badge, { variant: "secondary", className: "text-[10px]", children: p }, p))) })] }, k));
|
|
261
261
|
}) })] })), _jsx(CustomerPickerDialog, { open: customerPickerOpen, onOpenChange: setCustomerPickerOpen, value: selectedCustomerKeys, onChange: setSelectedCustomerKeys })] }), editingId && (_jsx(PriceGroupItemPrices, { priceGroupId: editingId, currency: form.currency })), error && _jsx("div", { className: "text-sm text-destructive", children: error })] }), form.pricing_mode === "calculated" && (_jsxs("aside", { className: "lg:sticky lg:top-4 space-y-3", children: [_jsx(Card, { children: _jsxs(CardContent, { className: "p-4 space-y-4", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("div", { className: "text-sm font-medium", children: "Voorbeeld prijscalculatie" }), _jsxs("div", { className: "flex items-center gap-1", children: [_jsx(Button, { variant: "outline", size: "icon", className: "h-7 w-7", onClick: () => cycleItem(-1), disabled: items.length < 2, "aria-label": "Vorige product", children: _jsx(ChevronLeft, { className: "h-4 w-4" }) }), _jsx(Button, { variant: "outline", size: "icon", className: "h-7 w-7", onClick: () => cycleItem(1), disabled: items.length < 2, "aria-label": "Volgend product", children: _jsx(ChevronRight, { className: "h-4 w-4" }) })] })] }), _jsxs("button", { type: "button", onClick: () => setPickerOpen(true), className: "w-full flex items-center gap-3 text-left hover:bg-muted/30 rounded-md p-2 -m-2", children: [_jsx("div", { className: "h-14 w-14 rounded bg-muted flex items-center justify-center overflow-hidden shrink-0", children: selectedItem?.image_url
|
|
262
262
|
? _jsx("img", { src: selectedItem.image_url, alt: "", className: "h-full w-full object-contain" })
|
|
263
263
|
: _jsx(ImageIcon, { className: "h-6 w-6 text-muted-foreground" }) }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("div", { className: "text-sm font-medium truncate", children: selectedItem?.name ?? "Geen product beschikbaar" }), _jsx("div", { className: "text-xs text-muted-foreground truncate", children: selectedItem
|
|
@@ -265,7 +265,7 @@ export function PriceGroupFormPage({ editingId }) {
|
|
|
265
265
|
`${selectedItem.units_per_fust}×${selectedItem.packages_per_layer}×${selectedItem.layers_per_carrier}`,
|
|
266
266
|
selectedItem.carrier_code]
|
|
267
267
|
.filter(Boolean).join(" - ")
|
|
268
|
-
: "
|
|
268
|
+
: "Nog niet opgehaald uit Floriday" })] })] }), _jsxs("div", { children: [_jsx(Label, { className: "text-xs", children: "Basisprijs" }), _jsxs("div", { className: "relative mt-1.5", children: [_jsx("span", { className: "absolute left-3 top-1/2 -translate-y-1/2 text-sm text-muted-foreground pointer-events-none", children: "\u20AC" }), _jsx(Input, { type: "number", step: "0.01", value: basePrice, onChange: (e) => setBasePrice(Number(e.target.value)), className: "pl-7" })] })] }), _jsxs("div", { children: [_jsx("div", { className: "flex justify-end text-xs text-muted-foreground mb-1", children: "Prijzen" }), _jsxs("div", { className: "space-y-1.5 text-sm tabular-nums border-t pt-2", children: [_jsxs("div", { className: "flex justify-between", children: [_jsx("span", { className: "text-muted-foreground", children: "Basisprijs" }), _jsx("span", { children: formatEur(basePrice) })] }), Math.abs(preview.discountAmount) > 0 && (_jsxs("div", { className: "flex justify-between", children: [_jsxs("span", { className: "text-muted-foreground", children: [form.calc_type === "surcharge" ? "Opslag" : "Korting", " ", form.calc_value_type === "percentage"
|
|
269
269
|
? `${(form.calc_value ?? 0).toLocaleString("nl-NL")}%`
|
|
270
270
|
: formatEur(form.calc_value ?? 0, { decimals: 3 })] }), _jsxs("span", { children: [preview.discountAmount < 0 ? "− " : "", formatEur(Math.abs(preview.discountAmount))] })] })), preview.handlingPerUnit > 0 && (_jsxs("div", { className: "flex justify-between items-baseline gap-2", children: [_jsxs("span", { className: "text-muted-foreground truncate", children: ["Afhandelingskosten (optioneel)", " ", _jsxs("span", { className: "italic text-muted-foreground/80", children: [formatEur(form.handling_fee ?? 0), handlingSuffix] })] }), _jsx("span", { children: formatEur(preview.handlingPerUnit, { decimals: 3 }) })] })), _jsxs("div", { className: "flex justify-between border-t pt-1.5", children: [_jsx("span", { className: form.rounding === "none" ? "font-medium" : "text-muted-foreground", children: "Berekende prijs" }), _jsx("span", { className: form.rounding === "none" ? "font-medium" : "", children: formatEur(preview.adjusted, { decimals: 3 }) })] }), form.rounding !== "none" && (_jsxs("div", { className: "flex justify-between font-medium border-t pt-1.5", children: [_jsx("span", { children: "Afgeronde prijs" }), _jsx("span", { children: formatEur(preview.rounded) })] }))] })] })] }) }), _jsx(ProductPickerDialog, { open: pickerOpen, onOpenChange: setPickerOpen, items: items, onSelect: (i) => setSelectedItemId(i.id) })] }))] })] }), _jsx("div", { className: "fixed bottom-0 left-0 right-0 bg-background/95 backdrop-blur border-t z-10", children: _jsxs("div", { className: "max-w-6xl mx-auto px-6 py-3 flex items-center justify-between", children: [_jsx(Button, { variant: "ghost", onClick: goBack, children: "Annuleren" }), _jsx(Button, { onClick: () => saveMut.mutate(), disabled: saveMut.isPending ||
|
|
271
271
|
!form.name.trim() ||
|
|
@@ -29,7 +29,7 @@ export function UnknownFloridayMembers({ members, priceGroupIdFor, }) {
|
|
|
29
29
|
qc.invalidateQueries({ queryKey: ["unknown-floriday-members"] });
|
|
30
30
|
qc.invalidateQueries({ queryKey: ["customer-price-group"] });
|
|
31
31
|
qc.invalidateQueries({ queryKey: ["external-customers-picker"] });
|
|
32
|
-
toast.success(`${m.commercial_name ?? m.name} is toegevoegd als klant
|
|
32
|
+
toast.success(`${m.commercial_name ?? m.name} is toegevoegd als klant en staat in deze prijsgroep.`);
|
|
33
33
|
},
|
|
34
34
|
onError: (e) => toast.error(friendlyError(e)),
|
|
35
35
|
});
|
|
@@ -39,5 +39,5 @@ export function UnknownFloridayMembers({ members, priceGroupIdFor, }) {
|
|
|
39
39
|
const added = done.has(m.organization_id);
|
|
40
40
|
const canAdd = m.groups.some((g) => priceGroupIdFor(g.external_id));
|
|
41
41
|
return (_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2 px-3 py-2", children: [_jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("div", { className: "text-sm font-medium truncate", children: m.commercial_name ?? m.name }), _jsxs("div", { className: "text-xs text-muted-foreground truncate", children: [m.commercial_name ? `${m.name} · ` : "", m.city ?? "—", " \u00B7 ", m.reason] }), m.groups.length > 0 && (_jsxs("div", { className: "text-xs text-muted-foreground truncate", children: ["In Floriday: ", m.groups.map((g) => g.name).join(", ")] }))] }), added ? (_jsx(Badge, { variant: "secondary", children: "Toegevoegd" })) : (_jsx(Button, { size: "sm", variant: "outline", disabled: !canAdd || addMut.isPending, title: canAdd ? undefined : "Importeer eerst de prijsgroep", onClick: () => addMut.mutate(m), children: "Toevoegen" }))] }, m.organization_id));
|
|
42
|
-
}) }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Toevoegen maakt een klant aan
|
|
42
|
+
}) }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Toevoegen maakt een nieuwe klant aan (nog niet in de boekhouding) en zet die in deze prijsgroep." })] }));
|
|
43
43
|
}
|
|
@@ -1231,38 +1231,59 @@ export type Database = {
|
|
|
1231
1231
|
Row: {
|
|
1232
1232
|
carrier_id: string;
|
|
1233
1233
|
cart_count: number;
|
|
1234
|
+
cart_ids: string[] | null;
|
|
1234
1235
|
created_at: string;
|
|
1236
|
+
fingerprint: string | null;
|
|
1235
1237
|
id: string;
|
|
1238
|
+
kind: string;
|
|
1236
1239
|
order_count: number;
|
|
1237
1240
|
outbox_id: string | null;
|
|
1238
1241
|
pickup_date: string;
|
|
1239
1242
|
pickup_moment_key: string;
|
|
1243
|
+
previous_cart_count: number | null;
|
|
1244
|
+
recipient: string | null;
|
|
1240
1245
|
sent_at: string;
|
|
1241
1246
|
signup_deadline: string;
|
|
1247
|
+
source: string;
|
|
1248
|
+
version: number;
|
|
1242
1249
|
};
|
|
1243
1250
|
Insert: {
|
|
1244
1251
|
carrier_id: string;
|
|
1245
1252
|
cart_count?: number;
|
|
1253
|
+
cart_ids?: string[] | null;
|
|
1246
1254
|
created_at?: string;
|
|
1255
|
+
fingerprint?: string | null;
|
|
1247
1256
|
id?: string;
|
|
1257
|
+
kind?: string;
|
|
1248
1258
|
order_count?: number;
|
|
1249
1259
|
outbox_id?: string | null;
|
|
1250
1260
|
pickup_date: string;
|
|
1251
1261
|
pickup_moment_key: string;
|
|
1262
|
+
previous_cart_count?: number | null;
|
|
1263
|
+
recipient?: string | null;
|
|
1252
1264
|
sent_at?: string;
|
|
1253
1265
|
signup_deadline: string;
|
|
1266
|
+
source?: string;
|
|
1267
|
+
version?: number;
|
|
1254
1268
|
};
|
|
1255
1269
|
Update: {
|
|
1256
1270
|
carrier_id?: string;
|
|
1257
1271
|
cart_count?: number;
|
|
1272
|
+
cart_ids?: string[] | null;
|
|
1258
1273
|
created_at?: string;
|
|
1274
|
+
fingerprint?: string | null;
|
|
1259
1275
|
id?: string;
|
|
1276
|
+
kind?: string;
|
|
1260
1277
|
order_count?: number;
|
|
1261
1278
|
outbox_id?: string | null;
|
|
1262
1279
|
pickup_date?: string;
|
|
1263
1280
|
pickup_moment_key?: string;
|
|
1281
|
+
previous_cart_count?: number | null;
|
|
1282
|
+
recipient?: string | null;
|
|
1264
1283
|
sent_at?: string;
|
|
1265
1284
|
signup_deadline?: string;
|
|
1285
|
+
source?: string;
|
|
1286
|
+
version?: number;
|
|
1266
1287
|
};
|
|
1267
1288
|
Relationships: [
|
|
1268
1289
|
{
|
|
@@ -28564,6 +28585,18 @@ export type Database = {
|
|
|
28564
28585
|
};
|
|
28565
28586
|
Returns: string;
|
|
28566
28587
|
};
|
|
28588
|
+
afh_html: {
|
|
28589
|
+
Args: {
|
|
28590
|
+
_s: string;
|
|
28591
|
+
};
|
|
28592
|
+
Returns: string;
|
|
28593
|
+
};
|
|
28594
|
+
afh_nl_date: {
|
|
28595
|
+
Args: {
|
|
28596
|
+
_d: string;
|
|
28597
|
+
};
|
|
28598
|
+
Returns: string;
|
|
28599
|
+
};
|
|
28567
28600
|
afh_normalize_country: {
|
|
28568
28601
|
Args: {
|
|
28569
28602
|
_raw: string;
|
|
@@ -28576,6 +28609,46 @@ export type Database = {
|
|
|
28576
28609
|
};
|
|
28577
28610
|
Returns: string;
|
|
28578
28611
|
};
|
|
28612
|
+
afh_signup_config: {
|
|
28613
|
+
Args: never;
|
|
28614
|
+
Returns: Json;
|
|
28615
|
+
};
|
|
28616
|
+
afh_signup_find_group: {
|
|
28617
|
+
Args: {
|
|
28618
|
+
p_carrier_id: string;
|
|
28619
|
+
p_moment_key: string;
|
|
28620
|
+
p_pickup_date: string;
|
|
28621
|
+
p_tz: string;
|
|
28622
|
+
};
|
|
28623
|
+
Returns: Json;
|
|
28624
|
+
};
|
|
28625
|
+
afh_signup_groups: {
|
|
28626
|
+
Args: {
|
|
28627
|
+
p_from: string;
|
|
28628
|
+
p_to: string;
|
|
28629
|
+
p_tz: string;
|
|
28630
|
+
};
|
|
28631
|
+
Returns: Json;
|
|
28632
|
+
};
|
|
28633
|
+
afh_signup_mail: {
|
|
28634
|
+
Args: {
|
|
28635
|
+
g: Json;
|
|
28636
|
+
p_prev: number;
|
|
28637
|
+
p_tz: string;
|
|
28638
|
+
};
|
|
28639
|
+
Returns: Json;
|
|
28640
|
+
};
|
|
28641
|
+
afh_signup_send_group: {
|
|
28642
|
+
Args: {
|
|
28643
|
+
g: Json;
|
|
28644
|
+
p_connection: string;
|
|
28645
|
+
p_purpose: string;
|
|
28646
|
+
p_source: string;
|
|
28647
|
+
p_tz: string;
|
|
28648
|
+
p_user: string;
|
|
28649
|
+
};
|
|
28650
|
+
Returns: Json;
|
|
28651
|
+
};
|
|
28579
28652
|
afh_subtract_workdays: {
|
|
28580
28653
|
Args: {
|
|
28581
28654
|
_date: string;
|
|
@@ -28583,6 +28656,14 @@ export type Database = {
|
|
|
28583
28656
|
};
|
|
28584
28657
|
Returns: string;
|
|
28585
28658
|
};
|
|
28659
|
+
afhandeling_signup_cron_status: {
|
|
28660
|
+
Args: never;
|
|
28661
|
+
Returns: Json;
|
|
28662
|
+
};
|
|
28663
|
+
afhandeling_sync_signup_cron: {
|
|
28664
|
+
Args: never;
|
|
28665
|
+
Returns: Json;
|
|
28666
|
+
};
|
|
28586
28667
|
ai_usage_by_module: {
|
|
28587
28668
|
Args: {
|
|
28588
28669
|
_from: string;
|
|
@@ -28724,6 +28805,29 @@ export type Database = {
|
|
|
28724
28805
|
id: string;
|
|
28725
28806
|
}[];
|
|
28726
28807
|
};
|
|
28808
|
+
carrier_signup_overview: {
|
|
28809
|
+
Args: {
|
|
28810
|
+
p_from: string;
|
|
28811
|
+
p_to: string;
|
|
28812
|
+
};
|
|
28813
|
+
Returns: Json;
|
|
28814
|
+
};
|
|
28815
|
+
carrier_signup_preview: {
|
|
28816
|
+
Args: {
|
|
28817
|
+
p_carrier_id: string;
|
|
28818
|
+
p_moment_key: string;
|
|
28819
|
+
p_pickup_date: string;
|
|
28820
|
+
};
|
|
28821
|
+
Returns: Json;
|
|
28822
|
+
};
|
|
28823
|
+
carrier_signup_send_now: {
|
|
28824
|
+
Args: {
|
|
28825
|
+
p_carrier_id: string;
|
|
28826
|
+
p_moment_key: string;
|
|
28827
|
+
p_pickup_date: string;
|
|
28828
|
+
};
|
|
28829
|
+
Returns: Json;
|
|
28830
|
+
};
|
|
28727
28831
|
check_offer_product_available_stock: {
|
|
28728
28832
|
Args: {
|
|
28729
28833
|
p_campaign_product_id: string;
|