@flowselections/floriday-verkoop-module 1.0.60 → 1.0.61
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-lib/components/floriday/FlorydaySyncHealthBanner.d.ts +9 -0
- package/dist-lib/components/floriday/FlorydaySyncHealthBanner.d.ts.map +1 -0
- package/dist-lib/components/floriday/FlorydaySyncHealthBanner.js +26 -0
- package/dist-lib/components/karindeling/AfmetingenTab.d.ts +6 -0
- package/dist-lib/components/karindeling/AfmetingenTab.d.ts.map +1 -0
- package/dist-lib/components/karindeling/AfmetingenTab.js +128 -0
- package/dist-lib/components/karindeling/KarindelingPage.d.ts +2 -0
- package/dist-lib/components/karindeling/KarindelingPage.d.ts.map +1 -0
- package/dist-lib/components/karindeling/KarindelingPage.js +102 -0
- package/dist-lib/components/ui/dropdown-menu.d.ts +9 -0
- package/dist-lib/components/ui/dropdown-menu.d.ts.map +1 -0
- package/dist-lib/components/ui/dropdown-menu.js +14 -0
- package/dist-lib/components/verkoop/AnalyticsCards.d.ts +1 -1
- package/dist-lib/components/verkoop/AnalyticsCards.d.ts.map +1 -1
- package/dist-lib/components/verkoop/AutoFulfillmentStatusCard.d.ts +1 -1
- package/dist-lib/components/verkoop/AutoFulfillmentStatusCard.d.ts.map +1 -1
- package/dist-lib/components/verkoop/CartAssignmentWizard.d.ts +1 -1
- package/dist-lib/components/verkoop/CartAssignmentWizard.d.ts.map +1 -1
- package/dist-lib/components/verkoop/CartLayersDialog.d.ts +1 -1
- package/dist-lib/components/verkoop/CartLayersDialog.d.ts.map +1 -1
- package/dist-lib/components/verkoop/CreateOrderDialog.d.ts +5 -0
- package/dist-lib/components/verkoop/CreateOrderDialog.d.ts.map +1 -0
- package/dist-lib/components/verkoop/CreateOrderDialog.js +33 -0
- package/dist-lib/components/verkoop/CreateOrderPage.d.ts +1 -1
- package/dist-lib/components/verkoop/CreateOrderPage.d.ts.map +1 -1
- package/dist-lib/components/verkoop/DiagnoseConnectionDialog.d.ts +1 -1
- package/dist-lib/components/verkoop/DiagnoseConnectionDialog.d.ts.map +1 -1
- package/dist-lib/components/verkoop/EditLocalOrderDialog.d.ts +1 -1
- package/dist-lib/components/verkoop/EditLocalOrderDialog.d.ts.map +1 -1
- package/dist-lib/components/verkoop/EngineBufferCard.d.ts +1 -1
- package/dist-lib/components/verkoop/EngineBufferCard.d.ts.map +1 -1
- package/dist-lib/components/verkoop/InvoiceTimingSettings.d.ts +1 -1
- package/dist-lib/components/verkoop/InvoiceTimingSettings.d.ts.map +1 -1
- package/dist-lib/components/verkoop/OrderAfleverbrievenCard.d.ts +1 -1
- package/dist-lib/components/verkoop/OrderAfleverbrievenCard.d.ts.map +1 -1
- package/dist-lib/components/verkoop/OrderCarrierPicker.d.ts +1 -1
- package/dist-lib/components/verkoop/OrderCarrierPicker.d.ts.map +1 -1
- package/dist-lib/components/verkoop/OrderControlStatusCard.d.ts +1 -1
- package/dist-lib/components/verkoop/OrderControlStatusCard.d.ts.map +1 -1
- package/dist-lib/components/verkoop/OrderKarindelingCard.d.ts +1 -1
- package/dist-lib/components/verkoop/OrderKarindelingCard.d.ts.map +1 -1
- package/dist-lib/components/verkoop/OrderPickupMomentPicker.d.ts +1 -1
- package/dist-lib/components/verkoop/OrderPickupMomentPicker.d.ts.map +1 -1
- package/dist-lib/components/verkoop/OrderShipmentsCard.d.ts +1 -1
- package/dist-lib/components/verkoop/OrderShipmentsCard.d.ts.map +1 -1
- package/dist-lib/components/verkoop/ProductDimensionsSettings.d.ts +1 -1
- package/dist-lib/components/verkoop/ProductDimensionsSettings.d.ts.map +1 -1
- package/dist-lib/components/verkoop/SalesOrderDetailPage.d.ts +1 -1
- package/dist-lib/components/verkoop/SalesOrderDetailPage.d.ts.map +1 -1
- package/dist-lib/components/verkoop/SalesOrderDetailView.d.ts +1 -1
- package/dist-lib/components/verkoop/SalesOrderDetailView.d.ts.map +1 -1
- package/dist-lib/components/verkoop/SalesOrdersFilterPanel.d.ts +1 -1
- package/dist-lib/components/verkoop/SalesOrdersFilterPanel.d.ts.map +1 -1
- package/dist-lib/components/verkoop/SalesOrdersFilters.d.ts +1 -1
- package/dist-lib/components/verkoop/SalesOrdersFilters.d.ts.map +1 -1
- package/dist-lib/components/verkoop/SalesOrdersTable.d.ts +1 -2
- package/dist-lib/components/verkoop/SalesOrdersTable.d.ts.map +1 -1
- package/dist-lib/components/verkoop/SearchableSelect.d.ts +1 -1
- package/dist-lib/components/verkoop/SearchableSelect.d.ts.map +1 -1
- package/dist-lib/components/verkoop/StockConsumptionAlertsBanner.d.ts +1 -1
- package/dist-lib/components/verkoop/StockConsumptionAlertsBanner.d.ts.map +1 -1
- package/dist-lib/components/verkoop/VerkoopOrdersPage.d.ts +1 -1
- package/dist-lib/components/verkoop/VerkoopOrdersPage.d.ts.map +1 -1
- package/dist-lib/components/verkoop/WeekCalendar.d.ts +1 -1
- package/dist-lib/components/verkoop/WeekCalendar.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/auth-middleware.d.ts +280 -0
- package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.server.d.ts +280 -0
- package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.d.ts +280 -0
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/lib/buffer-events.functions.d.ts +280 -0
- package/dist-lib/lib/buffer-events.functions.d.ts.map +1 -1
- package/dist-lib/lib/cart-layers.functions.d.ts +1124 -36
- package/dist-lib/lib/cart-layers.functions.d.ts.map +1 -1
- package/dist-lib/lib/cart-lock.functions.d.ts +280 -0
- package/dist-lib/lib/cart-lock.functions.d.ts.map +1 -1
- package/dist-lib/lib/cart-planning-settings.functions.d.ts +3080 -0
- package/dist-lib/lib/cart-planning-settings.functions.d.ts.map +1 -1
- package/dist-lib/lib/cart-replan.functions.d.ts +840 -0
- package/dist-lib/lib/cart-replan.functions.d.ts.map +1 -1
- package/dist-lib/lib/create-order.functions.d.ts +4777 -102
- package/dist-lib/lib/create-order.functions.d.ts.map +1 -1
- package/dist-lib/lib/cron/push-order-statuses.d.ts +4 -0
- package/dist-lib/lib/cron/push-order-statuses.d.ts.map +1 -0
- package/dist-lib/lib/cron/push-order-statuses.js +23 -0
- package/dist-lib/lib/floriday/floriday.server.d.ts +14 -0
- package/dist-lib/lib/floriday/floriday.server.d.ts.map +1 -0
- package/dist-lib/lib/floriday/floriday.server.js +92 -0
- package/dist-lib/lib/floriday/sales-orders.functions.d.ts +4736 -0
- package/dist-lib/lib/floriday/sales-orders.functions.d.ts.map +1 -0
- package/dist-lib/lib/floriday/sales-orders.functions.js +193 -0
- package/dist-lib/lib/floriday-auto-fulfillment.functions.d.ts +13288 -2362
- package/dist-lib/lib/floriday-auto-fulfillment.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-logistic-labels.functions.d.ts +560 -0
- package/dist-lib/lib/floriday-logistic-labels.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-mirror.functions.d.ts +280 -0
- package/dist-lib/lib/floriday-mirror.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-mirror.functions.js +282 -117
- package/dist-lib/lib/floriday-public.functions.d.ts +1960 -0
- package/dist-lib/lib/floriday-public.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-status-sync.server.d.ts +14 -0
- package/dist-lib/lib/floriday-status-sync.server.d.ts.map +1 -0
- package/dist-lib/lib/floriday-status-sync.server.js +130 -0
- package/dist-lib/lib/floriday-writes.functions.d.ts +560 -0
- package/dist-lib/lib/floriday-writes.functions.d.ts.map +1 -1
- package/dist-lib/lib/invoice-timing.functions.d.ts +1120 -0
- package/dist-lib/lib/invoice-timing.functions.d.ts.map +1 -1
- package/dist-lib/lib/load-carriers.functions.d.ts +280 -0
- package/dist-lib/lib/load-carriers.functions.d.ts.map +1 -1
- package/dist-lib/lib/logistics-engine.functions.d.ts +1405 -15
- package/dist-lib/lib/logistics-engine.functions.d.ts.map +1 -1
- package/dist-lib/lib/logistics-stock.functions.d.ts +560 -0
- package/dist-lib/lib/logistics-stock.functions.d.ts.map +1 -1
- package/dist-lib/lib/order-addresses.functions.d.ts +280 -0
- package/dist-lib/lib/order-addresses.functions.d.ts.map +1 -1
- package/dist-lib/lib/order-buffers.functions.d.ts +840 -0
- package/dist-lib/lib/order-buffers.functions.d.ts.map +1 -1
- package/dist-lib/lib/order-carrier.functions.d.ts +560 -0
- package/dist-lib/lib/order-carrier.functions.d.ts.map +1 -1
- package/dist-lib/lib/order-fulfill.functions.d.ts +280 -0
- package/dist-lib/lib/order-fulfill.functions.d.ts.map +1 -1
- package/dist-lib/lib/order-pickup.functions.d.ts +560 -0
- package/dist-lib/lib/order-pickup.functions.d.ts.map +1 -1
- package/dist-lib/lib/order-shipments.functions.d.ts +1120 -0
- package/dist-lib/lib/order-shipments.functions.d.ts.map +1 -1
- package/dist-lib/lib/product-dimensions.functions.d.ts +560 -0
- package/dist-lib/lib/product-dimensions.functions.d.ts.map +1 -1
- package/dist-lib/lib/salesorders-list.functions.d.ts +560 -0
- package/dist-lib/lib/salesorders-list.functions.d.ts.map +1 -1
- package/dist-lib/lib/salesorders-list.server.d.ts.map +1 -1
- package/dist-lib/lib/salesorders-list.server.js +62 -3
- package/dist-lib/lib/salesorders.functions.d.ts +1680 -0
- package/dist-lib/lib/salesorders.functions.d.ts.map +1 -1
- package/dist-lib/lib/salesorders.functions.js +6 -1
- package/dist-lib/lib/stock-consumption-alerts.functions.d.ts +560 -0
- package/dist-lib/lib/stock-consumption-alerts.functions.d.ts.map +1 -1
- package/dist-lib/lib/tradeitems.functions.d.ts +280 -0
- package/dist-lib/lib/tradeitems.functions.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -81,8 +81,9 @@ async function mirrorInternal(limit) {
|
|
|
81
81
|
}
|
|
82
82
|
const { data: existing } = await supabaseAdmin
|
|
83
83
|
.from("orders")
|
|
84
|
-
.select("id, floriday_sales_order_id, updated_at, custom_fields, status")
|
|
85
|
-
.in("floriday_sales_order_id", foIds)
|
|
84
|
+
.select("id, order_number, floriday_sales_order_id, floriday_order_group_key, updated_at, custom_fields, status, plan_locked_at, buffer_id")
|
|
85
|
+
.in("floriday_sales_order_id", foIds)
|
|
86
|
+
.is("deleted_at", null);
|
|
86
87
|
const existingByFoId = new Map();
|
|
87
88
|
for (const o of existing ?? []) {
|
|
88
89
|
existingByFoId.set(String(o.floriday_sales_order_id), o);
|
|
@@ -143,23 +144,38 @@ async function mirrorInternal(limit) {
|
|
|
143
144
|
let inserted = 0, updated = 0, skipped = 0, failed = 0;
|
|
144
145
|
const errors = [];
|
|
145
146
|
const affectedOrderIds = [];
|
|
146
|
-
|
|
147
|
+
// Statusmapping Floriday -> onze pickstatus. "gemeten" = feitelijk
|
|
148
|
+
// aangetroffen in floriday_salesorders_cache; "uit code" = staat wel in de
|
|
149
|
+
// Floriday-types/onze code, maar is nooit in de data gezien.
|
|
150
|
+
const FLORIDAY_STATUS_MAP = {
|
|
151
|
+
open: "Nog te picken", // uit code
|
|
152
|
+
new: "Nog te picken", // uit code
|
|
153
|
+
nieuw: "Nog te picken", // uit code
|
|
154
|
+
committed: "Nog te picken", // GEMETEN
|
|
155
|
+
confirmed: "Nog te picken", // uit code
|
|
156
|
+
accepted: "Nog te picken", // uit code
|
|
157
|
+
processing: "Nog te picken", // uit code
|
|
158
|
+
shipped: "Nog te picken", // uit code — Floriday-shipped is voor ons geen
|
|
159
|
+
delivered: "Nog te picken", // uit code — bewijs van vertrek; de karren-/
|
|
160
|
+
// shipmentroute bepaalt onze "Verzonden".
|
|
161
|
+
pending: "Nog te picken", // uit code
|
|
162
|
+
cancelled: "geannuleerd", // uit code
|
|
163
|
+
canceled: "geannuleerd", // uit code
|
|
164
|
+
rejected: "geannuleerd", // uit code
|
|
165
|
+
expired: "geannuleerd", // uit code
|
|
166
|
+
unfulfillable: "geannuleerd", // uit code
|
|
167
|
+
};
|
|
168
|
+
/** Eén Floriday-verkooporder = één toekomstige orderregel. */
|
|
169
|
+
function buildLine(row) {
|
|
147
170
|
const foId = String(row.floriday_id);
|
|
148
171
|
const d = row.data ?? {};
|
|
149
172
|
const orgId = String(d?.customerOrganizationId ?? "");
|
|
150
173
|
const cust = customerByOrgId.get(orgId);
|
|
151
174
|
const floCust = floCustByOrgId.get(orgId) ?? {};
|
|
152
|
-
const customerName = cust?.name ??
|
|
153
|
-
floCust?.commercialName ??
|
|
154
|
-
floCust?.name ??
|
|
155
|
-
"Onbekende klant";
|
|
175
|
+
const customerName = cust?.name ?? floCust?.commercialName ?? floCust?.name ?? "Onbekende klant";
|
|
156
176
|
const delivery = d?.delivery ?? {};
|
|
157
177
|
const deliveryIso = delivery?.latestDeliveryDateTime ?? "";
|
|
158
178
|
const orderDateIso = d?.orderDateTime ?? d?.creationDateTime ?? new Date().toISOString();
|
|
159
|
-
// Uitsluitend uit de map: dat nummer is het ERP-nummer dat overal (lijst,
|
|
160
|
-
// detail, pickstation) getoond wordt. Ontbreekt het, dan slaan we de order
|
|
161
|
-
// over — nooit zelf een nummer verzinnen.
|
|
162
|
-
const orderNumber = orderNumberByFoId.get(foId) ?? null;
|
|
163
179
|
const pickupLocation = pickString(d?.despatchWarehouseId ?? delivery?.pickupLocation);
|
|
164
180
|
const deliveryLocation = pickString(delivery?.location?.gln ??
|
|
165
181
|
delivery?.locationGln ??
|
|
@@ -196,126 +212,280 @@ async function mirrorInternal(limit) {
|
|
|
196
212
|
? Boolean(ff.received) || isFullyFulfilled(ff, expectedPackagesFromSalesOrder(d))
|
|
197
213
|
: false;
|
|
198
214
|
const rawStatus = String(d?.status ?? "").toLowerCase() || "open";
|
|
199
|
-
// Statusmapping Floriday -> onze pickstatus.
|
|
200
|
-
// "gemeten" = feitelijk aangetroffen in floriday_salesorders_cache (meting
|
|
201
|
-
// 2026-08-25: 1143 van 1143 rijen COMMITTED). "uit code" = staat wel in de
|
|
202
|
-
// Floriday-types/onze code, maar is nooit in de data gezien; daar houdt de
|
|
203
|
-
// zekerheid op.
|
|
204
|
-
const FLORIDAY_STATUS_MAP = {
|
|
205
|
-
open: "Nog te picken", // uit code
|
|
206
|
-
new: "Nog te picken", // uit code
|
|
207
|
-
nieuw: "Nog te picken", // uit code
|
|
208
|
-
committed: "Nog te picken", // GEMETEN (1143/1143)
|
|
209
|
-
confirmed: "Nog te picken", // uit code
|
|
210
|
-
accepted: "Nog te picken", // uit code
|
|
211
|
-
processing: "Nog te picken", // uit code
|
|
212
|
-
shipped: "Nog te picken", // uit code — Floriday-shipped is voor ons geen
|
|
213
|
-
delivered: "Nog te picken", // uit code — bewijs van vertrek; de karren-/
|
|
214
|
-
// shipmentroute bepaalt onze "Verzonden".
|
|
215
|
-
pending: "Nog te picken", // uit code
|
|
216
|
-
cancelled: "geannuleerd", // uit code
|
|
217
|
-
canceled: "geannuleerd", // uit code
|
|
218
|
-
rejected: "geannuleerd", // uit code
|
|
219
|
-
expired: "geannuleerd", // uit code
|
|
220
|
-
unfulfillable: "geannuleerd", // uit code
|
|
221
|
-
};
|
|
222
215
|
const mapped = FLORIDAY_STATUS_MAP[rawStatus];
|
|
223
216
|
if (!mapped) {
|
|
224
|
-
// Nooit stilzwijgend doorzetten: een onbekende waarde kan anders de
|
|
225
|
-
// consumptietrigger raken. Loggen met ruwe waarde zodat we hem kunnen
|
|
226
|
-
// toevoegen aan de mapping.
|
|
227
217
|
console.warn(`[floriday-mirror] onbekende Floriday-status "${rawStatus}" voor order ${foId} — val terug op "Nog te picken"`);
|
|
228
218
|
}
|
|
229
|
-
// fullyFulfilled betekent in Floriday alleen dat de beladingindeling is
|
|
230
|
-
// doorgegeven; niet dat de order de kwekerij heeft verlaten. Daarom raakt
|
|
231
|
-
// het de orderstatus niet meer, alleen custom_fields.
|
|
232
219
|
const status = mapped ?? "Nog te picken";
|
|
233
|
-
const
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
floriday_fulfilled_by_erp: fullyFulfilled && Boolean(ff?.createdByErp),
|
|
248
|
-
floriday_delivery_notes: ff?.deliveryNoteCodes ?? [],
|
|
249
|
-
floriday_received_at: ff?.receivedAt ?? null,
|
|
250
|
-
floriday_packing: {
|
|
251
|
-
pieces_per_package: piecesPerPackage,
|
|
252
|
-
packages_per_layer: packagesPerLayer,
|
|
253
|
-
layers_per_load_carrier: layersPerLoadCarrier,
|
|
254
|
-
// Fustcode en ladingdrager volgen dezelfde bronvolgorde: de order
|
|
255
|
-
// wint, het trade item is enkel terugval. Ontbreekt de code, dan
|
|
256
|
-
// blijft hij leeg — nooit een verzonnen waarde (800/999 zijn
|
|
257
|
-
// beweringen over de aanvoer, geen "onbekend").
|
|
258
|
-
vbn_package_code: d?.packingConfiguration?.package?.vbnPackageCode ??
|
|
259
|
-
(d?.packingConfiguration ? null : (packingCache?.package?.vbnPackageCode ?? null)),
|
|
260
|
-
custom_package_id: d?.packingConfiguration?.package?.customPackageId ??
|
|
261
|
-
(d?.packingConfiguration ? null : (packingCache?.package?.customPackageId ?? null)),
|
|
262
|
-
load_carrier: d?.packingConfiguration?.loadCarrier ??
|
|
263
|
-
(d?.packingConfiguration ? null : (packingCache?.loadCarrier ?? null)),
|
|
264
|
-
source: d?.packingConfiguration ? "order" : packingCache ? "trade-item" : null,
|
|
265
|
-
},
|
|
220
|
+
const packing = {
|
|
221
|
+
pieces_per_package: piecesPerPackage,
|
|
222
|
+
packages_per_layer: packagesPerLayer,
|
|
223
|
+
layers_per_load_carrier: layersPerLoadCarrier,
|
|
224
|
+
// Fustcode en ladingdrager volgen dezelfde bronvolgorde: de order wint,
|
|
225
|
+
// het trade item is enkel terugval. Ontbreekt de code, dan blijft hij
|
|
226
|
+
// leeg — nooit een verzonnen waarde.
|
|
227
|
+
vbn_package_code: d?.packingConfiguration?.package?.vbnPackageCode ??
|
|
228
|
+
(d?.packingConfiguration ? null : (packingCache?.package?.vbnPackageCode ?? null)),
|
|
229
|
+
custom_package_id: d?.packingConfiguration?.package?.customPackageId ??
|
|
230
|
+
(d?.packingConfiguration ? null : (packingCache?.package?.customPackageId ?? null)),
|
|
231
|
+
load_carrier: d?.packingConfiguration?.loadCarrier ??
|
|
232
|
+
(d?.packingConfiguration ? null : (packingCache?.loadCarrier ?? null)),
|
|
233
|
+
source: d?.packingConfiguration ? "order" : packingCache ? "trade-item" : null,
|
|
266
234
|
};
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
235
|
+
return {
|
|
236
|
+
foId,
|
|
237
|
+
d,
|
|
238
|
+
orgId,
|
|
239
|
+
custId: cust?.id ?? null,
|
|
240
|
+
customerName,
|
|
241
|
+
delivery,
|
|
242
|
+
deliveryIso,
|
|
243
|
+
orderDateIso,
|
|
244
|
+
orderDateMs: Date.parse(orderDateIso) || 0,
|
|
245
|
+
sequenceNumber: Number(d?.sequenceNumber ?? 0),
|
|
246
|
+
pickupLocation,
|
|
247
|
+
deliveryLocation,
|
|
248
|
+
numberOfPieces,
|
|
249
|
+
tradeItemId,
|
|
250
|
+
productId,
|
|
251
|
+
productName,
|
|
252
|
+
plants_per_crate,
|
|
253
|
+
crates_per_layer,
|
|
254
|
+
layers_per_cart,
|
|
255
|
+
unitPrice: Number(d?.pricePerPiece?.value ?? 0),
|
|
256
|
+
totalPrice,
|
|
257
|
+
status,
|
|
258
|
+
ff,
|
|
259
|
+
fullyFulfilled,
|
|
260
|
+
packing,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
const allLines = rows.map(buildLine);
|
|
264
|
+
// Floriday levert per artikel een aparte verkooporder; er is geen groeps-id
|
|
265
|
+
// in de payload (salesOrderGroupId/customerOrderId zijn leeg). Regels van
|
|
266
|
+
// dezelfde bestelling herkennen we daarom aan: zelfde klant, zelfde
|
|
267
|
+
// aflevermoment, zelfde afleverlocatie, zelfde uitleverlocatie én vlak na
|
|
268
|
+
// elkaar geplaatst.
|
|
269
|
+
const GROUP_WINDOW_MS = 5 * 60 * 1000;
|
|
270
|
+
const byKey = new Map();
|
|
271
|
+
for (const line of allLines) {
|
|
272
|
+
const key = [
|
|
273
|
+
line.orgId,
|
|
274
|
+
line.deliveryIso,
|
|
275
|
+
line.deliveryLocation ?? "",
|
|
276
|
+
line.pickupLocation ?? "",
|
|
277
|
+
line.status,
|
|
278
|
+
].join("|");
|
|
279
|
+
const arr = byKey.get(key);
|
|
280
|
+
if (arr)
|
|
281
|
+
arr.push(line);
|
|
282
|
+
else
|
|
283
|
+
byKey.set(key, [line]);
|
|
284
|
+
}
|
|
285
|
+
const groups = [];
|
|
286
|
+
for (const arr of byKey.values()) {
|
|
287
|
+
arr.sort((a, b) => a.orderDateMs - b.orderDateMs || a.sequenceNumber - b.sequenceNumber);
|
|
288
|
+
let current = [];
|
|
289
|
+
for (const line of arr) {
|
|
290
|
+
if (current.length === 0 ||
|
|
291
|
+
line.orderDateMs - current[current.length - 1].orderDateMs <= GROUP_WINDOW_MS) {
|
|
292
|
+
current.push(line);
|
|
293
|
+
}
|
|
294
|
+
else {
|
|
295
|
+
groups.push(current);
|
|
296
|
+
current = [line];
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
if (current.length > 0)
|
|
300
|
+
groups.push(current);
|
|
301
|
+
}
|
|
302
|
+
/** Een bestaande order mag niet opgeruimd worden als hij al onderweg is. */
|
|
303
|
+
const isFrozen = (o) => {
|
|
304
|
+
const st = String(o?.status ?? "").toLowerCase();
|
|
305
|
+
return Boolean(o?.plan_locked_at) || st === "verzonden" || st === "shipped";
|
|
306
|
+
};
|
|
307
|
+
// Regels waarvan de bestaande order bevroren is, blijven losse orders.
|
|
308
|
+
const finalGroups = [];
|
|
309
|
+
for (const group of groups) {
|
|
310
|
+
if (group.length === 1) {
|
|
311
|
+
finalGroups.push(group);
|
|
271
312
|
continue;
|
|
272
313
|
}
|
|
314
|
+
const frozen = [];
|
|
315
|
+
const free = [];
|
|
316
|
+
for (const line of group) {
|
|
317
|
+
const ex = existingByFoId.get(line.foId);
|
|
318
|
+
if (ex && isFrozen(ex))
|
|
319
|
+
frozen.push(line);
|
|
320
|
+
else
|
|
321
|
+
free.push(line);
|
|
322
|
+
}
|
|
323
|
+
for (const line of frozen)
|
|
324
|
+
finalGroups.push([line]);
|
|
325
|
+
if (free.length > 0)
|
|
326
|
+
finalGroups.push(free);
|
|
327
|
+
}
|
|
328
|
+
// Vaste bestelsleutel per groep. Hij is deterministisch: dezelfde klant,
|
|
329
|
+
// hetzelfde aflevermoment, dezelfde aflever-/uitleverlocatie en het vroegste
|
|
330
|
+
// besteltijdstip (op de minuut) leveren altijd dezelfde sleutel op. Daardoor
|
|
331
|
+
// vindt een volgende spiegelronde exact dezelfde lokale order terug en valt
|
|
332
|
+
// een samengevoegde bestelling nooit opnieuw uiteen.
|
|
333
|
+
const groupKeyFor = (group) => {
|
|
334
|
+
const first = group.reduce((a, b) => (a.orderDateMs <= b.orderDateMs ? a : b));
|
|
335
|
+
const minuteIso = new Date(Math.floor((first.orderDateMs || 0) / 60000) * 60000).toISOString();
|
|
336
|
+
return [
|
|
337
|
+
"fo",
|
|
338
|
+
first.orgId,
|
|
339
|
+
first.deliveryIso,
|
|
340
|
+
first.deliveryLocation ?? "",
|
|
341
|
+
first.pickupLocation ?? "",
|
|
342
|
+
minuteIso,
|
|
343
|
+
].join("|");
|
|
344
|
+
};
|
|
345
|
+
const keyedGroups = finalGroups.map((group) => ({ group, key: groupKeyFor(group) }));
|
|
346
|
+
// Bestaande orders die al aan een van deze bestelsleutels hangen. Zo vinden
|
|
347
|
+
// we ook de order terug waarvan de primaire Floriday-regel is gewijzigd.
|
|
348
|
+
const existingByGroupKey = new Map();
|
|
349
|
+
const allKeys = Array.from(new Set(keyedGroups.map((g) => g.key)));
|
|
350
|
+
if (allKeys.length > 0) {
|
|
351
|
+
const { data: keyRows } = await supabaseAdmin
|
|
352
|
+
.from("orders")
|
|
353
|
+
.select("id, order_number, floriday_sales_order_id, floriday_order_group_key, updated_at, custom_fields, status, plan_locked_at, buffer_id")
|
|
354
|
+
.in("floriday_order_group_key", allKeys)
|
|
355
|
+
.is("deleted_at", null);
|
|
356
|
+
for (const o of keyRows ?? []) {
|
|
357
|
+
const k = String(o.floriday_order_group_key ?? "");
|
|
358
|
+
if (!k)
|
|
359
|
+
continue;
|
|
360
|
+
const prev = existingByGroupKey.get(k);
|
|
361
|
+
// Laagste ordernummer wint; die order blijft bestaan bij samenvoegen.
|
|
362
|
+
if (!prev ||
|
|
363
|
+
String(o.order_number ?? "") < String(prev.order_number ?? "")) {
|
|
364
|
+
existingByGroupKey.set(k, o);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
for (const { group, key: groupKey } of keyedGroups) {
|
|
369
|
+
const primary = group[0];
|
|
370
|
+
const memberIds = group.map((l) => l.foId);
|
|
371
|
+
const anyFulfilled = group.some((l) => l.fullyFulfilled);
|
|
372
|
+
const ffPrimary = primary.ff;
|
|
373
|
+
const totalPrice = group.reduce((s, l) => s + (Number(l.totalPrice) || 0), 0);
|
|
374
|
+
const custom_fields = {
|
|
375
|
+
source: "floriday",
|
|
376
|
+
pickup_location: primary.pickupLocation,
|
|
377
|
+
delivery_location: primary.deliveryLocation,
|
|
378
|
+
delivery_location_gln: primary.delivery?.location?.gln ?? null,
|
|
379
|
+
region_gln: primary.delivery?.regionGln ?? null,
|
|
380
|
+
floriday_sales_order_id: primary.foId,
|
|
381
|
+
// Alle Floriday-verkooporders die samen deze ene order vormen.
|
|
382
|
+
floriday_sales_order_ids: memberIds,
|
|
383
|
+
floriday_line_count: group.length,
|
|
384
|
+
floriday_status: primary.d?.status ?? null,
|
|
385
|
+
floriday_trade_item_id: primary.tradeItemId || null,
|
|
386
|
+
floriday_fulfilled: anyFulfilled,
|
|
387
|
+
floriday_fulfilled_externally: anyFulfilled && !ffPrimary?.createdByErp,
|
|
388
|
+
floriday_fulfilled_by_erp: anyFulfilled && Boolean(ffPrimary?.createdByErp),
|
|
389
|
+
floriday_delivery_notes: ffPrimary?.deliveryNoteCodes ?? [],
|
|
390
|
+
floriday_received_at: ffPrimary?.receivedAt ?? null,
|
|
391
|
+
floriday_packing: primary.packing,
|
|
392
|
+
floriday_lines: group.map((l) => ({
|
|
393
|
+
sales_order_id: l.foId,
|
|
394
|
+
trade_item_id: l.tradeItemId || null,
|
|
395
|
+
pieces: l.numberOfPieces,
|
|
396
|
+
packing: l.packing,
|
|
397
|
+
})),
|
|
398
|
+
};
|
|
273
399
|
try {
|
|
400
|
+
// Bestaande order kiezen. Volgorde: de order die al aan deze
|
|
401
|
+
// bestelsleutel hangt (dat is de bestelling zelf), dan die van de
|
|
402
|
+
// primaire regel, dan de eerste vrije order van een andere regel.
|
|
403
|
+
let target = existingByGroupKey.get(groupKey) ?? null;
|
|
404
|
+
if (target && isFrozen(target))
|
|
405
|
+
target = null;
|
|
406
|
+
if (!target) {
|
|
407
|
+
const byPrimary = existingByFoId.get(primary.foId) ?? null;
|
|
408
|
+
if (byPrimary && !isFrozen(byPrimary))
|
|
409
|
+
target = byPrimary;
|
|
410
|
+
}
|
|
411
|
+
if (!target) {
|
|
412
|
+
for (const l of group) {
|
|
413
|
+
const ex = existingByFoId.get(l.foId);
|
|
414
|
+
if (ex && !isFrozen(ex)) {
|
|
415
|
+
target = ex;
|
|
416
|
+
break;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
// Ordernummer: een bestaande order houdt zijn nummer, anders het nummer
|
|
421
|
+
// uit floriday_order_number_map van de primaire regel.
|
|
422
|
+
const orderNumber = (target ? String(target.order_number ?? "").trim() : "") ||
|
|
423
|
+
orderNumberByFoId.get(primary.foId) ||
|
|
424
|
+
null;
|
|
425
|
+
if (!orderNumber) {
|
|
426
|
+
skipped += group.length;
|
|
427
|
+
errors.push(`${primary.foId}: geen ordernummer in floriday_order_number_map`);
|
|
428
|
+
continue;
|
|
429
|
+
}
|
|
430
|
+
// Dubbele orders van dezelfde bestelling opruimen: alles wat via een
|
|
431
|
+
// regel of via dezelfde bestelsleutel bestaat en niet het doel is.
|
|
432
|
+
const obsolete = [
|
|
433
|
+
...group.map((l) => existingByFoId.get(l.foId)),
|
|
434
|
+
existingByGroupKey.get(groupKey),
|
|
435
|
+
].filter((o) => o && (!target || o.id !== target.id) && !isFrozen(o));
|
|
436
|
+
const obsoleteIds = Array.from(new Set(obsolete.map((o) => String(o.id))));
|
|
437
|
+
if (obsoleteIds.length > 0) {
|
|
438
|
+
await supabaseAdmin.from("order_items").delete().in("order_id", obsoleteIds);
|
|
439
|
+
await supabaseAdmin.from("orders")
|
|
440
|
+
.update({ deleted_at: new Date().toISOString(), buffer_id: null })
|
|
441
|
+
.in("id", obsoleteIds);
|
|
442
|
+
}
|
|
274
443
|
let orderId;
|
|
275
|
-
if (
|
|
444
|
+
if (target) {
|
|
276
445
|
// Tweerichtingssynchronisatie: de Floriday-status overschrijft onze
|
|
277
446
|
// status alleen als hij aan de Floriday-kant echt is veranderd sinds de
|
|
278
|
-
// vorige spiegelronde.
|
|
279
|
-
|
|
280
|
-
const
|
|
281
|
-
const
|
|
282
|
-
const
|
|
283
|
-
|
|
284
|
-
? { status, status_sync_token: globalThis.crypto.randomUUID() }
|
|
447
|
+
// vorige spiegelronde.
|
|
448
|
+
const prevFloridayStatus = (target?.custom_fields ?? {})?.floriday_status ?? null;
|
|
449
|
+
const floridayStatusChanged = String(primary.d?.status ?? "") !== String(prevFloridayStatus ?? "");
|
|
450
|
+
const localStatus = String(target?.status ?? "");
|
|
451
|
+
const statusPatch = floridayStatusChanged && primary.status !== localStatus
|
|
452
|
+
? { status: primary.status, status_sync_token: globalThis.crypto.randomUUID() }
|
|
285
453
|
: {};
|
|
286
|
-
// Update basiskolommen; laat bestaande karren staan tot replan-queue draait.
|
|
287
454
|
const { error: uErr } = await supabaseAdmin.from("orders")
|
|
288
455
|
.update({
|
|
289
456
|
order_number: orderNumber,
|
|
290
|
-
customer_id:
|
|
291
|
-
customer_name: customerName,
|
|
292
|
-
delivery_date: deliveryIso ? deliveryIso.slice(0, 10) : null,
|
|
293
|
-
order_date: orderDateIso,
|
|
457
|
+
customer_id: primary.custId,
|
|
458
|
+
customer_name: primary.customerName,
|
|
459
|
+
delivery_date: primary.deliveryIso ? primary.deliveryIso.slice(0, 10) : null,
|
|
460
|
+
order_date: primary.orderDateIso,
|
|
294
461
|
...statusPatch,
|
|
295
462
|
total: String(totalPrice),
|
|
296
463
|
custom_fields,
|
|
297
464
|
order_source: "floriday",
|
|
465
|
+
floriday_sales_order_id: primary.foId,
|
|
466
|
+
floriday_order_group_key: groupKey,
|
|
467
|
+
deleted_at: null,
|
|
298
468
|
})
|
|
299
|
-
.eq("id",
|
|
469
|
+
.eq("id", target.id);
|
|
300
470
|
if (uErr)
|
|
301
471
|
throw new Error(uErr.message);
|
|
302
|
-
orderId =
|
|
472
|
+
orderId = target.id;
|
|
303
473
|
updated++;
|
|
304
474
|
}
|
|
305
475
|
else {
|
|
306
476
|
const { data: ins, error: iErr } = await supabaseAdmin.from("orders")
|
|
307
477
|
.insert({
|
|
308
478
|
order_number: orderNumber,
|
|
309
|
-
customer_name: customerName,
|
|
310
|
-
customer_id:
|
|
311
|
-
order_date: orderDateIso,
|
|
312
|
-
delivery_date: deliveryIso ? deliveryIso.slice(0, 10) : null,
|
|
313
|
-
status,
|
|
479
|
+
customer_name: primary.customerName,
|
|
480
|
+
customer_id: primary.custId,
|
|
481
|
+
order_date: primary.orderDateIso,
|
|
482
|
+
delivery_date: primary.deliveryIso ? primary.deliveryIso.slice(0, 10) : null,
|
|
483
|
+
status: primary.status,
|
|
314
484
|
total: String(totalPrice),
|
|
315
485
|
custom_fields,
|
|
316
486
|
order_source: "floriday",
|
|
317
|
-
floriday_sales_order_id: foId,
|
|
318
|
-
|
|
487
|
+
floriday_sales_order_id: primary.foId,
|
|
488
|
+
floriday_order_group_key: groupKey,
|
|
319
489
|
status_sync_token: globalThis.crypto.randomUUID(),
|
|
320
490
|
})
|
|
321
491
|
.select("id")
|
|
@@ -325,30 +495,25 @@ async function mirrorInternal(limit) {
|
|
|
325
495
|
orderId = ins.id;
|
|
326
496
|
inserted++;
|
|
327
497
|
}
|
|
328
|
-
//
|
|
498
|
+
// Eén orderregel per Floriday-verkooporder in deze groep.
|
|
329
499
|
await supabaseAdmin.from("order_items").delete().eq("order_id", orderId);
|
|
330
|
-
const { error: liErr } = await supabaseAdmin.from("order_items").insert({
|
|
500
|
+
const { error: liErr } = await supabaseAdmin.from("order_items").insert(group.map((l) => ({
|
|
331
501
|
order_id: orderId,
|
|
332
|
-
product_id: productId,
|
|
333
|
-
product_name: productName,
|
|
334
|
-
quantity: Math.max(0, numberOfPieces),
|
|
502
|
+
product_id: l.productId,
|
|
503
|
+
product_name: l.productName,
|
|
504
|
+
quantity: Math.max(0, l.numberOfPieces),
|
|
335
505
|
unit: "stuks",
|
|
336
|
-
unit_price:
|
|
337
|
-
|
|
506
|
+
unit_price: l.unitPrice,
|
|
507
|
+
plants_per_crate: l.plants_per_crate,
|
|
508
|
+
crates_per_layer: l.crates_per_layer,
|
|
509
|
+
layers_per_cart: l.layers_per_cart,
|
|
510
|
+
})));
|
|
338
511
|
if (liErr)
|
|
339
512
|
throw new Error(liErr.message);
|
|
340
|
-
// Zorg dat product-packing gebruikt wordt door de planner. Als er nog
|
|
341
|
-
// geen product-mapping is, valt de planner terug op 1×1×1 en dat wil
|
|
342
|
-
// je niet — sla dan tijdelijke packing op via een "shadow" product
|
|
343
|
-
// niet toe; in plaats daarvan berust de planner op de defaults.
|
|
344
|
-
// (Toekomstige verbetering: shadow product per trade item.)
|
|
345
|
-
void plants_per_crate;
|
|
346
|
-
void crates_per_layer;
|
|
347
|
-
void layers_per_cart;
|
|
348
513
|
// Buffer resolven en in de replan-queue zetten. Alleen orders die buiten
|
|
349
514
|
// ons ERP om in Floriday zijn afgehandeld hoeven niet meer gepland te
|
|
350
|
-
// worden
|
|
351
|
-
const externallyHandled =
|
|
515
|
+
// worden.
|
|
516
|
+
const externallyHandled = anyFulfilled && !ffPrimary?.createdByErp;
|
|
352
517
|
if (!externallyHandled) {
|
|
353
518
|
await resolveBufferForOrderInternal(orderId);
|
|
354
519
|
affectedOrderIds.push(orderId);
|
|
@@ -357,7 +522,7 @@ async function mirrorInternal(limit) {
|
|
|
357
522
|
catch (err) {
|
|
358
523
|
failed++;
|
|
359
524
|
const msg = err.message ?? "unknown";
|
|
360
|
-
errors.push(`${foId}: ${msg.slice(0, 200)}`);
|
|
525
|
+
errors.push(`${primary.foId}: ${msg.slice(0, 200)}`);
|
|
361
526
|
}
|
|
362
527
|
}
|
|
363
528
|
// Batch enqueue voor karherplanning. processed_at resetten, anders blijft een
|