@flowselections/floriday-afhandeling-module 1.0.18 → 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.
@@ -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":"AAoBA,OAAO,KAAK,EAAmB,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAC5E,YAAY,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AA8P5E,wBAAsB,2BAA2B,IAAI,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAwW/E;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,GAChE,OAAO,CAAC,IAAI,CAAC,CAmBf;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,GACvB,OAAO,CAAC,IAAI,CAAC,CA2Bf"}
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,3 @@
1
+ import type { SupabaseClient } from "@supabase/supabase-js";
2
+ export declare function handleDispatchCarrierSignups(_request: Request, injected?: SupabaseClient): Promise<Response>;
3
+ //# sourceMappingURL=dispatchCarrierSignups.handler.d.ts.map
@@ -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,3 @@
1
+ import { type SupabaseClient } from "@supabase/supabase-js";
2
+ export declare function handleLogisticsTick(_request: Request, injected?: SupabaseClient): Promise<Response>;
3
+ //# sourceMappingURL=logisticsTick.handler.d.ts.map
@@ -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,3 @@
1
+ import type { SupabaseClient } from "@supabase/supabase-js";
2
+ export declare function handlePickstationOrders(request: Request, supabase?: SupabaseClient): Promise<Response>;
3
+ //# sourceMappingURL=pickstationOrders.handler.d.ts.map
@@ -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,3 @@
1
+ import type { SupabaseClient } from "@supabase/supabase-js";
2
+ export declare function handlePickstationPrint(request: Request, supabase?: SupabaseClient): Promise<Response>;
3
+ //# sourceMappingURL=pickstationPrint.handler.d.ts.map
@@ -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"}
@@ -0,0 +1,116 @@
1
+ // Very small in-memory rate limit per worker instance: max 60 prints / minute per IP.
2
+ const buckets = new Map();
3
+ function rateLimited(ip) {
4
+ const now = Date.now();
5
+ const b = buckets.get(ip);
6
+ if (!b || now - b.ts > 60000) {
7
+ buckets.set(ip, { ts: now, count: 1 });
8
+ return false;
9
+ }
10
+ b.count++;
11
+ return b.count > 60;
12
+ }
13
+ export async function handlePickstationPrint(request, supabase) {
14
+ const requestId = crypto.randomUUID();
15
+ const startedAt = Date.now();
16
+ const ip = request.headers.get("cf-connecting-ip") ??
17
+ request.headers.get("x-forwarded-for")?.split(",")[0]?.trim() ??
18
+ null;
19
+ console.info("[pickstation-api] POST print", {
20
+ requestId,
21
+ at: new Date().toISOString(),
22
+ ip: ip ? "present" : "missing",
23
+ });
24
+ if (ip && rateLimited(ip)) {
25
+ return Response.json({ error: "Te veel verzoeken" }, { status: 429 });
26
+ }
27
+ let body;
28
+ try {
29
+ body = await request.json();
30
+ }
31
+ catch {
32
+ return Response.json({ error: "Ongeldige body" }, { status: 400 });
33
+ }
34
+ if (!body.orderId || typeof body.orderId !== "string") {
35
+ return Response.json({ error: "orderId verplicht" }, { status: 400 });
36
+ }
37
+ const docs = Array.isArray(body.documents) ? body.documents.slice(0, 5) : [];
38
+ const wantsAuction = docs.includes("auction-letter");
39
+ const wantsDeliveryNote = docs.includes("delivery-note");
40
+ const isConfirmation = body.confirmed === true;
41
+ const { fetchFloridayAuctionLetterPdf, fetchFloridayDeliveryNotePdf, logPrint } = await import("../pickstationOrders.server");
42
+ const sourceOrderId = typeof body.sourceOrderId === "string" && body.sourceOrderId
43
+ ? body.sourceOrderId
44
+ : body.orderId;
45
+ let auctionLetter = null;
46
+ let deliveryNote = null;
47
+ if (!isConfirmation) {
48
+ const tasks = [];
49
+ if (wantsAuction) {
50
+ tasks.push((async () => {
51
+ try {
52
+ auctionLetter = await fetchFloridayAuctionLetterPdf(sourceOrderId);
53
+ }
54
+ catch (e) {
55
+ console.warn("[pickstation-api] auction letter unavailable", {
56
+ requestId,
57
+ sourceOrderId,
58
+ message: e instanceof Error ? e.message : String(e),
59
+ });
60
+ auctionLetter = null;
61
+ }
62
+ })());
63
+ }
64
+ if (wantsDeliveryNote) {
65
+ tasks.push((async () => {
66
+ try {
67
+ deliveryNote = await fetchFloridayDeliveryNotePdf(sourceOrderId);
68
+ }
69
+ catch (e) {
70
+ console.warn("[pickstation-api] delivery note unavailable", {
71
+ requestId,
72
+ sourceOrderId,
73
+ message: e instanceof Error ? e.message : String(e),
74
+ });
75
+ deliveryNote = null;
76
+ }
77
+ })());
78
+ }
79
+ if (tasks.length > 0)
80
+ await Promise.all(tasks);
81
+ }
82
+ const printedDocs = docs;
83
+ let warning;
84
+ if (isConfirmation && printedDocs.length > 0) {
85
+ try {
86
+ await logPrint(body.orderId, body.orderNumber ?? null, body.customerName ?? null, printedDocs, ip, supabase);
87
+ }
88
+ catch (e) {
89
+ const message = e instanceof Error ? e.message : "Onbekende fout";
90
+ warning = `De printstatus kon niet worden opgeslagen: ${message}`;
91
+ console.error("[pickstation-api] print log failed", {
92
+ requestId,
93
+ orderId: body.orderId,
94
+ message,
95
+ stack: e instanceof Error ? e.stack : undefined,
96
+ });
97
+ }
98
+ }
99
+ console.info("[pickstation-api] POST print completed", {
100
+ requestId,
101
+ orderId: body.orderId,
102
+ printedDocuments: printedDocs,
103
+ auctionLetter: auctionLetter ? "present" : "absent",
104
+ deliveryNote: deliveryNote ? "present" : "absent",
105
+ warning: warning ?? null,
106
+ durationMs: Date.now() - startedAt,
107
+ });
108
+ return Response.json({
109
+ ok: !warning,
110
+ warning,
111
+ auctionLetter,
112
+ deliveryNote,
113
+ printedDocuments: printedDocs,
114
+ requestId,
115
+ }, { headers: { "Cache-Control": "no-store", "X-Robots-Tag": "noindex, nofollow" } });
116
+ }
@@ -0,0 +1,3 @@
1
+ import type { SupabaseClient } from "@supabase/supabase-js";
2
+ export declare function handlePickstationStatus(request: Request, supabase?: SupabaseClient): Promise<Response>;
3
+ //# sourceMappingURL=pickstationStatus.handler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pickstationStatus.handler.d.ts","sourceRoot":"","sources":["../../../src/lib/serverRoutes/pickstationStatus.handler.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAI5D,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,OAAO,EAChB,QAAQ,CAAC,EAAE,cAAc,GACxB,OAAO,CAAC,QAAQ,CAAC,CAwCnB"}
@@ -0,0 +1,28 @@
1
+ const ALLOWED = ["pending", "checked", "ready", "shipped", "delivered"];
2
+ export async function handlePickstationStatus(request, supabase) {
3
+ const requestId = crypto.randomUUID();
4
+ let body;
5
+ try {
6
+ body = await request.json();
7
+ }
8
+ catch {
9
+ return Response.json({ error: "Ongeldige body" }, { status: 400 });
10
+ }
11
+ if (!body.orderId ||
12
+ (body.orderSource !== "floriday" && body.orderSource !== "manual") ||
13
+ typeof body.cartIndex !== "number" ||
14
+ !body.status ||
15
+ !ALLOWED.includes(body.status)) {
16
+ return Response.json({ error: "Ongeldige invoer" }, { status: 400 });
17
+ }
18
+ try {
19
+ const { setCartStatus } = await import("../pickstationOrders.server");
20
+ await setCartStatus(body.orderSource, body.orderId, body.cartIndex, body.status, supabase);
21
+ return Response.json({ ok: true, requestId }, { headers: { "Cache-Control": "no-store", "X-Robots-Tag": "noindex, nofollow" } });
22
+ }
23
+ catch (e) {
24
+ const message = e instanceof Error ? e.message : String(e);
25
+ console.error("[pickstation-api] set status failed", { requestId, message });
26
+ return Response.json({ ok: false, error: message }, { status: 500 });
27
+ }
28
+ }
package/package.json CHANGED
@@ -1,128 +1,154 @@
1
- {
2
- "name": "@flowselections/floriday-afhandeling-module",
3
- "version": "1.0.18",
4
- "private": false,
5
- "sideEffects": false,
6
- "type": "module",
7
- "main": "./dist-lib/index.js",
8
- "types": "./dist-lib/index.d.ts",
9
- "exports": {
10
- ".": {
11
- "types": "./dist-lib/index.d.ts",
12
- "default": "./dist-lib/index.js"
13
- },
14
- "./styles": "./dist-lib/styles.css"
15
- },
16
- "files": [
17
- "dist-lib"
18
- ],
19
- "publishConfig": {
20
- "access": "public"
21
- },
22
- "scripts": {
23
- "dev": "vite dev",
24
- "build": "vite build",
25
- "build:lib": "tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json && node -e \"require('fs').writeFileSync('dist-lib/styles.css', '@source \\\"./\\\";\\n')\"",
26
- "build:lib:watch": "tsc -p tsconfig.build.json --watch",
27
- "build:dev": "vite build --mode development",
28
- "preview": "vite preview",
29
- "lint": "eslint ."
30
- },
31
- "peerDependencies": {
32
- "@flowselections/core": "*",
33
- "@tanstack/react-query": "^5",
34
- "react": "^18 || ^19",
35
- "react-dom": "^18 || ^19"
36
- },
37
- "dependencies": {
38
- "@ai-sdk/openai-compatible": "^3.0.9",
39
- "@supabase/supabase-js": "^2.106.2",
40
- "ai": "^7.0.26"
41
- },
42
- "devDependencies": {
43
- "@cloudflare/vite-plugin": "^1.25.5",
44
- "@eslint/js": "^9.32.0",
45
- "@flowselections/core": "1.0.16",
46
- "@lovable.dev/vite-tanstack-config": "2.7.7",
47
- "@rsbuild/core": "^1.0.0",
48
- "@tailwindcss/vite": "^4.2.1",
49
- "@tanstack/react-query": "^5.83.0",
50
- "@tanstack/router-plugin": "^1.167.10",
51
- "@types/node": "^22.16.5",
52
- "@types/react": "^19.2.0",
53
- "@types/react-dom": "^19.2.0",
54
- "@vitejs/plugin-react": "^5.0.4",
55
- "eslint": "^9.32.0",
56
- "eslint-plugin-react-hooks": "^5.2.0",
57
- "eslint-plugin-react-refresh": "^0.4.20",
58
- "globals": "^15.15.0",
59
- "react": "^19.2.0",
60
- "react-dom": "^19.2.0",
61
- "tailwindcss": "^4.2.1",
62
- "tw-animate-css": "^1.3.4",
63
- "typescript": "^5.8.3",
64
- "tsc-alias": "^1.8.0",
65
- "typescript-eslint": "^8.56.1",
66
- "vite": "^7.3.1",
67
- "vite-tsconfig-paths": "^6.0.2",
68
- "nitro": "3.0.260603-beta"
69
- },
70
- "flowselections": {
71
- "moduleId": "Afhandeling",
72
- "pages": [
73
- {
74
- "export": "LeveringenPage",
75
- "route": "afhandeling/leveringen",
76
- "title": "Planning en Leveringen"
77
- },
78
- {
79
- "export": "NieuweLeveringPage",
80
- "route": "afhandeling/leveringen/nieuw",
81
- "title": "Levering aanmaken",
82
- "nav": false
83
- },
84
- {
85
- "export": "PickingPage",
86
- "route": "afhandeling/picking",
87
- "title": "Pick Agenda"
88
- },
89
- {
90
- "export": "ControlePage",
91
- "route": "afhandeling/controle",
92
- "title": "Controle Karren"
93
- },
94
- {
95
- "export": "PickstationPage",
96
- "route": "pickstation",
97
- "title": "Pickstation",
98
- "nav": false,
99
- "public": true
100
- },
101
- {
102
- "export": "KioskControle",
103
- "route": "controle",
104
- "title": "KioskControle",
105
- "nav": false,
106
- "public": true
107
- },
108
- {
109
- "export": "VervoerdersPage",
110
- "route": "afhandeling/vervoerders",
111
- "title": "Vervoerders",
112
- "nav": false
113
- },
114
- {
115
- "export": "VervoerderForm",
116
- "route": "afhandeling/vervoerders/nieuw",
117
- "title": "Nieuwe vervoerder",
118
- "nav": false
119
- },
120
- {
121
- "export": "VervoerderForm",
122
- "route": "afhandeling/vervoerders/$id/bewerken",
123
- "title": "Vervoerder bewerken",
124
- "nav": false
125
- }
126
- ]
127
- }
128
- }
1
+ {
2
+ "name": "@flowselections/floriday-afhandeling-module",
3
+ "version": "1.0.19",
4
+ "private": false,
5
+ "sideEffects": false,
6
+ "type": "module",
7
+ "main": "./dist-lib/index.js",
8
+ "types": "./dist-lib/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist-lib/index.d.ts",
12
+ "default": "./dist-lib/index.js"
13
+ },
14
+ "./server-routes": {
15
+ "types": "./dist-lib/lib/serverRoutes/index.d.ts",
16
+ "default": "./dist-lib/lib/serverRoutes/index.js"
17
+ },
18
+ "./styles": "./dist-lib/styles.css"
19
+ },
20
+
21
+ "files": [
22
+ "dist-lib"
23
+ ],
24
+ "publishConfig": {
25
+ "access": "public"
26
+ },
27
+ "scripts": {
28
+ "dev": "vite dev",
29
+ "build": "vite build",
30
+ "build:lib": "tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json && node -e \"require('fs').writeFileSync('dist-lib/styles.css', '@source \\\"./\\\";\\n')\"",
31
+ "build:lib:watch": "tsc -p tsconfig.build.json --watch",
32
+ "build:dev": "vite build --mode development",
33
+ "preview": "vite preview",
34
+ "lint": "eslint ."
35
+ },
36
+ "peerDependencies": {
37
+ "@flowselections/core": "*",
38
+ "@tanstack/react-query": "^5",
39
+ "react": "^18 || ^19",
40
+ "react-dom": "^18 || ^19"
41
+ },
42
+ "dependencies": {
43
+ "@ai-sdk/openai-compatible": "^3.0.9",
44
+ "@supabase/supabase-js": "^2.106.2",
45
+ "ai": "^7.0.26"
46
+ },
47
+ "devDependencies": {
48
+ "@cloudflare/vite-plugin": "^1.25.5",
49
+ "@eslint/js": "^9.32.0",
50
+ "@flowselections/core": "1.0.16",
51
+ "@lovable.dev/vite-tanstack-config": "2.7.7",
52
+ "@rsbuild/core": "^1.0.0",
53
+ "@tailwindcss/vite": "^4.2.1",
54
+ "@tanstack/react-query": "^5.83.0",
55
+ "@tanstack/router-plugin": "^1.167.10",
56
+ "@types/node": "^22.16.5",
57
+ "@types/react": "^19.2.0",
58
+ "@types/react-dom": "^19.2.0",
59
+ "@vitejs/plugin-react": "^5.0.4",
60
+ "eslint": "^9.32.0",
61
+ "eslint-plugin-react-hooks": "^5.2.0",
62
+ "eslint-plugin-react-refresh": "^0.4.20",
63
+ "globals": "^15.15.0",
64
+ "react": "^19.2.0",
65
+ "react-dom": "^19.2.0",
66
+ "tailwindcss": "^4.2.1",
67
+ "tw-animate-css": "^1.3.4",
68
+ "typescript": "^5.8.3",
69
+ "tsc-alias": "^1.8.0",
70
+ "typescript-eslint": "^8.56.1",
71
+ "vite": "^7.3.1",
72
+ "vite-tsconfig-paths": "^6.0.2",
73
+ "nitro": "3.0.260603-beta"
74
+ },
75
+ "flowselections": {
76
+ "moduleId": "Afhandeling",
77
+ "pages": [
78
+ {
79
+ "export": "LeveringenPage",
80
+ "route": "afhandeling/leveringen",
81
+ "title": "Planning en Leveringen"
82
+ },
83
+ {
84
+ "export": "NieuweLeveringPage",
85
+ "route": "afhandeling/leveringen/nieuw",
86
+ "title": "Levering aanmaken",
87
+ "nav": false
88
+ },
89
+ {
90
+ "export": "PickingPage",
91
+ "route": "afhandeling/picking",
92
+ "title": "Pick Agenda"
93
+ },
94
+ {
95
+ "export": "ControlePage",
96
+ "route": "afhandeling/controle",
97
+ "title": "Controle Karren"
98
+ },
99
+ {
100
+ "export": "PickstationPage",
101
+ "route": "pickstation",
102
+ "title": "Pickstation",
103
+ "nav": false,
104
+ "public": true
105
+ },
106
+ {
107
+ "export": "KioskControle",
108
+ "route": "controle",
109
+ "title": "KioskControle",
110
+ "nav": false,
111
+ "public": true
112
+ },
113
+ {
114
+ "export": "VervoerdersPage",
115
+ "route": "afhandeling/vervoerders",
116
+ "title": "Vervoerders",
117
+ "nav": false
118
+ },
119
+ {
120
+ "export": "VervoerderForm",
121
+ "route": "afhandeling/vervoerders/nieuw",
122
+ "title": "Nieuwe vervoerder",
123
+ "nav": false
124
+ },
125
+ {
126
+ "export": "VervoerderForm",
127
+ "route": "afhandeling/vervoerders/$id/bewerken",
128
+ "title": "Vervoerder bewerken",
129
+ "nav": false
130
+ }
131
+ ],
132
+ "serverRoutes": [
133
+ {
134
+ "routePath": "/api/public/pickstation/orders",
135
+ "import": "@flowselections/floriday-afhandeling-module/server-routes",
136
+ "handler": "handlePickstationOrders",
137
+ "methods": ["GET"]
138
+ },
139
+ {
140
+ "routePath": "/api/public/pickstation/print",
141
+ "import": "@flowselections/floriday-afhandeling-module/server-routes",
142
+ "handler": "handlePickstationPrint",
143
+ "methods": ["POST"]
144
+ },
145
+ {
146
+ "routePath": "/api/public/pickstation/status",
147
+ "import": "@flowselections/floriday-afhandeling-module/server-routes",
148
+ "handler": "handlePickstationStatus",
149
+ "methods": ["POST"]
150
+ }
151
+ ]
152
+ }
153
+ }
154
+