@flowselections/floriday-afhandeling-module 1.0.9 → 1.0.11

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 (96) hide show
  1. package/dist-lib/components/afhandeling/ControleLijstPrint.d.ts +7 -0
  2. package/dist-lib/components/afhandeling/ControleLijstPrint.d.ts.map +1 -0
  3. package/dist-lib/components/afhandeling/ControleLijstPrint.js +67 -0
  4. package/dist-lib/components/afhandeling/ControlePage.d.ts +2 -0
  5. package/dist-lib/components/afhandeling/ControlePage.d.ts.map +1 -0
  6. package/dist-lib/components/afhandeling/ControlePage.js +456 -0
  7. package/dist-lib/components/afhandeling/DagDetailDialog.d.ts +9 -0
  8. package/dist-lib/components/afhandeling/DagDetailDialog.d.ts.map +1 -0
  9. package/dist-lib/components/afhandeling/DagDetailDialog.js +149 -0
  10. package/dist-lib/components/afhandeling/LeveringenPage.d.ts.map +1 -1
  11. package/dist-lib/components/afhandeling/LeveringenPage.js +11 -5
  12. package/dist-lib/components/afhandeling/PickAgendaView.d.ts.map +1 -1
  13. package/dist-lib/components/afhandeling/PickAgendaView.js +13 -4
  14. package/dist-lib/components/afhandeling/PlanningAgendaPage.d.ts.map +1 -1
  15. package/dist-lib/components/afhandeling/PlanningAgendaPage.js +2 -2
  16. package/dist-lib/components/afhandeling/ServiceAreaSelect.d.ts +12 -0
  17. package/dist-lib/components/afhandeling/ServiceAreaSelect.d.ts.map +1 -0
  18. package/dist-lib/components/afhandeling/ServiceAreaSelect.js +79 -0
  19. package/dist-lib/components/afhandeling/VervoerderForm.d.ts.map +1 -1
  20. package/dist-lib/components/afhandeling/VervoerderForm.js +62 -6
  21. package/dist-lib/components/afhandeling/VervoerdersPage.d.ts.map +1 -1
  22. package/dist-lib/components/afhandeling/VervoerdersPage.js +25 -6
  23. package/dist-lib/components/afhandeling/WeekAgendaView.d.ts +2 -1
  24. package/dist-lib/components/afhandeling/WeekAgendaView.d.ts.map +1 -1
  25. package/dist-lib/components/afhandeling/WeekAgendaView.js +63 -18
  26. package/dist-lib/components/afhandeling/WeekdayPicker.d.ts +12 -0
  27. package/dist-lib/components/afhandeling/WeekdayPicker.d.ts.map +1 -0
  28. package/dist-lib/components/afhandeling/WeekdayPicker.js +30 -0
  29. package/dist-lib/components/pickstation/PakbonTemplate.d.ts +6 -2
  30. package/dist-lib/components/pickstation/PakbonTemplate.d.ts.map +1 -1
  31. package/dist-lib/components/pickstation/PakbonTemplate.js +37 -12
  32. package/dist-lib/components/settings/AfhandelingSettingsCard.d.ts.map +1 -1
  33. package/dist-lib/components/settings/AfhandelingSettingsCard.js +25 -6
  34. package/dist-lib/components/ui/responsive-dialog.d.ts +20 -0
  35. package/dist-lib/components/ui/responsive-dialog.d.ts.map +1 -0
  36. package/dist-lib/components/ui/responsive-dialog.js +44 -0
  37. package/dist-lib/hooks/useOrderPlanning.d.ts +3 -0
  38. package/dist-lib/hooks/useOrderPlanning.d.ts.map +1 -1
  39. package/dist-lib/hooks/useOrderPlanning.js +64 -12
  40. package/dist-lib/index.d.ts +1 -0
  41. package/dist-lib/index.d.ts.map +1 -1
  42. package/dist-lib/index.js +4 -2
  43. package/dist-lib/integrations/supabase/auth-middleware.d.ts +2898 -147
  44. package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
  45. package/dist-lib/integrations/supabase/client.d.ts +2898 -147
  46. package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
  47. package/dist-lib/integrations/supabase/client.server.d.ts +2898 -147
  48. package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
  49. package/dist-lib/integrations/supabase/types.d.ts +2986 -155
  50. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  51. package/dist-lib/integrations/supabase/types.js +1 -0
  52. package/dist-lib/lib/afhandelingSettings.functions.d.ts +10666 -5151
  53. package/dist-lib/lib/afhandelingSettings.functions.d.ts.map +1 -1
  54. package/dist-lib/lib/afhandelingSettings.functions.js +42 -2
  55. package/dist-lib/lib/carrierMatch.server.d.ts +27 -0
  56. package/dist-lib/lib/carrierMatch.server.d.ts.map +1 -0
  57. package/dist-lib/lib/carrierMatch.server.js +75 -0
  58. package/dist-lib/lib/carrierSignupMail.server.d.ts +56 -0
  59. package/dist-lib/lib/carrierSignupMail.server.d.ts.map +1 -0
  60. package/dist-lib/lib/carrierSignupMail.server.js +229 -0
  61. package/dist-lib/lib/carriers.functions.d.ts +36404 -15470
  62. package/dist-lib/lib/carriers.functions.d.ts.map +1 -1
  63. package/dist-lib/lib/carriers.functions.js +43 -2
  64. package/dist-lib/lib/cartAssignments.functions.d.ts +49609 -0
  65. package/dist-lib/lib/cartAssignments.functions.d.ts.map +1 -0
  66. package/dist-lib/lib/cartAssignments.functions.js +130 -0
  67. package/dist-lib/lib/cartControl.functions.d.ts +99259 -0
  68. package/dist-lib/lib/cartControl.functions.d.ts.map +1 -0
  69. package/dist-lib/lib/cartControl.functions.js +489 -0
  70. package/dist-lib/lib/cartMoveImpact.d.ts +8 -0
  71. package/dist-lib/lib/cartMoveImpact.d.ts.map +1 -0
  72. package/dist-lib/lib/cartMoveImpact.js +26 -0
  73. package/dist-lib/lib/cartRescheduling.d.ts +30 -0
  74. package/dist-lib/lib/cartRescheduling.d.ts.map +1 -0
  75. package/dist-lib/lib/cartRescheduling.js +71 -0
  76. package/dist-lib/lib/floriday.functions.d.ts +2902 -147
  77. package/dist-lib/lib/floriday.functions.d.ts.map +1 -1
  78. package/dist-lib/lib/floriday.functions.js +178 -98
  79. package/dist-lib/lib/geo/euServiceAreas.d.ts +30 -0
  80. package/dist-lib/lib/geo/euServiceAreas.d.ts.map +1 -0
  81. package/dist-lib/lib/geo/euServiceAreas.js +277 -0
  82. package/dist-lib/lib/invalidatePlanning.d.ts +11 -0
  83. package/dist-lib/lib/invalidatePlanning.d.ts.map +1 -0
  84. package/dist-lib/lib/invalidatePlanning.js +25 -0
  85. package/dist-lib/lib/pickstationI18n.d.ts +12 -0
  86. package/dist-lib/lib/pickstationI18n.d.ts.map +1 -1
  87. package/dist-lib/lib/pickstationI18n.js +72 -0
  88. package/dist-lib/lib/pickstationOrders.server.d.ts +2 -1
  89. package/dist-lib/lib/pickstationOrders.server.d.ts.map +1 -1
  90. package/dist-lib/lib/pickstationOrders.server.js +393 -178
  91. package/dist-lib/lib/pickstationTypes.d.ts +23 -2
  92. package/dist-lib/lib/pickstationTypes.d.ts.map +1 -1
  93. package/dist-lib/lib/planning.d.ts +41 -2
  94. package/dist-lib/lib/planning.d.ts.map +1 -1
  95. package/dist-lib/lib/planning.js +159 -36
  96. 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,382 @@ 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)}`);
104
- }
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 [];
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
+ }
114
+ }
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 pickWeekdays = Array.isArray(settingsRaw.pick_weekdays) && settingsRaw.pick_weekdays.length > 0
177
+ ? settingsRaw.pick_weekdays
178
+ : [1, 2, 3, 4, 5];
179
+ const carrierPickupWeekdays = Array.isArray(settingsRaw.carrier_pickup_weekdays) && settingsRaw.carrier_pickup_weekdays.length > 0
180
+ ? settingsRaw.carrier_pickup_weekdays
181
+ : [1, 2, 3, 4, 5];
182
+ const carrierDeliveryWeekdays = Array.isArray(settingsRaw.carrier_delivery_weekdays) && settingsRaw.carrier_delivery_weekdays.length > 0
183
+ ? settingsRaw.carrier_delivery_weekdays
184
+ : [1, 2, 3, 4, 5];
185
+ const payload = (ordersRes.data ?? {
186
+ floriday: [],
187
+ manual: [],
188
+ overrides: [],
189
+ print_counts: {},
190
+ });
191
+ debug("raw data loaded", {
192
+ floridayOrders: payload.floriday?.length ?? 0,
193
+ manualOrders: payload.manual?.length ?? 0,
194
+ overrides: payload.overrides?.length ?? 0,
195
+ printLogEntries: Object.keys(payload.print_counts ?? {}).length,
196
+ pickDays,
197
+ distributionEnabled,
198
+ maxEarlier,
199
+ pickupMoments: pickupMoments.length,
200
+ });
201
+ const items = [];
202
+ const skippedWithoutPlanning = [];
203
+ // Batch: reserve/lookup FO-nummers voor Floriday-orders
204
+ const foNumberById = new Map();
205
+ const floridayIds = (payload.floriday ?? [])
206
+ .map((r) => (r.id ?? r.data?.salesOrderId) ?? null)
207
+ .filter((v) => !!v);
208
+ if (floridayIds.length > 0) {
209
+ const { data: mapped, error: mapErr } = await supabase.rpc("assign_floriday_order_numbers", { p_ids: floridayIds });
210
+ if (mapErr) {
211
+ warn("assign_floriday_order_numbers failed", { message: mapErr.message });
212
+ }
213
+ else {
214
+ for (const m of (mapped ?? [])) {
215
+ foNumberById.set(m.floriday_id, m.order_number);
182
216
  }
183
- for (const o of filtered)
184
- o.printedCount = counts.get(o.id) ?? 0;
185
217
  }
186
218
  }
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;
219
+ for (const row of payload.floriday ?? []) {
220
+ const data = (row.data ?? {});
221
+ const deliveryDate = pickDeliveryDate(data);
222
+ const planning = calculateOrderPlanning(deliveryDate, {
223
+ pickupMoments,
224
+ pickDurationWorkdays: pickDays,
225
+ orderCreatedAt: data.creationDateTime ??
226
+ data.createDateTime ??
227
+ data.orderDateTime ??
228
+ null,
229
+ pickWeekdays,
230
+ carrierPickupWeekdays,
231
+ carrierDeliveryWeekdays,
232
+ });
233
+ const id = row.id ?? data.salesOrderId ?? crypto.randomUUID();
234
+ if (!planning) {
235
+ skippedWithoutPlanning.push({
236
+ source: "floriday",
237
+ id,
238
+ reason: deliveryDate ? "invalid deliveryDate" : "missing deliveryDate",
239
+ });
240
+ }
241
+ const carts = buildFloridayCarts(data);
242
+ items.push({
243
+ meta: {
244
+ source: "floriday",
245
+ id,
246
+ orderNumber: foNumberById.get(id) ??
247
+ pickStr(data, "customerOrderId", "salesChannelOrderId", "salesOrderNumber", "orderNumber") ??
248
+ "—",
249
+ customerName: row.customer_name || pickCustomer(data),
250
+ country: pickCountry(data),
251
+ deliveryDate,
252
+ },
253
+ carts,
254
+ cartCount: carts.length,
255
+ planning,
256
+ });
257
+ }
258
+ for (const row of payload.manual ?? []) {
259
+ const planning = calculateOrderPlanning(row.delivery_date, {
260
+ pickupMoments,
261
+ pickDurationWorkdays: pickDays,
262
+ orderCreatedAt: row.created_at,
263
+ pickWeekdays,
264
+ carrierPickupWeekdays,
265
+ carrierDeliveryWeekdays,
266
+ });
267
+ if (!planning) {
268
+ skippedWithoutPlanning.push({
269
+ source: "manual",
270
+ id: row.id,
271
+ reason: row.delivery_date ? "invalid deliveryDate" : "missing deliveryDate",
272
+ });
191
273
  }
192
- return (a.deliveryDate ?? "").localeCompare(b.deliveryDate ?? "");
274
+ const carts = buildManualCarts(row.items ?? []);
275
+ items.push({
276
+ meta: {
277
+ source: "manual",
278
+ id: row.id,
279
+ orderNumber: row.order_number ?? "—",
280
+ customerName: row.customer_name ?? "—",
281
+ country: null,
282
+ deliveryDate: row.delivery_date,
283
+ },
284
+ carts,
285
+ cartCount: carts.length,
286
+ planning,
287
+ });
288
+ }
289
+ if (skippedWithoutPlanning.length > 0) {
290
+ warn("orders skipped because planning could not be calculated", {
291
+ count: skippedWithoutPlanning.length,
292
+ sample: skippedWithoutPlanning.slice(0, 10),
293
+ });
294
+ }
295
+ const overrideMap = new Map();
296
+ for (const o of payload.overrides ?? []) {
297
+ overrideMap.set(`${o.order_source}#${o.order_id}#${o.cart_index}`, {
298
+ date: new Date(o.pick_date),
299
+ movedFromDate: o.moved_from_date ? new Date(o.moved_from_date) : null,
300
+ });
301
+ }
302
+ const distributed = distributePickDates(items, {
303
+ enabled: distributionEnabled,
304
+ maxWorkdaysEarlier: maxEarlier,
305
+ pickWeekdays,
306
+ getCartCount: (it) => it.cartCount,
307
+ getCartOverride: (it, idx) => overrideMap.get(`${it.meta.source}#${it.meta.id}#${idx}`) ?? null,
193
308
  });
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
- },
216
- });
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" };
309
+ const todayIso = toIsoDay(new Date());
310
+ const counts = payload.print_counts ?? {};
311
+ const cartAssignments = [];
312
+ for (const it of distributed) {
313
+ if (!it.planning)
314
+ continue;
315
+ for (const a of it.planning.pickAssignments ?? []) {
316
+ const pickIso = toIsoDay(a.date);
317
+ for (const idx of a.cartIndexes ?? []) {
318
+ cartAssignments.push({
319
+ source: it.meta.source,
320
+ order_id: it.meta.id,
321
+ cart_index: idx,
322
+ pick_date: pickIso,
323
+ });
324
+ }
227
325
  }
228
- catch {
229
- // try next
326
+ }
327
+ const cartNumberMap = new Map();
328
+ const cartStatusMap = new Map();
329
+ if (cartAssignments.length > 0) {
330
+ const { data: assigned, error: assignErr } = await supabase.rpc("assign_cart_numbers", { p_carts: cartAssignments });
331
+ if (assignErr) {
332
+ warn("assign_cart_numbers failed", { message: assignErr.message });
333
+ }
334
+ else {
335
+ for (const row of (assigned ?? [])) {
336
+ const key = `${row.order_source}#${row.order_id}#${row.cart_index}`;
337
+ cartNumberMap.set(key, row.cart_number);
338
+ if (row.status)
339
+ cartStatusMap.set(key, row.status);
340
+ }
230
341
  }
231
342
  }
343
+ const out = [];
344
+ for (const it of distributed) {
345
+ if (!it.planning)
346
+ continue;
347
+ const todayAssignment = (it.planning.pickAssignments ?? []).find((a) => toIsoDay(a.date) === todayIso);
348
+ if (!todayAssignment)
349
+ continue;
350
+ const cartIndexes = todayAssignment.cartIndexes ?? [];
351
+ for (const idx of cartIndexes) {
352
+ const cart = it.carts[idx] ?? { lines: [], cratesOnCart: null };
353
+ const compoundId = `${it.meta.id}#${idx}`;
354
+ const c = counts[compoundId];
355
+ const printedCount = typeof c === "number" ? c : 0;
356
+ const key = `${it.meta.source}#${it.meta.id}#${idx}`;
357
+ const manualStatus = cartStatusMap.get(key) ?? null;
358
+ // Effective status: user-set status wins over auto printed/not_printed.
359
+ const status = manualStatus && manualStatus !== "pending"
360
+ ? manualStatus
361
+ : printedCount > 0
362
+ ? "printed"
363
+ : "not_printed";
364
+ out.push({
365
+ id: compoundId,
366
+ orderId: it.meta.id,
367
+ orderSource: it.meta.source,
368
+ orderNumber: it.meta.orderNumber,
369
+ customerName: it.meta.customerName,
370
+ country: it.meta.country,
371
+ deliveryDate: it.meta.deliveryDate,
372
+ pickupDate: it.planning?.pickupDate ? toIsoDay(it.planning.pickupDate) : null,
373
+ carrierName: it.planning?.carrierName ?? null,
374
+ pickDate: todayIso,
375
+ cartIndex: idx + 1,
376
+ cartTotal: it.cartCount,
377
+ cratesOnCart: cart.cratesOnCart,
378
+ lines: cart.lines,
379
+ printedCount,
380
+ cartNumber: cartNumberMap.get(key) ?? null,
381
+ status,
382
+ manualStatus,
383
+ });
384
+ }
385
+ }
386
+ // Sorteer puur op leverdatum (oudste eerst), dan levertijd (deel van ISO-string),
387
+ // dan karnummer, dan cart_index. Geprinte karren blijven op hun positie staan.
388
+ out.sort((a, b) => {
389
+ const ad = a.deliveryDate ?? "\uFFFF";
390
+ const bd = b.deliveryDate ?? "\uFFFF";
391
+ if (ad !== bd)
392
+ return ad < bd ? -1 : 1;
393
+ const an = a.cartNumber ?? "";
394
+ const bn = b.cartNumber ?? "";
395
+ if (an !== bn)
396
+ return an.localeCompare(bn);
397
+ if (a.orderNumber !== b.orderNumber)
398
+ return a.orderNumber.localeCompare(b.orderNumber);
399
+ return a.cartIndex - b.cartIndex;
400
+ });
401
+ debug("load orders completed", {
402
+ returnedCarts: out.length,
403
+ totalSourceOrders: items.length,
404
+ today: todayIso,
405
+ durationMs: Date.now() - startedAt,
406
+ });
407
+ return out;
408
+ }
409
+ export async function fetchFloridayAuctionLetterPdf(_orderId) {
232
410
  return null;
233
411
  }
