@flowselections/floriday-afhandeling-module 1.0.63 → 1.0.66

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.
Files changed (41) hide show
  1. package/dist-lib/components/afhandeling/DagDetailDialog.d.ts +4 -0
  2. package/dist-lib/components/afhandeling/DagDetailDialog.d.ts.map +1 -1
  3. package/dist-lib/components/afhandeling/DagDetailDialog.js +27 -11
  4. package/dist-lib/components/afhandeling/LeveringenPage.d.ts.map +1 -1
  5. package/dist-lib/components/afhandeling/LeveringenPage.js +24 -2
  6. package/dist-lib/components/afhandeling/SignupMailMoments.d.ts +8 -0
  7. package/dist-lib/components/afhandeling/SignupMailMoments.d.ts.map +1 -0
  8. package/dist-lib/components/afhandeling/SignupMailMoments.js +75 -0
  9. package/dist-lib/components/afhandeling/VervoerderForm.d.ts.map +1 -1
  10. package/dist-lib/components/afhandeling/VervoerderForm.js +14 -7
  11. package/dist-lib/components/afhandeling/WeekAgendaView.d.ts.map +1 -1
  12. package/dist-lib/components/afhandeling/WeekAgendaView.js +2 -1
  13. package/dist-lib/components/settings/AfhandelingSettingsCard.d.ts.map +1 -1
  14. package/dist-lib/components/settings/AfhandelingSettingsCard.js +47 -2
  15. package/dist-lib/hooks/useOrderPlanning.d.ts.map +1 -1
  16. package/dist-lib/hooks/useOrderPlanning.js +20 -5
  17. package/dist-lib/integrations/supabase/types.d.ts +359 -0
  18. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  19. package/dist-lib/lib/carrierPlanning.d.ts +4 -0
  20. package/dist-lib/lib/carrierPlanning.d.ts.map +1 -1
  21. package/dist-lib/lib/carrierPlanning.js +14 -8
  22. package/dist-lib/lib/carrierSignup.functions.d.ts +102 -0
  23. package/dist-lib/lib/carrierSignup.functions.d.ts.map +1 -0
  24. package/dist-lib/lib/carrierSignup.functions.js +119 -0
  25. package/dist-lib/lib/carriers.functions.d.ts.map +1 -1
  26. package/dist-lib/lib/carriers.functions.js +13 -6
  27. package/dist-lib/lib/pickWorkdays.d.ts +24 -0
  28. package/dist-lib/lib/pickWorkdays.d.ts.map +1 -0
  29. package/dist-lib/lib/pickWorkdays.js +30 -0
  30. package/dist-lib/lib/pickstationOrders.server.d.ts.map +1 -1
  31. package/dist-lib/lib/pickstationOrders.server.js +9 -3
  32. package/dist-lib/lib/planning.d.ts +19 -0
  33. package/dist-lib/lib/planning.d.ts.map +1 -1
  34. package/dist-lib/lib/planning.js +58 -11
  35. package/dist-lib/lib/serverRoutes/dispatchCarrierSignups.handler.d.ts +1 -1
  36. package/dist-lib/lib/serverRoutes/dispatchCarrierSignups.handler.d.ts.map +1 -1
  37. package/dist-lib/lib/serverRoutes/dispatchCarrierSignups.handler.js +2 -325
  38. package/dist-lib/lib/signupMailText.d.ts +4 -0
  39. package/dist-lib/lib/signupMailText.d.ts.map +1 -0
  40. package/dist-lib/lib/signupMailText.js +37 -0
  41. package/package.json +1 -1
