@flowselections/floriday-verkoop-module 1.0.40 → 1.0.41
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/verkoop/EngineBufferCard.d.ts.map +1 -1
- package/dist-lib/components/verkoop/EngineBufferCard.js +2 -2
- package/dist-lib/components/verkoop/InvoiceTimingSettings.d.ts +3 -0
- package/dist-lib/components/verkoop/InvoiceTimingSettings.d.ts.map +1 -0
- package/dist-lib/components/verkoop/InvoiceTimingSettings.js +54 -0
- package/dist-lib/components/verkoop/SalesOrdersTable.d.ts.map +1 -1
- package/dist-lib/components/verkoop/SalesOrdersTable.js +44 -91
- package/dist-lib/components/verkoop/VerkoopOrdersPage.d.ts.map +1 -1
- package/dist-lib/components/verkoop/VerkoopOrdersPage.js +9 -8
- package/dist-lib/index.d.ts +1 -0
- package/dist-lib/index.d.ts.map +1 -1
- package/dist-lib/index.js +3 -1
- package/dist-lib/integrations/supabase/auth-middleware.d.ts +577 -4
- package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.d.ts +577 -4
- package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.server.d.ts +577 -4
- package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.d.ts +578 -5
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.js +12 -1
- package/dist-lib/lib/cart-lock.functions.d.ts +577 -4
- package/dist-lib/lib/cart-lock.functions.d.ts.map +1 -1
- package/dist-lib/lib/cart-planning-settings.functions.d.ts +6347 -44
- package/dist-lib/lib/cart-planning-settings.functions.d.ts.map +1 -1
- package/dist-lib/lib/cart-replan.functions.d.ts +1731 -12
- package/dist-lib/lib/cart-replan.functions.d.ts.map +1 -1
- package/dist-lib/lib/create-order.functions.d.ts +8655 -60
- package/dist-lib/lib/create-order.functions.d.ts.map +1 -1
- package/dist-lib/lib/cron/index.d.ts +1 -0
- package/dist-lib/lib/cron/index.d.ts.map +1 -1
- package/dist-lib/lib/cron/index.js +1 -0
- package/dist-lib/lib/cron/invoice-due.d.ts +4 -0
- package/dist-lib/lib/cron/invoice-due.d.ts.map +1 -0
- package/dist-lib/lib/cron/invoice-due.js +21 -0
- package/dist-lib/lib/floriday-auto-fulfillment.functions.d.ts +1731 -12
- package/dist-lib/lib/floriday-auto-fulfillment.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-fulfillment-status.server.d.ts +28 -0
- package/dist-lib/lib/floriday-fulfillment-status.server.d.ts.map +1 -0
- package/dist-lib/lib/floriday-fulfillment-status.server.js +91 -0
- package/dist-lib/lib/floriday-logistic-labels.functions.d.ts +577 -4
- package/dist-lib/lib/floriday-logistic-labels.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-mirror.functions.d.ts +577 -4
- package/dist-lib/lib/floriday-mirror.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-mirror.functions.js +27 -4
- package/dist-lib/lib/floriday-public.functions.d.ts +4039 -28
- package/dist-lib/lib/floriday-public.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-writes.functions.d.ts +2885 -20
- package/dist-lib/lib/floriday-writes.functions.d.ts.map +1 -1
- package/dist-lib/lib/invoice-timing.d.ts +38 -0
- package/dist-lib/lib/invoice-timing.d.ts.map +1 -0
- package/dist-lib/lib/invoice-timing.functions.d.ts +83845 -0
- package/dist-lib/lib/invoice-timing.functions.d.ts.map +1 -0
- package/dist-lib/lib/invoice-timing.functions.js +88 -0
- package/dist-lib/lib/invoice-timing.js +139 -0
- package/dist-lib/lib/invoice-timing.server.d.ts +22 -0
- package/dist-lib/lib/invoice-timing.server.d.ts.map +1 -0
- package/dist-lib/lib/invoice-timing.server.js +154 -0
- package/dist-lib/lib/load-carriers.functions.d.ts +577 -4
- package/dist-lib/lib/load-carriers.functions.d.ts.map +1 -1
- package/dist-lib/lib/logistics-engine.functions.d.ts +2887 -22
- package/dist-lib/lib/logistics-engine.functions.d.ts.map +1 -1
- package/dist-lib/lib/logistics-stock.functions.d.ts +1154 -8
- package/dist-lib/lib/logistics-stock.functions.d.ts.map +1 -1
- package/dist-lib/lib/order-addresses.functions.d.ts +577 -4
- package/dist-lib/lib/order-addresses.functions.d.ts.map +1 -1
- package/dist-lib/lib/order-buffers.functions.d.ts +1731 -12
- package/dist-lib/lib/order-buffers.functions.d.ts.map +1 -1
- package/dist-lib/lib/order-carrier.functions.d.ts +1154 -8
- package/dist-lib/lib/order-carrier.functions.d.ts.map +1 -1
- package/dist-lib/lib/order-pickup.functions.d.ts +1154 -8
- package/dist-lib/lib/order-pickup.functions.d.ts.map +1 -1
- package/dist-lib/lib/order-shipments.functions.d.ts +2308 -16
- package/dist-lib/lib/order-shipments.functions.d.ts.map +1 -1
- package/dist-lib/lib/order-status.d.ts +33 -0
- package/dist-lib/lib/order-status.d.ts.map +1 -0
- package/dist-lib/lib/order-status.js +77 -0
- package/dist-lib/lib/salesorders-list.functions.d.ts +578 -5
- package/dist-lib/lib/salesorders-list.functions.d.ts.map +1 -1
- package/dist-lib/lib/salesorders-list.functions.js +1 -1
- package/dist-lib/lib/salesorders-list.server.d.ts +2 -1
- package/dist-lib/lib/salesorders-list.server.d.ts.map +1 -1
- package/dist-lib/lib/salesorders-list.server.js +145 -58
- package/dist-lib/lib/salesorders-mapping.d.ts +13 -0
- package/dist-lib/lib/salesorders-mapping.d.ts.map +1 -1
- package/dist-lib/lib/salesorders-mapping.js +2 -7
- package/dist-lib/lib/salesorders.functions.d.ts +2885 -20
- package/dist-lib/lib/salesorders.functions.d.ts.map +1 -1
- package/dist-lib/lib/tradeitems.functions.d.ts +577 -4
- package/dist-lib/lib/tradeitems.functions.d.ts.map +1 -1
- package/dist-lib/lib/useSalesOrdersQuery.d.ts +2 -1
- package/dist-lib/lib/useSalesOrdersQuery.d.ts.map +1 -1
- package/package.json +10 -2
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// Server functions voor het instelbare factuurmoment.
|
|
2
|
+
import { createServerFn } from "@tanstack/react-start";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { requireSupabaseAuth } from "../integrations/supabase/auth-middleware";
|
|
5
|
+
import { DEFAULT_INVOICE_TIMING, normalizeTimeOfDay, } from "./invoice-timing";
|
|
6
|
+
const ANCHORS = [
|
|
7
|
+
"order_created",
|
|
8
|
+
"confirmed",
|
|
9
|
+
"picked",
|
|
10
|
+
"shipped",
|
|
11
|
+
"expected_delivery",
|
|
12
|
+
"pickup_date",
|
|
13
|
+
"delivery_date",
|
|
14
|
+
];
|
|
15
|
+
const settingsSchema = z.object({
|
|
16
|
+
enabled: z.boolean(),
|
|
17
|
+
anchor: z.enum(ANCHORS),
|
|
18
|
+
offsetDays: z.number().int().min(-60).max(60),
|
|
19
|
+
businessDaysOnly: z.boolean(),
|
|
20
|
+
timeOfDay: z.string().min(4),
|
|
21
|
+
pickupAnchor: z.enum(ANCHORS),
|
|
22
|
+
pickupOffsetDays: z.number().int().min(-60).max(60),
|
|
23
|
+
pickupBusinessDaysOnly: z.boolean(),
|
|
24
|
+
pickupTimeOfDay: z.string().min(4),
|
|
25
|
+
});
|
|
26
|
+
export const getInvoiceTimingSettings = createServerFn({ method: "GET" })
|
|
27
|
+
.middleware([requireSupabaseAuth])
|
|
28
|
+
.handler(async ({ context }) => {
|
|
29
|
+
const { loadInvoiceTimingSettings } = await import("./invoice-timing.server");
|
|
30
|
+
return loadInvoiceTimingSettings(context.supabase);
|
|
31
|
+
});
|
|
32
|
+
export const saveInvoiceTimingSettings = createServerFn({ method: "POST" })
|
|
33
|
+
.middleware([requireSupabaseAuth])
|
|
34
|
+
.inputValidator((input) => settingsSchema.parse(input))
|
|
35
|
+
.handler(async ({ data, context }) => {
|
|
36
|
+
const supabase = context.supabase;
|
|
37
|
+
const payload = {
|
|
38
|
+
singleton: true,
|
|
39
|
+
enabled: data.enabled,
|
|
40
|
+
anchor: data.anchor,
|
|
41
|
+
offset_days: data.offsetDays,
|
|
42
|
+
business_days_only: data.businessDaysOnly,
|
|
43
|
+
time_of_day: normalizeTimeOfDay(data.timeOfDay),
|
|
44
|
+
pickup_anchor: data.pickupAnchor,
|
|
45
|
+
pickup_offset_days: data.pickupOffsetDays,
|
|
46
|
+
pickup_business_days_only: data.pickupBusinessDaysOnly,
|
|
47
|
+
pickup_time_of_day: normalizeTimeOfDay(data.pickupTimeOfDay),
|
|
48
|
+
};
|
|
49
|
+
const { error } = await supabase.from("invoice_timing_settings").upsert(payload, {
|
|
50
|
+
onConflict: "singleton",
|
|
51
|
+
});
|
|
52
|
+
if (error)
|
|
53
|
+
throw new Error(error.message);
|
|
54
|
+
// Direct doorrekenen zodat de orders het nieuwe moment tonen.
|
|
55
|
+
const { recomputeInvoiceDueDates } = await import("./invoice-timing.server");
|
|
56
|
+
const { updated } = await recomputeInvoiceDueDates(supabase, { limit: 500 });
|
|
57
|
+
return { ok: true, recomputed: updated };
|
|
58
|
+
});
|
|
59
|
+
export const recomputeInvoiceDueForOrder = createServerFn({ method: "POST" })
|
|
60
|
+
.middleware([requireSupabaseAuth])
|
|
61
|
+
.inputValidator((input) => z.object({ orderId: z.string().uuid() }).parse(input))
|
|
62
|
+
.handler(async ({ data, context }) => {
|
|
63
|
+
const { recomputeInvoiceDueDates } = await import("./invoice-timing.server");
|
|
64
|
+
await recomputeInvoiceDueDates(context.supabase, { orderIds: [data.orderId] });
|
|
65
|
+
const { data: row } = await context.supabase
|
|
66
|
+
.from("orders")
|
|
67
|
+
.select("invoice_due_at")
|
|
68
|
+
.eq("id", data.orderId)
|
|
69
|
+
.maybeSingle();
|
|
70
|
+
return { invoiceDueAt: row?.invoice_due_at ?? null };
|
|
71
|
+
});
|
|
72
|
+
export const getOrderInvoiceMoment = createServerFn({ method: "GET" })
|
|
73
|
+
.middleware([requireSupabaseAuth])
|
|
74
|
+
.inputValidator((input) => z.object({ orderId: z.string().uuid() }).parse(input))
|
|
75
|
+
.handler(async ({ data, context }) => {
|
|
76
|
+
const { loadInvoiceTimingSettings } = await import("./invoice-timing.server");
|
|
77
|
+
const settings = await loadInvoiceTimingSettings(context.supabase).catch(() => DEFAULT_INVOICE_TIMING);
|
|
78
|
+
const { data: row } = await context.supabase
|
|
79
|
+
.from("orders")
|
|
80
|
+
.select("invoice_due_at, status")
|
|
81
|
+
.eq("id", data.orderId)
|
|
82
|
+
.maybeSingle();
|
|
83
|
+
return {
|
|
84
|
+
invoiceDueAt: row?.invoice_due_at ?? null,
|
|
85
|
+
status: row?.status ?? null,
|
|
86
|
+
settings,
|
|
87
|
+
};
|
|
88
|
+
});
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
// Pure rekenlogica voor het instelbare factuurmoment.
|
|
2
|
+
// Formule: factuurmoment = ijkpunt + X dagen (op tijdstip T).
|
|
3
|
+
// Zelfafhalers kunnen een eigen ijkpunt/offset hebben.
|
|
4
|
+
export const INVOICE_ANCHORS = [
|
|
5
|
+
{ id: "order_created", label: "Order binnengekomen", hint: "Moment waarop de order is aangemaakt" },
|
|
6
|
+
{ id: "confirmed", label: "Order bevestigd", hint: "Zodra de order is bevestigd/ingepland" },
|
|
7
|
+
{ id: "picked", label: "Order gepickt", hint: "Zodra de order volledig is gepickt" },
|
|
8
|
+
{ id: "shipped", label: "Order verzonden", hint: "Meegegeven aan de vervoerder" },
|
|
9
|
+
{ id: "expected_delivery", label: "Verwachte levering", hint: "Berekend op basis van de leverdatum van de order" },
|
|
10
|
+
{ id: "pickup_date", label: "Afhaaldatum", hint: "Datum waarop de klant de order zelf ophaalt" },
|
|
11
|
+
{ id: "delivery_date", label: "Leverdatum op de order", hint: "De leverdatum zoals op de order staat" },
|
|
12
|
+
];
|
|
13
|
+
export const DEFAULT_INVOICE_TIMING = {
|
|
14
|
+
enabled: true,
|
|
15
|
+
anchor: "expected_delivery",
|
|
16
|
+
offsetDays: 0,
|
|
17
|
+
businessDaysOnly: false,
|
|
18
|
+
timeOfDay: "18:00",
|
|
19
|
+
pickupAnchor: "pickup_date",
|
|
20
|
+
pickupOffsetDays: 0,
|
|
21
|
+
pickupBusinessDaysOnly: false,
|
|
22
|
+
pickupTimeOfDay: "18:00",
|
|
23
|
+
};
|
|
24
|
+
export function normalizeTimeOfDay(value) {
|
|
25
|
+
const s = String(value ?? "").trim();
|
|
26
|
+
const m = /^(\d{1,2}):(\d{2})/.exec(s);
|
|
27
|
+
if (!m)
|
|
28
|
+
return "18:00";
|
|
29
|
+
const h = Math.min(23, Math.max(0, Number(m[1])));
|
|
30
|
+
const mi = Math.min(59, Math.max(0, Number(m[2])));
|
|
31
|
+
return `${String(h).padStart(2, "0")}:${String(mi).padStart(2, "0")}`;
|
|
32
|
+
}
|
|
33
|
+
function toDateOnly(value) {
|
|
34
|
+
if (!value)
|
|
35
|
+
return null;
|
|
36
|
+
const iso = String(value).slice(0, 10);
|
|
37
|
+
const [y, m, d] = iso.split("-").map(Number);
|
|
38
|
+
if (!y || !m || !d)
|
|
39
|
+
return null;
|
|
40
|
+
const dt = new Date(Date.UTC(y, m - 1, d));
|
|
41
|
+
return Number.isNaN(dt.getTime()) ? null : dt;
|
|
42
|
+
}
|
|
43
|
+
function toTimestamp(value) {
|
|
44
|
+
if (!value)
|
|
45
|
+
return null;
|
|
46
|
+
const dt = new Date(String(value));
|
|
47
|
+
return Number.isNaN(dt.getTime()) ? null : dt;
|
|
48
|
+
}
|
|
49
|
+
function addDays(date, days, businessOnly) {
|
|
50
|
+
const out = new Date(date.getTime());
|
|
51
|
+
if (!businessOnly) {
|
|
52
|
+
out.setUTCDate(out.getUTCDate() + days);
|
|
53
|
+
return out;
|
|
54
|
+
}
|
|
55
|
+
const step = days >= 0 ? 1 : -1;
|
|
56
|
+
let left = Math.abs(days);
|
|
57
|
+
while (left > 0) {
|
|
58
|
+
out.setUTCDate(out.getUTCDate() + step);
|
|
59
|
+
const wd = out.getUTCDay();
|
|
60
|
+
if (wd !== 0 && wd !== 6)
|
|
61
|
+
left -= 1;
|
|
62
|
+
}
|
|
63
|
+
// Landt het resultaat in het weekend, schuif naar de eerstvolgende werkdag.
|
|
64
|
+
while (out.getUTCDay() === 0 || out.getUTCDay() === 6) {
|
|
65
|
+
out.setUTCDate(out.getUTCDate() + (step >= 0 ? 1 : -1));
|
|
66
|
+
}
|
|
67
|
+
return out;
|
|
68
|
+
}
|
|
69
|
+
function applyTime(date, timeOfDay) {
|
|
70
|
+
const [h, m] = normalizeTimeOfDay(timeOfDay).split(":").map(Number);
|
|
71
|
+
const out = new Date(date.getTime());
|
|
72
|
+
out.setUTCHours(h ?? 18, m ?? 0, 0, 0);
|
|
73
|
+
return out;
|
|
74
|
+
}
|
|
75
|
+
function anchorDate(anchor, input) {
|
|
76
|
+
switch (anchor) {
|
|
77
|
+
case "order_created": {
|
|
78
|
+
const d = toTimestamp(input.createdAt);
|
|
79
|
+
return d ? { date: d, dateOnly: false } : null;
|
|
80
|
+
}
|
|
81
|
+
case "confirmed": {
|
|
82
|
+
const d = toTimestamp(input.confirmedAt);
|
|
83
|
+
return d ? { date: d, dateOnly: false } : null;
|
|
84
|
+
}
|
|
85
|
+
case "picked": {
|
|
86
|
+
const d = toTimestamp(input.pickedAt);
|
|
87
|
+
return d ? { date: d, dateOnly: false } : null;
|
|
88
|
+
}
|
|
89
|
+
case "shipped": {
|
|
90
|
+
const d = toTimestamp(input.shippedAt);
|
|
91
|
+
return d ? { date: d, dateOnly: false } : null;
|
|
92
|
+
}
|
|
93
|
+
case "expected_delivery": {
|
|
94
|
+
const d = toDateOnly(input.expectedDeliveryDate ?? input.deliveryDate);
|
|
95
|
+
return d ? { date: d, dateOnly: true } : null;
|
|
96
|
+
}
|
|
97
|
+
case "pickup_date": {
|
|
98
|
+
const d = toDateOnly(input.pickupDate ?? input.deliveryDate);
|
|
99
|
+
return d ? { date: d, dateOnly: true } : null;
|
|
100
|
+
}
|
|
101
|
+
case "delivery_date": {
|
|
102
|
+
const d = toDateOnly(input.deliveryDate);
|
|
103
|
+
return d ? { date: d, dateOnly: true } : null;
|
|
104
|
+
}
|
|
105
|
+
default:
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Berekent het moment waarop de order gefactureerd mag worden.
|
|
111
|
+
* Geeft `null` wanneer het ijkpunt (nog) onbekend is.
|
|
112
|
+
*/
|
|
113
|
+
export function computeInvoiceDueAt(input, settings) {
|
|
114
|
+
if (!settings.enabled)
|
|
115
|
+
return null;
|
|
116
|
+
const usePickup = !!input.selfPickup;
|
|
117
|
+
const anchor = usePickup ? settings.pickupAnchor : settings.anchor;
|
|
118
|
+
const offset = usePickup ? settings.pickupOffsetDays : settings.offsetDays;
|
|
119
|
+
const businessOnly = usePickup ? settings.pickupBusinessDaysOnly : settings.businessDaysOnly;
|
|
120
|
+
const timeOfDay = usePickup ? settings.pickupTimeOfDay : settings.timeOfDay;
|
|
121
|
+
const base = anchorDate(anchor, input);
|
|
122
|
+
if (!base)
|
|
123
|
+
return null;
|
|
124
|
+
const shifted = addDays(base.date, Math.trunc(offset || 0), businessOnly);
|
|
125
|
+
// Bij datum-ijkpunten bepaalt het ingestelde tijdstip het uur.
|
|
126
|
+
const final = base.dateOnly || offset !== 0 ? applyTime(shifted, timeOfDay) : shifted;
|
|
127
|
+
return final.toISOString();
|
|
128
|
+
}
|
|
129
|
+
export function describeInvoiceTiming(settings, forPickup = false) {
|
|
130
|
+
const anchor = forPickup ? settings.pickupAnchor : settings.anchor;
|
|
131
|
+
const offset = forPickup ? settings.pickupOffsetDays : settings.offsetDays;
|
|
132
|
+
const businessOnly = forPickup ? settings.pickupBusinessDaysOnly : settings.businessDaysOnly;
|
|
133
|
+
const time = forPickup ? settings.pickupTimeOfDay : settings.timeOfDay;
|
|
134
|
+
const label = INVOICE_ANCHORS.find((a) => a.id === anchor)?.label ?? anchor;
|
|
135
|
+
const days = offset === 0
|
|
136
|
+
? "zelfde dag"
|
|
137
|
+
: `${offset > 0 ? "+" : "−"}${Math.abs(offset)} ${businessOnly ? "werkdag(en)" : "dag(en)"}`;
|
|
138
|
+
return `${label} ${offset === 0 ? "" : days} om ${normalizeTimeOfDay(time)}`.replace(" ", " ");
|
|
139
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type InvoiceTimingSettings } from "./invoice-timing";
|
|
2
|
+
/** Status die de facturatiemodule oppakt zodra het factuurmoment is bereikt. */
|
|
3
|
+
export declare const INVOICE_READY_STATUS = "Gereed voor facturatie";
|
|
4
|
+
export declare function loadInvoiceTimingSettings(supabase: any): Promise<InvoiceTimingSettings>;
|
|
5
|
+
/** Herberekent `invoice_due_at` voor de meegegeven orders (of alle open orders). */
|
|
6
|
+
export declare function recomputeInvoiceDueDates(supabase: any, options?: {
|
|
7
|
+
orderIds?: string[];
|
|
8
|
+
limit?: number;
|
|
9
|
+
}): Promise<{
|
|
10
|
+
updated: number;
|
|
11
|
+
}>;
|
|
12
|
+
/** Zet orders waarvan het factuurmoment is bereikt op de facturatiestatus. */
|
|
13
|
+
export declare function releaseDueInvoices(supabase: any, limit?: number): Promise<{
|
|
14
|
+
released: number;
|
|
15
|
+
orderIds: string[];
|
|
16
|
+
}>;
|
|
17
|
+
/** Volledige cyclus: eerst herberekenen, daarna vrijgeven. */
|
|
18
|
+
export declare function runInvoiceTimingCycle(): Promise<{
|
|
19
|
+
recomputed: number;
|
|
20
|
+
released: number;
|
|
21
|
+
}>;
|
|
22
|
+
//# sourceMappingURL=invoice-timing.server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"invoice-timing.server.d.ts","sourceRoot":"","sources":["../../src/lib/invoice-timing.server.ts"],"names":[],"mappings":"AACA,OAAO,EAML,KAAK,qBAAqB,EAC3B,MAAM,kBAAkB,CAAC;AAE1B,gFAAgF;AAChF,eAAO,MAAM,oBAAoB,2BAA2B,CAAC;AAE7D,wBAAsB,yBAAyB,CAAC,QAAQ,EAAE,GAAG,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAkB7F;AAyED,oFAAoF;AACpF,wBAAsB,wBAAwB,CAC5C,QAAQ,EAAE,GAAG,EACb,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GACpD,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CA4B9B;AAED,8EAA8E;AAC9E,wBAAsB,kBAAkB,CACtC,QAAQ,EAAE,GAAG,EACb,KAAK,SAAM,GACV,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CA4BnD;AAED,8DAA8D;AAC9D,wBAAsB,qBAAqB,IAAI,OAAO,CAAC;IACrD,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC,CAKD"}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
// Server-side toepassing van het instelbare factuurmoment.
|
|
2
|
+
import { DEFAULT_INVOICE_TIMING, computeInvoiceDueAt, normalizeTimeOfDay, } from "./invoice-timing";
|
|
3
|
+
/** Status die de facturatiemodule oppakt zodra het factuurmoment is bereikt. */
|
|
4
|
+
export const INVOICE_READY_STATUS = "Gereed voor facturatie";
|
|
5
|
+
export async function loadInvoiceTimingSettings(supabase) {
|
|
6
|
+
const { data } = await supabase
|
|
7
|
+
.from("invoice_timing_settings")
|
|
8
|
+
.select("*")
|
|
9
|
+
.limit(1)
|
|
10
|
+
.maybeSingle();
|
|
11
|
+
if (!data)
|
|
12
|
+
return { ...DEFAULT_INVOICE_TIMING };
|
|
13
|
+
return {
|
|
14
|
+
enabled: !!data.enabled,
|
|
15
|
+
anchor: (data.anchor ?? "expected_delivery"),
|
|
16
|
+
offsetDays: Number(data.offset_days ?? 0),
|
|
17
|
+
businessDaysOnly: !!data.business_days_only,
|
|
18
|
+
timeOfDay: normalizeTimeOfDay(data.time_of_day),
|
|
19
|
+
pickupAnchor: (data.pickup_anchor ?? "pickup_date"),
|
|
20
|
+
pickupOffsetDays: Number(data.pickup_offset_days ?? 0),
|
|
21
|
+
pickupBusinessDaysOnly: !!data.pickup_business_days_only,
|
|
22
|
+
pickupTimeOfDay: normalizeTimeOfDay(data.pickup_time_of_day),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
function isSelfPickup(customFields) {
|
|
26
|
+
const cf = customFields ?? {};
|
|
27
|
+
if (cf.self_pickup === true || cf.afhalen === true)
|
|
28
|
+
return true;
|
|
29
|
+
const label = String(cf.carrier_name ?? cf.vervoerder ?? "").toLowerCase();
|
|
30
|
+
return /afhaal|zelf ophalen|eigen vervoer/.test(label);
|
|
31
|
+
}
|
|
32
|
+
function pickupDateFromCustomFields(cf) {
|
|
33
|
+
const raw = cf?.pickup_date ?? cf?.afhaaldatum ?? null;
|
|
34
|
+
if (raw)
|
|
35
|
+
return String(raw).slice(0, 10);
|
|
36
|
+
const moment = cf?.pickup_moment ? String(cf.pickup_moment) : null;
|
|
37
|
+
if (moment) {
|
|
38
|
+
const first = moment.split("|")[0];
|
|
39
|
+
if (first && /^\d{4}-\d{2}-\d{2}$/.test(first))
|
|
40
|
+
return first;
|
|
41
|
+
}
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
async function buildAnchorInputs(supabase, rows) {
|
|
45
|
+
const ids = rows.map((r) => String(r.id));
|
|
46
|
+
const shipped = new Map();
|
|
47
|
+
if (ids.length) {
|
|
48
|
+
try {
|
|
49
|
+
const { data } = await supabase
|
|
50
|
+
.from("order_shipments")
|
|
51
|
+
.select("order_id, status, shipped_at, updated_at")
|
|
52
|
+
.in("order_id", ids.slice(0, 1000));
|
|
53
|
+
for (const s of (data ?? [])) {
|
|
54
|
+
const key = String(s.order_id);
|
|
55
|
+
const prev = shipped.get(key) ?? { shippedAt: null, pickedAt: null };
|
|
56
|
+
const st = String(s.status ?? "").toLowerCase();
|
|
57
|
+
if (s.shipped_at)
|
|
58
|
+
prev.shippedAt = prev.shippedAt ?? String(s.shipped_at);
|
|
59
|
+
if (["gepickt", "picked", "gecontroleerd"].includes(st)) {
|
|
60
|
+
prev.pickedAt = prev.pickedAt ?? String(s.updated_at ?? s.shipped_at ?? "");
|
|
61
|
+
}
|
|
62
|
+
shipped.set(key, prev);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
/* verzendmomenten zijn optioneel */
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const out = new Map();
|
|
70
|
+
for (const r of rows) {
|
|
71
|
+
const cf = r.custom_fields ?? {};
|
|
72
|
+
const ship = shipped.get(String(r.id));
|
|
73
|
+
out.set(String(r.id), {
|
|
74
|
+
createdAt: r.created_at,
|
|
75
|
+
confirmedAt: cf.confirmed_at ? String(cf.confirmed_at) : r.created_at,
|
|
76
|
+
pickedAt: ship?.pickedAt || null,
|
|
77
|
+
shippedAt: ship?.shippedAt || null,
|
|
78
|
+
expectedDeliveryDate: r.delivery_date,
|
|
79
|
+
deliveryDate: r.delivery_date,
|
|
80
|
+
pickupDate: pickupDateFromCustomFields(cf),
|
|
81
|
+
selfPickup: isSelfPickup(cf),
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
return out;
|
|
85
|
+
}
|
|
86
|
+
/** Herberekent `invoice_due_at` voor de meegegeven orders (of alle open orders). */
|
|
87
|
+
export async function recomputeInvoiceDueDates(supabase, options = {}) {
|
|
88
|
+
const settings = await loadInvoiceTimingSettings(supabase);
|
|
89
|
+
let query = supabase
|
|
90
|
+
.from("orders")
|
|
91
|
+
.select("id, created_at, delivery_date, status, custom_fields")
|
|
92
|
+
.is("deleted_at", null)
|
|
93
|
+
.limit(options.limit ?? 500);
|
|
94
|
+
if (options.orderIds?.length)
|
|
95
|
+
query = query.in("id", options.orderIds.slice(0, 500));
|
|
96
|
+
else
|
|
97
|
+
query = query.order("created_at", { ascending: false });
|
|
98
|
+
const { data, error } = await query;
|
|
99
|
+
if (error)
|
|
100
|
+
throw new Error(error.message);
|
|
101
|
+
const rows = (data ?? []);
|
|
102
|
+
if (rows.length === 0)
|
|
103
|
+
return { updated: 0 };
|
|
104
|
+
const inputs = await buildAnchorInputs(supabase, rows);
|
|
105
|
+
let updated = 0;
|
|
106
|
+
for (const row of rows) {
|
|
107
|
+
const input = inputs.get(String(row.id));
|
|
108
|
+
if (!input)
|
|
109
|
+
continue;
|
|
110
|
+
const due = computeInvoiceDueAt(input, settings);
|
|
111
|
+
const { error: upErr } = await supabase
|
|
112
|
+
.from("orders")
|
|
113
|
+
.update({ invoice_due_at: due })
|
|
114
|
+
.eq("id", row.id);
|
|
115
|
+
if (!upErr)
|
|
116
|
+
updated += 1;
|
|
117
|
+
}
|
|
118
|
+
return { updated };
|
|
119
|
+
}
|
|
120
|
+
/** Zet orders waarvan het factuurmoment is bereikt op de facturatiestatus. */
|
|
121
|
+
export async function releaseDueInvoices(supabase, limit = 200) {
|
|
122
|
+
const settings = await loadInvoiceTimingSettings(supabase);
|
|
123
|
+
if (!settings.enabled)
|
|
124
|
+
return { released: 0, orderIds: [] };
|
|
125
|
+
const { data, error } = await supabase
|
|
126
|
+
.from("orders")
|
|
127
|
+
.select("id, status, order_source, floriday_sales_order_id")
|
|
128
|
+
.is("deleted_at", null)
|
|
129
|
+
.not("invoice_due_at", "is", null)
|
|
130
|
+
.lte("invoice_due_at", new Date().toISOString())
|
|
131
|
+
.neq("status", INVOICE_READY_STATUS)
|
|
132
|
+
.limit(limit);
|
|
133
|
+
if (error)
|
|
134
|
+
throw new Error(error.message);
|
|
135
|
+
const rows = (data ?? []).filter((r) => !r.floriday_sales_order_id &&
|
|
136
|
+
String(r.order_source ?? "").toLowerCase() !== "floriday");
|
|
137
|
+
const orderIds = [];
|
|
138
|
+
for (const r of rows) {
|
|
139
|
+
const { error: upErr } = await supabase
|
|
140
|
+
.from("orders")
|
|
141
|
+
.update({ status: INVOICE_READY_STATUS })
|
|
142
|
+
.eq("id", r.id);
|
|
143
|
+
if (!upErr)
|
|
144
|
+
orderIds.push(String(r.id));
|
|
145
|
+
}
|
|
146
|
+
return { released: orderIds.length, orderIds };
|
|
147
|
+
}
|
|
148
|
+
/** Volledige cyclus: eerst herberekenen, daarna vrijgeven. */
|
|
149
|
+
export async function runInvoiceTimingCycle() {
|
|
150
|
+
const { supabaseAdmin } = await import("../integrations/supabase/client.server");
|
|
151
|
+
const recomputed = (await recomputeInvoiceDueDates(supabaseAdmin, { limit: 500 })).updated;
|
|
152
|
+
const released = (await releaseDueInvoices(supabaseAdmin, 200)).released;
|
|
153
|
+
return { recomputed, released };
|
|
154
|
+
}
|