@flowselections/logistiek-engine 1.0.27 → 1.0.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/README.md +24 -24
  2. package/dist-lib/components/AfmetingenBronnenCard.d.ts +2 -0
  3. package/dist-lib/components/AfmetingenBronnenCard.d.ts.map +1 -0
  4. package/dist-lib/components/AfmetingenBronnenCard.js +46 -0
  5. package/dist-lib/components/BufferDebugCard.d.ts.map +1 -1
  6. package/dist-lib/components/BufferDebugCard.js +6 -1
  7. package/dist-lib/components/ConsistentieCard.d.ts.map +1 -1
  8. package/dist-lib/components/ConsistentieCard.js +1 -1
  9. package/dist-lib/components/KarplanStatusCard.d.ts +2 -0
  10. package/dist-lib/components/KarplanStatusCard.d.ts.map +1 -0
  11. package/dist-lib/components/KarplanStatusCard.js +31 -0
  12. package/dist-lib/components/RegiePage.d.ts.map +1 -1
  13. package/dist-lib/components/RegiePage.js +3 -1
  14. package/dist-lib/components/RuleEnginePage.d.ts.map +1 -1
  15. package/dist-lib/components/RuleEnginePage.js +7 -4
  16. package/dist-lib/components/rule-engine/FloridayConnectionBanner.d.ts +2 -0
  17. package/dist-lib/components/rule-engine/FloridayConnectionBanner.d.ts.map +1 -0
  18. package/dist-lib/components/rule-engine/FloridayConnectionBanner.js +41 -0
  19. package/dist-lib/components/rule-engine/RuleFieldInput.d.ts.map +1 -1
  20. package/dist-lib/components/rule-engine/RuleFieldInput.js +9 -0
  21. package/dist-lib/hooks/useRecalcJob.d.ts.map +1 -1
  22. package/dist-lib/hooks/useRecalcJob.js +26 -1
  23. package/dist-lib/hooks/useScannersPrinters.d.ts +11 -0
  24. package/dist-lib/hooks/useScannersPrinters.d.ts.map +1 -0
  25. package/dist-lib/hooks/useScannersPrinters.js +65 -0
  26. package/dist-lib/index.d.ts +2 -0
  27. package/dist-lib/index.d.ts.map +1 -1
  28. package/dist-lib/index.js +2 -0
  29. package/dist-lib/integrations/supabase/auth-middleware.d.ts +2063 -78
  30. package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
  31. package/dist-lib/integrations/supabase/client.d.ts +20293 -0
  32. package/dist-lib/integrations/supabase/client.d.ts.map +1 -0
  33. package/dist-lib/integrations/supabase/client.js +13 -0
  34. package/dist-lib/integrations/supabase/client.server.d.ts +2063 -78
  35. package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
  36. package/dist-lib/integrations/supabase/client.server.js +1 -1
  37. package/dist-lib/integrations/supabase/moduleClient.d.ts +2063 -78
  38. package/dist-lib/integrations/supabase/moduleClient.d.ts.map +1 -1
  39. package/dist-lib/integrations/supabase/types.d.ts +2097 -68
  40. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  41. package/dist-lib/integrations/supabase/types.js +1 -0
  42. package/dist-lib/lib/attachAuthToken.d.ts +2 -0
  43. package/dist-lib/lib/attachAuthToken.d.ts.map +1 -0
  44. package/dist-lib/lib/attachAuthToken.js +50 -0
  45. package/dist-lib/lib/bufferMatching.d.ts.map +1 -1
  46. package/dist-lib/lib/bufferMatching.js +1 -0
  47. package/dist-lib/lib/cartPlanRequests.server.d.ts +13 -0
  48. package/dist-lib/lib/cartPlanRequests.server.d.ts.map +1 -0
  49. package/dist-lib/lib/cartPlanRequests.server.js +168 -0
  50. package/dist-lib/lib/controlScan.server.d.ts +12 -0
  51. package/dist-lib/lib/controlScan.server.d.ts.map +1 -0
  52. package/dist-lib/lib/controlScan.server.js +197 -0
  53. package/dist-lib/lib/dimensionData.server.d.ts +11 -1
  54. package/dist-lib/lib/dimensionData.server.d.ts.map +1 -1
  55. package/dist-lib/lib/dimensionData.server.js +58 -48
  56. package/dist-lib/lib/dimensionPlanner.d.ts +26 -1
  57. package/dist-lib/lib/dimensionPlanner.d.ts.map +1 -1
  58. package/dist-lib/lib/dimensionPlanner.js +37 -6
  59. package/dist-lib/lib/floridayDocuments.functions.d.ts +6240 -285
  60. package/dist-lib/lib/floridayDocuments.functions.d.ts.map +1 -1
  61. package/dist-lib/lib/floridayDocuments.functions.js +4 -3
  62. package/dist-lib/lib/floridayPush.functions.d.ts +2064 -79
  63. package/dist-lib/lib/floridayPush.functions.d.ts.map +1 -1
  64. package/dist-lib/lib/floridayPush.functions.js +2 -1
  65. package/dist-lib/lib/handlers/logistics-tick.server.d.ts.map +1 -1
  66. package/dist-lib/lib/handlers/logistics-tick.server.js +57 -0
  67. package/dist-lib/lib/loadCarrierPlanning.functions.d.ts +10302 -377
  68. package/dist-lib/lib/loadCarrierPlanning.functions.d.ts.map +1 -1
  69. package/dist-lib/lib/loadCarrierPlanning.functions.js +6 -5
  70. package/dist-lib/lib/loadCarrierPlanning.server.d.ts +42 -0
  71. package/dist-lib/lib/loadCarrierPlanning.server.d.ts.map +1 -1
  72. package/dist-lib/lib/loadCarrierPlanning.server.js +563 -31
  73. package/dist-lib/lib/logisticsPlanner.d.ts.map +1 -1
  74. package/dist-lib/lib/logisticsPlanner.js +24 -9
  75. package/dist-lib/lib/logisticsRecalcJob.functions.d.ts +4163 -193
  76. package/dist-lib/lib/logisticsRecalcJob.functions.d.ts.map +1 -1
  77. package/dist-lib/lib/logisticsRecalcJob.functions.js +3 -2
  78. package/dist-lib/lib/logisticsRecalcJob.server.d.ts.map +1 -1
  79. package/dist-lib/lib/logisticsRecalcJob.server.js +1 -0
  80. package/dist-lib/lib/logisticsRecalculate.functions.d.ts +2064 -79
  81. package/dist-lib/lib/logisticsRecalculate.functions.d.ts.map +1 -1
  82. package/dist-lib/lib/logisticsRecalculate.functions.js +3 -1
  83. package/dist-lib/lib/modulePrompts.d.ts.map +1 -1
  84. package/dist-lib/lib/modulePrompts.js +2560 -1586
  85. package/dist-lib/lib/planQueue.server.d.ts.map +1 -1
  86. package/dist-lib/lib/planQueue.server.js +6 -0
  87. package/dist-lib/lib/readableServerErrors.d.ts +2 -0
  88. package/dist-lib/lib/readableServerErrors.d.ts.map +1 -0
  89. package/dist-lib/lib/readableServerErrors.js +36 -0
  90. package/dist-lib/lib/ruleEngineSchema.d.ts +1 -1
  91. package/dist-lib/lib/ruleEngineSchema.d.ts.map +1 -1
  92. package/dist-lib/lib/ruleEngineSchema.js +72 -51
  93. package/dist-lib/lib/ruleEngineWizard.d.ts.map +1 -1
  94. package/dist-lib/lib/ruleEngineWizard.js +5 -6
  95. package/package.json +1 -1