@@ -1 +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,OAAO,EAAE,OAAO,EAChB,QAAQ,CAAC,EAAE,cAAc,GACxB,OAAO,CAAC,QAAQ,CAAC,CAgVnB"}
1
+ {"version":3,"file":"dispatchCarrierSignups.handler.d.ts","sourceRoot":"","sources":["../../../src/lib/serverRoutes/dispatchCarrierSignups.handler.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAE5D,wBAAsB,4BAA4B,CAChD,QAAQ,EAAE,OAAO,EACjB,SAAS,CAAC,EAAE,cAAc,GACzB,OAAO,CAAC,QAAQ,CAAC,CAKnB"}
@@ -1,326 +1,3 @@
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
- const apikey = request.headers.get("apikey") ?? request.headers.get("x-api-key");
47
- const expected = process.env.SUPABASE_PUBLISHABLE_KEY;
48
- if (!expected || !apikey || apikey !== expected) {
49
- return new Response("Unauthorized", { status: 401 });
50
- }
51
- let admin;
52
- if (injected) {
53
- admin = injected;
54
- }
55
- else {
56
- const { supabaseAdmin } = await import("../../integrations/supabase/client.server");
57
- admin = supabaseAdmin;
58
- }
59
- const startedAt = new Date();
60
- const logRun = async (payload) => {
61
- try {
62
- await admin.from("carrier_signup_dispatch_runs").insert({
63
- started_at: startedAt.toISOString(),
64
- finished_at: new Date().toISOString(),
65
- ok: payload.ok,
66
- sent: payload.sent ?? 0,
67
- groups: payload.groups ?? 0,
68
- skipped: payload.skipped ?? [],
69
- unassigned: payload.unassigned ?? [],
70
- reason: payload.reason ?? null,
71
- error: payload.error ?? null,
72
- detail: payload.detail ?? null,
73
- });
74
- }
75
- catch (e) {
76
- console.error("[dispatch-carrier-signups] run-log insert failed", e);
77
- }
78
- };
79
- try {
80
- const { data: settingsRows, error: settingsErr } = await admin
81
- .from("afhandeling_settings")
82
- .select("signup_mail_enabled, signup_mail_lead_minutes, signup_mail_purpose")
83
- .eq("signup_mail_enabled", true)
84
- .limit(1);
85
- if (settingsErr) {
86
- await logRun({ ok: false, reason: "settings_error", error: settingsErr.message });
87
- return Response.json({ ok: false, error: settingsErr.message }, { status: 500 });
88
- }
89
- const settings = settingsRows?.[0] ?? null;
90
- if (!settings) {
91
- await logRun({ ok: true, reason: "mail_disabled" });
92
- return Response.json({ ok: true, skipped: "disabled", sent: 0 });
93
- }
94
- const leadMin = settings.signup_mail_lead_minutes ?? 60;
95
- const purpose = settings.signup_mail_purpose ?? "afhandeling_carrier_signup";
96
- const { data: purposeConnections, error: purposeErr } = await admin.rpc("email_auth_for_purpose", {
97
- _direction: "send",
98
- _purpose: purpose,
99
- });
100
- if (purposeErr)
101
- throw purposeErr;
102
- const connectionId = (purposeConnections ?? []).find((c) => c?.needs_reauth !== true && c?.status === "connected")?.id ?? null;
103
- if (!connectionId) {
104
- await logRun({
105
- ok: false,
106
- sent: 0,
107
- groups: 0,
108
- reason: "no_active_send_connection",
109
- error: `Geen actief send-adres voor doeleinde ${purpose}`,
110
- detail: { purpose },
111
- });
112
- return new Response(JSON.stringify({ ok: false, reason: "no_active_send_connection", purpose }), { status: 500, headers: { "content-type": "application/json" } });
113
- }
114
- const { data: allCarriers, error: carriersErr } = await admin
115
- .from("carriers")
116
- .select("id, name, email, is_default, sort_order, service_countries, service_provinces, service_cities, pickup_moments, pickup_weekdays, delivery_weekdays");
117
- if (carriersErr) {
118
- await logRun({ ok: false, reason: "carriers_error", error: carriersErr.message });
119
- return Response.json({ ok: false, error: carriersErr.message }, { status: 500 });
120
- }
121
- const carriers = (allCarriers ?? []).filter((c) => !!c.email);
122
- if (carriers.length === 0) {
123
- await logRun({ ok: true, reason: "no_carrier_with_email" });
124
- return Response.json({ ok: true, skipped: "no_carrier_with_email", sent: 0 });
125
- }
126
- const now = new Date();
127
- const horizon = new Date(now);
128
- horizon.setDate(horizon.getDate() + 14);
129
- const { data: orders } = await admin
130
- .from("orders")
131
- .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))")
132
- .is("deleted_at", null)
133
- .gte("delivery_date", ymd(now))
134
- .lte("delivery_date", ymd(horizon))
135
- .limit(2000);
136
- const rawOrders = orders ?? [];
137
- const customerIds = Array.from(new Set(rawOrders.map((o) => o.customer_id).filter((v) => !!v)));
138
- const dlIds = Array.from(new Set(rawOrders
139
- .map((o) => o.custom_fields?.delivery_location ?? null)
140
- .filter((v) => !!v)));
141
- const customerMap = new Map();
142
- if (customerIds.length > 0) {
143
- const { data: cs } = await admin
144
- .from("customers")
145
- .select("id, company_name, email, phone, contact_person, external_id")
146
- .in("id", customerIds);
147
- for (const c of cs ?? []) {
148
- customerMap.set(c.id, c);
149
- }
150
- }
151
- const dlMap = new Map();
152
- if (dlIds.length > 0) {
153
- const { data: ds } = await admin
154
- .from("customer_delivery_locations")
155
- .select("id, name, street, house_number, postal_code, city, province, country")
156
- .in("id", dlIds);
157
- for (const d of ds ?? []) {
158
- dlMap.set(d.id, d);
159
- }
160
- }
161
- const groups = new Map();
162
- const unassigned = [];
163
- for (const raw of rawOrders) {
164
- if (!raw.delivery_date)
165
- continue;
166
- const delivery = new Date(raw.delivery_date);
167
- if (Number.isNaN(delivery.getTime()))
168
- continue;
169
- const dlId = raw.custom_fields?.delivery_location ?? null;
170
- const dl = dlId ? dlMap.get(dlId) : undefined;
171
- const carrier = pickCarrierForAddress(carriers, {
172
- country: dl?.country ?? null,
173
- province: dl?.province ?? null,
174
- city: dl?.city ?? null,
175
- });
176
- if (!carrier) {
177
- unassigned.push(raw.order_number ?? raw.id);
178
- continue;
179
- }
180
- const moments = normalizeMoments(carrier.pickup_moments);
181
- const created = raw.created_at ? new Date(raw.created_at) : now;
182
- const moment = selectPickupMoment(delivery, created, moments);
183
- const pickupDate = subtractWorkdays(delivery, moment.pickup_days);
184
- const signupDeadline = getSignupDeadline(delivery, moment);
185
- const key = `${carrier.id}::${ymd(pickupDate)}::${momentKey(moment)}`;
186
- const { carts, plants } = cartsFor(raw.order_items);
187
- const cust = raw.customer_id ? customerMap.get(raw.customer_id) : undefined;
188
- const pickupLocations = Array.from(new Set((raw.order_items ?? [])
189
- .map((it) => {
190
- const p = Array.isArray(it.products) ? it.products[0] : it.products;
191
- return p?.location ?? null;
192
- })
193
- .filter((v) => !!v && v.trim() !== "")));
194
- const row = {
195
- orderNumber: raw.order_number ?? "—",
196
- orderDate: raw.order_date,
197
- deliveryDate: raw.delivery_date,
198
- deliveryType: "Mailbox",
199
- notes: raw.notes,
200
- cartCount: carts || null,
201
- plantCount: plants || null,
202
- customer: {
203
- name: cust?.company_name ?? raw.customer_name ?? "—",
204
- contactPerson: cust?.contact_person ?? null,
205
- email: cust?.email ?? null,
206
- phone: cust?.phone ?? null,
207
- customerNumber: cust?.external_id ?? null,
208
- },
209
- deliveryLocation: dl
210
- ? {
211
- name: dl.name,
212
- street: dl.street,
213
- houseNumber: dl.house_number,
214
- postalCode: dl.postal_code,
215
- city: dl.city,
216
- province: dl.province,
217
- country: dl.country,
218
- }
219
- : null,
220
- pickupLocations,
221
- items: (raw.order_items ?? []).map((it) => ({
222
- name: it.product_name ?? "—",
223
- quantity: it.quantity === null ? null : Number(it.quantity),
224
- unit: it.unit,
225
- })),
226
- };
227
- const g = groups.get(key);
228
- if (g) {
229
- g.rows.push(row);
230
- g.totalCarts += carts;
231
- }
232
- else {
233
- groups.set(key, {
234
- carrier,
235
- pickupDate,
236
- moment,
237
- signupDeadline,
238
- rows: [row],
239
- totalCarts: carts,
240
- });
241
- }
242
- }
243
- const leadMs = leadMin * 60 * 1000;
244
- let sent = 0;
245
- const skipped = [];
246
- for (const [key, g] of groups) {
247
- const triggerAt = g.signupDeadline.getTime() - leadMs;
248
- if (now.getTime() < triggerAt)
249
- continue;
250
- if (now.getTime() > g.signupDeadline.getTime() + 72 * 60 * 60 * 1000)
251
- continue;
252
- const pickupDateKey = ymd(g.pickupDate);
253
- const mKey = momentKey(g.moment);
254
- const { data: existing } = await admin
255
- .from("carrier_signup_dispatches")
256
- .select("id")
257
- .eq("carrier_id", g.carrier.id)
258
- .eq("pickup_date", pickupDateKey)
259
- .eq("pickup_moment_key", mKey)
260
- .maybeSingle();
261
- if (existing) {
262
- skipped.push(`${key}=already`);
263
- continue;
264
- }
265
- const mail = buildCarrierSignupMail({
266
- carrierName: g.carrier.name,
267
- pickupDate: g.pickupDate,
268
- pickupTime: g.moment.pickup_time,
269
- signupDeadline: g.signupDeadline,
270
- orders: g.rows,
271
- });
272
- const { data: outbox, error: outErr } = await admin
273
- .from("email_auth_outbox")
274
- .insert({
275
- purpose,
276
- connection_id: connectionId,
277
- to_addresses: [g.carrier.email],
278
- cc_addresses: [],
279
- bcc_addresses: [],
280
- subject: mail.subject,
281
- html_body: mail.html,
282
- text_body: mail.text,
283
- headers: {},
284
- scheduled_at: new Date().toISOString(),
285
- status: "queued",
286
- })
287
- .select("id")
288
- .single();
289
- if (outErr) {
290
- skipped.push(`${key}=outbox_err:${outErr.message}`);
291
- continue;
292
- }
293
- await admin.from("carrier_signup_dispatches").insert({
294
- carrier_id: g.carrier.id,
295
- pickup_date: pickupDateKey,
296
- pickup_moment_key: mKey,
297
- signup_deadline: g.signupDeadline.toISOString(),
298
- order_count: g.rows.length,
299
- cart_count: mail.totalCarts,
300
- outbox_id: outbox?.id ?? null,
301
- });
302
- sent++;
303
- }
304
- await logRun({
305
- ok: true,
306
- sent,
307
- groups: groups.size,
308
- skipped,
309
- unassigned,
310
- reason: sent === 0 ? "no_group_in_window" : null,
311
- });
312
- return Response.json({
313
- ok: true,
314
- sent,
315
- groups: groups.size,
316
- skipped,
317
- unassigned,
318
- });
319
- }
320
- catch (e) {
321
- const msg = e instanceof Error ? e.message : String(e);
322
- console.error("[dispatch-carrier-signups] unhandled error", e);
323
- await logRun({ ok: false, reason: "unhandled_exception", error: msg });
324
- return Response.json({ ok: false, error: msg }, { status: 500 });
325
- }
1
+ export async function handleDispatchCarrierSignups(_request, _injected) {
2
+ return Response.json({ ok: false, code: "gone", error: "De aanmeldmail wordt door de database zelf verstuurd; deze route is vervallen." }, { status: 410 });
326
3
  }
