@flowselections/floriday-afhandeling-module 1.0.9 → 1.0.10

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 (72) hide show
  1. package/dist-lib/components/afhandeling/DagDetailDialog.d.ts +9 -0
  2. package/dist-lib/components/afhandeling/DagDetailDialog.d.ts.map +1 -0
  3. package/dist-lib/components/afhandeling/DagDetailDialog.js +145 -0
  4. package/dist-lib/components/afhandeling/LeveringenPage.d.ts.map +1 -1
  5. package/dist-lib/components/afhandeling/LeveringenPage.js +11 -5
  6. package/dist-lib/components/afhandeling/PickAgendaView.d.ts.map +1 -1
  7. package/dist-lib/components/afhandeling/PickAgendaView.js +13 -4
  8. package/dist-lib/components/afhandeling/PlanningAgendaPage.d.ts.map +1 -1
  9. package/dist-lib/components/afhandeling/PlanningAgendaPage.js +2 -2
  10. package/dist-lib/components/afhandeling/ServiceAreaSelect.d.ts +12 -0
  11. package/dist-lib/components/afhandeling/ServiceAreaSelect.d.ts.map +1 -0
  12. package/dist-lib/components/afhandeling/ServiceAreaSelect.js +79 -0
  13. package/dist-lib/components/afhandeling/VervoerderForm.d.ts.map +1 -1
  14. package/dist-lib/components/afhandeling/VervoerderForm.js +53 -6
  15. package/dist-lib/components/afhandeling/VervoerdersPage.d.ts.map +1 -1
  16. package/dist-lib/components/afhandeling/VervoerdersPage.js +23 -5
  17. package/dist-lib/components/afhandeling/WeekAgendaView.d.ts +2 -1
  18. package/dist-lib/components/afhandeling/WeekAgendaView.d.ts.map +1 -1
  19. package/dist-lib/components/afhandeling/WeekAgendaView.js +53 -18
  20. package/dist-lib/components/pickstation/PakbonTemplate.d.ts +5 -2
  21. package/dist-lib/components/pickstation/PakbonTemplate.d.ts.map +1 -1
  22. package/dist-lib/components/pickstation/PakbonTemplate.js +12 -10
  23. package/dist-lib/components/settings/AfhandelingSettingsCard.d.ts.map +1 -1
  24. package/dist-lib/components/settings/AfhandelingSettingsCard.js +18 -6
  25. package/dist-lib/hooks/useOrderPlanning.d.ts +3 -0
  26. package/dist-lib/hooks/useOrderPlanning.d.ts.map +1 -1
  27. package/dist-lib/hooks/useOrderPlanning.js +47 -6
  28. package/dist-lib/integrations/supabase/auth-middleware.d.ts +2261 -136
  29. package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
  30. package/dist-lib/integrations/supabase/client.d.ts +2261 -136
  31. package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
  32. package/dist-lib/integrations/supabase/client.server.d.ts +2261 -136
  33. package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
  34. package/dist-lib/integrations/supabase/types.d.ts +2329 -134
  35. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  36. package/dist-lib/integrations/supabase/types.js +1 -0
  37. package/dist-lib/lib/afhandelingSettings.functions.d.ts +9431 -5172
  38. package/dist-lib/lib/afhandelingSettings.functions.d.ts.map +1 -1
  39. package/dist-lib/lib/afhandelingSettings.functions.js +27 -2
  40. package/dist-lib/lib/carrierSignupMail.server.d.ts +56 -0
  41. package/dist-lib/lib/carrierSignupMail.server.d.ts.map +1 -0
  42. package/dist-lib/lib/carrierSignupMail.server.js +229 -0
  43. package/dist-lib/lib/carriers.functions.d.ts +33377 -15579
  44. package/dist-lib/lib/carriers.functions.d.ts.map +1 -1
  45. package/dist-lib/lib/carriers.functions.js +28 -2
  46. package/dist-lib/lib/cartAssignments.functions.d.ts +46479 -0
  47. package/dist-lib/lib/cartAssignments.functions.d.ts.map +1 -0
  48. package/dist-lib/lib/cartAssignments.functions.js +130 -0
  49. package/dist-lib/lib/cartMoveImpact.d.ts +8 -0
  50. package/dist-lib/lib/cartMoveImpact.d.ts.map +1 -0
  51. package/dist-lib/lib/cartMoveImpact.js +26 -0
  52. package/dist-lib/lib/floriday.functions.d.ts +2265 -136
  53. package/dist-lib/lib/floriday.functions.d.ts.map +1 -1
  54. package/dist-lib/lib/floriday.functions.js +178 -98
  55. package/dist-lib/lib/geo/euServiceAreas.d.ts +30 -0
  56. package/dist-lib/lib/geo/euServiceAreas.d.ts.map +1 -0
  57. package/dist-lib/lib/geo/euServiceAreas.js +277 -0
  58. package/dist-lib/lib/invalidatePlanning.d.ts +11 -0
  59. package/dist-lib/lib/invalidatePlanning.d.ts.map +1 -0
  60. package/dist-lib/lib/invalidatePlanning.js +25 -0
  61. package/dist-lib/lib/pickstationI18n.d.ts +4 -0
  62. package/dist-lib/lib/pickstationI18n.d.ts.map +1 -1
  63. package/dist-lib/lib/pickstationI18n.js +24 -0
  64. package/dist-lib/lib/pickstationOrders.server.d.ts +1 -1
  65. package/dist-lib/lib/pickstationOrders.server.d.ts.map +1 -1
  66. package/dist-lib/lib/pickstationOrders.server.js +305 -177
  67. package/dist-lib/lib/pickstationTypes.d.ts +11 -2
  68. package/dist-lib/lib/pickstationTypes.d.ts.map +1 -1
  69. package/dist-lib/lib/planning.d.ts +18 -2
  70. package/dist-lib/lib/planning.d.ts.map +1 -1
  71. package/dist-lib/lib/planning.js +96 -33
  72. package/package.json +2 -2
