@flowselections/logistiek-engine 1.0.34 → 1.0.89
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/AfrondWachtrijCard.d.ts +2 -0
- package/dist-lib/components/AfrondWachtrijCard.d.ts.map +1 -0
- package/dist-lib/components/AfrondWachtrijCard.js +27 -0
- package/dist-lib/components/BeladingsafwijkingenCard.d.ts +2 -0
- package/dist-lib/components/BeladingsafwijkingenCard.d.ts.map +1 -0
- package/dist-lib/components/BeladingsafwijkingenCard.js +44 -0
- package/dist-lib/components/FlowCanvas.d.ts +34 -0
- package/dist-lib/components/FlowCanvas.d.ts.map +1 -0
- package/dist-lib/components/FlowCanvas.js +490 -0
- package/dist-lib/components/FlowOrders.d.ts +8 -0
- package/dist-lib/components/FlowOrders.d.ts.map +1 -0
- package/dist-lib/components/FlowOrders.js +171 -0
- package/dist-lib/components/FlowPage.d.ts +2 -0
- package/dist-lib/components/FlowPage.d.ts.map +1 -0
- package/dist-lib/components/FlowPage.js +310 -0
- package/dist-lib/components/GeblokkeerdeOrdersCard.d.ts.map +1 -1
- package/dist-lib/components/GeblokkeerdeOrdersCard.js +21 -2
- package/dist-lib/components/InklapbaarVenster.d.ts +9 -0
- package/dist-lib/components/InklapbaarVenster.d.ts.map +1 -0
- package/dist-lib/components/InklapbaarVenster.js +10 -0
- package/dist-lib/components/KardekkingCard.d.ts +2 -0
- package/dist-lib/components/KardekkingCard.d.ts.map +1 -0
- package/dist-lib/components/KardekkingCard.js +29 -0
- package/dist-lib/components/LogistiekEnginePage.d.ts.map +1 -1
- package/dist-lib/components/LogistiekEnginePage.js +15 -3
- package/dist-lib/components/LogistiekEngineQueuePage.d.ts +2 -0
- package/dist-lib/components/LogistiekEngineQueuePage.d.ts.map +1 -0
- package/dist-lib/components/LogistiekEngineQueuePage.js +13 -0
- package/dist-lib/components/NodeTracePanel.d.ts +14 -0
- package/dist-lib/components/NodeTracePanel.d.ts.map +1 -0
- package/dist-lib/components/NodeTracePanel.js +46 -0
- package/dist-lib/components/OrderJourneyBar.d.ts +9 -0
- package/dist-lib/components/OrderJourneyBar.d.ts.map +1 -0
- package/dist-lib/components/OrderJourneyBar.js +26 -0
- package/dist-lib/components/OrderLogboek.d.ts +10 -0
- package/dist-lib/components/OrderLogboek.d.ts.map +1 -0
- package/dist-lib/components/OrderLogboek.js +92 -0
- package/dist-lib/components/OrderStopBanner.d.ts +12 -0
- package/dist-lib/components/OrderStopBanner.d.ts.map +1 -0
- package/dist-lib/components/OrderStopBanner.js +36 -0
- package/dist-lib/components/OrderTraceDetails.d.ts +22 -0
- package/dist-lib/components/OrderTraceDetails.d.ts.map +1 -0
- package/dist-lib/components/OrderTraceDetails.js +88 -0
- package/dist-lib/components/PlanWachtrijPage.d.ts +2 -0
- package/dist-lib/components/PlanWachtrijPage.d.ts.map +1 -0
- package/dist-lib/components/PlanWachtrijPage.js +99 -0
- package/dist-lib/components/PlannerDiffCard.d.ts +2 -0
- package/dist-lib/components/PlannerDiffCard.d.ts.map +1 -0
- package/dist-lib/components/PlannerDiffCard.js +25 -0
- package/dist-lib/components/RegiePage.d.ts.map +1 -1
- package/dist-lib/components/RegiePage.js +28 -78
- package/dist-lib/components/TaakGezondheidCard.d.ts +2 -0
- package/dist-lib/components/TaakGezondheidCard.d.ts.map +1 -0
- package/dist-lib/components/TaakGezondheidCard.js +31 -0
- package/dist-lib/components/WerktijdenCard.d.ts +2 -0
- package/dist-lib/components/WerktijdenCard.d.ts.map +1 -0
- package/dist-lib/components/WerktijdenCard.js +37 -0
- package/dist-lib/components/WerkwijzeHerindelingCard.d.ts.map +1 -1
- package/dist-lib/components/WerkwijzeHerindelingCard.js +100 -6
- package/dist-lib/hooks/useAfrondWachtrij.d.ts +28 -0
- package/dist-lib/hooks/useAfrondWachtrij.d.ts.map +1 -0
- package/dist-lib/hooks/useAfrondWachtrij.js +88 -0
- package/dist-lib/hooks/useBeladingsafwijkingen.d.ts +39 -0
- package/dist-lib/hooks/useBeladingsafwijkingen.d.ts.map +1 -0
- package/dist-lib/hooks/useBeladingsafwijkingen.js +119 -0
- package/dist-lib/hooks/useGeblokkeerdeOrders.d.ts +7 -0
- package/dist-lib/hooks/useGeblokkeerdeOrders.d.ts.map +1 -1
- package/dist-lib/hooks/useGeblokkeerdeOrders.js +28 -2
- package/dist-lib/hooks/useLogisticsTrace.d.ts +78 -0
- package/dist-lib/hooks/useLogisticsTrace.d.ts.map +1 -0
- package/dist-lib/hooks/useLogisticsTrace.js +152 -0
- package/dist-lib/hooks/useOrderNodeTrace.d.ts +11 -0
- package/dist-lib/hooks/useOrderNodeTrace.d.ts.map +1 -0
- package/dist-lib/hooks/useOrderNodeTrace.js +26 -0
- package/dist-lib/hooks/useOrderTrace.d.ts +74 -0
- package/dist-lib/hooks/useOrderTrace.d.ts.map +1 -0
- package/dist-lib/hooks/useOrderTrace.js +134 -0
- package/dist-lib/hooks/useOrderVersies.d.ts +47 -0
- package/dist-lib/hooks/useOrderVersies.d.ts.map +1 -0
- package/dist-lib/hooks/useOrderVersies.js +194 -0
- package/dist-lib/hooks/usePlanWachtrij.d.ts +33 -0
- package/dist-lib/hooks/usePlanWachtrij.d.ts.map +1 -0
- package/dist-lib/hooks/usePlanWachtrij.js +104 -0
- package/dist-lib/hooks/useRecalcJob.js +1 -1
- package/dist-lib/hooks/useRuleEngine.d.ts +1 -0
- package/dist-lib/hooks/useRuleEngine.d.ts.map +1 -1
- package/dist-lib/hooks/useRuleEngine.js +1 -0
- package/dist-lib/hooks/useShippingNoteDiagnose.d.ts +20 -0
- package/dist-lib/hooks/useShippingNoteDiagnose.d.ts.map +1 -0
- package/dist-lib/hooks/useShippingNoteDiagnose.js +52 -0
- package/dist-lib/hooks/useShippingNoteStatus.d.ts +29 -0
- package/dist-lib/hooks/useShippingNoteStatus.d.ts.map +1 -0
- package/dist-lib/hooks/useShippingNoteStatus.js +107 -0
- package/dist-lib/hooks/useTaakGezondheid.d.ts +16 -0
- package/dist-lib/hooks/useTaakGezondheid.d.ts.map +1 -0
- package/dist-lib/hooks/useTaakGezondheid.js +43 -0
- package/dist-lib/hooks/useWerktijden.d.ts +33 -0
- package/dist-lib/hooks/useWerktijden.d.ts.map +1 -0
- package/dist-lib/hooks/useWerktijden.js +88 -0
- package/dist-lib/hooks/useWerkwijzeStatus.d.ts +12 -1
- package/dist-lib/hooks/useWerkwijzeStatus.d.ts.map +1 -1
- package/dist-lib/hooks/useWerkwijzeStatus.js +61 -5
- package/dist-lib/index.d.ts +23 -0
- package/dist-lib/index.d.ts.map +1 -1
- package/dist-lib/index.js +23 -0
- package/dist-lib/integrations/supabase/auth-middleware.d.ts +4993 -459
- package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.server.d.ts +4993 -459
- package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.server.js +1 -1
- package/dist-lib/integrations/supabase/moduleClient.d.ts +4993 -459
- package/dist-lib/integrations/supabase/moduleClient.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/previewAuthStorage.d.ts +1 -1
- package/dist-lib/integrations/supabase/previewAuthStorage.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/previewAuthStorage.js +8 -1
- package/dist-lib/integrations/supabase/types.d.ts +5022 -432
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/lib/attachAuthToken.js +1 -1
- package/dist-lib/lib/bufferDecision.test.d.ts +2 -0
- package/dist-lib/lib/bufferDecision.test.d.ts.map +1 -0
- package/dist-lib/lib/bufferDecision.test.js +104 -0
- package/dist-lib/lib/cartCoverage.functions.d.ts +30820 -0
- package/dist-lib/lib/cartCoverage.functions.d.ts.map +1 -0
- package/dist-lib/lib/cartCoverage.functions.js +20 -0
- package/dist-lib/lib/cartPlanRequests.server.d.ts +1 -1
- package/dist-lib/lib/cartPlanRequests.server.d.ts.map +1 -1
- package/dist-lib/lib/cartPlanRequests.server.js +49 -8
- package/dist-lib/lib/floridayDocuments.functions.d.ts +13623 -21
- package/dist-lib/lib/floridayDocuments.functions.d.ts.map +1 -1
- package/dist-lib/lib/floridayDocuments.functions.js +5 -5
- package/dist-lib/lib/floridayDocuments.server.d.ts +14 -0
- package/dist-lib/lib/floridayDocuments.server.d.ts.map +1 -1
- package/dist-lib/lib/floridayDocuments.server.js +58 -0
- package/dist-lib/lib/floridayGuard.server.d.ts +1 -1
- package/dist-lib/lib/floridayGuard.server.d.ts.map +1 -1
- package/dist-lib/lib/floridayGuard.server.js +6 -3
- package/dist-lib/lib/floridayPush.functions.d.ts +4993 -459
- package/dist-lib/lib/floridayPush.functions.d.ts.map +1 -1
- package/dist-lib/lib/floridayPush.functions.js +3 -3
- package/dist-lib/lib/flowGraph.d.ts +43 -0
- package/dist-lib/lib/flowGraph.d.ts.map +1 -0
- package/dist-lib/lib/flowGraph.js +153 -0
- package/dist-lib/lib/flowJourney.d.ts +40 -0
- package/dist-lib/lib/flowJourney.d.ts.map +1 -0
- package/dist-lib/lib/flowJourney.js +156 -0
- package/dist-lib/lib/flowNodes.d.ts +26 -0
- package/dist-lib/lib/flowNodes.d.ts.map +1 -0
- package/dist-lib/lib/flowNodes.js +64 -0
- package/dist-lib/lib/flowRoute.d.ts +117 -0
- package/dist-lib/lib/flowRoute.d.ts.map +1 -0
- package/dist-lib/lib/flowRoute.js +583 -0
- package/dist-lib/lib/flowTree.d.ts +149 -0
- package/dist-lib/lib/flowTree.d.ts.map +1 -0
- package/dist-lib/lib/flowTree.js +908 -0
- package/dist-lib/lib/foutTekst.d.ts +4 -0
- package/dist-lib/lib/foutTekst.d.ts.map +1 -0
- package/dist-lib/lib/foutTekst.js +43 -0
- package/dist-lib/lib/handlers/index.d.ts +1 -0
- package/dist-lib/lib/handlers/index.d.ts.map +1 -1
- package/dist-lib/lib/handlers/index.js +1 -0
- package/dist-lib/lib/handlers/logistics-shipping-note-now.server.d.ts +3 -0
- package/dist-lib/lib/handlers/logistics-shipping-note-now.server.d.ts.map +1 -0
- package/dist-lib/lib/handlers/logistics-shipping-note-now.server.js +100 -0
- package/dist-lib/lib/handlers/logistics-tick.server.d.ts +2 -0
- package/dist-lib/lib/handlers/logistics-tick.server.d.ts.map +1 -1
- package/dist-lib/lib/handlers/logistics-tick.server.js +153 -2
- package/dist-lib/lib/handlers/logistics-tick.test.d.ts +2 -0
- package/dist-lib/lib/handlers/logistics-tick.test.d.ts.map +1 -0
- package/dist-lib/lib/handlers/logistics-tick.test.js +63 -0
- package/dist-lib/lib/loadCarrierPlanner.d.ts +2 -0
- package/dist-lib/lib/loadCarrierPlanner.d.ts.map +1 -1
- package/dist-lib/lib/loadCarrierPlanner.js +18 -0
- package/dist-lib/lib/loadCarrierPlanning.functions.d.ts +22705 -35
- package/dist-lib/lib/loadCarrierPlanning.functions.d.ts.map +1 -1
- package/dist-lib/lib/loadCarrierPlanning.functions.js +7 -7
- package/dist-lib/lib/loadCarrierPlanning.server.d.ts +33 -12
- package/dist-lib/lib/loadCarrierPlanning.server.d.ts.map +1 -1
- package/dist-lib/lib/loadCarrierPlanning.server.js +534 -228
- package/dist-lib/lib/logisticsPlanner.d.ts.map +1 -1
- package/dist-lib/lib/logisticsPlanner.js +14 -2
- package/dist-lib/lib/logisticsRecalcJob.functions.d.ts +9082 -14
- package/dist-lib/lib/logisticsRecalcJob.functions.d.ts.map +1 -1
- package/dist-lib/lib/logisticsRecalcJob.functions.js +4 -4
- package/dist-lib/lib/logisticsRecalcJob.server.d.ts.map +1 -1
- package/dist-lib/lib/logisticsRecalcJob.server.js +17 -2
- package/dist-lib/lib/logisticsRecalculate.functions.d.ts +4993 -459
- package/dist-lib/lib/logisticsRecalculate.functions.d.ts.map +1 -1
- package/dist-lib/lib/logisticsRecalculate.functions.js +2 -2
- package/dist-lib/lib/modulePrompts.d.ts.map +1 -1
- package/dist-lib/lib/modulePrompts.js +893 -0
- package/dist-lib/lib/nodeTrace.d.ts +65 -0
- package/dist-lib/lib/nodeTrace.d.ts.map +1 -0
- package/dist-lib/lib/nodeTrace.js +182 -0
- package/dist-lib/lib/pickQueueCleanup.server.d.ts +14 -0
- package/dist-lib/lib/pickQueueCleanup.server.d.ts.map +1 -0
- package/dist-lib/lib/pickQueueCleanup.server.js +80 -0
- package/dist-lib/lib/planQueue.server.d.ts.map +1 -1
- package/dist-lib/lib/planQueue.server.js +164 -3
- package/dist-lib/lib/plannerDiff.d.ts +37 -0
- package/dist-lib/lib/plannerDiff.d.ts.map +1 -0
- package/dist-lib/lib/plannerDiff.functions.d.ts +30821 -0
- package/dist-lib/lib/plannerDiff.functions.d.ts.map +1 -0
- package/dist-lib/lib/plannerDiff.functions.js +21 -0
- package/dist-lib/lib/plannerDiff.js +74 -0
- package/dist-lib/lib/plannerDiff.server.d.ts +5 -0
- package/dist-lib/lib/plannerDiff.server.d.ts.map +1 -0
- package/dist-lib/lib/plannerDiff.server.js +60 -0
- package/dist-lib/lib/plannerDiff.test.d.ts +2 -0
- package/dist-lib/lib/plannerDiff.test.d.ts.map +1 -0
- package/dist-lib/lib/plannerDiff.test.js +41 -0
- package/dist-lib/lib/resolveBuffer.test.d.ts +2 -0
- package/dist-lib/lib/resolveBuffer.test.d.ts.map +1 -0
- package/dist-lib/lib/resolveBuffer.test.js +38 -0
- package/dist-lib/lib/ruleEngineImpact.d.ts.map +1 -1
- package/dist-lib/lib/ruleEngineImpact.js +11 -4
- package/dist-lib/lib/ruleEngineSchema.d.ts.map +1 -1
- package/dist-lib/lib/ruleEngineSchema.js +102 -6
- package/dist-lib/lib/ruleEngineWizard.d.ts.map +1 -1
- package/dist-lib/lib/ruleEngineWizard.js +3 -1
- package/dist-lib/lib/shippingNoteQueue.server.d.ts +18 -0
- package/dist-lib/lib/shippingNoteQueue.server.d.ts.map +1 -0
- package/dist-lib/lib/shippingNoteQueue.server.js +304 -0
- package/dist-lib/lib/stopReden.d.ts +43 -0
- package/dist-lib/lib/stopReden.d.ts.map +1 -0
- package/dist-lib/lib/stopReden.js +197 -0
- package/dist-lib/lib/taskCounts.d.ts +2 -0
- package/dist-lib/lib/taskCounts.d.ts.map +1 -0
- package/dist-lib/lib/taskCounts.js +15 -0
- package/dist-lib/lib/taskCounts.test.d.ts +2 -0
- package/dist-lib/lib/taskCounts.test.d.ts.map +1 -0
- package/dist-lib/lib/taskCounts.test.js +12 -0
- package/dist-lib/lib/trace.server.d.ts +75 -0
- package/dist-lib/lib/trace.server.d.ts.map +1 -0
- package/dist-lib/lib/trace.server.js +78 -0
- package/dist-lib/lib/vakUitleg.d.ts +19 -0
- package/dist-lib/lib/vakUitleg.d.ts.map +1 -0
- package/dist-lib/lib/vakUitleg.js +133 -0
- package/package.json +158 -131
- package/dist-lib/components/rule-engine/FloridayConnectionBanner.d.ts +0 -2
- package/dist-lib/components/rule-engine/FloridayConnectionBanner.d.ts.map +0 -1
- package/dist-lib/components/rule-engine/FloridayConnectionBanner.js +0 -41
- package/dist-lib/integrations/supabase/client.d.ts +0 -20293
- package/dist-lib/integrations/supabase/client.d.ts.map +0 -1
- package/dist-lib/integrations/supabase/client.js +0 -13
|
@@ -12,124 +12,40 @@ import { buildDimensionItems, carrierDimensions } from "./dimensionData.server";
|
|
|
12
12
|
// ---------------------------------------------------------------------------
|
|
13
13
|
// Buffer
|
|
14
14
|
// ---------------------------------------------------------------------------
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
if (Array.isArray(value)) {
|
|
19
|
-
for (const entry of value) {
|
|
20
|
-
const normalized = normalizeBufferPart(entry);
|
|
21
|
-
if (normalized)
|
|
22
|
-
return normalized;
|
|
23
|
-
}
|
|
24
|
-
return "";
|
|
25
|
-
}
|
|
26
|
-
if (typeof value === "object") {
|
|
27
|
-
const o = value;
|
|
28
|
-
return normalizeBufferPart(o.gln ?? o.locationGln ?? o.warehouseId ?? o.id ?? o.name ?? o.location ?? o.address?.addressLine);
|
|
29
|
-
}
|
|
30
|
-
return String(value).trim().toLowerCase().replace(/\s+/g, " ");
|
|
31
|
-
}
|
|
32
|
-
function pickFirstString(...values) {
|
|
33
|
-
for (const value of values) {
|
|
34
|
-
const normalized = normalizeBufferPart(value);
|
|
35
|
-
if (normalized)
|
|
36
|
-
return normalized;
|
|
37
|
-
}
|
|
38
|
-
return "";
|
|
39
|
-
}
|
|
40
|
-
export function buildBufferKey(order, mode) {
|
|
41
|
-
const cf = (order.custom_fields ?? {});
|
|
42
|
-
const pickupDate = pickFirstString(cf.pickup_moment, cf.pickup_date, cf.pickup_datetime, order.delivery_date);
|
|
43
|
-
const location = pickFirstString(cf.delivery_location_gln, cf.destination_gln, cf.region_gln, cf.location_gln, cf.delivery_location_id, cf.delivery_location);
|
|
44
|
-
const customer = normalizeBufferPart(order.customer_id);
|
|
45
|
-
let key;
|
|
46
|
-
switch (mode) {
|
|
47
|
-
case "same_pickup":
|
|
48
|
-
key = `p|${pickupDate}`;
|
|
49
|
-
break;
|
|
50
|
-
case "same_location":
|
|
51
|
-
key = `l|${location}`;
|
|
52
|
-
break;
|
|
53
|
-
case "same_customer":
|
|
54
|
-
key = `c|${customer}|${pickupDate}`;
|
|
55
|
-
break;
|
|
56
|
-
case "never":
|
|
57
|
-
key = `order-${order.id}`;
|
|
58
|
-
break;
|
|
59
|
-
case "same_pickup_and_location":
|
|
60
|
-
default:
|
|
61
|
-
key = `pl|${pickupDate}|${location}`;
|
|
62
|
-
}
|
|
63
|
-
return { key, pickupDate, location };
|
|
64
|
-
}
|
|
15
|
+
// De bufferkeuze is uitsluitend van de database (logistics_apply_buffer_decision,
|
|
16
|
+
// via trigger trg_orders_apply_buffer_decision). Deze code leest die keuze
|
|
17
|
+
// alleen; ze berekent geen eigen sleutel en schrijft nooit orders.buffer_id.
|
|
65
18
|
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
66
19
|
export async function resolveBufferForOrderInternal(sb, orderId) {
|
|
67
|
-
const
|
|
68
|
-
.from("orders")
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
.
|
|
72
|
-
|
|
20
|
+
const readBuffer = async () => {
|
|
21
|
+
const { data } = await sb.from("orders").select("id, buffer_id").eq("id", orderId).maybeSingle();
|
|
22
|
+
if (!data)
|
|
23
|
+
return { found: false, bufferId: null };
|
|
24
|
+
return { found: true, bufferId: data.buffer_id ?? null };
|
|
25
|
+
};
|
|
26
|
+
const first = await readBuffer();
|
|
27
|
+
if (!first.found)
|
|
28
|
+
return null;
|
|
29
|
+
if (first.bufferId)
|
|
30
|
+
return first.bufferId;
|
|
31
|
+
// Nog geen buffer: laat de database één keer beslissen, en lees opnieuw.
|
|
32
|
+
const { error } = await sb.rpc("logistics_apply_buffer_decision", { p_order_id: orderId });
|
|
33
|
+
if (error) {
|
|
34
|
+
console.warn(`[buffer] bufferbesluit voor order ${orderId} mislukt: ${error.message}`);
|
|
73
35
|
return null;
|
|
74
|
-
const { data: settings } = await sb
|
|
75
|
-
.from("cart_planning_settings")
|
|
76
|
-
.select("buffer_grouping_mode")
|
|
77
|
-
.eq("id", 1)
|
|
78
|
-
.maybeSingle();
|
|
79
|
-
const mode = settings?.buffer_grouping_mode ?? "same_pickup_and_location";
|
|
80
|
-
const cf = { ...(order.custom_fields ?? {}) };
|
|
81
|
-
const locId = cf.delivery_location_id;
|
|
82
|
-
if (typeof locId === "string" && UUID_RE.test(locId) && !cf.delivery_location_gln) {
|
|
83
|
-
const { data: loc } = await sb
|
|
84
|
-
.from("customer_delivery_locations")
|
|
85
|
-
.select("gln, name")
|
|
86
|
-
.eq("id", locId)
|
|
87
|
-
.maybeSingle();
|
|
88
|
-
if (loc?.gln)
|
|
89
|
-
cf.delivery_location_gln = loc.gln;
|
|
90
|
-
if (loc?.name)
|
|
91
|
-
cf.delivery_location_name = loc.name;
|
|
92
|
-
}
|
|
93
|
-
const { key, pickupDate, location } = buildBufferKey({ ...order, custom_fields: cf }, mode);
|
|
94
|
-
let bufferId = null;
|
|
95
|
-
const { data: existing } = await sb
|
|
96
|
-
.from("order_buffers")
|
|
97
|
-
.select("id")
|
|
98
|
-
.eq("buffer_key", key)
|
|
99
|
-
.maybeSingle();
|
|
100
|
-
if (existing?.id) {
|
|
101
|
-
bufferId = existing.id;
|
|
102
|
-
}
|
|
103
|
-
else {
|
|
104
|
-
const { data: inserted, error } = await sb
|
|
105
|
-
.from("order_buffers")
|
|
106
|
-
.insert({
|
|
107
|
-
buffer_key: key,
|
|
108
|
-
grouping_mode: mode,
|
|
109
|
-
pickup_date: pickupDate || null,
|
|
110
|
-
pickup_location: location || null,
|
|
111
|
-
customer_id: mode === "same_customer" ? order.customer_id : null,
|
|
112
|
-
})
|
|
113
|
-
.select("id")
|
|
114
|
-
.single();
|
|
115
|
-
if (error) {
|
|
116
|
-
// Race: iemand anders maakte dezelfde buffer aan.
|
|
117
|
-
const { data: retry } = await sb
|
|
118
|
-
.from("order_buffers")
|
|
119
|
-
.select("id")
|
|
120
|
-
.eq("buffer_key", key)
|
|
121
|
-
.maybeSingle();
|
|
122
|
-
bufferId = retry?.id ?? null;
|
|
123
|
-
}
|
|
124
|
-
else {
|
|
125
|
-
bufferId = inserted?.id ?? null;
|
|
126
|
-
}
|
|
127
36
|
}
|
|
128
|
-
|
|
129
|
-
|
|
37
|
+
const second = await readBuffer();
|
|
38
|
+
if (!second.bufferId) {
|
|
39
|
+
console.warn(`[buffer] order ${orderId} heeft geen buffer volgens de werkwijze (buffer uit of reservering uitgesloten); niet ingedeeld.`);
|
|
130
40
|
}
|
|
131
|
-
return bufferId;
|
|
41
|
+
return second.bufferId;
|
|
132
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Een belading die uit Floriday komt (per regel of, als noodgreep, order-breed) of
|
|
45
|
+
* die een mens bewust heeft vastgezet, is leidend: het aantal lagen per ladingdrager
|
|
46
|
+
* wordt dan exact gevolgd.
|
|
47
|
+
*/
|
|
48
|
+
const isAuthoritative = (s) => s === "handmatig" || s === "floriday_line" || s === "order_items" || s === "floriday_packing";
|
|
133
49
|
const positive = (v) => {
|
|
134
50
|
const n = Number(v);
|
|
135
51
|
return Number.isFinite(n) && n > 0 ? Math.round(n) : null;
|
|
@@ -151,17 +67,58 @@ export function beladingFromFloridayPacking(packing) {
|
|
|
151
67
|
return null;
|
|
152
68
|
return { plants_per_crate: ppc, crates_per_layer: cpl, layers_per_cart: lpc };
|
|
153
69
|
}
|
|
154
|
-
/**
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
70
|
+
/**
|
|
71
|
+
* Leest per artikel de Floriday-verpakking van de orderregel zelf uit
|
|
72
|
+
* `orders.custom_fields.floriday_lines`. Dit is de enige bron die per regel klopt;
|
|
73
|
+
* `custom_fields.floriday_packing` geldt voor de hele order.
|
|
74
|
+
*/
|
|
75
|
+
export function beladingPerRegelUitOrder(customFields) {
|
|
76
|
+
const out = new Map();
|
|
77
|
+
const cf = (customFields ?? {});
|
|
78
|
+
const lines = Array.isArray(cf.floriday_lines) ? cf.floriday_lines : [];
|
|
79
|
+
for (const raw of lines) {
|
|
80
|
+
const line = (raw ?? {});
|
|
81
|
+
const tradeItemId = line.trade_item_id ? String(line.trade_item_id) : null;
|
|
82
|
+
if (!tradeItemId)
|
|
83
|
+
continue;
|
|
84
|
+
const belading = beladingFromFloridayPacking(line.packing);
|
|
85
|
+
if (belading)
|
|
86
|
+
out.set(tradeItemId, belading);
|
|
87
|
+
}
|
|
88
|
+
return out;
|
|
89
|
+
}
|
|
90
|
+
/** Waarden van de orderregel zelf, of null als de regel niets heeft. */
|
|
91
|
+
function beladingVanRegel(row) {
|
|
92
|
+
if (positive(row.plants_per_crate) == null &&
|
|
93
|
+
positive(row.crates_per_layer) == null &&
|
|
94
|
+
positive(row.layers_per_cart) == null) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
plants_per_crate: positive(row.plants_per_crate) ?? 1,
|
|
99
|
+
crates_per_layer: positive(row.crates_per_layer) ?? 1,
|
|
100
|
+
layers_per_cart: positive(row.layers_per_cart) ?? 1,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Beladingbronnen in vaste volgorde:
|
|
105
|
+
* 1. handmatig vastgezette regel (packing_locked_at);
|
|
106
|
+
* 2. de Floriday-verpakking van díe regel (op artikel gekoppeld);
|
|
107
|
+
* 3. de eigen waarden van de bestelregel;
|
|
108
|
+
* 4. het product;
|
|
109
|
+
* 5. de order-brede Floriday-verpakking — uitsluitend als allerlaatste noodgreep
|
|
110
|
+
* voor een regel die van 1 t/m 4 niets heeft;
|
|
111
|
+
* 6. anders onbekend: er wordt niet gegokt, de indeling blokkeert.
|
|
112
|
+
*/
|
|
113
|
+
function resolveBelading(row, packing, linePacking = null) {
|
|
114
|
+
const fromRow = beladingVanRegel(row);
|
|
115
|
+
// 1. Handmatig vastgezet: niemand overschrijft dat, ook Floriday niet.
|
|
116
|
+
if (row.packing_locked_at && fromRow)
|
|
117
|
+
return { belading: fromRow, source: "handmatig" };
|
|
118
|
+
// 2. De Floriday-verpakking van deze regel.
|
|
119
|
+
if (linePacking)
|
|
120
|
+
return { belading: linePacking, source: "floriday_line" };
|
|
121
|
+
// 3. De eigen waarden van de bestelregel.
|
|
165
122
|
if (fromRow)
|
|
166
123
|
return { belading: fromRow, source: "order_items" };
|
|
167
124
|
const p = row.products ?? null;
|
|
@@ -172,8 +129,13 @@ function resolveBelading(row, packing) {
|
|
|
172
129
|
layers_per_cart: positive(p.layers_per_cart) ?? 1,
|
|
173
130
|
}
|
|
174
131
|
: null;
|
|
132
|
+
// 4. Het product.
|
|
175
133
|
if (fromProduct)
|
|
176
134
|
return { belading: fromProduct, source: "product" };
|
|
135
|
+
// 5. Allerlaatste noodgreep: de order-brede Floriday-verpakking.
|
|
136
|
+
if (packing)
|
|
137
|
+
return { belading: packing, source: "floriday_packing" };
|
|
138
|
+
// 6. Onbekend.
|
|
177
139
|
return { belading: { plants_per_crate: 1, crates_per_layer: 1, layers_per_cart: 1 }, source: "geen" };
|
|
178
140
|
}
|
|
179
141
|
export async function expandOrderItemsForPlanner(sb, rows, options = {}) {
|
|
@@ -197,8 +159,15 @@ export async function expandOrderItemsForPlanner(sb, rows, options = {}) {
|
|
|
197
159
|
groupSet = new Set((groups ?? []).map((g) => g.id));
|
|
198
160
|
}
|
|
199
161
|
for (const row of rows) {
|
|
200
|
-
const
|
|
201
|
-
const
|
|
162
|
+
const orderKey = row.order_id ? String(row.order_id) : null;
|
|
163
|
+
const packing = orderKey ? options.packingByOrder?.get(orderKey) ?? null : null;
|
|
164
|
+
const lineMap = orderKey ? options.packingByOrderLine?.get(orderKey) ?? null : null;
|
|
165
|
+
const linePacking = lineMap
|
|
166
|
+
? lineMap.get(String(row.product_id ?? "")) ??
|
|
167
|
+
lineMap.get(String(row.products?.floriday_trade_item_id ?? "")) ??
|
|
168
|
+
null
|
|
169
|
+
: null;
|
|
170
|
+
const { belading: base, source } = resolveBelading(row, packing, linePacking);
|
|
202
171
|
const plants = Math.max(0, Number(row.quantity ?? 0));
|
|
203
172
|
const groupNameKey = String(row.product_name ?? "")
|
|
204
173
|
.replace(/^\[Groep\]\s*/i, "")
|
|
@@ -218,7 +187,7 @@ export async function expandOrderItemsForPlanner(sb, rows, options = {}) {
|
|
|
218
187
|
product_name: row.product_name ?? "Onbekend",
|
|
219
188
|
plants,
|
|
220
189
|
...base,
|
|
221
|
-
packing_authoritative: source
|
|
190
|
+
packing_authoritative: isAuthoritative(source),
|
|
222
191
|
weight_per_crate: row.products?.weight != null ? Number(row.products.weight) : null,
|
|
223
192
|
});
|
|
224
193
|
continue;
|
|
@@ -241,7 +210,7 @@ export async function expandOrderItemsForPlanner(sb, rows, options = {}) {
|
|
|
241
210
|
product_name: row.product_name ?? "Onbekend",
|
|
242
211
|
plants,
|
|
243
212
|
...base,
|
|
244
|
-
packing_authoritative: source
|
|
213
|
+
packing_authoritative: isAuthoritative(source),
|
|
245
214
|
weight_per_crate: null,
|
|
246
215
|
});
|
|
247
216
|
continue;
|
|
@@ -259,21 +228,36 @@ export async function expandOrderItemsForPlanner(sb, rows, options = {}) {
|
|
|
259
228
|
}
|
|
260
229
|
const resolved = memberList.map((m) => {
|
|
261
230
|
const p = prodByTrade.get(String(m.trade_item_id));
|
|
262
|
-
// Een
|
|
263
|
-
if (
|
|
264
|
-
return { m, p, belading: { ...base }, source
|
|
231
|
+
// Een handmatig vastgezette regel blijft leidend voor de hele groep.
|
|
232
|
+
if (source === "handmatig") {
|
|
233
|
+
return { m, p, belading: { ...base }, source };
|
|
234
|
+
}
|
|
235
|
+
// 1. De Floriday-verpakking van dít lid.
|
|
236
|
+
const eigenLijn = lineMap?.get(String(m.trade_item_id)) ?? (p ? lineMap?.get(String(p.id)) ?? null : null);
|
|
237
|
+
if (eigenLijn)
|
|
238
|
+
return { m, p, belading: { ...eigenLijn }, source: "floriday_line" };
|
|
239
|
+
// 2. Het artikel zelf. Nooit de belading van de groepsregel of van een ander lid.
|
|
265
240
|
const known = p &&
|
|
266
241
|
(positive(p.plants_per_crate) != null ||
|
|
267
242
|
positive(p.crates_per_layer) != null ||
|
|
268
243
|
positive(p.layers_per_cart) != null);
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
244
|
+
if (known) {
|
|
245
|
+
return {
|
|
246
|
+
m,
|
|
247
|
+
p,
|
|
248
|
+
belading: {
|
|
249
|
+
plants_per_crate: positive(p.plants_per_crate) ?? 1,
|
|
250
|
+
crates_per_layer: positive(p.crates_per_layer) ?? 1,
|
|
251
|
+
layers_per_cart: positive(p.layers_per_cart) ?? 1,
|
|
252
|
+
},
|
|
253
|
+
source: "product",
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
// 3. Allerlaatste noodgreep: de order-brede Floriday-verpakking van de order.
|
|
257
|
+
if (packing)
|
|
258
|
+
return { m, p, belading: { ...packing }, source: "floriday_packing" };
|
|
259
|
+
// 4. Onbekend: er wordt niet gegokt, de indeling blokkeert.
|
|
260
|
+
return { m, p, belading: { ...base }, source: "geen" };
|
|
277
261
|
});
|
|
278
262
|
const weightSum = resolved.reduce((s, r) => s + Number(r.m.quantity ?? 1), 0) || resolved.length;
|
|
279
263
|
const shares = [];
|
|
@@ -302,7 +286,7 @@ export async function expandOrderItemsForPlanner(sb, rows, options = {}) {
|
|
|
302
286
|
product_name: `${row.product_name ?? "Groep"} — ${r.p?.product ?? r.m.trade_item_id}`,
|
|
303
287
|
plants: shares[idx],
|
|
304
288
|
...r.belading,
|
|
305
|
-
packing_authoritative: r.source
|
|
289
|
+
packing_authoritative: isAuthoritative(r.source),
|
|
306
290
|
weight_per_crate: r.p?.weight != null ? Number(r.p.weight) : null,
|
|
307
291
|
});
|
|
308
292
|
});
|
|
@@ -347,10 +331,29 @@ export async function loadBeladingRules(sb) {
|
|
|
347
331
|
maxCustomers: k.fill_customers_as_much_as_fits ? null : numOrNull(k.max_customers),
|
|
348
332
|
maxLayers: k.fill_layers_as_much_as_fits ? null : numOrNull(k.max_layers),
|
|
349
333
|
maxBoxes: k.fill_boxes_as_much_as_fits ? null : numOrNull(k.max_boxes_per_pallet),
|
|
334
|
+
maxCustomersPerLayer: numOrNull(k.max_customers_per_layer),
|
|
350
335
|
optimalFill: k.optimal_fill !== false,
|
|
351
336
|
layoutMethod: k.cart_layout_method === "afmetingen" ? "afmetingen" : "belading",
|
|
352
337
|
};
|
|
353
338
|
}
|
|
339
|
+
/**
|
|
340
|
+
* Wanneer ligt de karrenindeling vast?
|
|
341
|
+
* - "per_cart": alleen de geprinte/gescande/gecontroleerde kar zelf.
|
|
342
|
+
* - "first_packing_slip": zodra de eerste pakbon van de order of buffer is
|
|
343
|
+
* geprint, blijft de hele bestaande indeling van die order/buffer staan.
|
|
344
|
+
* Ontbreekt de instelling, dan geldt "per_cart".
|
|
345
|
+
*/
|
|
346
|
+
export async function loadCartPlanFreeze(sb) {
|
|
347
|
+
const { data } = await sb
|
|
348
|
+
.from("logistics_rule_configs")
|
|
349
|
+
.select("settings")
|
|
350
|
+
.eq("is_active", true)
|
|
351
|
+
.order("updated_at", { ascending: false })
|
|
352
|
+
.limit(1);
|
|
353
|
+
const settings = Array.isArray(data) && data.length > 0 ? data[0]?.settings : null;
|
|
354
|
+
const k = settings?.karrenindeling?.fields ?? settings?.karrenindeling;
|
|
355
|
+
return k?.cart_plan_freeze === "first_packing_slip" ? "first_packing_slip" : "per_cart";
|
|
356
|
+
}
|
|
354
357
|
/** Past de grenzen uit de werkwijze toe op het capaciteitsprofiel van een ladingdrager. */
|
|
355
358
|
export function applyRulesToProfile(profile, rules) {
|
|
356
359
|
if (!rules)
|
|
@@ -550,6 +553,53 @@ export function dimensionCartsToPlannedUnits(carts) {
|
|
|
550
553
|
// ---------------------------------------------------------------------------
|
|
551
554
|
/** Maximaal aantal ladingdragers dat één orderbuffer mag opleveren. */
|
|
552
555
|
const MAX_CARTS_PER_BUFFER = 500;
|
|
556
|
+
/** Statussen waarbij een order niet meer ingedeeld hoeft te worden. */
|
|
557
|
+
const TERMINALE_STATUSSEN = new Set([
|
|
558
|
+
"verzonden",
|
|
559
|
+
"afgehandeld",
|
|
560
|
+
"geannuleerd",
|
|
561
|
+
"cancelled",
|
|
562
|
+
"geleverd",
|
|
563
|
+
"afgerond",
|
|
564
|
+
]);
|
|
565
|
+
/** Reservering of al afgehandelde order: niet plannen, ook niet blokkeren. */
|
|
566
|
+
function isReservationOrTerminal(order) {
|
|
567
|
+
if (!order)
|
|
568
|
+
return true;
|
|
569
|
+
if (order.is_reservation === true)
|
|
570
|
+
return true;
|
|
571
|
+
const status = String(order.status ?? "").trim().toLowerCase();
|
|
572
|
+
return TERMINALE_STATUSSEN.has(status);
|
|
573
|
+
}
|
|
574
|
+
/**
|
|
575
|
+
* Duidelijk teveel identieke bestelregels wijst op een synchronisatie die
|
|
576
|
+
* regels toevoegt in plaats van bijwerkt. Dan niet rekenen: een indeling op
|
|
577
|
+
* opgeblazen aantallen is fout én loopt in een timeout.
|
|
578
|
+
*/
|
|
579
|
+
const DUPES_MIN_REGELS = 50;
|
|
580
|
+
const DUPES_FACTOR = 3;
|
|
581
|
+
function telDubbeleRegels(rows) {
|
|
582
|
+
const groepen = new Map();
|
|
583
|
+
const producten = new Set();
|
|
584
|
+
for (const r of rows) {
|
|
585
|
+
const product = String(r["product_id"] ?? r["product_name"] ?? "");
|
|
586
|
+
producten.add(product);
|
|
587
|
+
const sleutel = [
|
|
588
|
+
product,
|
|
589
|
+
r["quantity"] ?? 0,
|
|
590
|
+
r["plants_per_crate"] ?? 0,
|
|
591
|
+
r["crates_per_layer"] ?? 0,
|
|
592
|
+
r["layers_per_cart"] ?? 0,
|
|
593
|
+
].join("|");
|
|
594
|
+
groepen.set(sleutel, (groepen.get(sleutel) ?? 0) + 1);
|
|
595
|
+
}
|
|
596
|
+
return {
|
|
597
|
+
regels: rows.length,
|
|
598
|
+
uniek: groepen.size,
|
|
599
|
+
kopieen: rows.length - groepen.size,
|
|
600
|
+
producten: producten.size,
|
|
601
|
+
};
|
|
602
|
+
}
|
|
553
603
|
/**
|
|
554
604
|
* Bepaalt de definitieve, persistente identiteit van iedere fysieke kar.
|
|
555
605
|
* Bij een bufferorder met meerdere orders zijn alle karren gedeelde
|
|
@@ -643,7 +693,62 @@ export async function bufferOrdersWithoutCarts(sb, bufferId, orderIds) {
|
|
|
643
693
|
}
|
|
644
694
|
return ids.filter((id) => !withCarts.has(id));
|
|
645
695
|
}
|
|
696
|
+
/**
|
|
697
|
+
* Auditpunt 2: één buffer wordt nooit door twee processen tegelijk herschreven.
|
|
698
|
+
*
|
|
699
|
+
* De lees-verwijder-herschrijf-cyclus hieronder loopt over veel losse aanroepen
|
|
700
|
+
* en past dus niet in één databasetransactie; een advisory xact lock zou meteen
|
|
701
|
+
* na de eerste aanroep weer vrijvallen. Daarom een kortlopende lease met
|
|
702
|
+
* vervaltijd (logistics_plan_lock_acquire), die wél geldt over de planwachtrij,
|
|
703
|
+
* de massa-herberekening en een openstaand scherm heen.
|
|
704
|
+
*
|
|
705
|
+
* Lukt de vergrendeling niet, dan volgt een nette "wordt al bijgewerkt"-uitkomst
|
|
706
|
+
* in plaats van een tweede, botsende herberekening.
|
|
707
|
+
*/
|
|
646
708
|
export async function replanBufferInternal(sb, bufferId, options = {}) {
|
|
709
|
+
// Een proefberekening schrijft niets weg en hoeft dus niets te vergrendelen.
|
|
710
|
+
if (options.dryRun)
|
|
711
|
+
return replanBufferUnlocked(sb, bufferId, options);
|
|
712
|
+
const lockKey = `buffer:${bufferId}`;
|
|
713
|
+
let held = false;
|
|
714
|
+
try {
|
|
715
|
+
const { data, error } = await sb.rpc("logistics_plan_lock_acquire", {
|
|
716
|
+
_key: lockKey,
|
|
717
|
+
_owner: "logistiek-engine",
|
|
718
|
+
_ttl_seconds: 600,
|
|
719
|
+
});
|
|
720
|
+
held = !error && data === true;
|
|
721
|
+
}
|
|
722
|
+
catch {
|
|
723
|
+
held = false;
|
|
724
|
+
}
|
|
725
|
+
if (!held) {
|
|
726
|
+
return {
|
|
727
|
+
ok: true,
|
|
728
|
+
bufferId,
|
|
729
|
+
orderIds: [],
|
|
730
|
+
cartsCreated: 0,
|
|
731
|
+
replacedManualCount: 0,
|
|
732
|
+
usedFloridayPacking: false,
|
|
733
|
+
warnings: [BUFFER_BUSY_MESSAGE],
|
|
734
|
+
skipped: "locked",
|
|
735
|
+
};
|
|
736
|
+
}
|
|
737
|
+
try {
|
|
738
|
+
return await replanBufferUnlocked(sb, bufferId, options);
|
|
739
|
+
}
|
|
740
|
+
finally {
|
|
741
|
+
try {
|
|
742
|
+
await sb.rpc("logistics_plan_lock_release", { _key: lockKey, _owner: "logistiek-engine" });
|
|
743
|
+
}
|
|
744
|
+
catch {
|
|
745
|
+
// Vrijgeven mag nooit de uitkomst omgooien; de lease verloopt vanzelf.
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
/** Herbruikbare melding als een buffer al door een ander proces wordt bijgewerkt. */
|
|
750
|
+
export const BUFFER_BUSY_MESSAGE = "Deze orderbuffer wordt op dit moment al bijgewerkt; probeer het zo opnieuw.";
|
|
751
|
+
async function replanBufferUnlocked(sb, bufferId, options = {}) {
|
|
647
752
|
const warnings = [];
|
|
648
753
|
// Bevroren karren blijven staan; al het overige van de héle buffer wordt
|
|
649
754
|
// samen ingedeeld, zodat orders van de buffer op dezelfde karren komen.
|
|
@@ -651,11 +756,17 @@ export async function replanBufferInternal(sb, bufferId, options = {}) {
|
|
|
651
756
|
const isSupplement = keepCartIds.length > 0;
|
|
652
757
|
const { data: orders } = await sb
|
|
653
758
|
.from("orders")
|
|
654
|
-
.select("id, order_number, customer_id, custom_fields, plan_locked_at")
|
|
759
|
+
.select("id, order_number, customer_id, custom_fields, plan_locked_at, status, is_reservation")
|
|
655
760
|
.eq("buffer_id", bufferId)
|
|
656
761
|
.is("deleted_at", null);
|
|
657
762
|
const allOrderRows = Array.isArray(orders) ? orders : [];
|
|
658
|
-
|
|
763
|
+
// Reserveringen en al afgeronde orders horen niet in de indeling thuis. Ze
|
|
764
|
+
// leveren nooit karren op en zouden anders de hele buffer laten blokkeren.
|
|
765
|
+
const orderRows = allOrderRows.filter((o) => !isReservationOrTerminal(o));
|
|
766
|
+
const overgeslagen = allOrderRows.length - orderRows.length;
|
|
767
|
+
if (overgeslagen > 0) {
|
|
768
|
+
warnings.push(`${overgeslagen} order(s) in deze buffer zijn overgeslagen: reservering of al afgehandeld.`);
|
|
769
|
+
}
|
|
659
770
|
const orderIds = orderRows.map((o) => o.id);
|
|
660
771
|
// Binnen een bufferorder mogen orders en klanten altijd samen op een kar.
|
|
661
772
|
const isBufferOrder = orderRows.length > 1;
|
|
@@ -688,8 +799,10 @@ export async function replanBufferInternal(sb, bufferId, options = {}) {
|
|
|
688
799
|
};
|
|
689
800
|
}
|
|
690
801
|
// Een eenmaal geprinte, gescande of gecontroleerde fysieke kar heeft een
|
|
691
|
-
// blijvende identiteit.
|
|
692
|
-
//
|
|
802
|
+
// blijvende identiteit. Let op: een klaargezette barcode is nog géén bewijs
|
|
803
|
+
// van printen, en een wachtende, mislukte of geannuleerde printopdracht ook
|
|
804
|
+
// niet. Alleen een voltooide print of een echte scan-/controlehandeling
|
|
805
|
+
// bevriest een kar.
|
|
693
806
|
if (!isSupplement) {
|
|
694
807
|
const { data: existingCartRows, error: existingCartError } = await sb
|
|
695
808
|
.from("order_carts")
|
|
@@ -702,12 +815,15 @@ export async function replanBufferInternal(sb, bufferId, options = {}) {
|
|
|
702
815
|
.map((cart) => String(cart.id ?? ""))
|
|
703
816
|
.filter(Boolean);
|
|
704
817
|
if (existingCartIds.length > 0) {
|
|
705
|
-
const [controlResult,
|
|
818
|
+
const [controlResult, printResult] = await Promise.all([
|
|
706
819
|
sb.from("cart_control_items").select("cart_number_id").in("cart_number_id", existingCartIds),
|
|
707
|
-
sb
|
|
708
|
-
|
|
820
|
+
sb
|
|
821
|
+
.from("print_jobs")
|
|
822
|
+
.select("id, payload")
|
|
823
|
+
.eq("status", "voltooid")
|
|
824
|
+
.in("payload->>cart_id", existingCartIds),
|
|
709
825
|
]);
|
|
710
|
-
const lockCheckError = controlResult.error ??
|
|
826
|
+
const lockCheckError = controlResult.error ?? printResult.error;
|
|
711
827
|
if (lockCheckError) {
|
|
712
828
|
throw new Error(`Karvergrendeling kon niet veilig worden gecontroleerd: ${lockCheckError.message}`);
|
|
713
829
|
}
|
|
@@ -717,11 +833,6 @@ export async function replanBufferInternal(sb, bufferId, options = {}) {
|
|
|
717
833
|
if (id)
|
|
718
834
|
frozen.add(String(id));
|
|
719
835
|
}
|
|
720
|
-
for (const r of barcodeResult.data ?? []) {
|
|
721
|
-
const id = r?.cart_id;
|
|
722
|
-
if (id)
|
|
723
|
-
frozen.add(String(id));
|
|
724
|
-
}
|
|
725
836
|
for (const r of printResult.data ?? []) {
|
|
726
837
|
const id = r?.payload?.cart_id;
|
|
727
838
|
if (id)
|
|
@@ -729,19 +840,26 @@ export async function replanBufferInternal(sb, bufferId, options = {}) {
|
|
|
729
840
|
}
|
|
730
841
|
const frozenIds = existingCartIds.filter((id) => frozen.has(id));
|
|
731
842
|
if (frozenIds.length > 0) {
|
|
732
|
-
//
|
|
733
|
-
//
|
|
734
|
-
|
|
843
|
+
// Instelling uit de werkwijze: alleen de geprinte kar vastzetten, of de
|
|
844
|
+
// hele order/buffer zodra de eerste pakbon is geprint.
|
|
845
|
+
const freezeMode = await loadCartPlanFreeze(sb);
|
|
846
|
+
const keep = freezeMode === "first_packing_slip" ? existingCartIds : frozenIds;
|
|
847
|
+
// Bevroren karren blijven exact zoals ze zijn. Al het overige wordt in
|
|
848
|
+
// één ronde samen ingedeeld, zodat de resterende fusten van alle orders
|
|
849
|
+
// op dezelfde karren komen.
|
|
735
850
|
const supplement = await replanBufferInternal(sb, bufferId, {
|
|
736
851
|
...options,
|
|
737
|
-
keepCartIds:
|
|
852
|
+
keepCartIds: keep,
|
|
738
853
|
});
|
|
739
854
|
return {
|
|
740
855
|
...supplement,
|
|
741
856
|
warnings: [
|
|
742
857
|
...(supplement.warnings ?? []),
|
|
743
|
-
|
|
744
|
-
|
|
858
|
+
freezeMode === "first_packing_slip"
|
|
859
|
+
? `De eerste pakbon van deze order of buffer is al geprint; alle ${keep.length} bestaande kar(ren) blijven ongewijzigd. ` +
|
|
860
|
+
"Alleen fusten die nog nergens liggen zijn als extra kar toegevoegd."
|
|
861
|
+
: `${keep.length} reeds geprinte of gecontroleerde kar(ren) zijn ongewijzigd gebleven; ` +
|
|
862
|
+
"de overige fusten van de buffer zijn samen opnieuw ingedeeld.",
|
|
745
863
|
],
|
|
746
864
|
};
|
|
747
865
|
}
|
|
@@ -751,22 +869,86 @@ export async function replanBufferInternal(sb, bufferId, options = {}) {
|
|
|
751
869
|
const { data: items } = await sb
|
|
752
870
|
.from("order_items")
|
|
753
871
|
.select("id, order_id, product_id, product_name, quantity, plants_per_crate, crates_per_layer, layers_per_cart, " +
|
|
754
|
-
"
|
|
872
|
+
"packing_locked_at, packing_locked_by, " +
|
|
873
|
+
"products(plants_per_crate, crates_per_layer, layers_per_cart, weight, floriday_trade_item_id)")
|
|
755
874
|
.in("order_id", idList);
|
|
875
|
+
// Duidelijk teveel identieke bestelregels: niet rekenen maar blokkeren.
|
|
876
|
+
// Een indeling op opgeblazen aantallen is fout en loopt in een timeout.
|
|
877
|
+
{
|
|
878
|
+
const perOrder = new Map();
|
|
879
|
+
for (const it of Array.isArray(items) ? items : []) {
|
|
880
|
+
const id = String(it.order_id ?? "");
|
|
881
|
+
const lijst = perOrder.get(id) ?? [];
|
|
882
|
+
lijst.push(it);
|
|
883
|
+
perOrder.set(id, lijst);
|
|
884
|
+
}
|
|
885
|
+
const verdacht = [];
|
|
886
|
+
for (const [id, rijen] of perOrder) {
|
|
887
|
+
const telling = telDubbeleRegels(rijen);
|
|
888
|
+
if (telling.regels >= DUPES_MIN_REGELS && telling.regels > telling.uniek * DUPES_FACTOR) {
|
|
889
|
+
const order = orderRows.find((o) => String(o.id) === id);
|
|
890
|
+
verdacht.push({ orderId: id, orderRef: String(order?.order_number ?? id), telling });
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
if (verdacht.length > 0) {
|
|
894
|
+
const reason = "Dubbele bestelregels gevonden: " +
|
|
895
|
+
verdacht
|
|
896
|
+
.map((v) => `${v.orderRef} heeft ${v.telling.regels} bestelregels voor ${v.telling.producten} product(en) ` +
|
|
897
|
+
`(${v.telling.kopieen} kopie(ën))`)
|
|
898
|
+
.join("; ") +
|
|
899
|
+
". Er is niets ingedeeld; de kopieën moeten eerst worden opgeruimd en de bron ervan hersteld.";
|
|
900
|
+
console.error(`[logistiek-engine] buffer ${bufferId} geblokkeerd: ${reason}`);
|
|
901
|
+
if (!options.dryRun) {
|
|
902
|
+
const { error: blockError } = await sb.rpc("logistics_plan_block", {
|
|
903
|
+
p_order_ids: orderIds,
|
|
904
|
+
p_reason: reason,
|
|
905
|
+
p_details: {
|
|
906
|
+
reason_code: "dubbele_regels",
|
|
907
|
+
bufferId,
|
|
908
|
+
path: "typescript",
|
|
909
|
+
orders: verdacht.map((v) => ({ orderRef: v.orderRef, ...v.telling })),
|
|
910
|
+
},
|
|
911
|
+
});
|
|
912
|
+
if (blockError)
|
|
913
|
+
warnings.push(`Blokkade kon niet worden vastgelegd: ${blockError.message}`);
|
|
914
|
+
}
|
|
915
|
+
return {
|
|
916
|
+
ok: false,
|
|
917
|
+
bufferId,
|
|
918
|
+
orderIds,
|
|
919
|
+
cartsCreated: 0,
|
|
920
|
+
replacedManualCount: 0,
|
|
921
|
+
usedFloridayPacking: false,
|
|
922
|
+
warnings: [...warnings, reason],
|
|
923
|
+
skipped: "blocked",
|
|
924
|
+
blockedReason: reason,
|
|
925
|
+
};
|
|
926
|
+
}
|
|
927
|
+
}
|
|
756
928
|
const customerByOrder = new Map(orderRows.map((o) => [o.id, o.customer_id ?? null]));
|
|
757
929
|
const rawRows = (Array.isArray(items) ? items : []).map((it) => ({
|
|
758
930
|
...it,
|
|
759
931
|
customer_id: customerByOrder.get(it.order_id) ?? null,
|
|
760
932
|
}));
|
|
761
|
-
// Floriday-verpakking
|
|
933
|
+
// Order-brede Floriday-verpakking: alleen nog als allerlaatste noodgreep.
|
|
762
934
|
const packingByOrder = new Map();
|
|
935
|
+
// Floriday-verpakking per orderregel, gekoppeld op artikel. Dit is de bron die
|
|
936
|
+
// per regel klopt en gaat voor op de order-brede waarde.
|
|
937
|
+
const packingByOrderLine = new Map();
|
|
763
938
|
for (const o of orderRows) {
|
|
764
939
|
const belading = beladingFromFloridayPacking(o.custom_fields?.floriday_packing);
|
|
765
940
|
if (belading)
|
|
766
941
|
packingByOrder.set(String(o.id), belading);
|
|
942
|
+
const perRegel = beladingPerRegelUitOrder(o.custom_fields);
|
|
943
|
+
if (perRegel.size > 0)
|
|
944
|
+
packingByOrderLine.set(String(o.id), perRegel);
|
|
767
945
|
}
|
|
768
946
|
const sources = new Map();
|
|
769
|
-
let inputs = await expandOrderItemsForPlanner(sb, rawRows, {
|
|
947
|
+
let inputs = await expandOrderItemsForPlanner(sb, rawRows, {
|
|
948
|
+
packingByOrder,
|
|
949
|
+
packingByOrderLine,
|
|
950
|
+
sources,
|
|
951
|
+
});
|
|
770
952
|
// Wat al op een bevroren kar ligt, wordt niet opnieuw ingedeeld.
|
|
771
953
|
let frozenCartCount = 0;
|
|
772
954
|
let cartNumberStart = 1;
|
|
@@ -896,8 +1078,17 @@ export async function replanBufferInternal(sb, bufferId, options = {}) {
|
|
|
896
1078
|
// daarom verwijderd.)
|
|
897
1079
|
const solo = orderRows.length === 1 ? orderRows[0] : null;
|
|
898
1080
|
const soloPacking = solo ? beladingFromFloridayPacking(solo.custom_fields?.floriday_packing) : null;
|
|
1081
|
+
// Het overname-pad legt één verpakking over de hele order. Dat mag alleen als
|
|
1082
|
+
// elke regel toch al exact die verpakking heeft; anders rekent de gewone
|
|
1083
|
+
// planner, die per regel met de eigen belading werkt.
|
|
1084
|
+
const uniformeBelading = inputs.length > 0 &&
|
|
1085
|
+
soloPacking != null &&
|
|
1086
|
+
inputs.every((i) => i.plants_per_crate === soloPacking.plants_per_crate &&
|
|
1087
|
+
i.crates_per_layer === soloPacking.crates_per_layer &&
|
|
1088
|
+
i.layers_per_cart === soloPacking.layers_per_cart);
|
|
899
1089
|
const takeoverProfile = profileFor(profiles, options.carrierType ?? defaultProfile.code);
|
|
900
|
-
const takeoverAllowed = takeoverProfile.capacity_model === "layered" || takeoverProfile.capacity_model === "stacked"
|
|
1090
|
+
const takeoverAllowed = (takeoverProfile.capacity_model === "layered" || takeoverProfile.capacity_model === "stacked") &&
|
|
1091
|
+
uniformeBelading;
|
|
901
1092
|
if (useDimensions) {
|
|
902
1093
|
// Globale methode: indelen op basis van fysieke afmetingen.
|
|
903
1094
|
const carrierRow = profiles.find((p) => p.code === (options.carrierType ?? defaultProfile.code)) ?? null;
|
|
@@ -1008,6 +1199,7 @@ export async function replanBufferInternal(sb, bufferId, options = {}) {
|
|
|
1008
1199
|
maxOrders: isBufferOrder ? null : rules?.maxOrders ?? null,
|
|
1009
1200
|
maxCustomers: isBufferOrder ? null : rules?.maxCustomers ?? settings?.max_customers_per_cart ?? null,
|
|
1010
1201
|
oneCustomerPerLayer: Boolean(settings?.max_one_customer_per_layer),
|
|
1202
|
+
maxCustomersPerLayer: rules?.maxCustomersPerLayer ?? null,
|
|
1011
1203
|
ownCarrierFromLayers: settings?.own_cart_from_layers ?? null,
|
|
1012
1204
|
sortOrder: "none",
|
|
1013
1205
|
});
|
|
@@ -1020,8 +1212,13 @@ export async function replanBufferInternal(sb, bufferId, options = {}) {
|
|
|
1020
1212
|
}
|
|
1021
1213
|
// Veiligheidsgrens: een onrealistisch grote uitkomst wordt niet afgekapt maar
|
|
1022
1214
|
// geblokkeerd. Een half plan laat planten stilzwijgend uit de pickstroom vallen.
|
|
1023
|
-
|
|
1024
|
-
|
|
1215
|
+
// Auditpunt 2: de grens telt cumulatief. Bij aanvullen na bevroren karren
|
|
1216
|
+
// tellen die bevroren karren mee, zodat de grens niet in kleine stapjes
|
|
1217
|
+
// omzeild kan worden.
|
|
1218
|
+
const totalCartsAfterPlan = plannedCarts.length + frozenCartCount;
|
|
1219
|
+
if (totalCartsAfterPlan > MAX_CARTS_PER_BUFFER) {
|
|
1220
|
+
const reason = `Deze orderbuffer komt uit op ${totalCartsAfterPlan} ladingdragers ` +
|
|
1221
|
+
`(${frozenCartCount} al vastgelegd + ${plannedCarts.length} nieuw), meer dan de grens van ` +
|
|
1025
1222
|
`${MAX_CARTS_PER_BUFFER}. Er is niets ingedeeld; controleer de aantallen of de belading van de producten.`;
|
|
1026
1223
|
console.error(`[logistiek-engine] buffer ${bufferId} geblokkeerd: ${reason}`);
|
|
1027
1224
|
if (!options.dryRun) {
|
|
@@ -1032,7 +1229,9 @@ export async function replanBufferInternal(sb, bufferId, options = {}) {
|
|
|
1032
1229
|
reason_code: "kargrens_overschreden",
|
|
1033
1230
|
bufferId,
|
|
1034
1231
|
path: "typescript",
|
|
1035
|
-
carts:
|
|
1232
|
+
carts: totalCartsAfterPlan,
|
|
1233
|
+
newCarts: plannedCarts.length,
|
|
1234
|
+
frozenCarts: frozenCartCount,
|
|
1036
1235
|
limit: MAX_CARTS_PER_BUFFER,
|
|
1037
1236
|
beladingSources,
|
|
1038
1237
|
},
|
|
@@ -1139,37 +1338,167 @@ export async function replanBufferInternal(sb, bufferId, options = {}) {
|
|
|
1139
1338
|
await del().in("order_id", ids);
|
|
1140
1339
|
}
|
|
1141
1340
|
};
|
|
1142
|
-
|
|
1143
|
-
//
|
|
1144
|
-
//
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1341
|
+
// ---------------------------------------------------------------------------
|
|
1342
|
+
// Vergelijken in plaats van wissen. Een kar die exact hetzelfde blijft, blijft
|
|
1343
|
+
// staan: zelfde rij, zelfde id, zelfde barcode. Alleen echte verschillen worden
|
|
1344
|
+
// geschreven. Dit voorkomt dat elke herberekening honderdduizenden karren en
|
|
1345
|
+
// logboekregels produceert.
|
|
1346
|
+
// ---------------------------------------------------------------------------
|
|
1347
|
+
const getal = (v) => v === null || v === undefined || v === "" ? "" : Number.isFinite(Number(v)) ? Number(v).toFixed(3) : String(v);
|
|
1348
|
+
const itemSig = (r) => [
|
|
1349
|
+
String(r["order_item_id"] ?? ""),
|
|
1350
|
+
String(r["product_id"] ?? ""),
|
|
1351
|
+
getal(r["crates"]),
|
|
1352
|
+
getal(r["boxes"]),
|
|
1353
|
+
getal(r["weight_kg"]),
|
|
1354
|
+
getal(r["plants_per_crate"]),
|
|
1355
|
+
getal(r["crates_per_layer"]),
|
|
1356
|
+
getal(r["layers_per_cart"]),
|
|
1357
|
+
getal(r["layer_fraction"]),
|
|
1358
|
+
getal(r["layer_share_pct"]),
|
|
1359
|
+
getal(r["layer_index"]),
|
|
1360
|
+
getal(r["crates_in_layer"]),
|
|
1361
|
+
].join("|");
|
|
1362
|
+
const karSig = (row, items) => [
|
|
1363
|
+
String(row["order_id"] ?? "gedeeld"),
|
|
1364
|
+
getal(row["cart_number"]),
|
|
1365
|
+
getal(row["total_carts"]),
|
|
1366
|
+
String(row["carrier_type"] ?? ""),
|
|
1367
|
+
String(row["carrier_type_id"] ?? ""),
|
|
1368
|
+
getal(row["max_layers"]),
|
|
1369
|
+
getal(row["total_layers_used"]),
|
|
1370
|
+
getal(row["occupancy_pct"]),
|
|
1371
|
+
getal(row["weight_kg"]),
|
|
1372
|
+
String(row["layout_method"] ?? ""),
|
|
1373
|
+
getal(row["algorithm_version"]),
|
|
1374
|
+
String(row["is_complete"] ?? ""),
|
|
1375
|
+
String(row["source"] ?? ""),
|
|
1376
|
+
].join("|") +
|
|
1377
|
+
"#" +
|
|
1378
|
+
items.map(itemSig).sort().join(";");
|
|
1379
|
+
const gewenst = plannedCarts.map((cart, cartIndex) => {
|
|
1380
|
+
const identity = persistentIdentities[cartIndex];
|
|
1381
|
+
if (!identity) {
|
|
1382
|
+
throw new Error(`Karrenindeling ongeldig: identiteit ontbreekt voor kar ${cartIndex + 1}.`);
|
|
1383
|
+
}
|
|
1384
|
+
const row = {
|
|
1385
|
+
order_id: identity.ownerOrderId,
|
|
1386
|
+
buffer_id: bufferId,
|
|
1387
|
+
cart_number: identity.cartNumber,
|
|
1388
|
+
total_carts: identity.totalCarts,
|
|
1389
|
+
is_auto: true,
|
|
1390
|
+
is_complete: cart.is_complete,
|
|
1391
|
+
source: usedFloridayPacking ? "floriday" : "auto",
|
|
1392
|
+
occupancy_pct: cart.occupancy_pct,
|
|
1393
|
+
total_layers_used: cart.total_layers_used,
|
|
1394
|
+
carrier_type: cart.carrier_type,
|
|
1395
|
+
carrier_type_id: cart.carrier_type_id,
|
|
1396
|
+
max_layers: cart.max_layers,
|
|
1397
|
+
weight_kg: cart.weight_kg,
|
|
1398
|
+
algorithm_version: useDimensions ? DIMENSION_PLANNER_VERSION : LOAD_CARRIER_PLANNER_VERSION,
|
|
1399
|
+
layout_method: useDimensions ? "afmetingen" : "belading",
|
|
1400
|
+
};
|
|
1401
|
+
const items = cart.items.map((i) => ({
|
|
1402
|
+
order_item_id: i.order_item_id.includes("::") ? i.order_item_id.split("::")[0] : i.order_item_id,
|
|
1403
|
+
product_id: i.product_id,
|
|
1404
|
+
product_name: i.product_name,
|
|
1405
|
+
crates: Math.round(i.crates),
|
|
1406
|
+
boxes: i.boxes ?? null,
|
|
1407
|
+
weight_kg: i.weight_kg ?? null,
|
|
1408
|
+
plants_per_crate: i.plants_per_crate,
|
|
1409
|
+
crates_per_layer: i.crates_per_layer,
|
|
1410
|
+
layers_per_cart: i.layers_per_cart,
|
|
1411
|
+
layer_fraction: i.layer_fraction,
|
|
1412
|
+
layer_share_pct: i.layer_share_pct,
|
|
1413
|
+
layer_index: i.layer_index ?? null,
|
|
1414
|
+
crates_in_layer: i.crates_in_layer ?? null,
|
|
1415
|
+
}));
|
|
1416
|
+
return { planIndex: cartIndex, row, items, sig: karSig(row, items) };
|
|
1417
|
+
});
|
|
1418
|
+
// Bestaande (niet-bevroren) karren binnen dezelfde scope inlezen.
|
|
1419
|
+
const scopeOr = orderIds.length > 0
|
|
1420
|
+
? `buffer_id.eq.${bufferId},order_id.in.(${orderIds.join(",")})`
|
|
1421
|
+
: `buffer_id.eq.${bufferId}`;
|
|
1422
|
+
const { data: huidigeKarren, error: huidigeError } = await sb
|
|
1423
|
+
.from("order_carts")
|
|
1424
|
+
.select("id, order_id, buffer_id, cart_number, total_carts, is_complete, source, occupancy_pct, " +
|
|
1425
|
+
"total_layers_used, carrier_type, carrier_type_id, max_layers, weight_kg, algorithm_version, layout_method")
|
|
1426
|
+
.or(scopeOr);
|
|
1427
|
+
if (huidigeError) {
|
|
1428
|
+
throw new Error(`Bestaande karren konden niet worden vergeleken: ${huidigeError.message}`);
|
|
1429
|
+
}
|
|
1430
|
+
const bevroren = new Set(keepCartIds.map(String));
|
|
1431
|
+
const vergelijkbaar = (Array.isArray(huidigeKarren) ? huidigeKarren : []).filter((c) => !bevroren.has(String(c.id)));
|
|
1432
|
+
const huidigeIds = vergelijkbaar.map((c) => String(c.id));
|
|
1433
|
+
const huidigeItems = new Map();
|
|
1434
|
+
for (let s = 0; s < huidigeIds.length; s += 200) {
|
|
1435
|
+
const slice = huidigeIds.slice(s, s + 200);
|
|
1436
|
+
if (slice.length === 0)
|
|
1437
|
+
continue;
|
|
1438
|
+
const { data: rijen, error: itemsError } = await sb
|
|
1439
|
+
.from("order_cart_items")
|
|
1440
|
+
.select("cart_id, order_item_id, product_id, crates, boxes, weight_kg, plants_per_crate, " +
|
|
1441
|
+
"crates_per_layer, layers_per_cart, layer_fraction, layer_share_pct, layer_index, crates_in_layer")
|
|
1442
|
+
.in("cart_id", slice);
|
|
1443
|
+
if (itemsError) {
|
|
1444
|
+
throw new Error(`Inhoud van bestaande karren kon niet worden gelezen: ${itemsError.message}`);
|
|
1445
|
+
}
|
|
1446
|
+
for (const r of Array.isArray(rijen) ? rijen : []) {
|
|
1447
|
+
const id = String(r.cart_id);
|
|
1448
|
+
const lijst = huidigeItems.get(id) ?? [];
|
|
1449
|
+
lijst.push(r);
|
|
1450
|
+
huidigeItems.set(id, lijst);
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
const beschikbaarPerSig = new Map();
|
|
1454
|
+
for (const c of vergelijkbaar) {
|
|
1455
|
+
const id = String(c.id);
|
|
1456
|
+
const sig = karSig(c, huidigeItems.get(id) ?? []);
|
|
1457
|
+
const lijst = beschikbaarPerSig.get(sig) ?? [];
|
|
1458
|
+
lijst.push(id);
|
|
1459
|
+
beschikbaarPerSig.set(sig, lijst);
|
|
1460
|
+
}
|
|
1461
|
+
const behouden = new Map(); // planIndex -> bestaande cart id
|
|
1462
|
+
const nieuw = [];
|
|
1463
|
+
for (const g of gewenst) {
|
|
1464
|
+
const kandidaten = beschikbaarPerSig.get(g.sig);
|
|
1465
|
+
const hergebruik = kandidaten && kandidaten.length > 0 ? kandidaten.shift() : undefined;
|
|
1466
|
+
if (hergebruik)
|
|
1467
|
+
behouden.set(g.planIndex, hergebruik);
|
|
1468
|
+
else
|
|
1469
|
+
nieuw.push(g);
|
|
1470
|
+
}
|
|
1471
|
+
const teVerwijderen = [];
|
|
1472
|
+
for (const lijst of beschikbaarPerSig.values())
|
|
1473
|
+
teVerwijderen.push(...lijst);
|
|
1474
|
+
if (teVerwijderen.length > 0) {
|
|
1475
|
+
for (let s = 0; s < teVerwijderen.length; s += 200) {
|
|
1476
|
+
const slice = teVerwijderen.slice(s, s + 200);
|
|
1477
|
+
// Alleen voor karren die echt verdwijnen: openstaande printopdrachten en barcodes vervallen.
|
|
1478
|
+
await sb
|
|
1479
|
+
.from("print_jobs")
|
|
1480
|
+
.update({ status: "geannuleerd", foutmelding: "Vervallen: kar verwijderd bij herindeling", updated_at: new Date().toISOString() })
|
|
1481
|
+
.in("status", ["wachtend", "bezig"])
|
|
1482
|
+
.in("payload->>cart_id", slice);
|
|
1483
|
+
await sb.from("pakbon_barcode_inhoud").delete().in("cart_id", slice);
|
|
1484
|
+
await sb.from("order_cart_layer_positions").delete().in("cart_id", slice);
|
|
1485
|
+
const { error: delError } = await sb.from("order_carts").delete().in("id", slice);
|
|
1486
|
+
if (delError) {
|
|
1487
|
+
throw new Error(`Verouderde karren konden niet worden opgeruimd: ${delError.message}`);
|
|
1154
1488
|
}
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
weight_kg: cart.weight_kg,
|
|
1169
|
-
algorithm_version: useDimensions ? DIMENSION_PLANNER_VERSION : LOAD_CARRIER_PLANNER_VERSION,
|
|
1170
|
-
layout_method: useDimensions ? "afmetingen" : "belading",
|
|
1171
|
-
};
|
|
1172
|
-
});
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
let created = behouden.size;
|
|
1492
|
+
const idByPlanIndex = new Map(behouden);
|
|
1493
|
+
if (nieuw.length === 0) {
|
|
1494
|
+
console.log(`[logistiek-engine] buffer ${bufferId}: indeling ongewijzigd, ${behouden.size} kar(ren) behouden, ` +
|
|
1495
|
+
`${teVerwijderen.length} verouderde kar(ren) verwijderd.`);
|
|
1496
|
+
}
|
|
1497
|
+
// Alleen de echt nieuwe karren wegschrijven, in blokken.
|
|
1498
|
+
const CHUNK = 40;
|
|
1499
|
+
for (let start = 0; start < nieuw.length; start += CHUNK) {
|
|
1500
|
+
const chunk = nieuw.slice(start, start + CHUNK);
|
|
1501
|
+
const cartRows = chunk.map((g) => g.row);
|
|
1173
1502
|
const { data: insertedCarts, error } = await sb
|
|
1174
1503
|
.from("order_carts")
|
|
1175
1504
|
.insert(cartRows)
|
|
@@ -1184,33 +1513,13 @@ export async function replanBufferInternal(sb, bufferId, options = {}) {
|
|
|
1184
1513
|
const identityKey = (orderId, cartNumber) => `${orderId == null ? "shared" : String(orderId)}:${Number(cartNumber)}`;
|
|
1185
1514
|
const idByIdentity = new Map(insertedCarts.map((c) => [identityKey(c.order_id, c.cart_number), String(c.id)]));
|
|
1186
1515
|
const itemRows = [];
|
|
1187
|
-
for (
|
|
1188
|
-
const
|
|
1189
|
-
const identity = persistentIdentities[start + chunkIndex];
|
|
1190
|
-
const cartId = identity
|
|
1191
|
-
? idByIdentity.get(identityKey(identity.ownerOrderId, identity.cartNumber))
|
|
1192
|
-
: undefined;
|
|
1516
|
+
for (const g of chunk) {
|
|
1517
|
+
const cartId = idByIdentity.get(identityKey(g.row["order_id"], g.row["cart_number"]));
|
|
1193
1518
|
if (!cartId)
|
|
1194
1519
|
continue;
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
// Samengestelde sleutels van groepsleden bestaan niet als orderregel.
|
|
1199
|
-
order_item_id: i.order_item_id.includes("::") ? i.order_item_id.split("::")[0] : i.order_item_id,
|
|
1200
|
-
product_id: i.product_id,
|
|
1201
|
-
product_name: i.product_name,
|
|
1202
|
-
crates: Math.round(i.crates),
|
|
1203
|
-
boxes: i.boxes ?? null,
|
|
1204
|
-
weight_kg: i.weight_kg ?? null,
|
|
1205
|
-
plants_per_crate: i.plants_per_crate,
|
|
1206
|
-
crates_per_layer: i.crates_per_layer,
|
|
1207
|
-
layers_per_cart: i.layers_per_cart,
|
|
1208
|
-
layer_fraction: i.layer_fraction,
|
|
1209
|
-
layer_share_pct: i.layer_share_pct,
|
|
1210
|
-
layer_index: i.layer_index ?? null,
|
|
1211
|
-
crates_in_layer: i.crates_in_layer ?? null,
|
|
1212
|
-
});
|
|
1213
|
-
}
|
|
1520
|
+
idByPlanIndex.set(g.planIndex, cartId);
|
|
1521
|
+
for (const i of g.items)
|
|
1522
|
+
itemRows.push({ ...i, cart_id: cartId });
|
|
1214
1523
|
}
|
|
1215
1524
|
for (let s = 0; s < itemRows.length; s += 500) {
|
|
1216
1525
|
const slice = itemRows.slice(s, s + 500);
|
|
@@ -1231,14 +1540,11 @@ export async function replanBufferInternal(sb, bufferId, options = {}) {
|
|
|
1231
1540
|
// Fysieke laagindeling per kar. Alleen de afmetingenmethode levert posities;
|
|
1232
1541
|
// bij belading blijft de indeling leeg ("nog geen laagindeling aangeleverd").
|
|
1233
1542
|
if (dimensionPlan) {
|
|
1234
|
-
for (
|
|
1235
|
-
const
|
|
1236
|
-
const
|
|
1237
|
-
const
|
|
1238
|
-
|
|
1239
|
-
: undefined;
|
|
1240
|
-
const dimCart = dimensionPlan.get(start + chunkIndex);
|
|
1241
|
-
if (!cartId || !dimCart)
|
|
1543
|
+
for (const g of chunk) {
|
|
1544
|
+
const cartId = idByPlanIndex.get(g.planIndex);
|
|
1545
|
+
const dimCart = dimensionPlan.get(g.planIndex);
|
|
1546
|
+
const cart = plannedCarts[g.planIndex];
|
|
1547
|
+
if (!cartId || !dimCart || !cart)
|
|
1242
1548
|
continue;
|
|
1243
1549
|
const rows = layerPositionRows(cartId, dimCart);
|
|
1244
1550
|
// Atomair vervangen: eerst de vorige indeling van deze kar weg.
|