@flowselections/floriday-afhandeling-module 1.0.17 → 1.0.19
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/integrations/supabase/client.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.js +26 -7
- package/dist-lib/lib/authGateway.server.d.ts.map +1 -1
- package/dist-lib/lib/authGateway.server.js +11 -12
- package/dist-lib/lib/centralSecrets.server.d.ts +13 -0
- package/dist-lib/lib/centralSecrets.server.d.ts.map +1 -0
- package/dist-lib/lib/centralSecrets.server.js +83 -0
- package/dist-lib/lib/deliveryNotePrint.d.ts.map +1 -1
- package/dist-lib/lib/deliveryNotePrint.js +3 -3
- package/dist-lib/lib/floridayAutoProcess.d.ts.map +1 -1
- package/dist-lib/lib/floridayAutoProcess.js +3 -3
- package/dist-lib/lib/gatewayBase.d.ts +8 -0
- package/dist-lib/lib/gatewayBase.d.ts.map +1 -0
- package/dist-lib/lib/gatewayBase.js +26 -0
- package/dist-lib/lib/gatewayBase.server.d.ts +2 -0
- package/dist-lib/lib/gatewayBase.server.d.ts.map +1 -0
- package/dist-lib/lib/gatewayBase.server.js +18 -0
- package/dist-lib/lib/pickstationOrders.server.d.ts +4 -3
- package/dist-lib/lib/pickstationOrders.server.d.ts.map +1 -1
- package/dist-lib/lib/pickstationOrders.server.js +6 -6
- package/dist-lib/lib/serverRoutes/dispatchCarrierSignups.handler.d.ts +3 -0
- package/dist-lib/lib/serverRoutes/dispatchCarrierSignups.handler.d.ts.map +1 -0
- package/dist-lib/lib/serverRoutes/dispatchCarrierSignups.handler.js +321 -0
- package/dist-lib/lib/serverRoutes/index.d.ts +6 -0
- package/dist-lib/lib/serverRoutes/index.d.ts.map +1 -0
- package/dist-lib/lib/serverRoutes/index.js +8 -0
- package/dist-lib/lib/serverRoutes/logisticsTick.handler.d.ts +3 -0
- package/dist-lib/lib/serverRoutes/logisticsTick.handler.d.ts.map +1 -0
- package/dist-lib/lib/serverRoutes/logisticsTick.handler.js +37 -0
- package/dist-lib/lib/serverRoutes/pickstationOrders.handler.d.ts +3 -0
- package/dist-lib/lib/serverRoutes/pickstationOrders.handler.d.ts.map +1 -0
- package/dist-lib/lib/serverRoutes/pickstationOrders.handler.js +58 -0
- package/dist-lib/lib/serverRoutes/pickstationPrint.handler.d.ts +3 -0
- package/dist-lib/lib/serverRoutes/pickstationPrint.handler.d.ts.map +1 -0
- package/dist-lib/lib/serverRoutes/pickstationPrint.handler.js +116 -0
- package/dist-lib/lib/serverRoutes/pickstationStatus.handler.d.ts +3 -0
- package/dist-lib/lib/serverRoutes/pickstationStatus.handler.d.ts.map +1 -0
- package/dist-lib/lib/serverRoutes/pickstationStatus.handler.js +28 -0
- package/package.json +154 -128
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Server-only resolver voor de gateway-base-URL.
|
|
2
|
+
// Volgorde: PUBLIC_BASE_URL (shell levert dit) → central /api/shell-host.
|
|
3
|
+
// Nooit stille fallback: throw als niets werkt.
|
|
4
|
+
import { getShellHost } from "./centralSecrets.server";
|
|
5
|
+
function strip(url) {
|
|
6
|
+
const trimmed = url.trim().replace(/\/$/, "");
|
|
7
|
+
if (!/^https?:\/\//i.test(trimmed)) {
|
|
8
|
+
throw new Error(`[gatewayBase.server] ongeldige URL zonder scheme: ${trimmed}`);
|
|
9
|
+
}
|
|
10
|
+
return trimmed;
|
|
11
|
+
}
|
|
12
|
+
export async function resolveGatewayBaseServer() {
|
|
13
|
+
const envBase = process.env.PUBLIC_BASE_URL?.trim();
|
|
14
|
+
if (envBase)
|
|
15
|
+
return strip(envBase);
|
|
16
|
+
const host = await getShellHost();
|
|
17
|
+
return strip(host);
|
|
18
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { createClient } from "@supabase/supabase-js";
|
|
1
2
|
import type { PickstationOrder } from "./pickstationTypes";
|
|
2
3
|
export type { PickstationLine, PickstationOrder } from "./pickstationTypes";
|
|
3
|
-
export declare function loadTodaysPickstationOrders(): Promise<PickstationOrder[]>;
|
|
4
|
+
export declare function loadTodaysPickstationOrders(client?: ReturnType<typeof createClient>): Promise<PickstationOrder[]>;
|
|
4
5
|
export declare function fetchFloridayAuctionLetterPdf(_orderId: string): Promise<{
|
|
5
6
|
base64: string;
|
|
6
7
|
mime: string;
|
|
@@ -10,6 +11,6 @@ export declare function fetchFloridayDeliveryNotePdf(salesOrderId: string): Prom
|
|
|
10
11
|
base64: string;
|
|
11
12
|
mime: string;
|
|
12
13
|
} | null>;
|
|
13
|
-
export declare function setCartStatus(orderSource: "floriday" | "manual", orderId: string, cartIndex: number, status: "pending" | "checked" | "ready" | "shipped" | "delivered"): Promise<void>;
|
|
14
|
-
export declare function logPrint(orderId: string, orderNumber: string | null, customerName: string | null, documents: string[], ipAddress: string | null): Promise<void>;
|
|
14
|
+
export declare function setCartStatus(orderSource: "floriday" | "manual", orderId: string, cartIndex: number, status: "pending" | "checked" | "ready" | "shipped" | "delivered", client?: ReturnType<typeof createClient>): Promise<void>;
|
|
15
|
+
export declare function logPrint(orderId: string, orderNumber: string | null, customerName: string | null, documents: string[], ipAddress: string | null, client?: ReturnType<typeof createClient>): Promise<void>;
|
|
15
16
|
//# sourceMappingURL=pickstationOrders.server.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pickstationOrders.server.d.ts","sourceRoot":"","sources":["../../src/lib/pickstationOrders.server.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"pickstationOrders.server.d.ts","sourceRoot":"","sources":["../../src/lib/pickstationOrders.server.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAgBrD,OAAO,KAAK,EAAmB,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAC5E,YAAY,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AA8P5E,wBAAsB,2BAA2B,CAC/C,MAAM,CAAC,EAAE,UAAU,CAAC,OAAO,YAAY,CAAC,GACvC,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAyW7B;AAED,wBAAsB,6BAA6B,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;IAC7E,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACd,GAAG,IAAI,CAAC,CAER;AAED,kFAAkF;AAClF,wBAAsB,4BAA4B,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC;IAChF,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACd,GAAG,IAAI,CAAC,CAGR;AAED,wBAAsB,aAAa,CACjC,WAAW,EAAE,UAAU,GAAG,QAAQ,EAClC,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,SAAS,GAAG,WAAW,EACjE,MAAM,CAAC,EAAE,UAAU,CAAC,OAAO,YAAY,CAAC,GACvC,OAAO,CAAC,IAAI,CAAC,CAoBf;AAED,wBAAsB,QAAQ,CAC5B,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,MAAM,GAAG,IAAI,EAC1B,YAAY,EAAE,MAAM,GAAG,IAAI,EAC3B,SAAS,EAAE,MAAM,EAAE,EACnB,SAAS,EAAE,MAAM,GAAG,IAAI,EACxB,MAAM,CAAC,EAAE,UAAU,CAAC,OAAO,YAAY,CAAC,GACvC,OAAO,CAAC,IAAI,CAAC,CA4Bf"}
|
|
@@ -155,9 +155,9 @@ function buildFloridayCarts(raw) {
|
|
|
155
155
|
: [];
|
|
156
156
|
return [{ lines, cratesOnCart: null }];
|
|
157
157
|
}
|
|
158
|
-
export async function loadTodaysPickstationOrders() {
|
|
158
|
+
export async function loadTodaysPickstationOrders(client) {
|
|
159
159
|
const startedAt = Date.now();
|
|
160
|
-
const supabase = getPublicClient();
|
|
160
|
+
const supabase = client ?? getPublicClient();
|
|
161
161
|
debug("load orders started", {
|
|
162
162
|
query: "rpc pickstation_get_planning_context + pickstation_get_orders_raw",
|
|
163
163
|
});
|
|
@@ -475,8 +475,8 @@ export async function fetchFloridayDeliveryNotePdf(salesOrderId) {
|
|
|
475
475
|
const { fetchDeliveryNotePdf } = await import("./authGateway.server");
|
|
476
476
|
return fetchDeliveryNotePdf(salesOrderId);
|
|
477
477
|
}
|
|
478
|
-
export async function setCartStatus(orderSource, orderId, cartIndex, status) {
|
|
479
|
-
const supabase = getPublicClient();
|
|
478
|
+
export async function setCartStatus(orderSource, orderId, cartIndex, status, client) {
|
|
479
|
+
const supabase = client ?? getPublicClient();
|
|
480
480
|
const { error } = await supabase.rpc("pickstation_set_cart_status", {
|
|
481
481
|
p_order_source: orderSource,
|
|
482
482
|
p_order_id: orderId,
|
|
@@ -495,8 +495,8 @@ export async function setCartStatus(orderSource, orderId, cartIndex, status) {
|
|
|
495
495
|
}
|
|
496
496
|
debug("set cart status ok", { orderSource, orderId, cartIndex, status });
|
|
497
497
|
}
|
|
498
|
-
export async function logPrint(orderId, orderNumber, customerName, documents, ipAddress) {
|
|
499
|
-
const supabase = getPublicClient();
|
|
498
|
+
export async function logPrint(orderId, orderNumber, customerName, documents, ipAddress, client) {
|
|
499
|
+
const supabase = client ?? getPublicClient();
|
|
500
500
|
debug("log print started", {
|
|
501
501
|
query: "rpc pickstation_log_print",
|
|
502
502
|
orderId,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dispatchCarrierSignups.handler.d.ts","sourceRoot":"","sources":["../../../src/lib/serverRoutes/dispatchCarrierSignups.handler.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AA+G5D,wBAAsB,4BAA4B,CAChD,QAAQ,EAAE,OAAO,EACjB,QAAQ,CAAC,EAAE,cAAc,GACxB,OAAO,CAAC,QAAQ,CAAC,CA0UnB"}
|
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
import { DEFAULT_PICKUP_MOMENT, getSignupDeadline, selectPickupMoment, subtractWorkdays, } from "../planning";
|
|
2
|
+
import { buildCarrierSignupMail } from "../carrierSignupMail.server";
|
|
3
|
+
import { pickCarrierForAddress } from "../carrierMatch.server";
|
|
4
|
+
function normalizeMoments(raw) {
|
|
5
|
+
if (!Array.isArray(raw) || raw.length === 0)
|
|
6
|
+
return [DEFAULT_PICKUP_MOMENT];
|
|
7
|
+
return raw.map((m) => {
|
|
8
|
+
const r = (m ?? {});
|
|
9
|
+
return {
|
|
10
|
+
signup_days: Number(r.signup_days ?? DEFAULT_PICKUP_MOMENT.signup_days),
|
|
11
|
+
signup_time: String(r.signup_time ?? DEFAULT_PICKUP_MOMENT.signup_time),
|
|
12
|
+
pickup_days: Number(r.pickup_days ?? DEFAULT_PICKUP_MOMENT.pickup_days),
|
|
13
|
+
pickup_time: String(r.pickup_time ?? DEFAULT_PICKUP_MOMENT.pickup_time),
|
|
14
|
+
delivery_days: Number(r.delivery_days ?? DEFAULT_PICKUP_MOMENT.delivery_days),
|
|
15
|
+
delivery_time: String(r.delivery_time ?? DEFAULT_PICKUP_MOMENT.delivery_time),
|
|
16
|
+
};
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
function momentKey(m) {
|
|
20
|
+
return `${m.pickup_days}@${m.pickup_time}|${m.signup_days}@${m.signup_time}`;
|
|
21
|
+
}
|
|
22
|
+
function ymd(d) {
|
|
23
|
+
const y = d.getFullYear();
|
|
24
|
+
const mo = String(d.getMonth() + 1).padStart(2, "0");
|
|
25
|
+
const da = String(d.getDate()).padStart(2, "0");
|
|
26
|
+
return `${y}-${mo}-${da}`;
|
|
27
|
+
}
|
|
28
|
+
function cartsFor(items) {
|
|
29
|
+
let carts = 0;
|
|
30
|
+
let plants = 0;
|
|
31
|
+
for (const it of items ?? []) {
|
|
32
|
+
const qty = Number(it.quantity ?? 0);
|
|
33
|
+
if (!Number.isFinite(qty) || qty <= 0)
|
|
34
|
+
continue;
|
|
35
|
+
plants += qty;
|
|
36
|
+
const p = Array.isArray(it.products) ? it.products[0] : it.products;
|
|
37
|
+
const perCart = p && p.plants_per_crate && p.crates_per_layer && p.layers_per_cart
|
|
38
|
+
? Number(p.plants_per_crate) * Number(p.crates_per_layer) * Number(p.layers_per_cart)
|
|
39
|
+
: 0;
|
|
40
|
+
if (perCart > 0)
|
|
41
|
+
carts += Math.ceil(qty / perCart);
|
|
42
|
+
}
|
|
43
|
+
return { carts, plants };
|
|
44
|
+
}
|
|
45
|
+
export async function handleDispatchCarrierSignups(_request, injected) {
|
|
46
|
+
let admin;
|
|
47
|
+
if (injected) {
|
|
48
|
+
admin = injected;
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
const { supabaseAdmin } = await import("../../integrations/supabase/client.server");
|
|
52
|
+
admin = supabaseAdmin;
|
|
53
|
+
}
|
|
54
|
+
const startedAt = new Date();
|
|
55
|
+
const logRun = async (payload) => {
|
|
56
|
+
try {
|
|
57
|
+
await admin.from("carrier_signup_dispatch_runs").insert({
|
|
58
|
+
started_at: startedAt.toISOString(),
|
|
59
|
+
finished_at: new Date().toISOString(),
|
|
60
|
+
ok: payload.ok,
|
|
61
|
+
sent: payload.sent ?? 0,
|
|
62
|
+
groups: payload.groups ?? 0,
|
|
63
|
+
skipped: payload.skipped ?? [],
|
|
64
|
+
unassigned: payload.unassigned ?? [],
|
|
65
|
+
reason: payload.reason ?? null,
|
|
66
|
+
error: payload.error ?? null,
|
|
67
|
+
detail: payload.detail ?? null,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
catch (e) {
|
|
71
|
+
console.error("[dispatch-carrier-signups] run-log insert failed", e);
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
try {
|
|
75
|
+
const { data: settingsRows, error: settingsErr } = await admin
|
|
76
|
+
.from("afhandeling_settings")
|
|
77
|
+
.select("signup_mail_enabled, signup_mail_lead_minutes, signup_mail_purpose")
|
|
78
|
+
.eq("signup_mail_enabled", true)
|
|
79
|
+
.limit(1);
|
|
80
|
+
if (settingsErr) {
|
|
81
|
+
await logRun({ ok: false, reason: "settings_error", error: settingsErr.message });
|
|
82
|
+
return Response.json({ ok: false, error: settingsErr.message }, { status: 500 });
|
|
83
|
+
}
|
|
84
|
+
const settings = settingsRows?.[0] ?? null;
|
|
85
|
+
if (!settings) {
|
|
86
|
+
await logRun({ ok: true, reason: "mail_disabled" });
|
|
87
|
+
return Response.json({ ok: true, skipped: "disabled", sent: 0 });
|
|
88
|
+
}
|
|
89
|
+
const leadMin = settings.signup_mail_lead_minutes ?? 60;
|
|
90
|
+
const purpose = settings.signup_mail_purpose ?? "afhandeling_carrier_signup";
|
|
91
|
+
const { data: purposeConnections, error: purposeErr } = await admin.rpc("email_auth_for_purpose", {
|
|
92
|
+
_direction: "send",
|
|
93
|
+
_purpose: purpose,
|
|
94
|
+
});
|
|
95
|
+
if (purposeErr)
|
|
96
|
+
throw purposeErr;
|
|
97
|
+
const connectionId = (purposeConnections ?? []).find((c) => c?.needs_reauth !== true && c?.status === "connected")?.id ?? null;
|
|
98
|
+
if (!connectionId) {
|
|
99
|
+
await logRun({
|
|
100
|
+
ok: false,
|
|
101
|
+
sent: 0,
|
|
102
|
+
groups: 0,
|
|
103
|
+
reason: "no_active_send_connection",
|
|
104
|
+
error: `Geen actief send-adres voor doeleinde ${purpose}`,
|
|
105
|
+
detail: { purpose },
|
|
106
|
+
});
|
|
107
|
+
return new Response(JSON.stringify({ ok: false, reason: "no_active_send_connection", purpose }), { status: 500, headers: { "content-type": "application/json" } });
|
|
108
|
+
}
|
|
109
|
+
const { data: allCarriers, error: carriersErr } = await admin
|
|
110
|
+
.from("carriers")
|
|
111
|
+
.select("id, name, email, is_default, sort_order, service_countries, service_provinces, service_cities, pickup_moments, pickup_weekdays, delivery_weekdays");
|
|
112
|
+
if (carriersErr) {
|
|
113
|
+
await logRun({ ok: false, reason: "carriers_error", error: carriersErr.message });
|
|
114
|
+
return Response.json({ ok: false, error: carriersErr.message }, { status: 500 });
|
|
115
|
+
}
|
|
116
|
+
const carriers = (allCarriers ?? []).filter((c) => !!c.email);
|
|
117
|
+
if (carriers.length === 0) {
|
|
118
|
+
await logRun({ ok: true, reason: "no_carrier_with_email" });
|
|
119
|
+
return Response.json({ ok: true, skipped: "no_carrier_with_email", sent: 0 });
|
|
120
|
+
}
|
|
121
|
+
const now = new Date();
|
|
122
|
+
const horizon = new Date(now);
|
|
123
|
+
horizon.setDate(horizon.getDate() + 14);
|
|
124
|
+
const { data: orders } = await admin
|
|
125
|
+
.from("orders")
|
|
126
|
+
.select("id, order_number, customer_name, customer_id, order_date, delivery_date, notes, created_at, custom_fields, order_items(product_name, quantity, unit, products(plants_per_crate, crates_per_layer, layers_per_cart, location))")
|
|
127
|
+
.is("deleted_at", null)
|
|
128
|
+
.gte("delivery_date", ymd(now))
|
|
129
|
+
.lte("delivery_date", ymd(horizon))
|
|
130
|
+
.limit(2000);
|
|
131
|
+
const rawOrders = orders ?? [];
|
|
132
|
+
const customerIds = Array.from(new Set(rawOrders.map((o) => o.customer_id).filter((v) => !!v)));
|
|
133
|
+
const dlIds = Array.from(new Set(rawOrders
|
|
134
|
+
.map((o) => o.custom_fields?.delivery_location ?? null)
|
|
135
|
+
.filter((v) => !!v)));
|
|
136
|
+
const customerMap = new Map();
|
|
137
|
+
if (customerIds.length > 0) {
|
|
138
|
+
const { data: cs } = await admin
|
|
139
|
+
.from("customers")
|
|
140
|
+
.select("id, company_name, email, phone, contact_person, external_id")
|
|
141
|
+
.in("id", customerIds);
|
|
142
|
+
for (const c of cs ?? []) {
|
|
143
|
+
customerMap.set(c.id, c);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
const dlMap = new Map();
|
|
147
|
+
if (dlIds.length > 0) {
|
|
148
|
+
const { data: ds } = await admin
|
|
149
|
+
.from("customer_delivery_locations")
|
|
150
|
+
.select("id, name, street, house_number, postal_code, city, province, country")
|
|
151
|
+
.in("id", dlIds);
|
|
152
|
+
for (const d of ds ?? []) {
|
|
153
|
+
dlMap.set(d.id, d);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
const groups = new Map();
|
|
157
|
+
const unassigned = [];
|
|
158
|
+
for (const raw of rawOrders) {
|
|
159
|
+
if (!raw.delivery_date)
|
|
160
|
+
continue;
|
|
161
|
+
const delivery = new Date(raw.delivery_date);
|
|
162
|
+
if (Number.isNaN(delivery.getTime()))
|
|
163
|
+
continue;
|
|
164
|
+
const dlId = raw.custom_fields?.delivery_location ?? null;
|
|
165
|
+
const dl = dlId ? dlMap.get(dlId) : undefined;
|
|
166
|
+
const carrier = pickCarrierForAddress(carriers, {
|
|
167
|
+
country: dl?.country ?? null,
|
|
168
|
+
province: dl?.province ?? null,
|
|
169
|
+
city: dl?.city ?? null,
|
|
170
|
+
});
|
|
171
|
+
if (!carrier) {
|
|
172
|
+
unassigned.push(raw.order_number ?? raw.id);
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
const moments = normalizeMoments(carrier.pickup_moments);
|
|
176
|
+
const created = raw.created_at ? new Date(raw.created_at) : now;
|
|
177
|
+
const moment = selectPickupMoment(delivery, created, moments);
|
|
178
|
+
const pickupDate = subtractWorkdays(delivery, moment.pickup_days);
|
|
179
|
+
const signupDeadline = getSignupDeadline(delivery, moment);
|
|
180
|
+
const key = `${carrier.id}::${ymd(pickupDate)}::${momentKey(moment)}`;
|
|
181
|
+
const { carts, plants } = cartsFor(raw.order_items);
|
|
182
|
+
const cust = raw.customer_id ? customerMap.get(raw.customer_id) : undefined;
|
|
183
|
+
const pickupLocations = Array.from(new Set((raw.order_items ?? [])
|
|
184
|
+
.map((it) => {
|
|
185
|
+
const p = Array.isArray(it.products) ? it.products[0] : it.products;
|
|
186
|
+
return p?.location ?? null;
|
|
187
|
+
})
|
|
188
|
+
.filter((v) => !!v && v.trim() !== "")));
|
|
189
|
+
const row = {
|
|
190
|
+
orderNumber: raw.order_number ?? "—",
|
|
191
|
+
orderDate: raw.order_date,
|
|
192
|
+
deliveryDate: raw.delivery_date,
|
|
193
|
+
deliveryType: "Mailbox",
|
|
194
|
+
notes: raw.notes,
|
|
195
|
+
cartCount: carts || null,
|
|
196
|
+
plantCount: plants || null,
|
|
197
|
+
customer: {
|
|
198
|
+
name: cust?.company_name ?? raw.customer_name ?? "—",
|
|
199
|
+
contactPerson: cust?.contact_person ?? null,
|
|
200
|
+
email: cust?.email ?? null,
|
|
201
|
+
phone: cust?.phone ?? null,
|
|
202
|
+
customerNumber: cust?.external_id ?? null,
|
|
203
|
+
},
|
|
204
|
+
deliveryLocation: dl
|
|
205
|
+
? {
|
|
206
|
+
name: dl.name,
|
|
207
|
+
street: dl.street,
|
|
208
|
+
houseNumber: dl.house_number,
|
|
209
|
+
postalCode: dl.postal_code,
|
|
210
|
+
city: dl.city,
|
|
211
|
+
province: dl.province,
|
|
212
|
+
country: dl.country,
|
|
213
|
+
}
|
|
214
|
+
: null,
|
|
215
|
+
pickupLocations,
|
|
216
|
+
items: (raw.order_items ?? []).map((it) => ({
|
|
217
|
+
name: it.product_name ?? "—",
|
|
218
|
+
quantity: it.quantity === null ? null : Number(it.quantity),
|
|
219
|
+
unit: it.unit,
|
|
220
|
+
})),
|
|
221
|
+
};
|
|
222
|
+
const g = groups.get(key);
|
|
223
|
+
if (g) {
|
|
224
|
+
g.rows.push(row);
|
|
225
|
+
g.totalCarts += carts;
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
groups.set(key, {
|
|
229
|
+
carrier,
|
|
230
|
+
pickupDate,
|
|
231
|
+
moment,
|
|
232
|
+
signupDeadline,
|
|
233
|
+
rows: [row],
|
|
234
|
+
totalCarts: carts,
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
const leadMs = leadMin * 60 * 1000;
|
|
239
|
+
let sent = 0;
|
|
240
|
+
const skipped = [];
|
|
241
|
+
for (const [key, g] of groups) {
|
|
242
|
+
const triggerAt = g.signupDeadline.getTime() - leadMs;
|
|
243
|
+
if (now.getTime() < triggerAt)
|
|
244
|
+
continue;
|
|
245
|
+
if (now.getTime() > g.signupDeadline.getTime() + 72 * 60 * 60 * 1000)
|
|
246
|
+
continue;
|
|
247
|
+
const pickupDateKey = ymd(g.pickupDate);
|
|
248
|
+
const mKey = momentKey(g.moment);
|
|
249
|
+
const { data: existing } = await admin
|
|
250
|
+
.from("carrier_signup_dispatches")
|
|
251
|
+
.select("id")
|
|
252
|
+
.eq("carrier_id", g.carrier.id)
|
|
253
|
+
.eq("pickup_date", pickupDateKey)
|
|
254
|
+
.eq("pickup_moment_key", mKey)
|
|
255
|
+
.maybeSingle();
|
|
256
|
+
if (existing) {
|
|
257
|
+
skipped.push(`${key}=already`);
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
const mail = buildCarrierSignupMail({
|
|
261
|
+
carrierName: g.carrier.name,
|
|
262
|
+
pickupDate: g.pickupDate,
|
|
263
|
+
pickupTime: g.moment.pickup_time,
|
|
264
|
+
signupDeadline: g.signupDeadline,
|
|
265
|
+
orders: g.rows,
|
|
266
|
+
});
|
|
267
|
+
const { data: outbox, error: outErr } = await admin
|
|
268
|
+
.from("email_auth_outbox")
|
|
269
|
+
.insert({
|
|
270
|
+
purpose,
|
|
271
|
+
connection_id: connectionId,
|
|
272
|
+
to_addresses: [g.carrier.email],
|
|
273
|
+
cc_addresses: [],
|
|
274
|
+
bcc_addresses: [],
|
|
275
|
+
subject: mail.subject,
|
|
276
|
+
html_body: mail.html,
|
|
277
|
+
text_body: mail.text,
|
|
278
|
+
headers: {},
|
|
279
|
+
scheduled_at: new Date().toISOString(),
|
|
280
|
+
status: "queued",
|
|
281
|
+
})
|
|
282
|
+
.select("id")
|
|
283
|
+
.single();
|
|
284
|
+
if (outErr) {
|
|
285
|
+
skipped.push(`${key}=outbox_err:${outErr.message}`);
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
await admin.from("carrier_signup_dispatches").insert({
|
|
289
|
+
carrier_id: g.carrier.id,
|
|
290
|
+
pickup_date: pickupDateKey,
|
|
291
|
+
pickup_moment_key: mKey,
|
|
292
|
+
signup_deadline: g.signupDeadline.toISOString(),
|
|
293
|
+
order_count: g.rows.length,
|
|
294
|
+
cart_count: mail.totalCarts,
|
|
295
|
+
outbox_id: outbox?.id ?? null,
|
|
296
|
+
});
|
|
297
|
+
sent++;
|
|
298
|
+
}
|
|
299
|
+
await logRun({
|
|
300
|
+
ok: true,
|
|
301
|
+
sent,
|
|
302
|
+
groups: groups.size,
|
|
303
|
+
skipped,
|
|
304
|
+
unassigned,
|
|
305
|
+
reason: sent === 0 ? "no_group_in_window" : null,
|
|
306
|
+
});
|
|
307
|
+
return Response.json({
|
|
308
|
+
ok: true,
|
|
309
|
+
sent,
|
|
310
|
+
groups: groups.size,
|
|
311
|
+
skipped,
|
|
312
|
+
unassigned,
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
catch (e) {
|
|
316
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
317
|
+
console.error("[dispatch-carrier-signups] unhandled error", e);
|
|
318
|
+
await logRun({ ok: false, reason: "unhandled_exception", error: msg });
|
|
319
|
+
return Response.json({ ok: false, error: msg }, { status: 500 });
|
|
320
|
+
}
|
|
321
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { handlePickstationOrders } from "./pickstationOrders.handler";
|
|
2
|
+
export { handlePickstationPrint } from "./pickstationPrint.handler";
|
|
3
|
+
export { handlePickstationStatus } from "./pickstationStatus.handler";
|
|
4
|
+
export { handleDispatchCarrierSignups } from "./dispatchCarrierSignups.handler";
|
|
5
|
+
export { handleLogisticsTick } from "./logisticsTick.handler";
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/serverRoutes/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,uBAAuB,EAAE,MAAM,6BAA6B,CAAC;AACtE,OAAO,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AACpE,OAAO,EAAE,uBAAuB,EAAE,MAAM,6BAA6B,CAAC;AACtE,OAAO,EAAE,4BAA4B,EAAE,MAAM,kCAAkC,CAAC;AAChF,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Barrel voor shell-hostbare server-route handlers.
|
|
2
|
+
// Wordt via de subpath-export "@flowselections/floriday-afhandeling-module/server-routes"
|
|
3
|
+
// door het dashboard geladen; NIET re-exporteren vanuit src/index.ts (client-bundle-vrij).
|
|
4
|
+
export { handlePickstationOrders } from "./pickstationOrders.handler";
|
|
5
|
+
export { handlePickstationPrint } from "./pickstationPrint.handler";
|
|
6
|
+
export { handlePickstationStatus } from "./pickstationStatus.handler";
|
|
7
|
+
export { handleDispatchCarrierSignups } from "./dispatchCarrierSignups.handler";
|
|
8
|
+
export { handleLogisticsTick } from "./logisticsTick.handler";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logisticsTick.handler.d.ts","sourceRoot":"","sources":["../../../src/lib/serverRoutes/logisticsTick.handler.ts"],"names":[],"mappings":"AAEA,OAAO,EAAgB,KAAK,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAG1E,wBAAsB,mBAAmB,CACvC,QAAQ,EAAE,OAAO,EACjB,QAAQ,CAAC,EAAE,cAAc,GACxB,OAAO,CAAC,QAAQ,CAAC,CA8BnB"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// Handler-lift van /api/public/hooks/logistics-tick. Byte-identiek gedrag.
|
|
2
|
+
// NB: dit endpoint is publiek en heeft géén auth-check — nog niet in het shell-manifest.
|
|
3
|
+
import { createClient } from "@supabase/supabase-js";
|
|
4
|
+
import { runStrategyPlannerForUser } from "../logisticsEngine/evaluate.functions";
|
|
5
|
+
export async function handleLogisticsTick(_request, injected) {
|
|
6
|
+
let admin;
|
|
7
|
+
if (injected) {
|
|
8
|
+
admin = injected;
|
|
9
|
+
}
|
|
10
|
+
else {
|
|
11
|
+
const url = process.env.SUPABASE_URL;
|
|
12
|
+
const key = process.env.SUPABASE_SERVICE_ROLE_KEY;
|
|
13
|
+
if (!url || !key)
|
|
14
|
+
return new Response("Missing env", { status: 500 });
|
|
15
|
+
admin = createClient(url, key, {
|
|
16
|
+
auth: { persistSession: false, autoRefreshToken: false },
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
const { data: userIds, error } = await admin
|
|
20
|
+
.from("logistics_strategy_schedules")
|
|
21
|
+
.select("user_id")
|
|
22
|
+
.eq("is_enabled", true);
|
|
23
|
+
if (error)
|
|
24
|
+
return Response.json({ error: error.message }, { status: 500 });
|
|
25
|
+
const unique = Array.from(new Set((userIds ?? []).map((r) => r.user_id)));
|
|
26
|
+
const results = [];
|
|
27
|
+
for (const uid of unique) {
|
|
28
|
+
try {
|
|
29
|
+
const r = await runStrategyPlannerForUser(admin, uid, {});
|
|
30
|
+
results.push({ user: uid, ...r });
|
|
31
|
+
}
|
|
32
|
+
catch (e) {
|
|
33
|
+
results.push({ user: uid, reason: e instanceof Error ? e.message : String(e) });
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return Response.json({ ok: true, processed: unique.length, results });
|
|
37
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pickstationOrders.handler.d.ts","sourceRoot":"","sources":["../../../src/lib/serverRoutes/pickstationOrders.handler.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAgB5D,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,OAAO,EAChB,QAAQ,CAAC,EAAE,cAAc,GACxB,OAAO,CAAC,QAAQ,CAAC,CAiDnB"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
function friendlyPickstationError(e) {
|
|
2
|
+
const message = e instanceof Error ? e.message : String(e || "Onbekende fout");
|
|
3
|
+
if (message.includes("operator does not exist") || message.includes("invalid input syntax")) {
|
|
4
|
+
return "De picklijst kon niet worden vernieuwd door een databasefilter met een ongeldig datumformaat. De onderliggende orders zijn hiermee niet verwijderd.";
|
|
5
|
+
}
|
|
6
|
+
if (message.includes("statement timeout") || message.includes("canceling statement")) {
|
|
7
|
+
return "De database deed te lang over het vernieuwen van de picklijst. Probeer opnieuw; bestaande orders zijn niet verwijderd.";
|
|
8
|
+
}
|
|
9
|
+
if (message.includes("Supabase publishable env vars")) {
|
|
10
|
+
return "De Pickstation-databaseverbinding is niet geconfigureerd.";
|
|
11
|
+
}
|
|
12
|
+
return `De picklijst kon niet worden vernieuwd: ${message}`;
|
|
13
|
+
}
|
|
14
|
+
export async function handlePickstationOrders(request, supabase) {
|
|
15
|
+
const requestId = crypto.randomUUID();
|
|
16
|
+
const startedAt = Date.now();
|
|
17
|
+
console.info("[pickstation-api] GET orders", {
|
|
18
|
+
requestId,
|
|
19
|
+
at: new Date().toISOString(),
|
|
20
|
+
userAgent: request.headers.get("user-agent"),
|
|
21
|
+
});
|
|
22
|
+
try {
|
|
23
|
+
const { loadTodaysPickstationOrders } = await import("../pickstationOrders.server");
|
|
24
|
+
const orders = await loadTodaysPickstationOrders(supabase);
|
|
25
|
+
console.info("[pickstation-api] GET orders ok", {
|
|
26
|
+
requestId,
|
|
27
|
+
carts: orders.length,
|
|
28
|
+
durationMs: Date.now() - startedAt,
|
|
29
|
+
});
|
|
30
|
+
return Response.json({ orders, generatedAt: new Date().toISOString(), requestId }, {
|
|
31
|
+
headers: {
|
|
32
|
+
"Cache-Control": "no-store",
|
|
33
|
+
"X-Robots-Tag": "noindex, nofollow",
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
catch (e) {
|
|
38
|
+
const technicalMessage = e instanceof Error ? e.message : String(e || "Onbekende fout");
|
|
39
|
+
console.error("[pickstation-api] GET orders failed", {
|
|
40
|
+
requestId,
|
|
41
|
+
message: technicalMessage,
|
|
42
|
+
stack: e instanceof Error ? e.stack : undefined,
|
|
43
|
+
durationMs: Date.now() - startedAt,
|
|
44
|
+
});
|
|
45
|
+
return Response.json({
|
|
46
|
+
orders: [],
|
|
47
|
+
error: friendlyPickstationError(e),
|
|
48
|
+
technicalError: technicalMessage,
|
|
49
|
+
requestId,
|
|
50
|
+
}, {
|
|
51
|
+
status: 200,
|
|
52
|
+
headers: {
|
|
53
|
+
"Cache-Control": "no-store",
|
|
54
|
+
"X-Robots-Tag": "noindex, nofollow",
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pickstationPrint.handler.d.ts","sourceRoot":"","sources":["../../../src/lib/serverRoutes/pickstationPrint.handler.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAe5D,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,OAAO,EAChB,QAAQ,CAAC,EAAE,cAAc,GACxB,OAAO,CAAC,QAAQ,CAAC,CAoInB"}
|