@flowselections/floriday-voorraad 1.0.40 → 1.0.41
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/voorraad/ValidationOverviewTable.d.ts.map +1 -1
- package/dist-lib/components/voorraad/ValidationOverviewTable.js +45 -14
- package/dist-lib/integrations/supabase/auth-middleware.d.ts +349 -0
- package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.d.ts +349 -0
- package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.server.d.ts +349 -0
- package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.d.ts +349 -0
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/lib/floricode-required-features.functions.d.ts +349 -0
- package/dist-lib/lib/floricode-required-features.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-customer-offer.functions.d.ts +349 -0
- package/dist-lib/lib/floriday-customer-offer.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-env.functions.d.ts +698 -0
- package/dist-lib/lib/floriday-env.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-gateway.functions.d.ts +2094 -0
- package/dist-lib/lib/floriday-gateway.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-relink.d.ts +16 -0
- package/dist-lib/lib/floriday-relink.d.ts.map +1 -0
- package/dist-lib/lib/floriday-relink.js +14 -0
- package/dist-lib/lib/floriday-vbn-required.functions.d.ts +698 -0
- package/dist-lib/lib/floriday-vbn-required.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-warehouse.functions.d.ts +1047 -0
- package/dist-lib/lib/floriday-warehouse.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-writes.functions.d.ts +698 -0
- package/dist-lib/lib/floriday-writes.functions.d.ts.map +1 -1
- package/dist-lib/lib/inventory-settings.functions.d.ts +698 -0
- package/dist-lib/lib/inventory-settings.functions.d.ts.map +1 -1
- package/dist-lib/lib/logistics-buffer-events.server.d.ts +13 -0
- package/dist-lib/lib/logistics-buffer-events.server.d.ts.map +1 -0
- package/dist-lib/lib/logistics-buffer-events.server.js +365 -0
- package/dist-lib/lib/logistics-events.d.ts.map +1 -1
- package/dist-lib/lib/logistics-events.js +36 -7
- package/dist-lib/lib/logistics-tasks.server.d.ts.map +1 -1
- package/dist-lib/lib/logistics-tasks.server.js +38 -6
- package/dist-lib/lib/module-health.functions.d.ts +698 -0
- package/dist-lib/lib/module-health.functions.d.ts.map +1 -1
- package/dist-lib/lib/stock-sync.functions.d.ts +698 -0
- package/dist-lib/lib/stock-sync.functions.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
type Admin = any;
|
|
2
|
+
export interface BufferEventSummary {
|
|
3
|
+
events: number;
|
|
4
|
+
closed: number;
|
|
5
|
+
detached: number;
|
|
6
|
+
recalculated: number;
|
|
7
|
+
errors: string[];
|
|
8
|
+
}
|
|
9
|
+
export declare function runBufferEvents(): Promise<BufferEventSummary>;
|
|
10
|
+
/** True wanneer een taak niet meer uitgevoerd mag worden (buffer dicht/af). */
|
|
11
|
+
export declare function isBufferClosed(admin: Admin, bufferRef: string | null): Promise<boolean>;
|
|
12
|
+
export {};
|
|
13
|
+
//# sourceMappingURL=logistics-buffer-events.server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logistics-buffer-events.server.d.ts","sourceRoot":"","sources":["../../src/lib/logistics-buffer-events.server.ts"],"names":[],"mappings":"AA0BA,KAAK,KAAK,GAAG,GAAG,CAAC;AAEjB,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAqUD,wBAAsB,eAAe,IAAI,OAAO,CAAC,kBAAkB,CAAC,CA6EnE;AAED,+EAA+E;AAC/E,wBAAsB,cAAc,CAClC,KAAK,EAAE,KAAK,EACZ,SAAS,EAAE,MAAM,GAAG,IAAI,GACvB,OAAO,CAAC,OAAO,CAAC,CAQlB"}
|
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verwerkt de seintjes van de Logistics Engine over orderbuffers.
|
|
3
|
+
*
|
|
4
|
+
* De Engine sluit een buffer zodra alle karren afgehandeld zijn
|
|
5
|
+
* (`buffer.closed`) en koppelt orders die er nog aan hingen los
|
|
6
|
+
* (`buffer.order_detached`). Deze module beslist niets over buffers: ze
|
|
7
|
+
* herberekent alleen reserveringen en beschikbare voorraad.
|
|
8
|
+
*
|
|
9
|
+
* Eigenschappen:
|
|
10
|
+
* - Idempotent: elk seintje wordt hoogstens één keer doorgerekend
|
|
11
|
+
* (`voorraad_logistics_event_log`, primaire sleutel = event-id).
|
|
12
|
+
* - Nooit terugboeken: `physical_stock` wordt niet aangeraakt; al afgeboekte
|
|
13
|
+
* voorraad van afgehandelde karren blijft staan.
|
|
14
|
+
* - Reserveringen worden absoluut gezet uit de nog openstaande taken, dus een
|
|
15
|
+
* herhaalde run kan niet dubbeltellen.
|
|
16
|
+
* - Gesloten of afgehandelde buffers krijgen niets meer toegewezen.
|
|
17
|
+
*
|
|
18
|
+
* Server-only: nooit importeren vanuit componenten.
|
|
19
|
+
*/
|
|
20
|
+
import { OWNER_MODULE, parseStockReservePlan } from "./logistics-engine";
|
|
21
|
+
const BUFFER_EVENT_TYPES = ["buffer.closed", "buffer.order_detached"];
|
|
22
|
+
const LOOKBACK_DAYS = 7;
|
|
23
|
+
const EVENT_BATCH = 50;
|
|
24
|
+
const ACTIVE_TASK_STATUSES = ["planned", "ready", "in_progress"];
|
|
25
|
+
function str(value) {
|
|
26
|
+
const s = String(value ?? "").trim();
|
|
27
|
+
return s ? s : null;
|
|
28
|
+
}
|
|
29
|
+
/** Alle manieren waarop een buffer in `logistics_tasks.buffer_ref` kan staan. */
|
|
30
|
+
function bufferRefsFrom(payload, prefix) {
|
|
31
|
+
const pick = (base) => {
|
|
32
|
+
if (prefix === "")
|
|
33
|
+
return payload?.[base];
|
|
34
|
+
const key = prefix + base.charAt(0).toUpperCase() + base.slice(1);
|
|
35
|
+
return payload?.[key];
|
|
36
|
+
};
|
|
37
|
+
return [str(pick("bufferId")), str(pick("bufferKey")), str(pick("bufferRef"))].filter((v) => Boolean(v));
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Verwijzingen die als "dicht" gelden.
|
|
41
|
+
*
|
|
42
|
+
* Een buffer-id is uniek, dus die telt altijd. Een buffersleutel wordt door
|
|
43
|
+
* meerdere buffers gedeeld: die telt alleen mee wanneer er geen enkele nog
|
|
44
|
+
* open buffer met dezelfde sleutel bekend is. Zo blokkeert het sluiten van
|
|
45
|
+
* één buffer nooit de andere buffers met dezelfde sleutel.
|
|
46
|
+
*/
|
|
47
|
+
async function closedBufferRefs(admin) {
|
|
48
|
+
const { data } = await admin
|
|
49
|
+
.from("voorraad_buffer_state")
|
|
50
|
+
.select("buffer_id,buffer_key,closed_at");
|
|
51
|
+
const rows = (data ?? []);
|
|
52
|
+
const set = new Set();
|
|
53
|
+
const openKeys = new Set();
|
|
54
|
+
for (const row of rows) {
|
|
55
|
+
if (row.closed_at)
|
|
56
|
+
set.add(String(row.buffer_id));
|
|
57
|
+
else if (row.buffer_key)
|
|
58
|
+
openKeys.add(row.buffer_key);
|
|
59
|
+
}
|
|
60
|
+
for (const row of rows) {
|
|
61
|
+
if (row.closed_at && row.buffer_key && !openKeys.has(row.buffer_key)) {
|
|
62
|
+
set.add(row.buffer_key);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return set;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Toets bij de Engine of een buffer echt afgehandeld is. Deze module bepaalt
|
|
69
|
+
* dat nooit zelf; bij twijfel behandelen we de buffer als open.
|
|
70
|
+
*/
|
|
71
|
+
async function bufferHandled(admin, bufferId) {
|
|
72
|
+
if (!bufferId)
|
|
73
|
+
return false;
|
|
74
|
+
try {
|
|
75
|
+
const { data, error } = await admin.rpc("logistics_buffer_handled", { p_buffer: bufferId });
|
|
76
|
+
if (error)
|
|
77
|
+
return false;
|
|
78
|
+
return data === true;
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/** Artikelsleutels uit alle taken van een buffer (ongeacht status). */
|
|
85
|
+
async function keysForBuffer(admin, refs) {
|
|
86
|
+
if (refs.length === 0)
|
|
87
|
+
return [];
|
|
88
|
+
const { data } = await admin
|
|
89
|
+
.from("logistics_tasks")
|
|
90
|
+
.select("payload")
|
|
91
|
+
.eq("owner_module", OWNER_MODULE)
|
|
92
|
+
.in("buffer_ref", refs);
|
|
93
|
+
const keys = new Set();
|
|
94
|
+
for (const row of (data ?? [])) {
|
|
95
|
+
for (const line of parseStockReservePlan(row.payload).lines)
|
|
96
|
+
keys.add(line.tradeItemKey);
|
|
97
|
+
}
|
|
98
|
+
return [...keys];
|
|
99
|
+
}
|
|
100
|
+
/** Artikelsleutels uit alle taken van één order. */
|
|
101
|
+
async function keysForOrder(admin, orderRef) {
|
|
102
|
+
const { data } = await admin
|
|
103
|
+
.from("logistics_tasks")
|
|
104
|
+
.select("payload")
|
|
105
|
+
.eq("owner_module", OWNER_MODULE)
|
|
106
|
+
.eq("order_ref", orderRef);
|
|
107
|
+
const keys = new Set();
|
|
108
|
+
for (const row of (data ?? [])) {
|
|
109
|
+
for (const line of parseStockReservePlan(row.payload).lines)
|
|
110
|
+
keys.add(line.tradeItemKey);
|
|
111
|
+
}
|
|
112
|
+
return [...keys];
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Zet de reservering van de opgegeven artikelen opnieuw op de som van de nog
|
|
116
|
+
* openstaande taken. Taken die klaar (`done`) zijn horen bij afgehandelde
|
|
117
|
+
* karren: die voorraad is al geboekt en telt hier bewust niet mee, zodat er
|
|
118
|
+
* niets wordt teruggedraaid of dubbel geteld. Taken op een gesloten buffer
|
|
119
|
+
* tellen evenmin mee.
|
|
120
|
+
*/
|
|
121
|
+
async function recalculateReservations(admin, keys, closed, connectionId) {
|
|
122
|
+
if (keys.length === 0)
|
|
123
|
+
return [];
|
|
124
|
+
const { data: tasks } = await admin
|
|
125
|
+
.from("logistics_tasks")
|
|
126
|
+
.select("order_ref,buffer_ref,payload,status")
|
|
127
|
+
.eq("owner_module", OWNER_MODULE)
|
|
128
|
+
.in("status", ACTIVE_TASK_STATUSES);
|
|
129
|
+
const totals = new Map();
|
|
130
|
+
const orderRefs = new Map();
|
|
131
|
+
for (const key of keys) {
|
|
132
|
+
totals.set(key, 0);
|
|
133
|
+
orderRefs.set(key, new Set());
|
|
134
|
+
}
|
|
135
|
+
for (const task of (tasks ?? [])) {
|
|
136
|
+
if (task.buffer_ref && closed.has(task.buffer_ref))
|
|
137
|
+
continue;
|
|
138
|
+
for (const line of parseStockReservePlan(task.payload).lines) {
|
|
139
|
+
if (!totals.has(line.tradeItemKey))
|
|
140
|
+
continue;
|
|
141
|
+
totals.set(line.tradeItemKey, (totals.get(line.tradeItemKey) ?? 0) + line.quantity);
|
|
142
|
+
orderRefs.get(line.tradeItemKey).add(task.order_ref);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
let query = admin
|
|
146
|
+
.from("trade_item_stock")
|
|
147
|
+
.select("connection_id,trade_item_key,physical_stock,reserved_stock")
|
|
148
|
+
.in("trade_item_key", keys);
|
|
149
|
+
if (connectionId)
|
|
150
|
+
query = query.eq("connection_id", connectionId);
|
|
151
|
+
const { data: rows } = await query;
|
|
152
|
+
const result = [];
|
|
153
|
+
for (const row of (rows ?? [])) {
|
|
154
|
+
const target = totals.get(row.trade_item_key) ?? 0;
|
|
155
|
+
if (target !== row.reserved_stock) {
|
|
156
|
+
// Absolute waarde: idempotent, en `physical_stock` blijft ongemoeid.
|
|
157
|
+
await admin
|
|
158
|
+
.from("trade_item_stock")
|
|
159
|
+
.update({ reserved_stock: target })
|
|
160
|
+
.eq("connection_id", row.connection_id)
|
|
161
|
+
.eq("trade_item_key", row.trade_item_key);
|
|
162
|
+
}
|
|
163
|
+
result.push({
|
|
164
|
+
tradeItemKey: row.trade_item_key,
|
|
165
|
+
reserved: target,
|
|
166
|
+
physical: row.physical_stock,
|
|
167
|
+
orderRefs: [...(orderRefs.get(row.trade_item_key) ?? [])],
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
return result;
|
|
171
|
+
}
|
|
172
|
+
/** Terugmelden aan de Engine met het ordernummer van de (losgekoppelde) order. */
|
|
173
|
+
async function emitStockChanged(admin, orderRef, bufferRef, lines) {
|
|
174
|
+
if (!orderRef || lines.length === 0)
|
|
175
|
+
return;
|
|
176
|
+
const rows = lines.map((line) => ({
|
|
177
|
+
source_module: OWNER_MODULE,
|
|
178
|
+
event_type: "stock.changed",
|
|
179
|
+
order_ref: orderRef,
|
|
180
|
+
shadow: false,
|
|
181
|
+
payload: {
|
|
182
|
+
tradeItemKey: line.tradeItemKey,
|
|
183
|
+
physicalStock: line.physical,
|
|
184
|
+
reservedStock: line.reserved,
|
|
185
|
+
availableStock: Math.max(0, line.physical - line.reserved),
|
|
186
|
+
bufferRef,
|
|
187
|
+
reason: "buffer_recalculated",
|
|
188
|
+
},
|
|
189
|
+
}));
|
|
190
|
+
await admin.from("logistics_events").insert(rows);
|
|
191
|
+
}
|
|
192
|
+
async function handleBufferClosed(admin, event, closed, connectionId) {
|
|
193
|
+
const payload = event.payload ?? {};
|
|
194
|
+
const bufferId = str(payload.bufferId);
|
|
195
|
+
const refs = bufferRefsFrom(payload, "");
|
|
196
|
+
if (bufferId) {
|
|
197
|
+
await admin.from("voorraad_buffer_state").upsert({
|
|
198
|
+
buffer_id: bufferId,
|
|
199
|
+
buffer_key: str(payload.bufferKey) ?? str(payload.bufferRef),
|
|
200
|
+
closed_at: event.created_at ?? new Date().toISOString(),
|
|
201
|
+
reason: str(payload.reason) ?? "buffer_closed",
|
|
202
|
+
updated_at: new Date().toISOString(),
|
|
203
|
+
}, { onConflict: "buffer_id" });
|
|
204
|
+
}
|
|
205
|
+
// Alleen verwijzingen die écht dicht zijn tellen mee: het buffer-id altijd,
|
|
206
|
+
// een gedeelde buffersleutel pas wanneer er geen open buffer meer op staat.
|
|
207
|
+
const fresh = await closedBufferRefs(admin);
|
|
208
|
+
for (const ref of fresh)
|
|
209
|
+
closed.add(ref);
|
|
210
|
+
const closedRefs = refs.filter((r) => closed.has(r));
|
|
211
|
+
const keys = await keysForBuffer(admin, refs);
|
|
212
|
+
// Nog niet gestarte reserveringen voor deze buffer worden niet meer
|
|
213
|
+
// uitgevoerd: een gesloten buffer krijgt geen voorraad meer toegewezen.
|
|
214
|
+
if (closedRefs.length > 0) {
|
|
215
|
+
await admin
|
|
216
|
+
.from("logistics_tasks")
|
|
217
|
+
.update({
|
|
218
|
+
status: "skipped",
|
|
219
|
+
claimed_by: null,
|
|
220
|
+
claimed_at: null,
|
|
221
|
+
next_attempt_at: null,
|
|
222
|
+
completed_at: new Date().toISOString(),
|
|
223
|
+
updated_at: new Date().toISOString(),
|
|
224
|
+
result: { reason: "buffer_closed", bufferId },
|
|
225
|
+
})
|
|
226
|
+
.eq("owner_module", OWNER_MODULE)
|
|
227
|
+
.in("buffer_ref", closedRefs)
|
|
228
|
+
.in("status", ["planned", "ready"]);
|
|
229
|
+
}
|
|
230
|
+
const lines = await recalculateReservations(admin, keys, closed, connectionId);
|
|
231
|
+
const orderRef = str(event.order_ref);
|
|
232
|
+
if (orderRef)
|
|
233
|
+
await emitStockChanged(admin, orderRef, refs[0] ?? null, lines);
|
|
234
|
+
return { keys: keys.length, lines: lines.length };
|
|
235
|
+
}
|
|
236
|
+
async function handleOrderDetached(admin, event, closed, connectionId) {
|
|
237
|
+
const payload = event.payload ?? {};
|
|
238
|
+
const orderRef = str(payload.orderRef) ?? str(event.order_ref);
|
|
239
|
+
if (!orderRef)
|
|
240
|
+
return { keys: 0, lines: 0 };
|
|
241
|
+
const newRefs = bufferRefsFrom(payload, "new");
|
|
242
|
+
const previousRefs = bufferRefsFrom(payload, "previous");
|
|
243
|
+
for (const ref of previousRefs) {
|
|
244
|
+
if (await bufferHandled(admin, str(payload.previousBufferId)))
|
|
245
|
+
closed.add(ref);
|
|
246
|
+
}
|
|
247
|
+
const newBufferId = str(payload.newBufferId);
|
|
248
|
+
const newBufferRef = newRefs[0] ?? null;
|
|
249
|
+
// De order staat vanaf nu op zichzelf (of in de nieuwe buffer): openstaande
|
|
250
|
+
// taken volgen mee, zodat de reservering onder het eigen ordernummer hangt.
|
|
251
|
+
await admin
|
|
252
|
+
.from("logistics_tasks")
|
|
253
|
+
.update({ buffer_ref: newBufferRef, updated_at: new Date().toISOString() })
|
|
254
|
+
.eq("owner_module", OWNER_MODULE)
|
|
255
|
+
.eq("order_ref", orderRef)
|
|
256
|
+
.in("status", ACTIVE_TASK_STATUSES);
|
|
257
|
+
if (newBufferId) {
|
|
258
|
+
await admin.from("voorraad_buffer_state").upsert({
|
|
259
|
+
buffer_id: newBufferId,
|
|
260
|
+
buffer_key: str(payload.newBufferKey),
|
|
261
|
+
updated_at: new Date().toISOString(),
|
|
262
|
+
}, { onConflict: "buffer_id" });
|
|
263
|
+
}
|
|
264
|
+
const previousBufferId = str(payload.previousBufferId);
|
|
265
|
+
if (previousBufferId) {
|
|
266
|
+
const { data: state } = await admin
|
|
267
|
+
.from("voorraad_buffer_state")
|
|
268
|
+
.select("detached_orders")
|
|
269
|
+
.eq("buffer_id", previousBufferId)
|
|
270
|
+
.maybeSingle();
|
|
271
|
+
const list = new Set(Array.isArray(state?.detached_orders) ? state.detached_orders.map(String) : []);
|
|
272
|
+
list.add(orderRef);
|
|
273
|
+
await admin.from("voorraad_buffer_state").upsert({
|
|
274
|
+
buffer_id: previousBufferId,
|
|
275
|
+
buffer_key: str(payload.previousBufferKey),
|
|
276
|
+
detached_orders: [...list],
|
|
277
|
+
updated_at: new Date().toISOString(),
|
|
278
|
+
}, { onConflict: "buffer_id" });
|
|
279
|
+
}
|
|
280
|
+
const keys = await keysForOrder(admin, orderRef);
|
|
281
|
+
const lines = await recalculateReservations(admin, keys, closed, connectionId);
|
|
282
|
+
await emitStockChanged(admin, orderRef, newBufferRef, lines);
|
|
283
|
+
return { keys: keys.length, lines: lines.length };
|
|
284
|
+
}
|
|
285
|
+
export async function runBufferEvents() {
|
|
286
|
+
const summary = {
|
|
287
|
+
events: 0,
|
|
288
|
+
closed: 0,
|
|
289
|
+
detached: 0,
|
|
290
|
+
recalculated: 0,
|
|
291
|
+
errors: [],
|
|
292
|
+
};
|
|
293
|
+
const { supabaseAdmin } = await import("../integrations/supabase/client.server");
|
|
294
|
+
const admin = supabaseAdmin;
|
|
295
|
+
const since = new Date(Date.now() - LOOKBACK_DAYS * 86400000).toISOString();
|
|
296
|
+
const { data: events, error } = await admin
|
|
297
|
+
.from("logistics_events")
|
|
298
|
+
.select("id,event_type,order_ref,payload,created_at")
|
|
299
|
+
.in("event_type", BUFFER_EVENT_TYPES)
|
|
300
|
+
.gte("created_at", since)
|
|
301
|
+
.order("created_at", { ascending: true })
|
|
302
|
+
.limit(EVENT_BATCH * 4);
|
|
303
|
+
if (error)
|
|
304
|
+
throw new Error(`seintjes lezen: ${error.message}`);
|
|
305
|
+
const all = (events ?? []);
|
|
306
|
+
if (all.length === 0)
|
|
307
|
+
return summary;
|
|
308
|
+
const { data: seenRows } = await admin
|
|
309
|
+
.from("voorraad_logistics_event_log")
|
|
310
|
+
.select("event_id")
|
|
311
|
+
.in("event_id", all.map((e) => e.id));
|
|
312
|
+
const seen = new Set((seenRows ?? []).map((r) => String(r.event_id)));
|
|
313
|
+
const todo = all.filter((e) => !seen.has(String(e.id))).slice(0, EVENT_BATCH);
|
|
314
|
+
if (todo.length === 0)
|
|
315
|
+
return summary;
|
|
316
|
+
let connectionId = null;
|
|
317
|
+
try {
|
|
318
|
+
const { resolveActiveFloridayConnection } = await import("./floriday-env.server");
|
|
319
|
+
connectionId = (await resolveActiveFloridayConnection(admin)).id;
|
|
320
|
+
}
|
|
321
|
+
catch {
|
|
322
|
+
connectionId = null;
|
|
323
|
+
}
|
|
324
|
+
const closed = await closedBufferRefs(admin);
|
|
325
|
+
for (const event of todo) {
|
|
326
|
+
try {
|
|
327
|
+
const result = event.event_type === "buffer.closed"
|
|
328
|
+
? await handleBufferClosed(admin, event, closed, connectionId)
|
|
329
|
+
: await handleOrderDetached(admin, event, closed, connectionId);
|
|
330
|
+
if (event.event_type === "buffer.closed")
|
|
331
|
+
summary.closed++;
|
|
332
|
+
else
|
|
333
|
+
summary.detached++;
|
|
334
|
+
summary.recalculated += result.lines;
|
|
335
|
+
summary.events++;
|
|
336
|
+
await admin.from("voorraad_logistics_event_log").upsert({
|
|
337
|
+
event_id: event.id,
|
|
338
|
+
event_type: event.event_type,
|
|
339
|
+
order_ref: event.order_ref ?? null,
|
|
340
|
+
result,
|
|
341
|
+
processed_at: new Date().toISOString(),
|
|
342
|
+
}, { onConflict: "event_id" });
|
|
343
|
+
}
|
|
344
|
+
catch (e) {
|
|
345
|
+
const message = String(e?.message ?? e).slice(0, 500);
|
|
346
|
+
summary.errors.push(`${event.id}: ${message}`);
|
|
347
|
+
console.warn(`[voorraad] bufferseintje ${event.id} mislukt: ${message}`);
|
|
348
|
+
// Niet loggen als verwerkt: de volgende ronde probeert het opnieuw.
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
return summary;
|
|
352
|
+
}
|
|
353
|
+
/** True wanneer een taak niet meer uitgevoerd mag worden (buffer dicht/af). */
|
|
354
|
+
export async function isBufferClosed(admin, bufferRef) {
|
|
355
|
+
if (!bufferRef)
|
|
356
|
+
return false;
|
|
357
|
+
const closed = await closedBufferRefs(admin);
|
|
358
|
+
if (closed.has(bufferRef))
|
|
359
|
+
return true;
|
|
360
|
+
// Onbekend bij ons? Dan heeft de Engine het laatste woord.
|
|
361
|
+
const isUuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(bufferRef);
|
|
362
|
+
if (isUuid)
|
|
363
|
+
return await bufferHandled(admin, bufferRef);
|
|
364
|
+
return false;
|
|
365
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logistics-events.d.ts","sourceRoot":"","sources":["../../src/lib/logistics-events.ts"],"names":[],"mappings":"AAcA,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,gBAAgB,GAAG,eAAe,GAAG,eAAe,GAAG,MAAM,CAAC;IACzE,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAC/B;AAED,wBAAsB,oBAAoB,CAAC,KAAK,EAAE,mBAAmB,iBAUpE;AAED,UAAU,iBAAiB;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;
|
|
1
|
+
{"version":3,"file":"logistics-events.d.ts","sourceRoot":"","sources":["../../src/lib/logistics-events.ts"],"names":[],"mappings":"AAcA,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,gBAAgB,GAAG,eAAe,GAAG,eAAe,GAAG,MAAM,CAAC;IACzE,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAC/B;AAED,wBAAsB,oBAAoB,CAAC,KAAK,EAAE,mBAAmB,iBAUpE;AAED,UAAU,iBAAiB;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AA6DD,6CAA6C;AAC7C,wBAAsB,qBAAqB,CAAC,KAAK,EAAE,iBAAiB,iBAmBnE"}
|
|
@@ -19,18 +19,47 @@ export async function insertLogisticsEvent(input) {
|
|
|
19
19
|
if (error)
|
|
20
20
|
throw new Error(error.message);
|
|
21
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* Buffers die door de Engine gesloten zijn; die krijgen niets meer. Een
|
|
24
|
+
* gedeelde buffersleutel telt pas mee als er geen open buffer meer op staat.
|
|
25
|
+
*/
|
|
26
|
+
async function closedBufferRefs() {
|
|
27
|
+
const { data } = await supabase
|
|
28
|
+
.from("voorraad_buffer_state")
|
|
29
|
+
.select("buffer_id,buffer_key,closed_at");
|
|
30
|
+
const rows = (data ?? []);
|
|
31
|
+
const set = new Set();
|
|
32
|
+
const openKeys = new Set();
|
|
33
|
+
for (const row of rows) {
|
|
34
|
+
if (row.closed_at)
|
|
35
|
+
set.add(String(row.buffer_id));
|
|
36
|
+
else if (row.buffer_key)
|
|
37
|
+
openKeys.add(row.buffer_key);
|
|
38
|
+
}
|
|
39
|
+
for (const row of rows) {
|
|
40
|
+
if (row.closed_at && row.buffer_key && !openKeys.has(row.buffer_key))
|
|
41
|
+
set.add(row.buffer_key);
|
|
42
|
+
}
|
|
43
|
+
return set;
|
|
44
|
+
}
|
|
22
45
|
/**
|
|
23
46
|
* Zoekt de openstaande Engine-taak die dit artikel raakt, zodat het
|
|
24
|
-
* stock.changed-event dezelfde order_ref (en context) draagt.
|
|
47
|
+
* stock.changed-event dezelfde order_ref (en context) draagt. Taken op een
|
|
48
|
+
* gesloten buffer tellen niet mee.
|
|
25
49
|
*/
|
|
26
50
|
async function findOpenTaskFor(tradeItemKey) {
|
|
27
|
-
const { data } = await
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
51
|
+
const [{ data }, closed] = await Promise.all([
|
|
52
|
+
supabase
|
|
53
|
+
.from("logistics_tasks")
|
|
54
|
+
.select("order_ref,strategy_id,shadow,payload,buffer_ref")
|
|
55
|
+
.eq("owner_module", OWNER_MODULE)
|
|
56
|
+
.in("status", ["planned", "ready", "in_progress"])
|
|
57
|
+
.order("planned_start", { ascending: true }),
|
|
58
|
+
closedBufferRefs(),
|
|
59
|
+
]);
|
|
33
60
|
for (const task of (data ?? [])) {
|
|
61
|
+
if (task.buffer_ref && closed.has(task.buffer_ref))
|
|
62
|
+
continue;
|
|
34
63
|
const plan = parseStockReservePlan(task.payload);
|
|
35
64
|
if (plan.lines.some((l) => l.tradeItemKey === tradeItemKey)) {
|
|
36
65
|
return { task, loadCarriers: plan.loadCarriers };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logistics-tasks.server.d.ts","sourceRoot":"","sources":["../../src/lib/logistics-tasks.server.ts"],"names":[],"mappings":"AA0BA,eAAO,MAAM,SAAS,kBAAkB,CAAC;AACzC,eAAO,MAAM,YAAY,IAAI,CAAC;AAQ9B,qBAAa,kBAAmB,SAAQ,KAAK;IAC3C,QAAQ,CAAC,SAAS,QAAQ;CAC3B;AA0BD,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;CACpB;
|
|
1
|
+
{"version":3,"file":"logistics-tasks.server.d.ts","sourceRoot":"","sources":["../../src/lib/logistics-tasks.server.ts"],"names":[],"mappings":"AA0BA,eAAO,MAAM,SAAS,kBAAkB,CAAC;AACzC,eAAO,MAAM,YAAY,IAAI,CAAC;AAQ9B,qBAAa,kBAAmB,SAAQ,KAAK;IAC3C,QAAQ,CAAC,SAAS,QAAQ;CAC3B;AA0BD,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;CACpB;AAuGD,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,UAAU,CAAC,CA2K7D;AAgBD,wBAAgB,uBAAuB,IAAI,IAAI,CAiC9C"}
|
|
@@ -63,6 +63,17 @@ function appendAttempt(task, entry) {
|
|
|
63
63
|
* of er opnieuw geprobeerd wordt.
|
|
64
64
|
*/
|
|
65
65
|
async function executeTask(supabaseAdmin, task, connectionId) {
|
|
66
|
+
// Een gesloten of volledig afgehandelde buffer krijgt geen voorraad meer
|
|
67
|
+
// toegewezen; de taak wordt overgeslagen in plaats van uitgevoerd.
|
|
68
|
+
const { isBufferClosed } = await import("./logistics-buffer-events.server");
|
|
69
|
+
if (await isBufferClosed(supabaseAdmin, task.buffer_ref)) {
|
|
70
|
+
return {
|
|
71
|
+
applied: false,
|
|
72
|
+
skipped: true,
|
|
73
|
+
diff: [],
|
|
74
|
+
message: "Buffer is gesloten of afgehandeld; reservering overgeslagen.",
|
|
75
|
+
};
|
|
76
|
+
}
|
|
66
77
|
const plan = parseStockReservePlan(task.payload);
|
|
67
78
|
if (plan.lines.length === 0) {
|
|
68
79
|
return {
|
|
@@ -130,6 +141,18 @@ export async function runLogisticsTasks() {
|
|
|
130
141
|
durationMs: 0,
|
|
131
142
|
};
|
|
132
143
|
const { supabaseAdmin } = await import("../integrations/supabase/client.server");
|
|
144
|
+
// Eerst de seintjes over gesloten en losgekoppelde buffers verwerken, zodat
|
|
145
|
+
// taken op een dichte buffer daarna niet meer worden uitgevoerd.
|
|
146
|
+
try {
|
|
147
|
+
const { runBufferEvents } = await import("./logistics-buffer-events.server");
|
|
148
|
+
const buffers = await runBufferEvents();
|
|
149
|
+
if (buffers.events > 0) {
|
|
150
|
+
console.info(`[voorraad] bufferseintjes verwerkt: ${buffers.closed} gesloten, ${buffers.detached} losgekoppeld, ${buffers.recalculated} artikel(en) herberekend`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
catch (e) {
|
|
154
|
+
console.warn("[voorraad] bufferseintjes verwerken mislukt:", e?.message ?? e);
|
|
155
|
+
}
|
|
133
156
|
// Actieve koppeling is optioneel: zonder koppeling reserveren we op alle
|
|
134
157
|
// rijen van het artikel. Faalt de resolutie, dan blokkeren we niet.
|
|
135
158
|
let connectionId = null;
|
|
@@ -181,7 +204,7 @@ export async function runLogisticsTasks() {
|
|
|
181
204
|
await supabaseAdmin
|
|
182
205
|
.from("logistics_tasks")
|
|
183
206
|
.update({
|
|
184
|
-
status: "done",
|
|
207
|
+
status: result.skipped ? "skipped" : "done",
|
|
185
208
|
attempts: attempt,
|
|
186
209
|
attempt_log: log,
|
|
187
210
|
last_error: null,
|
|
@@ -200,9 +223,14 @@ export async function runLogisticsTasks() {
|
|
|
200
223
|
updated_at: nowIso,
|
|
201
224
|
})
|
|
202
225
|
.eq("id", task.id);
|
|
203
|
-
if (
|
|
204
|
-
summary.
|
|
205
|
-
|
|
226
|
+
if (result.skipped) {
|
|
227
|
+
summary.skipped++;
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
if (task.shadow)
|
|
231
|
+
summary.shadow++;
|
|
232
|
+
summary.done++;
|
|
233
|
+
}
|
|
206
234
|
}
|
|
207
235
|
catch (e) {
|
|
208
236
|
const message = errorMessage(e);
|
|
@@ -289,9 +317,13 @@ export function maybeTickLogisticsTasks() {
|
|
|
289
317
|
.or(`next_attempt_at.is.null,next_attempt_at.lte.${new Date().toISOString()}`);
|
|
290
318
|
if (error)
|
|
291
319
|
throw new Error(error.message);
|
|
292
|
-
if (
|
|
320
|
+
if (count) {
|
|
321
|
+
await runLogisticsTasks(); // verwerkt zelf ook de bufferseintjes
|
|
293
322
|
return;
|
|
294
|
-
|
|
323
|
+
}
|
|
324
|
+
// Geen openstaande taken, maar er kunnen wel nieuwe bufferseintjes zijn.
|
|
325
|
+
const { runBufferEvents } = await import("./logistics-buffer-events.server");
|
|
326
|
+
await runBufferEvents();
|
|
295
327
|
}
|
|
296
328
|
catch (e) {
|
|
297
329
|
console.warn("[voorraad] achtergrond-tick mislukt:", e?.message ?? e);
|