@@ -19,6 +19,7 @@ export const Constants = {
19
19
  "sales",
20
20
  "developer",
21
21
  "receptie",
22
+ "superadmin",
22
23
  ],
23
24
  doc_layout_type: [
24
25
  "pakbon",
@@ -0,0 +1,2 @@
1
+ export declare const attachAuthToken: import("@tanstack/start-client-core").FunctionMiddlewareAfterClient<{}, unknown, undefined, undefined, undefined>;
2
+ //# sourceMappingURL=attachAuthToken.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attachAuthToken.d.ts","sourceRoot":"","sources":["../../src/lib/attachAuthToken.ts"],"names":[],"mappings":"AAmCA,eAAO,MAAM,eAAe,mHAY3B,CAAC"}
@@ -0,0 +1,50 @@
1
+ // ============================================================================
2
+ // src/lib/attachAuthToken.ts — bearer-token meesturen met serverfuncties
3
+ // ============================================================================
4
+ // De module kan in een shell draaien waar het inloggen door de shell zelf is
5
+ // gedaan. De eigen Supabase-client ziet die sessie soms niet, waardoor de
6
+ // server "niet ingelogd" terugkreeg. Daarom kijken we ook naar de sessie die
7
+ // de shell in de browseropslag heeft gezet. Geen vaste project-verwijzingen:
8
+ // we herkennen de sleutel op zijn vorm (sb-...-auth-token).
9
+ // ============================================================================
10
+ import { createMiddleware } from "@tanstack/react-start";
11
+ import { supabase } from "../integrations/supabase/moduleClient";
12
+ function tokenFromStorage() {
13
+ if (typeof window === "undefined")
14
+ return null;
15
+ try {
16
+ for (let i = 0; i < window.localStorage.length; i++) {
17
+ const key = window.localStorage.key(i);
18
+ if (!key || !/^sb-.*-auth-token$/.test(key))
19
+ continue;
20
+ const raw = window.localStorage.getItem(key);
21
+ if (!raw)
22
+ continue;
23
+ const parsed = JSON.parse(raw.startsWith("base64-") ? atob(raw.slice(7)) : raw);
24
+ const token = parsed?.access_token ?? parsed?.currentSession?.access_token;
25
+ const expires = parsed?.expires_at ?? parsed?.currentSession?.expires_at;
26
+ if (typeof token === "string" && token.length > 0) {
27
+ if (typeof expires === "number" && expires * 1000 < Date.now())
28
+ continue;
29
+ return token;
30
+ }
31
+ }
32
+ }
33
+ catch {
34
+ /* onleesbare opslag negeren */
35
+ }
36
+ return null;
37
+ }
38
+ export const attachAuthToken = createMiddleware({ type: "function" }).client(async ({ next }) => {
39
+ let token = null;
40
+ try {
41
+ const { data } = await supabase.auth.getSession();
42
+ token = data.session?.access_token ?? null;
43
+ }
44
+ catch {
45
+ /* eigen client heeft geen sessie */
46
+ }
47
+ if (!token)
48
+ token = tokenFromStorage();
49
+ return next({ headers: token ? { Authorization: `Bearer ${token}` } : {} });
50
+ });
@@ -1 +1 @@
1
- {"version":3,"file":"bufferMatching.d.ts","sourceRoot":"","sources":["../../src/lib/bufferMatching.ts"],"names":[],"mappings":"AAQA,eAAO,MAAM,sBAAsB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAazD,CAAC;AAGF,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,UAE/C;AAED,iFAAiF;AACjF,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,EAAE,CAc9D;AAED,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,uBAAuB,EAAE,CAAC;CAClC;AAED,eAAO,MAAM,mBAAmB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAKtD,CAAC;AAGF,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,UAGlE;AAED,qEAAqE;AACrE,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,OAAO,GAAG,cAAc,GAAG,IAAI,CAepE"}
1
+ {"version":3,"file":"bufferMatching.d.ts","sourceRoot":"","sources":["../../src/lib/bufferMatching.ts"],"names":[],"mappings":"AAQA,eAAO,MAAM,sBAAsB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAezD,CAAC;AAGF,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,UAE/C;AAED,iFAAiF;AACjF,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,EAAE,CAc9D;AAED,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,uBAAuB,EAAE,CAAC;CAClC;AAED,eAAO,MAAM,mBAAmB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAKtD,CAAC;AAGF,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,UAGlE;AAED,qEAAqE;AACrE,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,OAAO,GAAG,cAAc,GAAG,IAAI,CAepE"}
@@ -17,6 +17,7 @@ export const BUFFER_CRITERION_LABEL = {
17
17
  country: "Zelfde land",
18
18
  no_criteria_selected: "Geen criteria gekozen",
19
19
  buffer_enabled: "Orderbuffer aan/uit",
20
+ buffer_close_reference: "Sluitmoment van de buffer",
20
21
  buffer_include_reservations: "Gereserveerde bestellingen in buffers",
21
22
  };
22
23
  export function bufferCriterionLabel(key) {
@@ -0,0 +1,13 @@
1
+ import type { SupabaseClient } from "@supabase/supabase-js";
2
+ export interface CartPlanRequestResult {
3
+ claimed: number;
4
+ resolved: number;
5
+ planned: number;
6
+ failed: number;
7
+ errors: string[];
8
+ }
9
+ /** Verwerkt openstaande 'cartplan.missing'-seintjes. Idempotent. */
10
+ export declare function runCartPlanRequests(sb: SupabaseClient, options?: {
11
+ limit?: number;
12
+ }): Promise<CartPlanRequestResult>;
13
+ //# sourceMappingURL=cartPlanRequests.server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cartPlanRequests.server.d.ts","sourceRoot":"","sources":["../../src/lib/cartPlanRequests.server.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAG5D,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAoGD,oEAAoE;AACpE,wBAAsB,mBAAmB,CACvC,EAAE,EAAE,cAAc,EAClB,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GAC/B,OAAO,CAAC,qBAAqB,CAAC,CAmGhC"}
@@ -0,0 +1,168 @@
1
+ // ============================================================================
2
+ // src/lib/cartPlanRequests.server.ts — seintjes "karplan ontbreekt" afhandelen
3
+ // ============================================================================
4
+ // De pickmodule zet een rij in public.logistics_events met
5
+ // event_type = 'cartplan.missing' zodra een order in het pickstation staat
6
+ // zonder karplan. De Engine pakt dat seintje direct op: bestaat er al een plan,
7
+ // dan wordt het uitgeleverd; bestaat het niet, dan wordt het volledig gemaakt.
8
+ // Lukt dat niet, dan wordt het seintje afgemeld met een leesbare fout
9
+ // (fail closed) zodat de pickmedewerker weet dat er iets handmatig moet.
10
+ //
11
+ // Geen omgevingswaarden: alles loopt via de meegegeven Supabase-client.
12
+ // ============================================================================
13
+ import { replanOrderInternal } from "./loadCarrierPlanning.server.js";
14
+ const EVENT_TYPE = "cartplan.missing";
15
+ /** Leest het karplan van één order; leeg betekent: geen plan. */
16
+ async function slipPlanCartCount(sb, orderId) {
17
+ const { data, error } = await sb.rpc("logistics_cart_slip_plan", { p_order_id: orderId });
18
+ if (error)
19
+ return { carts: 0, totalCarts: null, cartIds: [], error: error.message };
20
+ // De functie geeft één JSON-object terug met de karren van deze order.
21
+ const plan = (Array.isArray(data) ? data[0] : data);
22
+ const carts = Array.isArray(plan?.["carts"]) ? plan["carts"] : [];
23
+ const total = Number(plan?.["cartTotal"] ?? carts.length);
24
+ const cartIds = carts
25
+ .map((c) => String(c?.["cartId"] ?? ""))
26
+ .filter(Boolean)
27
+ .sort();
28
+ return {
29
+ carts: carts.length,
30
+ totalCarts: Number.isFinite(total) ? total : null,
31
+ cartIds,
32
+ error: null,
33
+ };
34
+ }
35
+ /**
36
+ * Een plan is pas volledig als elk lid karren heeft én alle leden van een
37
+ * buffer exact dezelfde fysieke karren en hetzelfde totaal teruggeven.
38
+ */
39
+ function plansConsistent(plans) {
40
+ if (plans.length === 0)
41
+ return false;
42
+ if (plans.some((p) => p.error || p.carts === 0))
43
+ return false;
44
+ const first = plans[0];
45
+ return plans.every((p) => p.totalCarts === first.totalCarts && p.cartIds.join(",") === first.cartIds.join(","));
46
+ }
47
+ /** Zoekt de order-UUID uit de payload of via het ordernummer. */
48
+ async function resolveOrderId(sb, row) {
49
+ const fromPayload = row.payload?.["orderId"];
50
+ if (typeof fromPayload === "string" && fromPayload.length > 0)
51
+ return fromPayload;
52
+ const ref = row.order_ref;
53
+ if (!ref)
54
+ return null;
55
+ const { data } = await sb
56
+ .from("orders")
57
+ .select("id")
58
+ .eq("order_number", ref)
59
+ .is("deleted_at", null)
60
+ .limit(1)
61
+ .maybeSingle();
62
+ return data?.id ?? null;
63
+ }
64
+ /** Alle orders van dezelfde buffer, inclusief de order zelf. */
65
+ async function bufferMemberIds(sb, orderId) {
66
+ const { data: order } = await sb
67
+ .from("orders")
68
+ .select("id, buffer_id")
69
+ .eq("id", orderId)
70
+ .maybeSingle();
71
+ const bufferId = order?.buffer_id ?? null;
72
+ if (!bufferId)
73
+ return [orderId];
74
+ const { data: members } = await sb
75
+ .from("orders")
76
+ .select("id")
77
+ .eq("buffer_id", bufferId)
78
+ .is("deleted_at", null);
79
+ const ids = Array.isArray(members) ? members.map((m) => String(m.id)) : [];
80
+ return ids.length > 0 ? ids : [orderId];
81
+ }
82
+ async function finish(sb, row, status, extra, errorText) {
83
+ await sb
84
+ .from("logistics_events")
85
+ .update({
86
+ status,
87
+ error: errorText,
88
+ processed_at: new Date().toISOString(),
89
+ payload: { ...(row.payload ?? {}), result: extra },
90
+ })
91
+ .eq("id", row.id);
92
+ }
93
+ /** Verwerkt openstaande 'cartplan.missing'-seintjes. Idempotent. */
94
+ export async function runCartPlanRequests(sb, options = {}) {
95
+ const result = { claimed: 0, resolved: 0, planned: 0, failed: 0, errors: [] };
96
+ const { data, error } = await sb
97
+ .from("logistics_events")
98
+ .select("id, order_ref, payload")
99
+ .eq("event_type", EVENT_TYPE)
100
+ // Een bestaande databasetrigger kan het seintje al op 'processed' zetten.
101
+ // Afgehandeld is het pas als de Engine er zelf een resultaat bij schrijft.
102
+ .not("status", "in", "(resolved,failed)")
103
+ .is("payload->result", null)
104
+ .order("created_at", { ascending: true })
105
+ .limit(options.limit ?? 20);
106
+ if (error) {
107
+ result.failed++;
108
+ result.errors.push(error.message);
109
+ return result;
110
+ }
111
+ const rows = Array.isArray(data) ? data : [];
112
+ result.claimed = rows.length;
113
+ for (const row of rows) {
114
+ try {
115
+ const orderId = await resolveOrderId(sb, row);
116
+ if (!orderId) {
117
+ result.failed++;
118
+ await finish(sb, row, "failed", {}, `Order onbekend: ${row.order_ref ?? "(geen ordernummer)"}`);
119
+ continue;
120
+ }
121
+ let members = await bufferMemberIds(sb, orderId);
122
+ let plans = await Promise.all(members.map((id) => slipPlanCartCount(sb, id)));
123
+ let complete = plansConsistent(plans);
124
+ if (!complete) {
125
+ // Nog geen (volledig) plan: eerst maken, daarna uitleveren.
126
+ const plan = await replanOrderInternal(sb, orderId);
127
+ if (!plan) {
128
+ result.failed++;
129
+ await finish(sb, row, "failed", { orderId }, "Order hoort bij geen enkele buffer; karplan kan niet worden gemaakt.");
130
+ continue;
131
+ }
132
+ if (plan.skipped === "blocked") {
133
+ result.failed++;
134
+ await finish(sb, row, "failed", { orderId }, plan.blockedReason ?? "Karplan kan niet worden gemaakt: onbekende of onvolledige belading.");
135
+ continue;
136
+ }
137
+ result.planned++;
138
+ members = await bufferMemberIds(sb, orderId);
139
+ plans = await Promise.all(members.map((id) => slipPlanCartCount(sb, id)));
140
+ complete = plansConsistent(plans);
141
+ }
142
+ if (!complete) {
143
+ const missing = members.filter((_, i) => (plans[i]?.carts ?? 0) === 0);
144
+ result.failed++;
145
+ await finish(sb, row, "failed", { orderId, bufferOrderIds: members }, missing.length > 0
146
+ ? `Karplan onvolledig: ${missing.length} van ${members.length} orders zonder karren.`
147
+ : "Karplan onvolledig: de orders van deze buffer staan niet op dezelfde karren.");
148
+ continue;
149
+ }
150
+ result.resolved++;
151
+ await finish(sb, row, "resolved", {
152
+ orderId,
153
+ bufferOrderIds: members,
154
+ carts: plans[0]?.carts ?? 0,
155
+ cartIds: plans[0]?.cartIds ?? [],
156
+ totalCarts: plans[0]?.totalCarts ?? plans[0]?.carts ?? 0,
157
+ }, null);
158
+ }
159
+ catch (e) {
160
+ const message = e instanceof Error ? e.message : String(e);
161
+ result.failed++;
162
+ if (result.errors.length < 5)
163
+ result.errors.push(message);
164
+ await finish(sb, row, "failed", {}, message);
165
+ }
166
+ }
167
+ return result;
168
+ }
@@ -0,0 +1,12 @@
1
+ import type { SupabaseClient } from "@supabase/supabase-js";
2
+ export interface ControlScanRun {
3
+ claimed: number;
4
+ printed: number;
5
+ waiting: number;
6
+ failed: number;
7
+ errors: string[];
8
+ }
9
+ export declare function runControlScanQueue(sb: SupabaseClient, options?: {
10
+ limit?: number;
11
+ }): Promise<ControlScanRun>;
12
+ //# sourceMappingURL=controlScan.server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"controlScan.server.d.ts","sourceRoot":"","sources":["../../src/lib/controlScan.server.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAE5D,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAmCD,wBAAsB,mBAAmB,CACvC,EAAE,EAAE,cAAc,EAClB,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GAC/B,OAAO,CAAC,cAAc,CAAC,CAoLzB"}
@@ -0,0 +1,197 @@
1
+ // ============================================================================
2
+ // src/lib/controlScan.server.ts
3
+ // Controlepost: na het scannen en goedkeuren van een pakbon zorgt de Engine
4
+ // voor de afleverbrief.
5
+ //
6
+ // - Geen Floriday-order: de afleverbrief wordt al in de database gemaakt door
7
+ // public.logistics_control_scan_approve. Hier gebeurt niets meer.
8
+ // - Wel een Floriday-order (FO): de definitieve belading gaat via de bestaande
9
+ // pushwachtrij naar Floriday. Zodra Floriday de fulfillment-order kent,
10
+ // halen we hier de officiele afleverbrief op en zetten we die als printjob
11
+ // klaar op de printer die bij de controlestap is gekozen.
12
+ //
13
+ // Alles is idempotent: per controle hooguit een printjob (idempotentiesleutel)
14
+ // en dubbele scans worden al in de database geblokkeerd. Geen omgevings- of
15
+ // klantwaarden in deze code; de printer komt uit de werkwijze in de database.
16
+ // ============================================================================
17
+ function pickPdf(result) {
18
+ const base64 = result["pdfBase64"] ??
19
+ result["base64"] ??
20
+ result["pdf"] ??
21
+ null;
22
+ const url = result["url"] ??
23
+ result["pdfUrl"] ??
24
+ result["documentUrl"] ??
25
+ null;
26
+ return { base64: base64 ?? null, url: url ?? null };
27
+ }
28
+ /** Zet de wachtende controle terug in de wachtrij zonder pogingen op te tellen. */
29
+ async function keepWaiting(sb, id) {
30
+ await sb
31
+ .from("logistics_control_scan")
32
+ .update({ locked_until: null, attempts: 0, updated_at: new Date().toISOString() })
33
+ .eq("id", id);
34
+ }
35
+ export async function runControlScanQueue(sb, options = {}) {
36
+ const run = { claimed: 0, printed: 0, waiting: 0, failed: 0, errors: [] };
37
+ const { data, error } = await sb.rpc("logistics_control_scan_claim", {
38
+ _limit: options.limit ?? 10,
39
+ _lease_seconds: 120,
40
+ });
41
+ if (error) {
42
+ run.errors.push(`Controles niet leesbaar: ${error.message}`);
43
+ return run;
44
+ }
45
+ const rows = Array.isArray(data) ? data : [];
46
+ run.claimed = rows.length;
47
+ if (rows.length === 0)
48
+ return run;
49
+ const { getFlorydayDocument } = await import("./floridayDocuments.server.js");
50
+ for (const row of rows) {
51
+ try {
52
+ if (!row.is_floriday) {
53
+ await sb.rpc("logistics_control_scan_finish", { _id: row.id, _ok: true, _status: "geprint" });
54
+ run.printed++;
55
+ continue;
56
+ }
57
+ if (!row.order_number) {
58
+ await sb.rpc("logistics_control_scan_finish", {
59
+ _id: row.id,
60
+ _ok: false,
61
+ _status: "mislukt",
62
+ _error_code: "geen_ordernummer",
63
+ _error: "Bij deze kar hoort geen ordernummer meer; de afleverbrief kan niet worden opgevraagd.",
64
+ });
65
+ run.failed++;
66
+ continue;
67
+ }
68
+ // Stand van de push naar Floriday. De belading gaat via de bestaande
69
+ // pushwachtrij; wij wachten tot Floriday de fulfillment-order kent.
70
+ const { data: push } = await sb
71
+ .from("logistics_floriday_pushes")
72
+ .select("status, fulfillment_order_id, last_error, last_error_code")
73
+ .eq("order_ref", row.order_number)
74
+ .maybeSingle();
75
+ const pushRow = (push ?? null);
76
+ const fulfillmentOrderId = pushRow?.["fulfillment_order_id"] ?? null;
77
+ const pushStatus = String(pushRow?.["status"] ?? "");
78
+ if (pushStatus === "failed" || pushStatus === "dead") {
79
+ await sb.rpc("logistics_control_scan_finish", {
80
+ _id: row.id,
81
+ _ok: false,
82
+ _status: "mislukt",
83
+ _error_code: String(pushRow?.["last_error_code"] ?? "floriday_push_mislukt"),
84
+ _error: String(pushRow?.["last_error"] ?? "") ||
85
+ "De belading kon niet naar Floriday worden gestuurd. Probeer het opnieuw.",
86
+ });
87
+ run.failed++;
88
+ continue;
89
+ }
90
+ if (!fulfillmentOrderId) {
91
+ await keepWaiting(sb, row.id);
92
+ run.waiting++;
93
+ continue;
94
+ }
95
+ const doc = (await getFlorydayDocument(sb, {
96
+ kind: "delivery-note",
97
+ fulfillmentOrderId,
98
+ }));
99
+ if (!doc["ok"]) {
100
+ await sb.rpc("logistics_control_scan_finish", {
101
+ _id: row.id,
102
+ _ok: false,
103
+ _status: "mislukt",
104
+ _fulfillment_order_id: fulfillmentOrderId,
105
+ _error_code: "floriday_document",
106
+ _error: String(doc["error"] ?? "") ||
107
+ "Floriday gaf geen afleverbrief terug. De controle is niet afgerond.",
108
+ });
109
+ run.failed++;
110
+ continue;
111
+ }
112
+ const { base64, url } = pickPdf(doc);
113
+ if (!base64 && !url) {
114
+ await sb.rpc("logistics_control_scan_finish", {
115
+ _id: row.id,
116
+ _ok: false,
117
+ _status: "mislukt",
118
+ _fulfillment_order_id: fulfillmentOrderId,
119
+ _error_code: "floriday_document_leeg",
120
+ _error: "Floriday leverde geen bruikbaar bestand voor de afleverbrief.",
121
+ });
122
+ run.failed++;
123
+ continue;
124
+ }
125
+ // Idempotent: per controle hooguit een printjob.
126
+ const idempotentie = `controle-scan:${row.id}`;
127
+ const { data: bestaand } = await sb
128
+ .from("print_jobs")
129
+ .select("id")
130
+ .eq("idempotentie_sleutel", idempotentie)
131
+ .maybeSingle();
132
+ let jobId = bestaand?.["id"] ?? null;
133
+ if (!jobId) {
134
+ const { data: job, error: jobError } = await sb
135
+ .from("print_jobs")
136
+ .insert({
137
+ printer_id: row.printer_id,
138
+ documenttype: "afleverbrief",
139
+ documentnaam: `Afleverbrief ${row.order_number}`,
140
+ status: "wachtend",
141
+ idempotentie_sleutel: idempotentie,
142
+ pdf_base64: base64,
143
+ pdf_url: url,
144
+ herkomst: "logistiek-engine",
145
+ gebruiker_naam: "Controle scan",
146
+ payload: {
147
+ source: "controle_scan",
148
+ is_floriday: true,
149
+ order_id: row.order_id,
150
+ order_number: row.order_number,
151
+ cart_id: row.cart_id,
152
+ fulfillment_order_id: fulfillmentOrderId,
153
+ printer_id: row.printer_id,
154
+ control_scan_id: row.id,
155
+ },
156
+ })
157
+ .select("id")
158
+ .maybeSingle();
159
+ if (jobError) {
160
+ await sb.rpc("logistics_control_scan_finish", {
161
+ _id: row.id,
162
+ _ok: false,
163
+ _status: "mislukt",
164
+ _fulfillment_order_id: fulfillmentOrderId,
165
+ _error_code: "printjob",
166
+ _error: `De afleverbrief kon niet worden klaargezet om te printen: ${jobError.message}`,
167
+ });
168
+ run.failed++;
169
+ continue;
170
+ }
171
+ jobId = job?.["id"] ?? null;
172
+ }
173
+ await sb.rpc("logistics_control_scan_finish", {
174
+ _id: row.id,
175
+ _ok: true,
176
+ _status: "geprint",
177
+ _print_job_id: jobId,
178
+ _document_url: url,
179
+ _fulfillment_order_id: fulfillmentOrderId,
180
+ });
181
+ run.printed++;
182
+ }
183
+ catch (e) {
184
+ const message = String(e?.message ?? e);
185
+ run.errors.push(message);
186
+ await sb.rpc("logistics_control_scan_finish", {
187
+ _id: row.id,
188
+ _ok: false,
189
+ _status: "mislukt",
190
+ _error_code: "onverwacht",
191
+ _error: message,
192
+ });
193
+ run.failed++;
194
+ }
195
+ }
196
+ return run;
197
+ }
@@ -12,6 +12,8 @@ export interface MissingDimension {
12
12
  export interface DimensionDataResult {
13
13
  items: DimensionItem[];
14
14
  missing: MissingDimension[];
15
+ /** Leesbare meldingen over planthoogtes die zijn afgeleid uit de belading. */
16
+ derived: string[];
15
17
  }
16
18
  /** Bruikbare afmetingen van een ladingdrager, of de ontbrekende velden. */
17
19
  export declare function carrierDimensions(row: LoadCarrierTypeRow | null | undefined): {
@@ -22,6 +24,14 @@ export declare function carrierDimensions(row: LoadCarrierTypeRow | null | undef
22
24
  * Haalt fust- en planthoogtegegevens op bij de producten van de plannerinvoer.
23
25
  * Groepsleden gebruiken hun eigen product; regels zonder product_id kunnen niet
24
26
  * op afmetingen worden ingedeeld.
27
+ *
28
+ * Ontbreekt alleen de planthoogte, dan wordt die afgeleid uit de belading:
29
+ * de bruikbare hoogte van de ladingdrager gedeeld door het aantal lagen dat
30
+ * er volgens de belading op hoort, min de hoogte van het fust. Lukt dat niet
31
+ * (geen ladingdragerhoogte, geen lagen, of een uitkomst van nul of minder),
32
+ * dan blijft het een ontbrekend gegeven en wordt er niets ingedeeld.
25
33
  */
26
- export declare function buildDimensionItems(sb: SupabaseClient, inputs: PlannerInputItem[]): Promise<DimensionDataResult>;
34
+ export declare function buildDimensionItems(sb: SupabaseClient, inputs: PlannerInputItem[], carrier?: DimensionCarrier | null,
35
+ /** Aantal lagen dat volgens het beheerscherm op deze ladingdrager past. */
36
+ carrierLayers?: number | null): Promise<DimensionDataResult>;
27
37
  //# sourceMappingURL=dimensionData.server.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"dimensionData.server.d.ts","sourceRoot":"","sources":["../../src/lib/dimensionData.server.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAC7D,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAE1E,MAAM,WAAW,gBAAgB;IAC/B,oCAAoC;IACpC,OAAO,EAAE,SAAS,GAAG,MAAM,GAAG,cAAc,CAAC;IAC7C,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,aAAa,EAAE,CAAC;IACvB,OAAO,EAAE,gBAAgB,EAAE,CAAC;CAC7B;AAOD,2EAA2E;AAC3E,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,kBAAkB,GAAG,IAAI,GAAG,SAAS,GAAG;IAC7E,OAAO,EAAE,gBAAgB,GAAG,IAAI,CAAC;IACjC,OAAO,EAAE,gBAAgB,EAAE,CAAC;CAC7B,CAsCA;AAMD;;;;GAIG;AACH,wBAAsB,mBAAmB,CACvC,EAAE,EAAE,cAAc,EAClB,MAAM,EAAE,gBAAgB,EAAE,GACzB,OAAO,CAAC,mBAAmB,CAAC,CAuG9B"}
1
+ {"version":3,"file":"dimensionData.server.d.ts","sourceRoot":"","sources":["../../src/lib/dimensionData.server.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAC7D,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAE1E,MAAM,WAAW,gBAAgB;IAC/B,oCAAoC;IACpC,OAAO,EAAE,SAAS,GAAG,MAAM,GAAG,cAAc,CAAC;IAC7C,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,aAAa,EAAE,CAAC;IACvB,OAAO,EAAE,gBAAgB,EAAE,CAAC;IAC5B,8EAA8E;IAC9E,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAQD,2EAA2E;AAC3E,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,kBAAkB,GAAG,IAAI,GAAG,SAAS,GAAG;IAC7E,OAAO,EAAE,gBAAgB,GAAG,IAAI,CAAC;IACjC,OAAO,EAAE,gBAAgB,EAAE,CAAC;CAC7B,CAsCA;AAMD;;;;;;;;;;GAUG;AACH,wBAAsB,mBAAmB,CACvC,EAAE,EAAE,cAAc,EAClB,MAAM,EAAE,gBAAgB,EAAE,EAC1B,OAAO,CAAC,EAAE,gBAAgB,GAAG,IAAI;AACjC,2EAA2E;AAC3E,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,GAC5B,OAAO,CAAC,mBAAmB,CAAC,CAkH9B"}