412
+ export async function setCartStatus(orderSource, orderId, cartIndex, status) {
413
+ const supabase = getPublicClient();
414
+ const { error } = await supabase.rpc("pickstation_set_cart_status", {
415
+ p_order_source: orderSource,
416
+ p_order_id: orderId,
417
+ p_cart_index: cartIndex,
418
+ p_status: status,
419
+ });
420
+ if (error) {
421
+ errorLog("set cart status failed", new Error(error.message), {
422
+ code: error.code,
423
+ orderSource,
424
+ orderId,
425
+ cartIndex,
426
+ status,
427
+ });
428
+ throw new Error(`Pickstation-status: ${error.message}`);
429
+ }
430
+ debug("set cart status ok", { orderSource, orderId, cartIndex, status });
431
+ }
234
432
  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,
433
+ const supabase = getPublicClient();
434
+ debug("log print started", {
435
+ query: "rpc pickstation_log_print",
436
+ orderId,
437
+ orderNumber,
438
+ customerName,
241
439
  documents,
242
- ip_address: ipAddress,
243
440
  });
441
+ const { error } = await supabase.rpc("pickstation_log_print", {
442
+ p_order_id: orderId,
443
+ p_order_number: orderNumber,
444
+ p_customer_name: customerName,
445
+ p_documents: documents,
446
+ p_ip_address: ipAddress,
447
+ });
448
+ if (error) {
449
+ errorLog("log print failed", new Error(error.message), {
450
+ code: error.code,
451
+ details: error.details,
452
+ hint: error.hint,
453
+ orderId,
454
+ query: "rpc pickstation_log_print",
455
+ });
456
+ throw new Error(`Pickstation-log: ${error.message}`);
457
+ }
458
+ debug("log print completed", { orderId, documents: documents.length });
244
459
  }