@@ -1,11 +1,39 @@
1
1
  // Server-only helper. Imported by /api/public/pickstation.* routes.
2
- // Uses the service-role admin client to bypass RLS for the *public* page.
3
- // Returns ONLY safe fields (no email/phone/address/prices).
4
- import { calculateOrderPlanning } from "./planning";
5
- const FLORIDAY_BASE_URLS = {
6
- staging: "https://api.staging.floriday.io/suppliers-api-2026v1",
7
- production: "https://api.floriday.io/suppliers-api-2026v1",
8
- };
2
+ // Uses the publishable-key Supabase client + SECURITY DEFINER RPCs so the
3
+ // public Pickstation works without a service-role key.
4
+ import { createClient } from "@supabase/supabase-js";
5
+ import { calculateOrderPlanning, distributePickDates, DEFAULT_PICKUP_MOMENT, } from "./planning";
6
+ const LOG_SCOPE = "[pickstation]";
7
+ function debug(action, details) {
8
+ console.info(`${LOG_SCOPE} ${action}`, {
9
+ at: new Date().toISOString(),
10
+ ...(details ?? {}),
11
+ });
12
+ }
13
+ function warn(action, details) {
14
+ console.warn(`${LOG_SCOPE} ${action}`, {
15
+ at: new Date().toISOString(),
16
+ ...(details ?? {}),
17
+ });
18
+ }
19
+ function errorLog(action, err, details) {
20
+ console.error(`${LOG_SCOPE} ${action}`, {
21
+ at: new Date().toISOString(),
22
+ message: err instanceof Error ? err.message : String(err),
23
+ stack: err instanceof Error ? err.stack : undefined,
24
+ ...(details ?? {}),
25
+ });
26
+ }
27
+ function getPublicClient() {
28
+ const url = process.env.SUPABASE_URL ?? process.env.VITE_SUPABASE_URL;
29
+ const key = process.env.SUPABASE_PUBLISHABLE_KEY ?? process.env.VITE_SUPABASE_PUBLISHABLE_KEY;
30
+ if (!url || !key) {
31
+ throw new Error("Supabase publishable env vars ontbreken");
32
+ }
33
+ return createClient(url, key, {
34
+ auth: { storage: undefined, persistSession: false, autoRefreshToken: false },
35
+ });
36
+ }
9
37
  function pickStr(r, ...keys) {
10
38
  for (const k of keys) {
11
39
  const v = r[k];
@@ -50,195 +78,295 @@ function toIsoDay(d) {
50
78
  const day = String(d.getDate()).padStart(2, "0");
51
79
  return `${y}-${m}-${day}`;
52
80
  }
53
- async function getActiveConnection(admin) {
54
- const { data, error } = await admin
55
- .from("floriday_connections")
56
- .select("api_key, access_token, environment")
57
- .eq("is_active", true)
58
- .order("updated_at", { ascending: false })
59
- .limit(1)
60
- .maybeSingle();
61
- if (error)
62
- throw new Error(`Floriday-verbinding: ${error.message}`);
63
- if (!data)
64
- throw new Error("Geen actieve Floriday-verbinding gevonden");
65
- if (!data.access_token)
66
- throw new Error("Floriday access-token ontbreekt");
67
- return data;
68
- }
69
- async function getSettings(admin) {
70
- const { data: settingsRow } = await admin
71
- .from("afhandeling_settings")
72
- .select("pick_days_before_transport")
73
- .order("updated_at", { ascending: false })
74
- .limit(1)
75
- .maybeSingle();
76
- const { data: defCarrier } = await admin
77
- .from("carriers")
78
- .select("pickup_moments, delivery_days, signup_days_before_delivery, signup_time_1")
79
- .eq("is_default", true)
80
- .limit(1)
81
- .maybeSingle();
82
- const { DEFAULT_PICKUP_MOMENT } = await import("./planning");
83
- const raw = (defCarrier?.pickup_moments ?? []);
84
- const moments = Array.isArray(raw) && raw.length > 0
85
- ? raw
86
- : [DEFAULT_PICKUP_MOMENT];
87
- return {
88
- pickDays: settingsRow?.pick_days_before_transport ?? 1,
89
- pickupMoments: moments,
90
- };
91
- }
92
- async function fetchFloridayOrders(conn) {
93
- const baseUrl = FLORIDAY_BASE_URLS[conn.environment] ?? FLORIDAY_BASE_URLS.staging;
94
- const res = await fetch(`${baseUrl}/sales-orders/sync/0?limit=200`, {
95
- headers: {
96
- Authorization: `Bearer ${conn.access_token}`,
97
- "x-api-key": conn.api_key,
98
- Accept: "application/json",
99
- },
100
- });
101
- if (!res.ok) {
102
- const body = await res.text().catch(() => "");
103
- throw new Error(`Floriday API ${res.status}: ${body.slice(0, 200)}`);
81
+ /**
82
+ * Verdeel de regels van een handmatige order over karren.
83
+ * Eén product per kar; de laatste kar van een product mag minder fusten bevatten.
84
+ */
85
+ function buildManualCarts(items) {
86
+ const carts = [];
87
+ for (const it of items) {
88
+ const qty = Number(it.quantity ?? 0);
89
+ const plantsPerCrate = Number(it.plants_per_crate ?? 0);
90
+ const cratesPerLayer = Number(it.crates_per_layer ?? 0);
91
+ const layersPerCart = Number(it.layers_per_cart ?? 0);
92
+ const description = it.product ?? "—";
93
+ if (!Number.isFinite(qty) || qty <= 0)
94
+ continue;
95
+ const cratesPerCart = cratesPerLayer > 0 && layersPerCart > 0 ? cratesPerLayer * layersPerCart : 0;
96
+ const totalCrates = plantsPerCrate > 0 ? Math.ceil(qty / plantsPerCrate) : 0;
97
+ if (cratesPerCart > 0 && totalCrates > 0) {
98
+ const cartsNeeded = Math.ceil(totalCrates / cratesPerCart);
99
+ for (let c = 0; c < cartsNeeded; c++) {
100
+ const crates = Math.min(cratesPerCart, totalCrates - c * cratesPerCart);
101
+ carts.push({
102
+ lines: [{ description, crates, unit: "fust" }],
103
+ cratesOnCart: crates,
104
+ });
105
+ }
106
+ }
107
+ else {
108
+ // Onbekende verpakking → één kar met onbekend aantal fusten.
109
+ carts.push({
110
+ lines: [{ description, crates: null, unit: "fust" }],
111
+ cratesOnCart: null,
112
+ });
113
+ }
104
114
  }
105
- const json = (await res.json());
106
- if (Array.isArray(json))
107
- return json;
108
- const obj = json;
109
- if (Array.isArray(obj.results))
110
- return obj.results;
111
- if (Array.isArray(obj.items))
112
- return obj.items;
113
- return [];
115
+ if (carts.length === 0) {
116
+ carts.push({ lines: [], cratesOnCart: null });
117
+ }
118
+ return carts;
114
119
  }
115
- function mapLines(raw) {
120
+ function buildFloridayCarts(raw) {
116
121
  const list = (raw.salesOrderLines ?? raw.lines ?? raw.orderLines);
117
- if (!Array.isArray(list))
118
- return [];
119
- return list.map((l) => {
120
- const r = (l ?? {});
121
- const tradeItem = r.tradeItem;
122
- const product = r.product;
123
- return {
124
- description: pickStr(r, "description", "name") ??
125
- pickStr(tradeItem ?? {}, "name", "description") ??
126
- pickStr(product ?? {}, "name") ??
127
- "—",
128
- quantity: (typeof r.numberOfPieces === "number" && r.numberOfPieces) ||
129
- (typeof r.quantity === "number" && r.quantity) ||
130
- (typeof r.numberOfTradeUnits === "number" && r.numberOfTradeUnits) ||
131
- null,
132
- unit: pickStr(r, "packingUnit", "unit", "uom"),
133
- };
134
- });
122
+ const lines = Array.isArray(list)
123
+ ? list.map((l) => {
124
+ const r = (l ?? {});
125
+ const tradeItem = r.tradeItem;
126
+ const product = r.product;
127
+ return {
128
+ description: pickStr(r, "description", "name") ??
129
+ pickStr(tradeItem ?? {}, "name", "description") ??
130
+ pickStr(product ?? {}, "name") ??
131
+ "—",
132
+ crates: null,
133
+ unit: "fust",
134
+ };
135
+ })
136
+ : [];
137
+ return [{ lines, cratesOnCart: null }];
135
138
  }
136
139
  export async function loadTodaysPickstationOrders() {
137
- const { supabaseAdmin } = await import("../integrations/supabase/client.server");
138
- const admin = supabaseAdmin;
139
- const [conn, settings] = await Promise.all([getActiveConnection(admin), getSettings(admin)]);
140
- const raw = await fetchFloridayOrders(conn);
141
- const todayIso = toIsoDay(new Date());
142
- const filtered = [];
143
- for (const r of raw) {
144
- const deliveryDate = pickDeliveryDate(r);
145
- const planning = calculateOrderPlanning(deliveryDate, {
146
- pickupMoments: settings.pickupMoments,
147
- pickDurationWorkdays: settings.pickDays,
148
- orderCreatedAt: r.createDateTime ?? r.createdAt ?? null,
140
+ const startedAt = Date.now();
141
+ const supabase = getPublicClient();
142
+ debug("load orders started", {
143
+ query: "rpc pickstation_get_settings + pickstation_get_orders_raw",
144
+ });
145
+ const [settingsRes, ordersRes] = await Promise.all([
146
+ supabase.rpc("pickstation_get_settings"),
147
+ supabase.rpc("pickstation_get_orders_raw"),
148
+ ]);
149
+ if (settingsRes.error) {
150
+ errorLog("settings rpc failed", new Error(settingsRes.error.message), {
151
+ code: settingsRes.error.code,
152
+ details: settingsRes.error.details,
153
+ hint: settingsRes.error.hint,
154
+ query: "rpc pickstation_get_settings",
149
155
  });
150
- if (!planning)
151
- continue;
152
- const pickIso = toIsoDay(planning.pickStartDate);
153
- if (pickIso !== todayIso)
154
- continue;
155
- const id = r.id ?? r.salesOrderId ?? crypto.randomUUID();
156
- filtered.push({
157
- id,
158
- orderNumber: pickStr(r, "salesOrderNumber", "orderNumber", "number") ?? "—",
159
- customerName: pickCustomer(r),
160
- country: pickCountry(r),
161
- deliveryDate,
162
- pickDate: pickIso,
163
- lineCount: Array.isArray(r.salesOrderLines ?? r.lines ?? r.orderLines)
164
- ? (r.salesOrderLines ?? r.lines ?? r.orderLines).length
165
- : 0,
166
- lines: mapLines(r),
167
- printedCount: 0,
156
+ throw new Error(`Pickstation-instellingen: ${settingsRes.error.message}`);
157
+ }
158
+ if (ordersRes.error) {
159
+ errorLog("orders rpc failed", new Error(ordersRes.error.message), {
160
+ code: ordersRes.error.code,
161
+ details: ordersRes.error.details,
162
+ hint: ordersRes.error.hint,
163
+ query: "rpc pickstation_get_orders_raw",
168
164
  });
165
+ throw new Error(`Pickstation-orders: ${ordersRes.error.message}`);
169
166
  }
170
- // Hang print counts on each order (today only)
171
- if (filtered.length > 0) {
172
- const sinceIso = new Date();
173
- sinceIso.setHours(0, 0, 0, 0);
174
- const { data: logs } = await admin
175
- .from("pickstation_print_log")
176
- .select("order_id")
177
- .gte("printed_at", sinceIso.toISOString());
178
- if (logs) {
179
- const counts = new Map();
180
- for (const l of logs) {
181
- counts.set(l.order_id, (counts.get(l.order_id) ?? 0) + 1);
167
+ const settingsRaw = (settingsRes.data ?? {});
168
+ const pickDays = typeof settingsRaw.pick_days === "number" ? settingsRaw.pick_days : 1;
169
+ const distributionEnabled = Boolean(settingsRaw.pick_distribution_enabled);
170
+ const maxEarlier = typeof settingsRaw.max_workdays_earlier_pick === "number"
171
+ ? settingsRaw.max_workdays_earlier_pick
172
+ : 2;
173
+ const pickupMoments = Array.isArray(settingsRaw.pickup_moments) && settingsRaw.pickup_moments.length > 0
174
+ ? settingsRaw.pickup_moments
175
+ : [DEFAULT_PICKUP_MOMENT];
176
+ const payload = (ordersRes.data ?? {
177
+ floriday: [],
178
+ manual: [],
179
+ overrides: [],
180
+ print_counts: {},
181
+ });
182
+ debug("raw data loaded", {
183
+ floridayOrders: payload.floriday?.length ?? 0,
184
+ manualOrders: payload.manual?.length ?? 0,
185
+ overrides: payload.overrides?.length ?? 0,
186
+ printLogEntries: Object.keys(payload.print_counts ?? {}).length,
187
+ pickDays,
188
+ distributionEnabled,
189
+ maxEarlier,
190
+ pickupMoments: pickupMoments.length,
191
+ });
192
+ const items = [];
193
+ const skippedWithoutPlanning = [];
194
+ // Batch: reserve/lookup FO-nummers voor Floriday-orders
195
+ const foNumberById = new Map();
196
+ const floridayIds = (payload.floriday ?? [])
197
+ .map((r) => (r.id ?? r.data?.salesOrderId) ?? null)
198
+ .filter((v) => !!v);
199
+ if (floridayIds.length > 0) {
200
+ const { data: mapped, error: mapErr } = await supabase.rpc("assign_floriday_order_numbers", { p_ids: floridayIds });
201
+ if (mapErr) {
202
+ warn("assign_floriday_order_numbers failed", { message: mapErr.message });
203
+ }
204
+ else {
205
+ for (const m of (mapped ?? [])) {
206
+ foNumberById.set(m.floriday_id, m.order_number);
182
207
  }
183
- for (const o of filtered)
184
- o.printedCount = counts.get(o.id) ?? 0;
185
208
  }
186
209
  }
187
- // Sort: not-printed first, then by delivery date
188
- filtered.sort((a, b) => {
189
- if ((a.printedCount === 0) !== (b.printedCount === 0)) {
190
- return a.printedCount === 0 ? -1 : 1;
210
+ for (const row of payload.floriday ?? []) {
211
+ const data = (row.data ?? {});
212
+ const deliveryDate = pickDeliveryDate(data);
213
+ const planning = calculateOrderPlanning(deliveryDate, {
214
+ pickupMoments,
215
+ pickDurationWorkdays: pickDays,
216
+ orderCreatedAt: data.creationDateTime ??
217
+ data.createDateTime ??
218
+ data.orderDateTime ??
219
+ null,
220
+ });
221
+ const id = row.id ?? data.salesOrderId ?? crypto.randomUUID();
222
+ if (!planning) {
223
+ skippedWithoutPlanning.push({
224
+ source: "floriday",
225
+ id,
226
+ reason: deliveryDate ? "invalid deliveryDate" : "missing deliveryDate",
227
+ });
191
228
  }
192
- return (a.deliveryDate ?? "").localeCompare(b.deliveryDate ?? "");
193
- });
194
- return filtered;
195
- }
196
- export async function fetchFloridayAuctionLetterPdf(orderId) {
197
- // Best-effort: probe a couple of likely endpoints from Floriday's swagger.
198
- // If none returns a PDF, return null so the UI silently skips.
199
- const { supabaseAdmin } = await import("../integrations/supabase/client.server");
200
- const admin = supabaseAdmin;
201
- const conn = await getActiveConnection(admin);
202
- const baseUrl = FLORIDAY_BASE_URLS[conn.environment] ?? FLORIDAY_BASE_URLS.staging;
203
- const candidates = [
204
- `${baseUrl}/sales-orders/${orderId}/auction-letter`,
205
- `${baseUrl}/sales-orders/${orderId}/shipping-documents`,
206
- `${baseUrl}/sales-orders/${orderId}/documents/auction-letter`,
207
- ];
208
- for (const url of candidates) {
209
- try {
210
- const res = await fetch(url, {
211
- headers: {
212
- Authorization: `Bearer ${conn.access_token}`,
213
- "x-api-key": conn.api_key,
214
- Accept: "application/pdf",
215
- },
229
+ const carts = buildFloridayCarts(data);
230
+ items.push({
231
+ meta: {
232
+ source: "floriday",
233
+ id,
234
+ orderNumber: foNumberById.get(id) ??
235
+ pickStr(data, "customerOrderId", "salesChannelOrderId", "salesOrderNumber", "orderNumber") ??
236
+ "—",
237
+ customerName: row.customer_name || pickCustomer(data),
238
+ country: pickCountry(data),
239
+ deliveryDate,
240
+ },
241
+ carts,
242
+ cartCount: carts.length,
243
+ planning,
244
+ });
245
+ }
246
+ for (const row of payload.manual ?? []) {
247
+ const planning = calculateOrderPlanning(row.delivery_date, {
248
+ pickupMoments,
249
+ pickDurationWorkdays: pickDays,
250
+ orderCreatedAt: row.created_at,
251
+ });
252
+ if (!planning) {
253
+ skippedWithoutPlanning.push({
254
+ source: "manual",
255
+ id: row.id,
256
+ reason: row.delivery_date ? "invalid deliveryDate" : "missing deliveryDate",
216
257
  });
217
- if (!res.ok)
218
- continue;
219
- const ct = res.headers.get("content-type") ?? "";
220
- if (!ct.includes("pdf"))
221
- continue;
222
- const buf = new Uint8Array(await res.arrayBuffer());
223
- let bin = "";
224
- for (let i = 0; i < buf.length; i++)
225
- bin += String.fromCharCode(buf[i]);
226
- return { base64: btoa(bin), mime: "application/pdf" };
227
258
  }
228
- catch {
229
- // try next
259
+ const carts = buildManualCarts(row.items ?? []);
260
+ items.push({
261
+ meta: {
262
+ source: "manual",
263
+ id: row.id,
264
+ orderNumber: row.order_number ?? "—",
265
+ customerName: row.customer_name ?? "—",
266
+ country: null,
267
+ deliveryDate: row.delivery_date,
268
+ },
269
+ carts,
270
+ cartCount: carts.length,
271
+ planning,
272
+ });
273
+ }
274
+ if (skippedWithoutPlanning.length > 0) {
275
+ warn("orders skipped because planning could not be calculated", {
276
+ count: skippedWithoutPlanning.length,
277
+ sample: skippedWithoutPlanning.slice(0, 10),
278
+ });
279
+ }
280
+ const overrideMap = new Map();
281
+ for (const o of payload.overrides ?? []) {
282
+ overrideMap.set(`${o.order_source}#${o.order_id}#${o.cart_index}`, {
283
+ date: new Date(o.pick_date),
284
+ movedFromDate: o.moved_from_date ? new Date(o.moved_from_date) : null,
285
+ });
286
+ }
287
+ const distributed = distributePickDates(items, {
288
+ enabled: distributionEnabled,
289
+ maxWorkdaysEarlier: maxEarlier,
290
+ getCartCount: (it) => it.cartCount,
291
+ getCartOverride: (it, idx) => overrideMap.get(`${it.meta.source}#${it.meta.id}#${idx}`) ?? null,
292
+ });
293
+ const todayIso = toIsoDay(new Date());
294
+ const counts = payload.print_counts ?? {};
295
+ const out = [];
296
+ for (const it of distributed) {
297
+ if (!it.planning)
298
+ continue;
299
+ const todayAssignment = (it.planning.pickAssignments ?? []).find((a) => toIsoDay(a.date) === todayIso);
300
+ if (!todayAssignment)
301
+ continue;
302
+ const cartIndexes = todayAssignment.cartIndexes ?? [];
303
+ for (const idx of cartIndexes) {
304
+ const cart = it.carts[idx] ?? { lines: [], cratesOnCart: null };
305
+ const compoundId = `${it.meta.id}#${idx}`;
306
+ const c = counts[compoundId];
307
+ out.push({
308
+ id: compoundId,
309
+ orderId: it.meta.id,
310
+ orderNumber: it.meta.orderNumber,
311
+ customerName: it.meta.customerName,
312
+ country: it.meta.country,
313
+ deliveryDate: it.meta.deliveryDate,
314
+ pickDate: todayIso,
315
+ cartIndex: idx + 1,
316
+ cartTotal: it.cartCount,
317
+ cratesOnCart: cart.cratesOnCart,
318
+ lines: cart.lines,
319
+ printedCount: typeof c === "number" ? c : 0,
320
+ });
230
321
  }
231
322
  }
323
+ out.sort((a, b) => {
324
+ if ((a.printedCount === 0) !== (b.printedCount === 0)) {
325
+ return a.printedCount === 0 ? -1 : 1;
326
+ }
327
+ const d = (a.deliveryDate ?? "").localeCompare(b.deliveryDate ?? "");
328
+ if (d !== 0)
329
+ return d;
330
+ if (a.orderNumber !== b.orderNumber)
331
+ return a.orderNumber.localeCompare(b.orderNumber);
332
+ return a.cartIndex - b.cartIndex;
333
+ });
334
+ debug("load orders completed", {
335
+ returnedCarts: out.length,
336
+ totalSourceOrders: items.length,
337
+ today: todayIso,
338
+ durationMs: Date.now() - startedAt,
339
+ });
340
+ return out;
341
+ }
342
+ export async function fetchFloridayAuctionLetterPdf(_orderId) {
232
343
  return null;
233
344
  }
234
345
  export async function logPrint(orderId, orderNumber, customerName, documents, ipAddress) {
235
- const { supabaseAdmin } = await import("../integrations/supabase/client.server");
236
- const admin = supabaseAdmin;
237
- await admin.from("pickstation_print_log").insert({
238
- order_id: orderId,
239
- order_number: orderNumber,
240
- customer_name: customerName,
346
+ const supabase = getPublicClient();
347
+ debug("log print started", {
348
+ query: "rpc pickstation_log_print",
349
+ orderId,
350
+ orderNumber,
351
+ customerName,
241
352
  documents,
242
- ip_address: ipAddress,
243
353
  });
354
+ const { error } = await supabase.rpc("pickstation_log_print", {
355
+ p_order_id: orderId,
356
+ p_order_number: orderNumber,
357
+ p_customer_name: customerName,
358
+ p_documents: documents,
359
+ p_ip_address: ipAddress,
360
+ });
361
+ if (error) {
362
+ errorLog("log print failed", new Error(error.message), {
363
+ code: error.code,
364
+ details: error.details,
365
+ hint: error.hint,
366
+ orderId,
367
+ query: "rpc pickstation_log_print",
368
+ });
369
+ throw new Error(`Pickstation-log: ${error.message}`);
370
+ }
371
+ debug("log print completed", { orderId, documents: documents.length });
244
372
  }
@@ -1,16 +1,25 @@
1
1
  export interface PickstationLine {
2
2
  description: string;
3
- quantity: number | null;
3
+ /** Aantal fusten op deze kar voor deze regel. */
4
+ crates: number | null;
4
5
  unit: string | null;
5
6
  }
6
7
  export interface PickstationOrder {
8
+ /** Unieke id per kar: `${orderId}#${cartIndex}`. */
7
9
  id: string;
10
+ /** Order-id (zonder kar-suffix). */
11
+ orderId: string;
8
12
  orderNumber: string;
9
13
  customerName: string;
10
14
  country: string | null;
11
15
  deliveryDate: string | null;
12
16
  pickDate: string;
13
- lineCount: number;
17
+ /** 1-based volgnummer van de kar binnen de order. */
18
+ cartIndex: number;
19
+ /** Totaal aantal karren van de order. */
20
+ cartTotal: number;
21
+ /** Totaal aantal fusten op deze kar (som van alle regels). null = onbekend. */
22
+ cratesOnCart: number | null;
14
23
  lines: PickstationLine[];
15
24
  printedCount: number;
16
25
  }
@@ -1 +1 @@
1
- {"version":3,"file":"pickstationTypes.d.ts","sourceRoot":"","sources":["../../src/lib/pickstationTypes.ts"],"names":[],"mappings":"AACA,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CACrB;AAED,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,eAAe,EAAE,CAAC;IACzB,YAAY,EAAE,MAAM,CAAC;CACtB"}
1
+ {"version":3,"file":"pickstationTypes.d.ts","sourceRoot":"","sources":["../../src/lib/pickstationTypes.ts"],"names":[],"mappings":"AACA,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,iDAAiD;IACjD,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CACrB;AAED,MAAM,WAAW,gBAAgB;IAC/B,oDAAoD;IACpD,EAAE,EAAE,MAAM,CAAC;IACX,oCAAoC;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,qDAAqD;IACrD,SAAS,EAAE,MAAM,CAAC;IAClB,yCAAyC;IACzC,SAAS,EAAE,MAAM,CAAC;IAClB,+EAA+E;IAC/E,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,KAAK,EAAE,eAAe,EAAE,CAAC;IACzB,YAAY,EAAE,MAAM,CAAC;CACtB"}
@@ -30,12 +30,20 @@ export interface PlanningRules {
30
30
  orderCreatedAt?: string | Date | null;
31
31
  holidays?: Date[];
32
32
  }
33
+ export interface PickAssignment {
34
+ date: Date;
35
+ cartCount: number;
36
+ /** Concrete kar-indexen op deze datum; nodig voor handmatig verplaatsen. */
37
+ cartIndexes?: number[];
38
+ }
33
39
  export interface OrderPlanning {
34
40
  deliveryDate: Date;
35
41
  pickupDate: Date;
36
42
  transportRegisterDate: Date;
37
43
  pickStartDate: Date;
38
44
  scheduledPickDate: Date;
45
+ /** Verdeling van karren over één of meerdere pickdagen. */
46
+ pickAssignments: PickAssignment[];
39
47
  movedEarlier: boolean;
40
48
  /** Welk moment is gekozen (handig voor debug / UI). */
41
49
  selectedMoment: PickupMoment;
@@ -55,12 +63,20 @@ export declare function getSignupDeadline(deliveryDate: Date, moment: PickupMome
55
63
  */
56
64
  export declare function selectPickupMoment(deliveryDate: Date, orderCreatedAt: Date | null | undefined, moments: PickupMoment[], holidays?: Date[]): PickupMoment;
57
65
  export declare function calculateOrderPlanning(deliveryDate: string | Date | null | undefined, rules: PlanningRules): OrderPlanning | null;
58
- export interface PickDistributionOptions {
66
+ export interface PickDistributionOptions<T = unknown> {
59
67
  enabled: boolean;
60
68
  maxWorkdaysEarlier: number;
61
69
  holidays?: Date[];
70
+ /** Aantal karren per item — bepaalt het gewicht in de verdeling. */
71
+ getCartCount?: (item: T) => number;
72
+ /** Manual per-cart overrides — geïndexeerd op (itemKey, cartIndex) → Date. */
73
+ getCartOverride?: (item: T, cartIndex: number) => Date | PickCartOverride | null;
74
+ }
75
+ export interface PickCartOverride {
76
+ date: Date;
77
+ movedFromDate?: Date | null;
62
78
  }
63
79
  export declare function distributePickDates<T extends {
64
80
  planning: OrderPlanning | null;
65
- }>(items: T[], options: PickDistributionOptions): T[];
81
+ }>(items: T[], options: PickDistributionOptions<T>): T[];
66
82
  //# sourceMappingURL=planning.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"planning.d.ts","sourceRoot":"","sources":["../../src/lib/planning.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,WAAW,YAAY;IAC3B,iEAAiE;IACjE,WAAW,EAAE,MAAM,CAAC;IACpB,6BAA6B;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,uDAAuD;IACvD,WAAW,EAAE,MAAM,CAAC;IACpB,4BAA4B;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,iEAAiE;IACjE,aAAa,EAAE,MAAM,CAAC;IACtB,4BAA4B;IAC5B,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,eAAO,MAAM,qBAAqB,EAAE,YAOnC,CAAC;AAEF,MAAM,WAAW,aAAa;IAC5B,iDAAiD;IACjD,aAAa,EAAE,YAAY,EAAE,CAAC;IAC9B,wDAAwD;IACxD,oBAAoB,EAAE,MAAM,CAAC;IAC7B,wEAAwE;IACxE,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAAC;IACtC,QAAQ,CAAC,EAAE,IAAI,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B,YAAY,EAAE,IAAI,CAAC;IACnB,UAAU,EAAE,IAAI,CAAC;IACjB,qBAAqB,EAAE,IAAI,CAAC;IAC5B,aAAa,EAAE,IAAI,CAAC;IACpB,iBAAiB,EAAE,IAAI,CAAC;IACxB,YAAY,EAAE,OAAO,CAAC;IACtB,uDAAuD;IACvD,cAAc,EAAE,YAAY,CAAC;CAC9B;AAUD,wBAAgB,SAAS,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,CAAC,EAAE,IAAI,EAAE,GAAG,OAAO,CAK7D;AAQD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,IAAI,EAAE,GAAG,IAAI,CAQ/E;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,IAAI,EAAE,GAAG,IAAI,CAQ1E;AAcD;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,YAAY,EAAE,IAAI,EAClB,MAAM,EAAE,YAAY,EACpB,QAAQ,CAAC,EAAE,IAAI,EAAE,GAChB,IAAI,CAGN;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,YAAY,EAAE,IAAI,EAClB,cAAc,EAAE,IAAI,GAAG,IAAI,GAAG,SAAS,EACvC,OAAO,EAAE,YAAY,EAAE,EACvB,QAAQ,CAAC,EAAE,IAAI,EAAE,GAChB,YAAY,CAgBd;AAED,wBAAgB,sBAAsB,CACpC,YAAY,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,GAAG,SAAS,EAC9C,KAAK,EAAE,aAAa,GACnB,aAAa,GAAG,IAAI,CA6BtB;AAED,MAAM,WAAW,uBAAuB;IACtC,OAAO,EAAE,OAAO,CAAC;IACjB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,EAAE,IAAI,EAAE,CAAC;CACnB;AAED,wBAAgB,mBAAmB,CAAC,CAAC,SAAS;IAAE,QAAQ,EAAE,aAAa,GAAG,IAAI,CAAA;CAAE,EAC9E,KAAK,EAAE,CAAC,EAAE,EACV,OAAO,EAAE,uBAAuB,GAC/B,CAAC,EAAE,CA2DL"}
1
+ {"version":3,"file":"planning.d.ts","sourceRoot":"","sources":["../../src/lib/planning.ts"],"names":[],"mappings":"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"}