@@ -0,0 +1,4 @@
1
+ export declare function signupReasonText(reason: string | null | undefined): string | null;
2
+ export declare function fmtTime(iso: string | null | undefined, tz: string | null): string;
3
+ export declare function fmtDayTime(iso: string | null | undefined, tz: string | null): string;
4
+ //# sourceMappingURL=signupMailText.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signupMailText.d.ts","sourceRoot":"","sources":["../../src/lib/signupMailText.ts"],"names":[],"mappings":"AACA,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAyBjF;AAED,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAGjF;AAED,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAGpF"}
@@ -0,0 +1,37 @@
1
+ // Gebruikersteksten voor de aanmeldmail (geen techniek).
2
+ export function signupReasonText(reason) {
3
+ switch (reason) {
4
+ case null:
5
+ case undefined:
6
+ case "":
7
+ return null;
8
+ case "no_send_connection":
9
+ return "Er is nog geen verzendadres gekoppeld aan ‘Aanmeldmail vervoerders’. Koppel er een bij E-mailkoppeling.";
10
+ case "send_connection_needs_reauth":
11
+ return "Het verzendadres voor ‘Aanmeldmail vervoerders’ moet opnieuw worden gekoppeld bij E-mailkoppeling.";
12
+ case "no_timezone":
13
+ return "De tijdzone van de vestiging ontbreekt. Stel die in bij Werktijden; tot dan gaan er geen aanmeldmails weg.";
14
+ case "no_pick_workdays":
15
+ return "Er zijn geen werkdagen ingesteld bij Werktijden; de aanmeldmail kan niet worden berekend.";
16
+ case "mail_disabled":
17
+ return "De aanmeldmail staat uit.";
18
+ case "group_not_found":
19
+ return "Dit ophaalmoment heeft geen orders meer.";
20
+ case "after_pickup":
21
+ return "De ophaaltijd is al voorbij.";
22
+ case "already_sent":
23
+ return "Deze stand is al verstuurd.";
24
+ default:
25
+ return "De aanmeldmail kon niet worden gecontroleerd.";
26
+ }
27
+ }
28
+ export function fmtTime(iso, tz) {
29
+ if (!iso || !tz)
30
+ return "—";
31
+ return new Intl.DateTimeFormat("nl-NL", { timeZone: tz, hour: "2-digit", minute: "2-digit" }).format(new Date(iso));
32
+ }
33
+ export function fmtDayTime(iso, tz) {
34
+ if (!iso || !tz)
35
+ return "—";
36
+ return new Intl.DateTimeFormat("nl-NL", { timeZone: tz, weekday: "short", day: "numeric", month: "short", hour: "2-digit", minute: "2-digit" }).format(new Date(iso));
37
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flowselections/floriday-afhandeling-module",
3
- "version": "1.0.63",
3
+ "version": "1.0.66",
4
4
  "private": false,
5
5
  "sideEffects": false,
6
6
  "type": "module",