@@ -1,17 +1,38 @@
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
  }
7
+ export type CartStatus = "not_printed" | "printed" | "checked" | "ready" | "shipped" | "delivered";
8
+ export type CartManualStatus = "pending" | "checked" | "ready" | "shipped" | "delivered";
6
9
  export interface PickstationOrder {
10
+ /** Unieke id per kar: `${orderId}#${cartIndex}`. */
7
11
  id: string;
12
+ /** Order-id (zonder kar-suffix). */
13
+ orderId: string;
14
+ /** 'floriday' of 'manual' — nodig voor status-mutaties. */
15
+ orderSource: "floriday" | "manual";
8
16
  orderNumber: string;
9
17
  customerName: string;
10
18
  country: string | null;
11
19
  deliveryDate: string | null;
20
+ pickupDate: string | null;
21
+ carrierName: string | null;
12
22
  pickDate: string;
13
- lineCount: number;
23
+ /** 1-based volgnummer van de kar binnen de order. */
24
+ cartIndex: number;
25
+ /** Totaal aantal karren van de order. */
26
+ cartTotal: number;
27
+ /** Totaal aantal fusten op deze kar (som van alle regels). null = onbekend. */
28
+ cratesOnCart: number | null;
14
29
  lines: PickstationLine[];
15
30
  printedCount: number;
31
+ /** Permanent uniek karnummer, formaat 'DAG-DD-N' (bv. 'MA-01-3'). */
32
+ cartNumber: string | null;
33
+ /** Effectieve status voor de UI (afgeleid uit handmatige status + printedCount). */
34
+ status: CartStatus;
35
+ /** Handmatige status uit cart_numbers.status (null bij nog niet toegewezen). */
36
+ manualStatus: CartManualStatus | null;
16
37
  }
17
38
  //# sourceMappingURL=pickstationTypes.d.ts.map
@@ -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,MAAM,UAAU,GAClB,aAAa,GACb,SAAS,GACT,SAAS,GACT,OAAO,GACP,SAAS,GACT,WAAW,CAAC;AAEhB,MAAM,MAAM,gBAAgB,GACxB,SAAS,GACT,SAAS,GACT,OAAO,GACP,SAAS,GACT,WAAW,CAAC;AAEhB,MAAM,WAAW,gBAAgB;IAC/B,oDAAoD;IACpD,EAAE,EAAE,MAAM,CAAC;IACX,oCAAoC;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,2DAA2D;IAC3D,WAAW,EAAE,UAAU,GAAG,QAAQ,CAAC;IACnC,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,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;IACrB,qEAAqE;IACrE,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,oFAAoF;IACpF,MAAM,EAAE,UAAU,CAAC;IACnB,gFAAgF;IAChF,YAAY,EAAE,gBAAgB,GAAG,IAAI,CAAC;CACvC"}