@flowselections/logistiek-engine 1.0.1
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/README.md +24 -0
- package/dist-lib/_core-safelist.d.ts +2 -0
- package/dist-lib/_core-safelist.d.ts.map +1 -0
- package/dist-lib/_core-safelist.js +15 -0
- package/dist-lib/components/BeladingIndelingCard.d.ts +2 -0
- package/dist-lib/components/BeladingIndelingCard.d.ts.map +1 -0
- package/dist-lib/components/BeladingIndelingCard.js +77 -0
- package/dist-lib/components/BufferDebugCard.d.ts +19 -0
- package/dist-lib/components/BufferDebugCard.d.ts.map +1 -0
- package/dist-lib/components/BufferDebugCard.js +69 -0
- package/dist-lib/components/CapaciteitMeldingenCard.d.ts +2 -0
- package/dist-lib/components/CapaciteitMeldingenCard.d.ts.map +1 -0
- package/dist-lib/components/CapaciteitMeldingenCard.js +40 -0
- package/dist-lib/components/LadingdragersPage.d.ts +2 -0
- package/dist-lib/components/LadingdragersPage.d.ts.map +1 -0
- package/dist-lib/components/LadingdragersPage.js +102 -0
- package/dist-lib/components/LogistiekEngineLogo.d.ts +6 -0
- package/dist-lib/components/LogistiekEngineLogo.d.ts.map +1 -0
- package/dist-lib/components/LogistiekEngineLogo.js +4 -0
- package/dist-lib/components/LogistiekEnginePage.d.ts +2 -0
- package/dist-lib/components/LogistiekEnginePage.d.ts.map +1 -0
- package/dist-lib/components/LogistiekEnginePage.js +53 -0
- package/dist-lib/components/ModulePromptsPage.d.ts +2 -0
- package/dist-lib/components/ModulePromptsPage.d.ts.map +1 -0
- package/dist-lib/components/ModulePromptsPage.js +58 -0
- package/dist-lib/components/RegiePage.d.ts +2 -0
- package/dist-lib/components/RegiePage.d.ts.map +1 -0
- package/dist-lib/components/RegiePage.js +82 -0
- package/dist-lib/components/RuleEnginePage.d.ts +7 -0
- package/dist-lib/components/RuleEnginePage.d.ts.map +1 -0
- package/dist-lib/components/RuleEnginePage.js +114 -0
- package/dist-lib/components/StrategieDialog.d.ts +11 -0
- package/dist-lib/components/StrategieDialog.d.ts.map +1 -0
- package/dist-lib/components/StrategieDialog.js +38 -0
- package/dist-lib/components/VertragingMeldingenCard.d.ts +2 -0
- package/dist-lib/components/VertragingMeldingenCard.d.ts.map +1 -0
- package/dist-lib/components/VertragingMeldingenCard.js +32 -0
- package/dist-lib/components/VoorraadMeldingenCard.d.ts +2 -0
- package/dist-lib/components/VoorraadMeldingenCard.d.ts.map +1 -0
- package/dist-lib/components/VoorraadMeldingenCard.js +26 -0
- package/dist-lib/components/rule-engine/CollapsibleGroup.d.ts +12 -0
- package/dist-lib/components/rule-engine/CollapsibleGroup.d.ts.map +1 -0
- package/dist-lib/components/rule-engine/CollapsibleGroup.js +11 -0
- package/dist-lib/components/rule-engine/ConditionsEditor.d.ts +8 -0
- package/dist-lib/components/rule-engine/ConditionsEditor.d.ts.map +1 -0
- package/dist-lib/components/rule-engine/ConditionsEditor.js +31 -0
- package/dist-lib/components/rule-engine/FieldHelp.d.ts +14 -0
- package/dist-lib/components/rule-engine/FieldHelp.d.ts.map +1 -0
- package/dist-lib/components/rule-engine/FieldHelp.js +18 -0
- package/dist-lib/components/rule-engine/FloridayConnectionBanner.d.ts +2 -0
- package/dist-lib/components/rule-engine/FloridayConnectionBanner.d.ts.map +1 -0
- package/dist-lib/components/rule-engine/FloridayConnectionBanner.js +41 -0
- package/dist-lib/components/rule-engine/ImpactPanel.d.ts +5 -0
- package/dist-lib/components/rule-engine/ImpactPanel.d.ts.map +1 -0
- package/dist-lib/components/rule-engine/ImpactPanel.js +25 -0
- package/dist-lib/components/rule-engine/RuleFieldInput.d.ts +9 -0
- package/dist-lib/components/rule-engine/RuleFieldInput.d.ts.map +1 -0
- package/dist-lib/components/rule-engine/RuleFieldInput.js +80 -0
- package/dist-lib/components/rule-engine/VersionsPanel.d.ts +10 -0
- package/dist-lib/components/rule-engine/VersionsPanel.d.ts.map +1 -0
- package/dist-lib/components/rule-engine/VersionsPanel.js +16 -0
- package/dist-lib/components/settings/LogistiekEngineSettingsCard.d.ts +2 -0
- package/dist-lib/components/settings/LogistiekEngineSettingsCard.d.ts.map +1 -0
- package/dist-lib/components/settings/LogistiekEngineSettingsCard.js +22 -0
- package/dist-lib/hooks/useCapaciteitMeldingen.d.ts +20 -0
- package/dist-lib/hooks/useCapaciteitMeldingen.d.ts.map +1 -0
- package/dist-lib/hooks/useCapaciteitMeldingen.js +46 -0
- package/dist-lib/hooks/useDocumentLayouts.d.ts +13 -0
- package/dist-lib/hooks/useDocumentLayouts.d.ts.map +1 -0
- package/dist-lib/hooks/useDocumentLayouts.js +54 -0
- package/dist-lib/hooks/useLadingdragers.d.ts +16 -0
- package/dist-lib/hooks/useLadingdragers.d.ts.map +1 -0
- package/dist-lib/hooks/useLadingdragers.js +86 -0
- package/dist-lib/hooks/useLogistiekStrategieen.d.ts +29 -0
- package/dist-lib/hooks/useLogistiekStrategieen.d.ts.map +1 -0
- package/dist-lib/hooks/useLogistiekStrategieen.js +111 -0
- package/dist-lib/hooks/useLogistiekTaken.d.ts +89 -0
- package/dist-lib/hooks/useLogistiekTaken.d.ts.map +1 -0
- package/dist-lib/hooks/useLogistiekTaken.js +314 -0
- package/dist-lib/hooks/usePickstations.d.ts +10 -0
- package/dist-lib/hooks/usePickstations.d.ts.map +1 -0
- package/dist-lib/hooks/usePickstations.js +51 -0
- package/dist-lib/hooks/useRecalcJob.d.ts +24 -0
- package/dist-lib/hooks/useRecalcJob.d.ts.map +1 -0
- package/dist-lib/hooks/useRecalcJob.js +83 -0
- package/dist-lib/hooks/useRuleEngine.d.ts +48 -0
- package/dist-lib/hooks/useRuleEngine.d.ts.map +1 -0
- package/dist-lib/hooks/useRuleEngine.js +222 -0
- package/dist-lib/hooks/useVertragingMeldingen.d.ts +29 -0
- package/dist-lib/hooks/useVertragingMeldingen.d.ts.map +1 -0
- package/dist-lib/hooks/useVertragingMeldingen.js +68 -0
- package/dist-lib/hooks/useVoorraadMeldingen.d.ts +30 -0
- package/dist-lib/hooks/useVoorraadMeldingen.d.ts.map +1 -0
- package/dist-lib/hooks/useVoorraadMeldingen.js +70 -0
- package/dist-lib/index.d.ts +36 -0
- package/dist-lib/index.d.ts.map +1 -0
- package/dist-lib/index.js +52 -0
- package/dist-lib/integrations/supabase/auth-attacher.d.ts +2 -0
- package/dist-lib/integrations/supabase/auth-attacher.d.ts.map +1 -0
- package/dist-lib/integrations/supabase/auth-attacher.js +12 -0
- package/dist-lib/integrations/supabase/auth-middleware.d.ts +19781 -0
- package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -0
- package/dist-lib/integrations/supabase/auth-middleware.js +52 -0
- package/dist-lib/integrations/supabase/client.d.ts +19777 -0
- package/dist-lib/integrations/supabase/client.d.ts.map +1 -0
- package/dist-lib/integrations/supabase/client.js +13 -0
- package/dist-lib/integrations/supabase/client.server.d.ts +19777 -0
- package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -0
- package/dist-lib/integrations/supabase/client.server.js +30 -0
- package/dist-lib/integrations/supabase/types.d.ts +20355 -0
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -0
- package/dist-lib/integrations/supabase/types.js +78 -0
- package/dist-lib/lib/bufferMatching.d.ts +26 -0
- package/dist-lib/lib/bufferMatching.d.ts.map +1 -0
- package/dist-lib/lib/bufferMatching.js +73 -0
- package/dist-lib/lib/bulkLoadCarrierPlanning.server.d.ts +23 -0
- package/dist-lib/lib/bulkLoadCarrierPlanning.server.d.ts.map +1 -0
- package/dist-lib/lib/bulkLoadCarrierPlanning.server.js +126 -0
- package/dist-lib/lib/floridayDocuments.functions.d.ts +59415 -0
- package/dist-lib/lib/floridayDocuments.functions.d.ts.map +1 -0
- package/dist-lib/lib/floridayDocuments.functions.js +68 -0
- package/dist-lib/lib/floridayDocuments.server.d.ts +51 -0
- package/dist-lib/lib/floridayDocuments.server.d.ts.map +1 -0
- package/dist-lib/lib/floridayDocuments.server.js +109 -0
- package/dist-lib/lib/loadCarrierPlanner.d.ts +71 -0
- package/dist-lib/lib/loadCarrierPlanner.d.ts.map +1 -0
- package/dist-lib/lib/loadCarrierPlanner.js +374 -0
- package/dist-lib/lib/loadCarrierPlanning.functions.d.ts +98984 -0
- package/dist-lib/lib/loadCarrierPlanning.functions.d.ts.map +1 -0
- package/dist-lib/lib/loadCarrierPlanning.functions.js +106 -0
- package/dist-lib/lib/loadCarrierPlanning.server.d.ts +46 -0
- package/dist-lib/lib/loadCarrierPlanning.server.d.ts.map +1 -0
- package/dist-lib/lib/loadCarrierPlanning.server.js +515 -0
- package/dist-lib/lib/loadCarrierTaskSync.server.d.ts +3 -0
- package/dist-lib/lib/loadCarrierTaskSync.server.d.ts.map +1 -0
- package/dist-lib/lib/loadCarrierTaskSync.server.js +56 -0
- package/dist-lib/lib/loadCarrierTypes.d.ts +19 -0
- package/dist-lib/lib/loadCarrierTypes.d.ts.map +1 -0
- package/dist-lib/lib/loadCarrierTypes.js +41 -0
- package/dist-lib/lib/logisticsPlanner.d.ts +56 -0
- package/dist-lib/lib/logisticsPlanner.d.ts.map +1 -0
- package/dist-lib/lib/logisticsPlanner.js +273 -0
- package/dist-lib/lib/logisticsRecalcJob.functions.d.ts +39575 -0
- package/dist-lib/lib/logisticsRecalcJob.functions.d.ts.map +1 -0
- package/dist-lib/lib/logisticsRecalcJob.functions.js +30 -0
- package/dist-lib/lib/logisticsRecalcJob.server.d.ts +32 -0
- package/dist-lib/lib/logisticsRecalcJob.server.d.ts.map +1 -0
- package/dist-lib/lib/logisticsRecalcJob.server.js +116 -0
- package/dist-lib/lib/logisticsRecalculate.functions.d.ts +19792 -0
- package/dist-lib/lib/logisticsRecalculate.functions.d.ts.map +1 -0
- package/dist-lib/lib/logisticsRecalculate.functions.js +35 -0
- package/dist-lib/lib/logisticsTasks.d.ts +23 -0
- package/dist-lib/lib/logisticsTasks.d.ts.map +1 -0
- package/dist-lib/lib/logisticsTasks.js +92 -0
- package/dist-lib/lib/modulePrompts.d.ts +31 -0
- package/dist-lib/lib/modulePrompts.d.ts.map +1 -0
- package/dist-lib/lib/modulePrompts.js +1195 -0
- package/dist-lib/lib/ruleEngineImpact.d.ts +27 -0
- package/dist-lib/lib/ruleEngineImpact.d.ts.map +1 -0
- package/dist-lib/lib/ruleEngineImpact.js +257 -0
- package/dist-lib/lib/ruleEngineSchema.d.ts +69 -0
- package/dist-lib/lib/ruleEngineSchema.d.ts.map +1 -0
- package/dist-lib/lib/ruleEngineSchema.js +899 -0
- package/dist-lib/lib/ruleEngineWizard.d.ts +17 -0
- package/dist-lib/lib/ruleEngineWizard.d.ts.map +1 -0
- package/dist-lib/lib/ruleEngineWizard.js +204 -0
- package/dist-lib/lib/utils.d.ts +3 -0
- package/dist-lib/lib/utils.d.ts.map +1 -0
- package/dist-lib/lib/utils.js +5 -0
- package/dist-lib/lib/validationSchemas.d.ts +15 -0
- package/dist-lib/lib/validationSchemas.d.ts.map +1 -0
- package/dist-lib/lib/validationSchemas.js +25 -0
- package/dist-lib/styles.css +1 -0
- package/package.json +113 -0
- package/public/logo.png +0 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// src/lib/loadCarrierPlanning.functions.ts — server functions (thin wrappers)
|
|
3
|
+
// ============================================================================
|
|
4
|
+
import { createServerFn } from "@tanstack/react-start";
|
|
5
|
+
import { requireSupabaseAuth } from "../integrations/supabase/auth-middleware";
|
|
6
|
+
export const replanOrderLoadCarriers = createServerFn({ method: "POST" })
|
|
7
|
+
.middleware([requireSupabaseAuth])
|
|
8
|
+
.inputValidator((data) => {
|
|
9
|
+
if (!data || typeof data.orderId !== "string" || data.orderId.length === 0) {
|
|
10
|
+
throw new Error("orderId is verplicht");
|
|
11
|
+
}
|
|
12
|
+
return data;
|
|
13
|
+
})
|
|
14
|
+
.handler(async ({ data }) => {
|
|
15
|
+
const { replanOrderInternal } = await import("./loadCarrierPlanning.server");
|
|
16
|
+
const { attachIndelingToTasks } = await import("./loadCarrierTaskSync.server");
|
|
17
|
+
const result = await replanOrderInternal(data.orderId, {
|
|
18
|
+
dryRun: data.dryRun,
|
|
19
|
+
carrierType: data.carrierType ?? null,
|
|
20
|
+
});
|
|
21
|
+
if (result && !data.dryRun)
|
|
22
|
+
await attachIndelingToTasks(result);
|
|
23
|
+
return result;
|
|
24
|
+
});
|
|
25
|
+
export const replanBufferLoadCarriers = createServerFn({ method: "POST" })
|
|
26
|
+
.middleware([requireSupabaseAuth])
|
|
27
|
+
.inputValidator((data) => {
|
|
28
|
+
if (!data || typeof data.bufferId !== "string" || data.bufferId.length === 0) {
|
|
29
|
+
throw new Error("bufferId is verplicht");
|
|
30
|
+
}
|
|
31
|
+
return data;
|
|
32
|
+
})
|
|
33
|
+
.handler(async ({ data }) => {
|
|
34
|
+
const { replanBufferInternal } = await import("./loadCarrierPlanning.server");
|
|
35
|
+
const { attachIndelingToTasks } = await import("./loadCarrierTaskSync.server");
|
|
36
|
+
const result = await replanBufferInternal(data.bufferId, {
|
|
37
|
+
dryRun: data.dryRun,
|
|
38
|
+
carrierType: data.carrierType ?? null,
|
|
39
|
+
});
|
|
40
|
+
if (!data.dryRun)
|
|
41
|
+
await attachIndelingToTasks(result);
|
|
42
|
+
return result;
|
|
43
|
+
});
|
|
44
|
+
/**
|
|
45
|
+
* Deelt alle orders in, in batches. De client roept dit net zo lang aan tot
|
|
46
|
+
* `done` waar is en schakelt dan door naar de volgende fase.
|
|
47
|
+
*/
|
|
48
|
+
export const planAllLoadCarriers = createServerFn({ method: "POST" })
|
|
49
|
+
.middleware([requireSupabaseAuth])
|
|
50
|
+
.inputValidator((data) => data ?? {})
|
|
51
|
+
.handler(async ({ data }) => {
|
|
52
|
+
const { planBuffersBatch, planLooseOrdersBatch } = await import("./bulkLoadCarrierPlanning.server");
|
|
53
|
+
const limit = Math.min(Math.max(Number(data.limit ?? 20), 1), 50);
|
|
54
|
+
const cursor = data.cursor ?? null;
|
|
55
|
+
if (data.phase === "orders")
|
|
56
|
+
return planLooseOrdersBatch(cursor, limit);
|
|
57
|
+
return planBuffersBatch(cursor, limit);
|
|
58
|
+
});
|
|
59
|
+
/**
|
|
60
|
+
* Zet voor orders zonder takenketen alsnog een melding klaar, zodat de Engine
|
|
61
|
+
* de volledige keten (inclusief load.compose) aanmaakt.
|
|
62
|
+
*/
|
|
63
|
+
export const ensureLogisticsTasks = createServerFn({ method: "POST" })
|
|
64
|
+
.middleware([requireSupabaseAuth])
|
|
65
|
+
.inputValidator((data) => data ?? {})
|
|
66
|
+
.handler(async ({ data, context }) => {
|
|
67
|
+
const { findOrdersWithoutTasks } = await import("./bulkLoadCarrierPlanning.server");
|
|
68
|
+
const limit = Math.min(Math.max(Number(data.limit ?? 50), 1), 200);
|
|
69
|
+
const orders = await findOrdersWithoutTasks(limit);
|
|
70
|
+
let created = 0;
|
|
71
|
+
const errors = [];
|
|
72
|
+
for (const order of orders) {
|
|
73
|
+
const { error } = await context.supabase.rpc("logistics_emit_event", {
|
|
74
|
+
p_source_module: "logistiek-engine",
|
|
75
|
+
p_event_type: "order.received",
|
|
76
|
+
p_order_ref: order.orderRef,
|
|
77
|
+
p_payload: {
|
|
78
|
+
source: "bulk-indeling",
|
|
79
|
+
orderId: order.id,
|
|
80
|
+
...(order.pickupAt ? { pickupAt: order.pickupAt } : {}),
|
|
81
|
+
},
|
|
82
|
+
p_strategy_id: data.strategyId ?? null,
|
|
83
|
+
p_shadow: false,
|
|
84
|
+
p_idempotency_key: `bulk:${order.orderRef}`,
|
|
85
|
+
});
|
|
86
|
+
if (error) {
|
|
87
|
+
if (errors.length < 5)
|
|
88
|
+
errors.push(error.message);
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
created++;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return { checked: orders.length, created, errors };
|
|
95
|
+
});
|
|
96
|
+
export const previewLoadCarrierPlan = createServerFn({ method: "POST" })
|
|
97
|
+
.middleware([requireSupabaseAuth])
|
|
98
|
+
.inputValidator((data) => {
|
|
99
|
+
if (!data || typeof data.orderId !== "string")
|
|
100
|
+
throw new Error("orderId is verplicht");
|
|
101
|
+
return data;
|
|
102
|
+
})
|
|
103
|
+
.handler(async ({ data }) => {
|
|
104
|
+
const { replanOrderInternal } = await import("./loadCarrierPlanning.server");
|
|
105
|
+
return replanOrderInternal(data.orderId, { dryRun: true, carrierType: data.carrierType ?? null });
|
|
106
|
+
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { type CarrierProfile, type PlannedLoadUnit, type PlannerInputItem } from "./loadCarrierPlanner";
|
|
2
|
+
import { type LoadCarrierTypeRow } from "./loadCarrierTypes";
|
|
3
|
+
export declare function buildBufferKey(order: {
|
|
4
|
+
id: string;
|
|
5
|
+
customer_id?: string | null;
|
|
6
|
+
delivery_date?: string | null;
|
|
7
|
+
custom_fields?: any;
|
|
8
|
+
}, mode: string): {
|
|
9
|
+
key: string;
|
|
10
|
+
pickupDate: string;
|
|
11
|
+
location: string;
|
|
12
|
+
};
|
|
13
|
+
export declare function resolveBufferForOrderInternal(orderId: string): Promise<string | null>;
|
|
14
|
+
export declare function expandOrderItemsForPlanner(rows: Array<Record<string, any>>): Promise<PlannerInputItem[]>;
|
|
15
|
+
export declare function loadCarrierProfiles(): Promise<LoadCarrierTypeRow[]>;
|
|
16
|
+
export interface BeladingRules {
|
|
17
|
+
maxOrders: number | null;
|
|
18
|
+
maxCustomers: number | null;
|
|
19
|
+
maxLayers: number | null;
|
|
20
|
+
maxBoxes: number | null;
|
|
21
|
+
optimalFill: boolean;
|
|
22
|
+
}
|
|
23
|
+
/** Leest de actieve werkwijze en vertaalt "2. Beladingindeling" naar plannergrenzen. */
|
|
24
|
+
export declare function loadBeladingRules(): Promise<BeladingRules | null>;
|
|
25
|
+
/** Past de grenzen uit de werkwijze toe op het capaciteitsprofiel van een ladingdrager. */
|
|
26
|
+
export declare function applyRulesToProfile(profile: CarrierProfile, rules: BeladingRules | null): CarrierProfile;
|
|
27
|
+
export interface ReplanResult {
|
|
28
|
+
ok: boolean;
|
|
29
|
+
bufferId: string;
|
|
30
|
+
orderIds: string[];
|
|
31
|
+
cartsCreated: number;
|
|
32
|
+
replacedManualCount: number;
|
|
33
|
+
usedFloridayPacking: boolean;
|
|
34
|
+
warnings: string[];
|
|
35
|
+
skipped?: "locked";
|
|
36
|
+
carts?: PlannedLoadUnit[];
|
|
37
|
+
}
|
|
38
|
+
export interface ReplanOptions {
|
|
39
|
+
/** Alleen berekenen, niets wegschrijven. */
|
|
40
|
+
dryRun?: boolean;
|
|
41
|
+
/** Overschrijf het ladingdragertype dat de Rule Engine zou kiezen. */
|
|
42
|
+
carrierType?: string | null;
|
|
43
|
+
}
|
|
44
|
+
export declare function replanBufferInternal(bufferId: string, options?: ReplanOptions): Promise<ReplanResult>;
|
|
45
|
+
export declare function replanOrderInternal(orderId: string, options?: ReplanOptions): Promise<ReplanResult | null>;
|
|
46
|
+
//# sourceMappingURL=loadCarrierPlanning.server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loadCarrierPlanning.server.d.ts","sourceRoot":"","sources":["../../src/lib/loadCarrierPlanning.server.ts"],"names":[],"mappings":"AASA,OAAO,EAGL,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACtB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAA0B,KAAK,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAkCrF,wBAAgB,cAAc,CAC5B,KAAK,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,aAAa,CAAC,EAAE,GAAG,CAAA;CAAE,EACtG,IAAI,EAAE,MAAM,GACX;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAqCvD;AAID,wBAAsB,6BAA6B,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAmE3F;AAgBD,wBAAsB,0BAA0B,CAC9C,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,GAC/B,OAAO,CAAC,gBAAgB,EAAE,CAAC,CA0H7B;AAMD,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAOzE;AAUD,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,WAAW,EAAE,OAAO,CAAC;CACtB;AAOD,wFAAwF;AACxF,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAsBvE;AAED,2FAA2F;AAC3F,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,cAAc,EAAE,KAAK,EAAE,aAAa,GAAG,IAAI,GAAG,cAAc,CAaxG;AAWD,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,OAAO,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,mBAAmB,EAAE,OAAO,CAAC;IAC7B,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,CAAC,EAAE,QAAQ,CAAC;IACnB,KAAK,CAAC,EAAE,eAAe,EAAE,CAAC;CAC3B;AAED,MAAM,WAAW,aAAa;IAC5B,4CAA4C;IAC5C,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,sEAAsE;IACtE,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED,wBAAsB,oBAAoB,CACxC,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,aAAkB,GAC1B,OAAO,CAAC,YAAY,CAAC,CA0OvB;AAED,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,aAAkB,GAC1B,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAI9B"}
|
|
@@ -0,0 +1,515 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// src/lib/loadCarrierPlanning.server.ts — orkestratie van de beladingindeling
|
|
3
|
+
// ============================================================================
|
|
4
|
+
// Leest orders, orderregels, ladingdragertypen en instellingen uit de database,
|
|
5
|
+
// draait de pure planner per ladingdragertype en schrijft het resultaat weg
|
|
6
|
+
// naar order_carts / order_cart_items.
|
|
7
|
+
// ============================================================================
|
|
8
|
+
import { supabaseAdmin } from "../integrations/supabase/client.server";
|
|
9
|
+
import { planLoadUnits, LOAD_CARRIER_PLANNER_VERSION, } from "./loadCarrierPlanner";
|
|
10
|
+
import { fallbackCarrierProfile } from "./loadCarrierTypes";
|
|
11
|
+
const db = () => supabaseAdmin;
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// Buffer
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
function normalizeBufferPart(value) {
|
|
16
|
+
if (value == null)
|
|
17
|
+
return "";
|
|
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
|
+
}
|
|
65
|
+
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
|
+
export async function resolveBufferForOrderInternal(orderId) {
|
|
67
|
+
const { data: order } = await db()
|
|
68
|
+
.from("orders")
|
|
69
|
+
.select("id, customer_id, delivery_date, custom_fields, buffer_id")
|
|
70
|
+
.eq("id", orderId)
|
|
71
|
+
.maybeSingle();
|
|
72
|
+
if (!order)
|
|
73
|
+
return null;
|
|
74
|
+
const { data: settings } = await db()
|
|
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 db()
|
|
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 db()
|
|
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 db()
|
|
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 db()
|
|
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
|
+
}
|
|
128
|
+
if (bufferId && order.buffer_id !== bufferId) {
|
|
129
|
+
await db().from("orders").update({ buffer_id: bufferId }).eq("id", orderId);
|
|
130
|
+
}
|
|
131
|
+
return bufferId;
|
|
132
|
+
}
|
|
133
|
+
function isDefaultBelading(b) {
|
|
134
|
+
return b.plants_per_crate === 1 && b.crates_per_layer === 1 && b.layers_per_cart === 1;
|
|
135
|
+
}
|
|
136
|
+
export async function expandOrderItemsForPlanner(rows) {
|
|
137
|
+
const out = [];
|
|
138
|
+
const groupIds = rows
|
|
139
|
+
.map((r) => r.product_id)
|
|
140
|
+
.filter((id) => typeof id === "string" && UUID_RE.test(id));
|
|
141
|
+
let groupSet = new Set();
|
|
142
|
+
if (groupIds.length > 0) {
|
|
143
|
+
const { data: groups } = await db().from("product_groups").select("id").in("id", groupIds);
|
|
144
|
+
groupSet = new Set((groups ?? []).map((g) => g.id));
|
|
145
|
+
}
|
|
146
|
+
for (const row of rows) {
|
|
147
|
+
const base = {
|
|
148
|
+
plants_per_crate: Math.max(1, Number(row.plants_per_crate ?? row.products?.plants_per_crate ?? 1)),
|
|
149
|
+
crates_per_layer: Math.max(1, Number(row.crates_per_layer ?? row.products?.crates_per_layer ?? 1)),
|
|
150
|
+
layers_per_cart: Math.max(1, Number(row.layers_per_cart ?? row.products?.layers_per_cart ?? 1)),
|
|
151
|
+
};
|
|
152
|
+
const plants = Math.max(0, Number(row.quantity ?? 0));
|
|
153
|
+
const isGroup = (row.product_id && groupSet.has(row.product_id)) ||
|
|
154
|
+
String(row.product_name ?? "").startsWith("[Groep]");
|
|
155
|
+
if (!isGroup) {
|
|
156
|
+
out.push({
|
|
157
|
+
order_item_id: row.id,
|
|
158
|
+
order_id: row.order_id ?? null,
|
|
159
|
+
customer_id: row.customer_id ?? null,
|
|
160
|
+
product_id: row.product_id ?? null,
|
|
161
|
+
product_name: row.product_name ?? "Onbekend",
|
|
162
|
+
plants,
|
|
163
|
+
...base,
|
|
164
|
+
weight_per_crate: row.products?.weight != null ? Number(row.products.weight) : null,
|
|
165
|
+
});
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
const { data: members } = await db()
|
|
169
|
+
.from("product_group_items")
|
|
170
|
+
.select("trade_item_id, quantity, sort_order")
|
|
171
|
+
.eq("product_group_id", row.product_id)
|
|
172
|
+
.order("sort_order", { ascending: true });
|
|
173
|
+
const memberList = Array.isArray(members) ? members : [];
|
|
174
|
+
if (memberList.length === 0) {
|
|
175
|
+
out.push({
|
|
176
|
+
order_item_id: row.id,
|
|
177
|
+
order_id: row.order_id ?? null,
|
|
178
|
+
customer_id: row.customer_id ?? null,
|
|
179
|
+
product_id: row.product_id ?? null,
|
|
180
|
+
product_name: row.product_name ?? "Onbekend",
|
|
181
|
+
plants,
|
|
182
|
+
...base,
|
|
183
|
+
weight_per_crate: null,
|
|
184
|
+
});
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
const tradeIds = memberList.map((m) => String(m.trade_item_id));
|
|
188
|
+
const { data: prods } = await db()
|
|
189
|
+
.from("products")
|
|
190
|
+
.select("id, product, plants_per_crate, crates_per_layer, layers_per_cart, weight, floriday_trade_item_id")
|
|
191
|
+
.or(tradeIds.map((t) => `floriday_trade_item_id.eq.${t}`).join(","));
|
|
192
|
+
const prodByTrade = new Map();
|
|
193
|
+
for (const p of prods ?? []) {
|
|
194
|
+
if (p.floriday_trade_item_id)
|
|
195
|
+
prodByTrade.set(String(p.floriday_trade_item_id), p);
|
|
196
|
+
prodByTrade.set(String(p.id), p);
|
|
197
|
+
}
|
|
198
|
+
const resolved = memberList.map((m) => {
|
|
199
|
+
const p = prodByTrade.get(String(m.trade_item_id));
|
|
200
|
+
const belading = p
|
|
201
|
+
? {
|
|
202
|
+
plants_per_crate: Math.max(1, Number(p.plants_per_crate ?? 1)),
|
|
203
|
+
crates_per_layer: Math.max(1, Number(p.crates_per_layer ?? 1)),
|
|
204
|
+
layers_per_cart: Math.max(1, Number(p.layers_per_cart ?? 1)),
|
|
205
|
+
}
|
|
206
|
+
: { plants_per_crate: 1, crates_per_layer: 1, layers_per_cart: 1 };
|
|
207
|
+
return { m, p, belading };
|
|
208
|
+
});
|
|
209
|
+
// Fallback: sibling met bekende belading, anders de groepsregel zelf.
|
|
210
|
+
const known = resolved.find((r) => !isDefaultBelading(r.belading))?.belading;
|
|
211
|
+
for (const r of resolved) {
|
|
212
|
+
if (isDefaultBelading(r.belading)) {
|
|
213
|
+
r.belading = known ?? (isDefaultBelading(base) ? r.belading : base);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
const weightSum = resolved.reduce((s, r) => s + Number(r.m.quantity ?? 1), 0) || resolved.length;
|
|
217
|
+
const shares = [];
|
|
218
|
+
let assigned = 0;
|
|
219
|
+
for (const r of resolved) {
|
|
220
|
+
const raw = (plants * Number(r.m.quantity ?? 1)) / weightSum;
|
|
221
|
+
const ppc = r.belading.plants_per_crate;
|
|
222
|
+
const crates = Math.max(0, Math.round(raw / ppc));
|
|
223
|
+
shares.push(crates * ppc);
|
|
224
|
+
assigned += crates * ppc;
|
|
225
|
+
}
|
|
226
|
+
if (assigned !== plants && resolved.length > 0) {
|
|
227
|
+
const firstPpc = resolved[0].belading.plants_per_crate;
|
|
228
|
+
const deltaCrates = Math.round((plants - assigned) / firstPpc);
|
|
229
|
+
shares[0] = Math.max(0, shares[0] + deltaCrates * firstPpc);
|
|
230
|
+
}
|
|
231
|
+
resolved.forEach((r, idx) => {
|
|
232
|
+
if (shares[idx] <= 0)
|
|
233
|
+
return;
|
|
234
|
+
out.push({
|
|
235
|
+
order_item_id: `${row.id}::${r.m.trade_item_id}`,
|
|
236
|
+
order_id: row.order_id ?? null,
|
|
237
|
+
customer_id: row.customer_id ?? null,
|
|
238
|
+
product_id: r.p?.id ?? null,
|
|
239
|
+
product_name: `${row.product_name ?? "Groep"} — ${r.p?.product ?? r.m.trade_item_id}`,
|
|
240
|
+
plants: shares[idx],
|
|
241
|
+
...r.belading,
|
|
242
|
+
weight_per_crate: r.p?.weight != null ? Number(r.p.weight) : null,
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
return out;
|
|
247
|
+
}
|
|
248
|
+
// ---------------------------------------------------------------------------
|
|
249
|
+
// Ladingdragertypen
|
|
250
|
+
// ---------------------------------------------------------------------------
|
|
251
|
+
export async function loadCarrierProfiles() {
|
|
252
|
+
const { data } = await db()
|
|
253
|
+
.from("load_carrier_types")
|
|
254
|
+
.select("*")
|
|
255
|
+
.eq("is_active", true)
|
|
256
|
+
.order("sort_order", { ascending: true });
|
|
257
|
+
return Array.isArray(data) ? data : [];
|
|
258
|
+
}
|
|
259
|
+
function profileFor(profiles, code) {
|
|
260
|
+
return profiles.find((p) => p.code === code) ?? fallbackCarrierProfile(code);
|
|
261
|
+
}
|
|
262
|
+
const numOrNull = (v) => {
|
|
263
|
+
const n = typeof v === "string" ? Number(v) : v;
|
|
264
|
+
return Number.isFinite(n) && n > 0 ? Math.round(n) : null;
|
|
265
|
+
};
|
|
266
|
+
/** Leest de actieve werkwijze en vertaalt "2. Beladingindeling" naar plannergrenzen. */
|
|
267
|
+
export async function loadBeladingRules() {
|
|
268
|
+
const { data } = await db()
|
|
269
|
+
.from("logistics_rule_configs")
|
|
270
|
+
.select("settings")
|
|
271
|
+
.eq("is_active", true)
|
|
272
|
+
.order("updated_at", { ascending: false })
|
|
273
|
+
.limit(1);
|
|
274
|
+
const settings = Array.isArray(data) && data.length > 0 ? data[0]?.settings : null;
|
|
275
|
+
// De wizard bewaart de waarden onder `fields`; oudere configuraties platter.
|
|
276
|
+
const k = settings?.karrenindeling?.fields ?? settings?.karrenindeling;
|
|
277
|
+
if (!k || typeof k !== "object")
|
|
278
|
+
return null;
|
|
279
|
+
if (k.mode && k.mode !== "auto")
|
|
280
|
+
return null;
|
|
281
|
+
return {
|
|
282
|
+
maxOrders: k.multi_order === false ? 1 : k.fill_orders_as_much_as_fits ? null : numOrNull(k.max_orders),
|
|
283
|
+
maxCustomers: k.fill_customers_as_much_as_fits ? null : numOrNull(k.max_customers),
|
|
284
|
+
maxLayers: k.fill_layers_as_much_as_fits ? null : numOrNull(k.max_layers),
|
|
285
|
+
maxBoxes: k.fill_boxes_as_much_as_fits ? null : numOrNull(k.max_boxes_per_pallet),
|
|
286
|
+
optimalFill: k.optimal_fill !== false,
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
/** Past de grenzen uit de werkwijze toe op het capaciteitsprofiel van een ladingdrager. */
|
|
290
|
+
export function applyRulesToProfile(profile, rules) {
|
|
291
|
+
if (!rules)
|
|
292
|
+
return profile;
|
|
293
|
+
return {
|
|
294
|
+
...profile,
|
|
295
|
+
layers_per_carrier: rules.maxLayers != null
|
|
296
|
+
? Math.max(1, Math.min(profile.layers_per_carrier || 1, rules.maxLayers))
|
|
297
|
+
: profile.layers_per_carrier,
|
|
298
|
+
max_boxes: rules.maxBoxes != null
|
|
299
|
+
? Math.min(profile.max_boxes ?? Number.MAX_SAFE_INTEGER, rules.maxBoxes)
|
|
300
|
+
: profile.max_boxes ?? null,
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
// ---------------------------------------------------------------------------
|
|
304
|
+
// Herplannen van een buffer
|
|
305
|
+
// ---------------------------------------------------------------------------
|
|
306
|
+
/** Maximaal aantal ladingdragers dat één orderbuffer mag opleveren. */
|
|
307
|
+
const MAX_CARTS_PER_BUFFER = 500;
|
|
308
|
+
export async function replanBufferInternal(bufferId, options = {}) {
|
|
309
|
+
const warnings = [];
|
|
310
|
+
const { data: orders } = await db()
|
|
311
|
+
.from("orders")
|
|
312
|
+
.select("id, customer_id, custom_fields, plan_locked_at")
|
|
313
|
+
.eq("buffer_id", bufferId)
|
|
314
|
+
.is("deleted_at", null);
|
|
315
|
+
const orderRows = Array.isArray(orders) ? orders : [];
|
|
316
|
+
const orderIds = orderRows.map((o) => o.id);
|
|
317
|
+
if (orderRows.some((o) => o.plan_locked_at)) {
|
|
318
|
+
return {
|
|
319
|
+
ok: true,
|
|
320
|
+
bufferId,
|
|
321
|
+
orderIds,
|
|
322
|
+
cartsCreated: 0,
|
|
323
|
+
replacedManualCount: 0,
|
|
324
|
+
usedFloridayPacking: false,
|
|
325
|
+
warnings: ["plan_locked"],
|
|
326
|
+
skipped: "locked",
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
const idList = orderIds.length > 0 ? orderIds : ["00000000-0000-0000-0000-000000000000"];
|
|
330
|
+
const { data: items } = await db()
|
|
331
|
+
.from("order_items")
|
|
332
|
+
.select("id, order_id, product_id, product_name, quantity, plants_per_crate, crates_per_layer, layers_per_cart, " +
|
|
333
|
+
"products(plants_per_crate, crates_per_layer, layers_per_cart, weight)")
|
|
334
|
+
.in("order_id", idList);
|
|
335
|
+
const customerByOrder = new Map(orderRows.map((o) => [o.id, o.customer_id ?? null]));
|
|
336
|
+
const rawRows = (Array.isArray(items) ? items : []).map((it) => ({
|
|
337
|
+
...it,
|
|
338
|
+
customer_id: customerByOrder.get(it.order_id) ?? null,
|
|
339
|
+
}));
|
|
340
|
+
const inputs = await expandOrderItemsForPlanner(rawRows);
|
|
341
|
+
const itemToOrder = new Map();
|
|
342
|
+
for (const i of inputs)
|
|
343
|
+
if (i.order_id)
|
|
344
|
+
itemToOrder.set(i.order_item_id, i.order_id);
|
|
345
|
+
// Bestaande beladingen tellen en wissen.
|
|
346
|
+
const { data: existingCarts } = await db()
|
|
347
|
+
.from("order_carts")
|
|
348
|
+
.select("id, source")
|
|
349
|
+
.eq("buffer_id", bufferId);
|
|
350
|
+
const replacedManualCount = (existingCarts ?? []).filter((c) => c.source === "manual").length;
|
|
351
|
+
// Instellingen + never-mix.
|
|
352
|
+
const { data: settings } = await db().from("cart_planning_settings").select("*").eq("id", 1).maybeSingle();
|
|
353
|
+
const { data: overrides } = await db()
|
|
354
|
+
.from("cart_planning_product_overrides")
|
|
355
|
+
.select("product_id, never_mix");
|
|
356
|
+
const neverMix = new Set((overrides ?? []).filter((o) => o.never_mix && o.product_id).map((o) => String(o.product_id)));
|
|
357
|
+
const profiles = await loadCarrierProfiles();
|
|
358
|
+
const rules = await loadBeladingRules();
|
|
359
|
+
const defaultProfile = profiles.find((p) => p.is_default) ?? profileFor(profiles, "trolley");
|
|
360
|
+
let plannedCarts = [];
|
|
361
|
+
let usedFloridayPacking = false;
|
|
362
|
+
const solo = orderRows.length === 1 ? orderRows[0] : null;
|
|
363
|
+
const floridayPacking = solo?.custom_fields?.floriday_packing ?? null;
|
|
364
|
+
const isFo = solo && (solo.custom_fields?.source === "floriday" || floridayPacking?.pieces_per_package);
|
|
365
|
+
if (isFo && floridayPacking && Array.isArray(floridayPacking.carts)) {
|
|
366
|
+
usedFloridayPacking = true;
|
|
367
|
+
const code = options.carrierType ?? floridayPacking.carrier_type ?? defaultProfile.code;
|
|
368
|
+
const profile = profileFor(profiles, code);
|
|
369
|
+
plannedCarts = floridayPacking.carts.map((c, idx) => ({
|
|
370
|
+
cart_number: idx + 1,
|
|
371
|
+
carrier_type: profile.code,
|
|
372
|
+
carrier_type_id: profile.id ?? null,
|
|
373
|
+
source: "auto",
|
|
374
|
+
is_complete: Boolean(c.is_complete),
|
|
375
|
+
occupancy_pct: Number(c.occupancy_pct ?? 100),
|
|
376
|
+
total_layers_used: Number(c.total_layers_used ?? profile.layers_per_carrier),
|
|
377
|
+
max_layers: profile.layers_per_carrier,
|
|
378
|
+
weight_kg: null,
|
|
379
|
+
items: Array.isArray(c.items) ? c.items : [],
|
|
380
|
+
}));
|
|
381
|
+
}
|
|
382
|
+
else {
|
|
383
|
+
// Ladingdrager per orderregel bepalen en per type een ronde draaien.
|
|
384
|
+
const groups = new Map();
|
|
385
|
+
for (const input of inputs) {
|
|
386
|
+
const code = options.carrierType ?? defaultProfile.code;
|
|
387
|
+
const list = groups.get(code) ?? [];
|
|
388
|
+
list.push(input);
|
|
389
|
+
groups.set(code, list);
|
|
390
|
+
}
|
|
391
|
+
for (const [code, list] of groups) {
|
|
392
|
+
const profile = applyRulesToProfile(profileFor(profiles, code), rules);
|
|
393
|
+
const result = planLoadUnits(list, {
|
|
394
|
+
carrier: profile,
|
|
395
|
+
maxPartySharePerLayerPct: settings?.max_party_share_per_layer_pct ?? 50,
|
|
396
|
+
// Zonder optimale vulling wordt er niet bijgemengd in een laag.
|
|
397
|
+
maxBeladingenPerLayer: rules && !rules.optimalFill ? 1 : (settings?.max_beladingen_per_layer ?? 2),
|
|
398
|
+
neverMixProductIds: neverMix,
|
|
399
|
+
maxOrders: rules?.maxOrders ?? null,
|
|
400
|
+
maxCustomers: rules?.maxCustomers ?? settings?.max_customers_per_cart ?? null,
|
|
401
|
+
oneCustomerPerLayer: Boolean(settings?.max_one_customer_per_layer),
|
|
402
|
+
ownCarrierFromLayers: settings?.own_cart_from_layers ?? null,
|
|
403
|
+
sortOrder: "none",
|
|
404
|
+
});
|
|
405
|
+
plannedCarts = plannedCarts.concat(result.carts);
|
|
406
|
+
warnings.push(...result.warnings);
|
|
407
|
+
}
|
|
408
|
+
plannedCarts.forEach((c, idx) => {
|
|
409
|
+
c.cart_number = idx + 1;
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
// Veiligheidsgrens: een onrealistisch grote order (bijv. een verkeerd
|
|
413
|
+
// ingevoerd aantal planten) mag de indeling niet laten vastlopen.
|
|
414
|
+
if (plannedCarts.length > MAX_CARTS_PER_BUFFER) {
|
|
415
|
+
warnings.push(`Deze orderbuffer levert ${plannedCarts.length} ladingdragers op. Er zijn er ${MAX_CARTS_PER_BUFFER} ingedeeld; controleer de aantallen of de belading van de producten.`);
|
|
416
|
+
plannedCarts = plannedCarts.slice(0, MAX_CARTS_PER_BUFFER);
|
|
417
|
+
}
|
|
418
|
+
if (options.dryRun) {
|
|
419
|
+
return {
|
|
420
|
+
ok: true,
|
|
421
|
+
bufferId,
|
|
422
|
+
orderIds,
|
|
423
|
+
cartsCreated: plannedCarts.length,
|
|
424
|
+
replacedManualCount,
|
|
425
|
+
usedFloridayPacking,
|
|
426
|
+
warnings,
|
|
427
|
+
carts: plannedCarts,
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
await db().from("order_carts").delete().eq("buffer_id", bufferId);
|
|
431
|
+
// In blokken wegschrijven: per ladingdrager een aparte insert is bij grote
|
|
432
|
+
// orders veel te traag.
|
|
433
|
+
let created = 0;
|
|
434
|
+
const CHUNK = 100;
|
|
435
|
+
for (let start = 0; start < plannedCarts.length; start += CHUNK) {
|
|
436
|
+
const chunk = plannedCarts.slice(start, start + CHUNK);
|
|
437
|
+
const cartRows = chunk.map((cart) => {
|
|
438
|
+
const orderIdsOnCart = new Set();
|
|
439
|
+
for (const i of cart.items) {
|
|
440
|
+
const oid = i.order_id ?? itemToOrder.get(i.order_item_id);
|
|
441
|
+
if (oid)
|
|
442
|
+
orderIdsOnCart.add(oid);
|
|
443
|
+
}
|
|
444
|
+
return {
|
|
445
|
+
order_id: orderIdsOnCart.size === 1 ? [...orderIdsOnCart][0] : null,
|
|
446
|
+
buffer_id: bufferId,
|
|
447
|
+
cart_number: cart.cart_number,
|
|
448
|
+
is_auto: true,
|
|
449
|
+
is_complete: cart.is_complete,
|
|
450
|
+
source: usedFloridayPacking ? "floriday" : "auto",
|
|
451
|
+
occupancy_pct: cart.occupancy_pct,
|
|
452
|
+
total_layers_used: cart.total_layers_used,
|
|
453
|
+
carrier_type: cart.carrier_type,
|
|
454
|
+
carrier_type_id: cart.carrier_type_id,
|
|
455
|
+
max_layers: cart.max_layers,
|
|
456
|
+
weight_kg: cart.weight_kg,
|
|
457
|
+
algorithm_version: LOAD_CARRIER_PLANNER_VERSION,
|
|
458
|
+
};
|
|
459
|
+
});
|
|
460
|
+
const { data: insertedCarts, error } = await db()
|
|
461
|
+
.from("order_carts")
|
|
462
|
+
.insert(cartRows)
|
|
463
|
+
.select("id, cart_number");
|
|
464
|
+
if (error || !Array.isArray(insertedCarts)) {
|
|
465
|
+
warnings.push(`Een deel van de ladingdragers kon niet worden opgeslagen: ${error?.message ?? ""}`);
|
|
466
|
+
continue;
|
|
467
|
+
}
|
|
468
|
+
created += insertedCarts.length;
|
|
469
|
+
const idByNumber = new Map(insertedCarts.map((c) => [Number(c.cart_number), String(c.id)]));
|
|
470
|
+
const itemRows = [];
|
|
471
|
+
for (const cart of chunk) {
|
|
472
|
+
const cartId = idByNumber.get(cart.cart_number);
|
|
473
|
+
if (!cartId)
|
|
474
|
+
continue;
|
|
475
|
+
for (const i of cart.items) {
|
|
476
|
+
itemRows.push({
|
|
477
|
+
cart_id: cartId,
|
|
478
|
+
// Samengestelde sleutels van groepsleden bestaan niet als orderregel.
|
|
479
|
+
order_item_id: i.order_item_id.includes("::") ? i.order_item_id.split("::")[0] : i.order_item_id,
|
|
480
|
+
product_id: i.product_id,
|
|
481
|
+
product_name: i.product_name,
|
|
482
|
+
crates: Math.max(1, Math.round(i.crates)),
|
|
483
|
+
boxes: i.boxes ?? null,
|
|
484
|
+
weight_kg: i.weight_kg ?? null,
|
|
485
|
+
plants_per_crate: i.plants_per_crate,
|
|
486
|
+
crates_per_layer: i.crates_per_layer,
|
|
487
|
+
layers_per_cart: i.layers_per_cart,
|
|
488
|
+
layer_fraction: i.layer_fraction,
|
|
489
|
+
layer_share_pct: i.layer_share_pct,
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
for (let s = 0; s < itemRows.length; s += 500) {
|
|
494
|
+
const slice = itemRows.slice(s, s + 500);
|
|
495
|
+
if (slice.length > 0)
|
|
496
|
+
await db().from("order_cart_items").insert(slice);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
return {
|
|
500
|
+
ok: true,
|
|
501
|
+
bufferId,
|
|
502
|
+
orderIds,
|
|
503
|
+
cartsCreated: created,
|
|
504
|
+
replacedManualCount,
|
|
505
|
+
usedFloridayPacking,
|
|
506
|
+
warnings,
|
|
507
|
+
carts: plannedCarts,
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
export async function replanOrderInternal(orderId, options = {}) {
|
|
511
|
+
const bufferId = await resolveBufferForOrderInternal(orderId);
|
|
512
|
+
if (!bufferId)
|
|
513
|
+
return null;
|
|
514
|
+
return replanBufferInternal(bufferId, options);
|
|
515
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loadCarrierTaskSync.server.d.ts","sourceRoot":"","sources":["../../src/lib/loadCarrierTaskSync.server.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAKjE,wBAAsB,qBAAqB,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CA6CxF"}
|