@flowselections/logistiek-engine 1.0.8 → 1.0.9

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 (29) hide show
  1. package/README.md +24 -24
  2. package/dist-lib/hooks/useRecalcJob.js +1 -1
  3. package/dist-lib/integrations/supabase/auth-attacher.js +1 -1
  4. package/dist-lib/integrations/supabase/auth-middleware.d.ts +1 -12
  5. package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
  6. package/dist-lib/integrations/supabase/client.d.ts +158 -1515
  7. package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
  8. package/dist-lib/integrations/supabase/client.server.d.ts +1 -12
  9. package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
  10. package/dist-lib/integrations/supabase/moduleClient.d.ts +21640 -0
  11. package/dist-lib/integrations/supabase/moduleClient.d.ts.map +1 -0
  12. package/dist-lib/integrations/supabase/moduleClient.js +31 -0
  13. package/dist-lib/integrations/supabase/types.d.ts +2 -13
  14. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  15. package/dist-lib/integrations/supabase/types.js +3 -0
  16. package/dist-lib/lib/floridayDocuments.functions.d.ts +3 -36
  17. package/dist-lib/lib/floridayDocuments.functions.d.ts.map +1 -1
  18. package/dist-lib/lib/floridayPush.functions.d.ts +1 -12
  19. package/dist-lib/lib/floridayPush.functions.d.ts.map +1 -1
  20. package/dist-lib/lib/loadCarrierPlanning.functions.d.ts +35 -65
  21. package/dist-lib/lib/loadCarrierPlanning.functions.d.ts.map +1 -1
  22. package/dist-lib/lib/logisticsRecalcJob.functions.d.ts +2 -24
  23. package/dist-lib/lib/logisticsRecalcJob.functions.d.ts.map +1 -1
  24. package/dist-lib/lib/logisticsRecalculate.functions.d.ts +1 -12
  25. package/dist-lib/lib/logisticsRecalculate.functions.d.ts.map +1 -1
  26. package/dist-lib/lib/modulePrompts.d.ts +0 -1
  27. package/dist-lib/lib/modulePrompts.d.ts.map +1 -1
  28. package/dist-lib/lib/modulePrompts.js +970 -960
  29. package/package.json +1 -1
@@ -7,147 +7,157 @@
7
7
  // staan en stuur je alleen het nieuwe stuk door.
8
8
  // ============================================================================
9
9
  import { LOGISTICS_MODULES } from "./logisticsTasks";
10
- export const LOGISTICS_ENGINE_PROJECT_REF = "wieomvgjyxoerzndxnla";
11
10
  function sharedBlock(moduleKey) {
12
11
  const key = moduleKey === "<modulesleutel>" ? "<modulesleutel>" : moduleKey;
13
- return `Alle communicatie met de Logistics Engine loopt uitsluitend via de gedeelde
14
- Supabase-database. Je leest en schrijft gewoon tabellen met de bestaande
15
- Supabase-client van deze module (ingelogde gebruiker, RLS). Geen API-key,
16
- geen header, geen secret, geen edge function.
17
-
18
- Supabase project ref: ${LOGISTICS_ENGINE_PROJECT_REF}
19
- Client: import { supabase } from "../integrations/supabase/client"
20
-
21
- De Engine beslist alles over buffering, belading, picken, controle, printen en
22
- transport. Deze module voert alleen nog taken uit en beslist zelf niets meer.
23
-
24
- --- Gebeurtenis melden: RPC logistics_emit_event (idempotent) ---
25
- const { data } = await supabase.rpc("logistics_emit_event", {
26
- p_source_module: "${key}",
27
- p_event_type: "order.received", // of order.changed, stock.changed
28
- p_order_ref: "SO-12345",
29
- p_payload: {
30
- checkinAt: "2026-08-01T10:00:00Z",
31
- pickupAt: "2026-08-01T14:00:00Z",
32
- customerRef: "CUST-1",
33
- deliveryLocationRef: "LOC-1",
34
- bufferRef: null,
35
- loadCarriers: [{ type: "trolley", count: 3 }],
36
- ruleVersion: null, // optioneel: plan op een vastgelegde regelversie
37
- },
38
- p_strategy_id: null, // uuid van logistics_strategies indien bekend
39
- p_shadow: true,
40
- p_idempotency_key: "${key}:SO-12345:order.received:v1",
41
- });
42
- // data = { eventId, planRunId, status, duplicate }
43
- Geef ALTIJD een p_idempotency_key mee die uniek is per gebeurtenis. Stuur je
44
- dezelfde melding twee keer (retry, dubbele webhook), dan plant de Engine maar
45
- één keer en krijg je duplicate: true terug.
46
- De Engine pakt de rij direct op (status pending -> processed), plant de keten en
47
- zet plan_run_id en error terug op dezelfde rij.
48
-
49
- --- Taken ophalen: SELECT op public.logistics_tasks ---
50
- const { data } = await supabase
51
- .from("logistics_tasks")
52
- .select("*")
53
- .eq("owner_module", "${key}")
54
- .eq("status", "planned")
55
- .order("planned_start", { ascending: true });
56
- Kolommen: order_ref, buffer_ref, task_type, sequence, depends_on,
57
- planned_start, planned_end, due_at, status, shadow, payload, result,
58
- rule_version, claimed_by, claimed_at, error.
59
-
60
- Realtime kan gewoon met supabase.channel() op logistics_tasks (in useEffect,
61
- met removeChannel in de cleanup).
62
-
63
- --- Taak claimen: ALTIJD via RPC, nooit met een eigen UPDATE ---
64
- const { data: taak } = await supabase.rpc("logistics_claim_task", {
65
- p_task_id: taskId, p_worker: "station-3",
66
- });
67
- if (!taak) { /* al door iemand anders geclaimd */ }
68
-
69
- // of automatisch de eerstvolgende taak van deze module:
70
- const { data: volgende } = await supabase.rpc("logistics_claim_next_task", {
71
- p_owner_module: "${key}", p_worker: "station-3",
72
- });
73
-
74
- --- Taak afmelden: ook via RPC ---
75
- await supabase.rpc("logistics_complete_task", { p_task_id: taskId, p_result: { ... } });
76
- await supabase.rpc("logistics_fail_task", { p_task_id: taskId, p_error: "reden" });
77
-
78
- --- Order annuleren ---
79
- await supabase.rpc("logistics_emit_event", {
80
- p_source_module: "${key}",
81
- p_event_type: "order.cancelled",
82
- p_order_ref: "SO-12345",
83
- p_payload: { reason: "klant belde af" },
84
- p_idempotency_key: "${key}:SO-12345:cancel:v1",
85
- });
86
- Al afgeronde taken blijven staan. Annuleer nooit zelf met een eigen UPDATE.
87
-
88
- --- Order splitsen ---
89
- await supabase.rpc("logistics_emit_event", {
90
- p_source_module: "${key}",
91
- p_event_type: "order.split",
92
- p_order_ref: "SO-12345",
93
- p_payload: {
94
- checkinAt: "...", pickupAt: "...",
95
- children: [
96
- { orderRef: "SO-12345-A", loadCarriers: [{ type: "trolley", count: 1 }] },
97
- { orderRef: "SO-12345-B", loadCarriers: [{ type: "trolley", count: 2 }] },
98
- ],
99
- },
100
- p_idempotency_key: "${key}:SO-12345:split:v1",
101
- });
102
-
103
- --- Spoed / prioriteit ---
104
- await supabase.rpc("logistics_set_order_priority", {
105
- p_order_ref: "SO-12345", p_priority: 100,
106
- });
107
-
108
- --- Instellingen inzien (nooit zelf beslissen) ---
109
- public.logistics_strategies de logistieke strategieën
110
- public.logistics_rule_configs settings (jsonb) per strategie + current_version
111
- public.logistics_plan_runs momentopname van de instellingen per planronde
112
- Alleen lezen, en alleen om te tonen.
113
-
114
- Ladingdragers: trolley, danish_container, euro_rolcontainer, pallet, euro_pallet, loose
115
- Taakstatussen: planned, claimed, in_progress, done, skipped, cancelled,
116
- superseded, failed
117
-
118
- Eigenaarschap: meldingen, planruns en taken hebben een kolom user_id die de
119
- database automatisch vult met het ingelogde account.
120
-
121
- Start in schaduwmodus (shadow: true): eigen logica blijft draaien, Engine-taken
122
- worden alleen getoond en vergeleken. Elke taak draagt rule_version — een lopende
12
+ return `Alle communicatie met de Logistics Engine loopt uitsluitend via de gedeelde
13
+ Supabase-database. Je leest en schrijft gewoon tabellen met de bestaande
14
+ Supabase-client van deze module (ingelogde gebruiker, RLS). Geen API-key,
15
+ geen header, geen secret, geen edge function.
16
+
17
+ Verbinding — verplicht en fail-closed:
18
+ - Gebruik de Supabase-client van je EIGEN module, lazy geïnstantieerd en
19
+ uitsluitend gevoed door import.meta.env.VITE_SUPABASE_URL en
20
+ import.meta.env.VITE_SUPABASE_PUBLISHABLE_KEY. Ontbreekt er een waarde, gooi
21
+ dan een harde fout; raad nooit een verbinding.
22
+ - Hardcodeer NOOIT een Supabase project-ref, URL of anon-key — ook niet als
23
+ fallback, voorbeeld of default. De shell bepaalt via env welke database geldt.
24
+ - Importeer NOOIT uit "@/integrations/supabase/client": dat bestand wordt door
25
+ de Supabase-integratie automatisch gegenereerd en overschreven met een
26
+ vastgezette project-ref, wat in een andere shell naar de verkeerde database
27
+ schrijft. Zet je eigen client in bijvoorbeeld
28
+ "@/integrations/supabase/moduleClient" en sluit het gegenereerde bestand uit
29
+ van je library-build.
30
+
31
+ De Engine beslist alles over buffering, belading, picken, controle, printen en
32
+ transport. Deze module voert alleen nog taken uit en beslist zelf niets meer.
33
+
34
+ --- Gebeurtenis melden: RPC logistics_emit_event (idempotent) ---
35
+ const { data } = await supabase.rpc("logistics_emit_event", {
36
+ p_source_module: "${key}",
37
+ p_event_type: "order.received", // of order.changed, stock.changed
38
+ p_order_ref: "SO-12345",
39
+ p_payload: {
40
+ checkinAt: "2026-08-01T10:00:00Z",
41
+ pickupAt: "2026-08-01T14:00:00Z",
42
+ customerRef: "CUST-1",
43
+ deliveryLocationRef: "LOC-1",
44
+ bufferRef: null,
45
+ loadCarriers: [{ type: "trolley", count: 3 }],
46
+ ruleVersion: null, // optioneel: plan op een vastgelegde regelversie
47
+ },
48
+ p_strategy_id: null, // uuid van logistics_strategies indien bekend
49
+ p_shadow: true,
50
+ p_idempotency_key: "${key}:SO-12345:order.received:v1",
51
+ });
52
+ // data = { eventId, planRunId, status, duplicate }
53
+ Geef ALTIJD een p_idempotency_key mee die uniek is per gebeurtenis. Stuur je
54
+ dezelfde melding twee keer (retry, dubbele webhook), dan plant de Engine maar
55
+ één keer en krijg je duplicate: true terug.
56
+ De Engine pakt de rij direct op (status pending -> processed), plant de keten en
57
+ zet plan_run_id en error terug op dezelfde rij.
58
+
59
+ --- Taken ophalen: SELECT op public.logistics_tasks ---
60
+ const { data } = await supabase
61
+ .from("logistics_tasks")
62
+ .select("*")
63
+ .eq("owner_module", "${key}")
64
+ .eq("status", "planned")
65
+ .order("planned_start", { ascending: true });
66
+ Kolommen: order_ref, buffer_ref, task_type, sequence, depends_on,
67
+ planned_start, planned_end, due_at, status, shadow, payload, result,
68
+ rule_version, claimed_by, claimed_at, error.
69
+
70
+ Realtime kan gewoon met supabase.channel() op logistics_tasks (in useEffect,
71
+ met removeChannel in de cleanup).
72
+
73
+ --- Taak claimen: ALTIJD via RPC, nooit met een eigen UPDATE ---
74
+ const { data: taak } = await supabase.rpc("logistics_claim_task", {
75
+ p_task_id: taskId, p_worker: "station-3",
76
+ });
77
+ if (!taak) { /* al door iemand anders geclaimd */ }
78
+
79
+ // of automatisch de eerstvolgende taak van deze module:
80
+ const { data: volgende } = await supabase.rpc("logistics_claim_next_task", {
81
+ p_owner_module: "${key}", p_worker: "station-3",
82
+ });
83
+
84
+ --- Taak afmelden: ook via RPC ---
85
+ await supabase.rpc("logistics_complete_task", { p_task_id: taskId, p_result: { ... } });
86
+ await supabase.rpc("logistics_fail_task", { p_task_id: taskId, p_error: "reden" });
87
+
88
+ --- Order annuleren ---
89
+ await supabase.rpc("logistics_emit_event", {
90
+ p_source_module: "${key}",
91
+ p_event_type: "order.cancelled",
92
+ p_order_ref: "SO-12345",
93
+ p_payload: { reason: "klant belde af" },
94
+ p_idempotency_key: "${key}:SO-12345:cancel:v1",
95
+ });
96
+ Al afgeronde taken blijven staan. Annuleer nooit zelf met een eigen UPDATE.
97
+
98
+ --- Order splitsen ---
99
+ await supabase.rpc("logistics_emit_event", {
100
+ p_source_module: "${key}",
101
+ p_event_type: "order.split",
102
+ p_order_ref: "SO-12345",
103
+ p_payload: {
104
+ checkinAt: "...", pickupAt: "...",
105
+ children: [
106
+ { orderRef: "SO-12345-A", loadCarriers: [{ type: "trolley", count: 1 }] },
107
+ { orderRef: "SO-12345-B", loadCarriers: [{ type: "trolley", count: 2 }] },
108
+ ],
109
+ },
110
+ p_idempotency_key: "${key}:SO-12345:split:v1",
111
+ });
112
+
113
+ --- Spoed / prioriteit ---
114
+ await supabase.rpc("logistics_set_order_priority", {
115
+ p_order_ref: "SO-12345", p_priority: 100,
116
+ });
117
+
118
+ --- Instellingen inzien (nooit zelf beslissen) ---
119
+ public.logistics_strategies de logistieke strategieën
120
+ public.logistics_rule_configs settings (jsonb) per strategie + current_version
121
+ public.logistics_plan_runs momentopname van de instellingen per planronde
122
+ Alleen lezen, en alleen om te tonen.
123
+
124
+ Ladingdragers: trolley, danish_container, euro_rolcontainer, pallet, euro_pallet, loose
125
+ Taakstatussen: planned, claimed, in_progress, done, skipped, cancelled,
126
+ superseded, failed
127
+
128
+ Eigenaarschap: meldingen, planruns en taken hebben een kolom user_id die de
129
+ database automatisch vult met het ingelogde account.
130
+
131
+ Start in schaduwmodus (shadow: true): eigen logica blijft draaien, Engine-taken
132
+ worden alleen getoond en vergeleken. Elke taak draagt rule_version — een lopende
123
133
  order verandert nooit stilletjes mee als de Rule Engine wijzigt.`;
124
134
  }
125
- const STOCK_CONTRACT = `De Engine bepaalt of een bestelling met producten die niet op voorraad zijn
126
- überhaupt aangemaakt mag worden. Die keuze staat in de Rule Engine
127
- (Order verwerking -> "Bestellingen uitleveren wanneer een product niet op
128
- voorraad is"). Beslis dit NOOIT zelf.
129
-
130
- Roep vóór het opslaan van een nieuwe order (of het wijzigen van de aantallen)
131
- altijd deze RPC aan:
132
-
133
- const { data: check } = await supabase.rpc("logistics_check_order_allowed", {
134
- p_lines: [
135
- { productId: "<uuid of artikelnummer of barcode of floriday trade item id>", quantity: 12 },
136
- ],
137
- p_strategy_id: null, // uuid van logistics_strategies indien bekend
138
- });
139
- // check = { allowed, allowBackorder, shortages: [{ productId, product, wanted, available, missing }], reason }
140
-
141
- Regels:
142
- - allowed === false -> blokkeer het aanmaken/opslaan van de order volledig en
143
- toon per regel uit check.shortages:
144
- "<product>: besteld <wanted>, beschikbaar <available>, tekort <missing>".
145
- Geen omweg, geen "toch doorgaan"-knop. reason = "insufficient_stock".
146
- - allowed === true en shortages niet leeg -> order mag wél aangemaakt worden
147
- (uitleveren zonder voorraad staat aan); de Engine maakt zelf de melding en/of
148
- e-mail aan. Toon de tekorten alleen ter informatie.
149
- - Alleen de instelling opvragen (bv. live waarschuwing tijdens typen):
150
- const { data: mag } = await supabase.rpc("logistics_backorder_allowed", { p_strategy_id: null });
135
+ const STOCK_CONTRACT = `De Engine bepaalt of een bestelling met producten die niet op voorraad zijn
136
+ überhaupt aangemaakt mag worden. Die keuze staat in de Rule Engine
137
+ (Order verwerking -> "Bestellingen uitleveren wanneer een product niet op
138
+ voorraad is"). Beslis dit NOOIT zelf.
139
+
140
+ Roep vóór het opslaan van een nieuwe order (of het wijzigen van de aantallen)
141
+ altijd deze RPC aan:
142
+
143
+ const { data: check } = await supabase.rpc("logistics_check_order_allowed", {
144
+ p_lines: [
145
+ { productId: "<uuid of artikelnummer of barcode of floriday trade item id>", quantity: 12 },
146
+ ],
147
+ p_strategy_id: null, // uuid van logistics_strategies indien bekend
148
+ });
149
+ // check = { allowed, allowBackorder, shortages: [{ productId, product, wanted, available, missing }], reason }
150
+
151
+ Regels:
152
+ - allowed === false -> blokkeer het aanmaken/opslaan van de order volledig en
153
+ toon per regel uit check.shortages:
154
+ "<product>: besteld <wanted>, beschikbaar <available>, tekort <missing>".
155
+ Geen omweg, geen "toch doorgaan"-knop. reason = "insufficient_stock".
156
+ - allowed === true en shortages niet leeg -> order mag wél aangemaakt worden
157
+ (uitleveren zonder voorraad staat aan); de Engine maakt zelf de melding en/of
158
+ e-mail aan. Toon de tekorten alleen ter informatie.
159
+ - Alleen de instelling opvragen (bv. live waarschuwing tijdens typen):
160
+ const { data: mag } = await supabase.rpc("logistics_backorder_allowed", { p_strategy_id: null });
151
161
  - Cache het antwoord niet: de instelling kan op elk moment wijzigen.`;
152
162
  const MODULES = {
153
163
  verkoop: {
@@ -160,16 +170,16 @@ const MODULES = {
160
170
  date: "2026-07-24",
161
171
  summary: "Orderbufferlogica eruit, events en taken via Supabase.",
162
172
  withSharedContract: true,
163
- body: `Haal weg: alle eigen orderbufferlogica, eigen samenvoegregels en alle
164
- fetch-calls naar een Engine-URL plus eventuele secrets.
165
-
166
- Bouw:
167
- - Bij nieuwe/gewijzigde order een gebeurtenis melden via logistics_emit_event
168
- (event_type order.received of order.changed).
169
- - Taken van type buffer.close lezen uit logistics_tasks (owner_module "verkoop"),
170
- uitvoeren en afmelden via logistics_claim_task + logistics_complete_task.
171
- - Buffer-samenstelling tonen zoals die in payload staat (matchCriteria, release).
172
- - release "manual" -> knop Vrijgeven. release "none" -> alleen tonen, niets doen.
173
+ body: `Haal weg: alle eigen orderbufferlogica, eigen samenvoegregels en alle
174
+ fetch-calls naar een Engine-URL plus eventuele secrets.
175
+
176
+ Bouw:
177
+ - Bij nieuwe/gewijzigde order een gebeurtenis melden via logistics_emit_event
178
+ (event_type order.received of order.changed).
179
+ - Taken van type buffer.close lezen uit logistics_tasks (owner_module "verkoop"),
180
+ uitvoeren en afmelden via logistics_claim_task + logistics_complete_task.
181
+ - Buffer-samenstelling tonen zoals die in payload staat (matchCriteria, release).
182
+ - release "manual" -> knop Vrijgeven. release "none" -> alleen tonen, niets doen.
173
183
  - In schaduwmodus: Engine-buffersluittijd naast de eigen berekening tonen.`,
174
184
  },
175
185
  {
@@ -177,16 +187,16 @@ Bouw:
177
187
  title: "Voorraadcontrole vóór het aanmaken van een order",
178
188
  date: "2026-07-31",
179
189
  summary: "Nieuwe eis bovenop prompt 1: order blokkeren als een product niet op voorraad is.",
180
- body: `AANVULLING op koppelprompt 1 (die blijft gewoon geldig, niets terugdraaien).
181
-
182
- Nieuwe verplichting: vóór het aanmaken of wijzigen van een order eerst de
183
- voorraadcontrole van de Engine aanroepen.
184
-
185
- ${STOCK_CONTRACT}
186
-
187
- In de Verkoop-module concreet:
188
- - Blokkeer de opslaan-knop van de order zolang allowed false is.
189
- - Toon de tekorten in het orderscherm, per regel bij het product.
190
+ body: `AANVULLING op koppelprompt 1 (die blijft gewoon geldig, niets terugdraaien).
191
+
192
+ Nieuwe verplichting: vóór het aanmaken of wijzigen van een order eerst de
193
+ voorraadcontrole van de Engine aanroepen.
194
+
195
+ ${STOCK_CONTRACT}
196
+
197
+ In de Verkoop-module concreet:
198
+ - Blokkeer de opslaan-knop van de order zolang allowed false is.
199
+ - Toon de tekorten in het orderscherm, per regel bij het product.
190
200
  - Doe de controle ook opnieuw bij het wijzigen van aantallen of regels.`,
191
201
  },
192
202
  {
@@ -194,51 +204,51 @@ In de Verkoop-module concreet:
194
204
  title: "Bufferen mag alleen als de Engine dat zegt",
195
205
  date: "2026-07-31",
196
206
  summary: "Nieuwe eis bovenop prompt 1 en 2: geen eigen bufferbesluit meer, altijd eerst logistics_buffer_decision aanroepen.",
197
- body: `AANVULLING op koppelprompt 1 en 2 (die blijven gewoon geldig).
198
-
199
- Probleem: er stonden orders in een orderbuffer terwijl bufferen in de Rule Engine
200
- UIT stond. De Verkoop-module beslist dat dus nog zelf. Dat moet eruit.
201
-
202
- Haal weg: elke eigen regel, instelling of code die bepaalt of een order in een
203
- orderbuffer / gecombineerde order terechtkomt, inclusief automatisch aanmaken of
204
- toewijzen van een buffer bij het opslaan van een order.
205
-
206
- Nieuwe verplichting: vraag het per order aan de Engine.
207
-
208
- const { data: besluit } = await supabase.rpc("logistics_buffer_decision", {
209
- p_order: {
210
- orderRef: "SO-12345",
211
- checkinAt: "2026-08-01T10:00:00Z",
212
- customerRef: "CUST-1",
213
- floridayCustomerRef: "FLOR-1",
214
- bvoCustomerRef: "BVO-1",
215
- deliveryLocationRef: "LOC-1",
216
- deliveryDate: "2026-08-01",
217
- carrierRef: "TRANS-1",
218
- loadCarrierType: "trolley",
219
- orderType: "standaard",
220
- },
221
- p_strategy_id: null, // uuid van logistics_strategies indien bekend
222
- });
223
- // besluit = { buffer, reason, bufferRef, bufferKey, matchCriteria,
224
- // closeBeforeMinutes, bufferCloseAt, release }
225
-
226
- Regels:
227
- - besluit.buffer === false (reason "buffering_disabled") -> de order gaat LOS door.
228
- Geen buffer aanmaken, geen bestaande buffer toewijzen, geen buffer tonen.
229
- - besluit.buffer === true -> gebruik exact besluit.bufferRef als buffersleutel.
230
- Orders met dezelfde bufferRef horen in dezelfde gecombineerde order. Bepaal die
231
- groepering nooit zelf op eigen criteria.
232
- - Toon buffersluittijd uit besluit.bufferCloseAt, en de vrijgave uit
233
- besluit.release ("auto" | "manual" | "none"). Bij "manual" een knop Vrijgeven,
234
- bij "none" alleen tonen.
235
- - Cache het antwoord niet: de instelling kan op elk moment wijzigen. Vraag het
236
- opnieuw bij elke wijziging van de order (aanmeldtijd, klant, locatie, aantallen).
237
- - Stuur bufferRef mee in de payload van logistics_emit_event (veld bufferRef),
238
- zodat de Engine en de Verkoop-module dezelfde buffer bedoelen.
239
-
240
- Eenmalige opschoning: haal bij bestaande, nog niet afgehandelde orders de
241
- bufferkoppeling weg zodra logistics_buffer_decision voor die order buffer=false
207
+ body: `AANVULLING op koppelprompt 1 en 2 (die blijven gewoon geldig).
208
+
209
+ Probleem: er stonden orders in een orderbuffer terwijl bufferen in de Rule Engine
210
+ UIT stond. De Verkoop-module beslist dat dus nog zelf. Dat moet eruit.
211
+
212
+ Haal weg: elke eigen regel, instelling of code die bepaalt of een order in een
213
+ orderbuffer / gecombineerde order terechtkomt, inclusief automatisch aanmaken of
214
+ toewijzen van een buffer bij het opslaan van een order.
215
+
216
+ Nieuwe verplichting: vraag het per order aan de Engine.
217
+
218
+ const { data: besluit } = await supabase.rpc("logistics_buffer_decision", {
219
+ p_order: {
220
+ orderRef: "SO-12345",
221
+ checkinAt: "2026-08-01T10:00:00Z",
222
+ customerRef: "CUST-1",
223
+ floridayCustomerRef: "FLOR-1",
224
+ bvoCustomerRef: "BVO-1",
225
+ deliveryLocationRef: "LOC-1",
226
+ deliveryDate: "2026-08-01",
227
+ carrierRef: "TRANS-1",
228
+ loadCarrierType: "trolley",
229
+ orderType: "standaard",
230
+ },
231
+ p_strategy_id: null, // uuid van logistics_strategies indien bekend
232
+ });
233
+ // besluit = { buffer, reason, bufferRef, bufferKey, matchCriteria,
234
+ // closeBeforeMinutes, bufferCloseAt, release }
235
+
236
+ Regels:
237
+ - besluit.buffer === false (reason "buffering_disabled") -> de order gaat LOS door.
238
+ Geen buffer aanmaken, geen bestaande buffer toewijzen, geen buffer tonen.
239
+ - besluit.buffer === true -> gebruik exact besluit.bufferRef als buffersleutel.
240
+ Orders met dezelfde bufferRef horen in dezelfde gecombineerde order. Bepaal die
241
+ groepering nooit zelf op eigen criteria.
242
+ - Toon buffersluittijd uit besluit.bufferCloseAt, en de vrijgave uit
243
+ besluit.release ("auto" | "manual" | "none"). Bij "manual" een knop Vrijgeven,
244
+ bij "none" alleen tonen.
245
+ - Cache het antwoord niet: de instelling kan op elk moment wijzigen. Vraag het
246
+ opnieuw bij elke wijziging van de order (aanmeldtijd, klant, locatie, aantallen).
247
+ - Stuur bufferRef mee in de payload van logistics_emit_event (veld bufferRef),
248
+ zodat de Engine en de Verkoop-module dezelfde buffer bedoelen.
249
+
250
+ Eenmalige opschoning: haal bij bestaande, nog niet afgehandelde orders de
251
+ bufferkoppeling weg zodra logistics_buffer_decision voor die order buffer=false
242
252
  teruggeeft. Al verwerkte/verzonden orders laat je met rust.`,
243
253
  },
244
254
  {
@@ -246,33 +256,33 @@ teruggeeft. Al verwerkte/verzonden orders laat je met rust.`,
246
256
  title: "Correctie bufferbesluit: nieuwe velden en uitleg",
247
257
  date: "2026-08-02",
248
258
  summary: "Nieuwe eis bovenop prompt 3: logistics_buffer_decision is gecorrigeerd, stuur alle matchvelden mee en gebruik de uitleg (trace).",
249
- body: `AANVULLING op koppelprompt 1, 2 en 3 (die blijven gewoon geldig).
250
-
251
- Wat er is gewijzigd aan de Engine-kant:
252
- - logistics_buffer_decision las de Rule Engine-instellingen fout uit en negeerde de
253
- aangevinkte criteria. Dat is opgelost: de gekozen criteria worden nu echt vergeleken.
254
- - De buffersleutel bevat niet langer het exacte sluitmoment. Orders met dezelfde
255
- gegevens maar een iets andere aanmeldtijd komen nu wél in dezelfde buffer.
256
- - De functie heeft een derde parameter p_user_id (laat die weg of op null staan).
257
- - Het antwoord bevat nu ook een veld trace: per criterium waarom het klopt of niet.
258
-
259
- Wat de Verkoop-module moet doen:
260
- 1. Stuur bij elke aanroep ALLE velden mee die als criterium gekozen kunnen zijn.
261
- Ontbreekt een waarde van een aangevinkt criterium, dan combineert de Engine de
262
- order bewust NIET (reason "buffering_enabled_missing_data").
263
- Verplichte velden in p_order:
264
- orderRef, checkinAt, deliveryDate, customerRef, floridayCustomerRef,
265
- bvoCustomerRef, deliveryLocationRef, carrierRef, loadCarrierType, orderType
266
- deliveryLocationRef moet een stabiele sleutel zijn van het afleveradres
267
- (locatie-id of genormaliseerd adres), niet een vrije tekst die per order verschilt.
268
- 2. Toon in het orderscherm de reden uit besluit.reason en, bij twijfel, de regels uit
269
- besluit.trace ("waarom zit deze order niet bij die andere order?").
270
- 3. Vraag het besluit opnieuw op zodra een van bovenstaande velden wijzigt, en
271
- verplaats de order dan naar de nieuwe bufferRef.
272
- 4. Bepaal nooit zelf of twee orders bij elkaar horen; vergelijk alleen bufferRef.
273
-
274
- Eenmalige opschoning: haal voor alle nog niet verwerkte orders opnieuw het besluit op
275
- en werk de bufferkoppeling bij. Orders die volgens de Engine samen horen, moeten daarna
259
+ body: `AANVULLING op koppelprompt 1, 2 en 3 (die blijven gewoon geldig).
260
+
261
+ Wat er is gewijzigd aan de Engine-kant:
262
+ - logistics_buffer_decision las de Rule Engine-instellingen fout uit en negeerde de
263
+ aangevinkte criteria. Dat is opgelost: de gekozen criteria worden nu echt vergeleken.
264
+ - De buffersleutel bevat niet langer het exacte sluitmoment. Orders met dezelfde
265
+ gegevens maar een iets andere aanmeldtijd komen nu wél in dezelfde buffer.
266
+ - De functie heeft een derde parameter p_user_id (laat die weg of op null staan).
267
+ - Het antwoord bevat nu ook een veld trace: per criterium waarom het klopt of niet.
268
+
269
+ Wat de Verkoop-module moet doen:
270
+ 1. Stuur bij elke aanroep ALLE velden mee die als criterium gekozen kunnen zijn.
271
+ Ontbreekt een waarde van een aangevinkt criterium, dan combineert de Engine de
272
+ order bewust NIET (reason "buffering_enabled_missing_data").
273
+ Verplichte velden in p_order:
274
+ orderRef, checkinAt, deliveryDate, customerRef, floridayCustomerRef,
275
+ bvoCustomerRef, deliveryLocationRef, carrierRef, loadCarrierType, orderType
276
+ deliveryLocationRef moet een stabiele sleutel zijn van het afleveradres
277
+ (locatie-id of genormaliseerd adres), niet een vrije tekst die per order verschilt.
278
+ 2. Toon in het orderscherm de reden uit besluit.reason en, bij twijfel, de regels uit
279
+ besluit.trace ("waarom zit deze order niet bij die andere order?").
280
+ 3. Vraag het besluit opnieuw op zodra een van bovenstaande velden wijzigt, en
281
+ verplaats de order dan naar de nieuwe bufferRef.
282
+ 4. Bepaal nooit zelf of twee orders bij elkaar horen; vergelijk alleen bufferRef.
283
+
284
+ Eenmalige opschoning: haal voor alle nog niet verwerkte orders opnieuw het besluit op
285
+ en werk de bufferkoppeling bij. Orders die volgens de Engine samen horen, moeten daarna
276
286
  dezelfde bufferRef hebben.`,
277
287
  },
278
288
  {
@@ -280,44 +290,44 @@ dezelfde bufferRef hebben.`,
280
290
  title: "Alleen ladingdragers tonen die de Engine toestaat",
281
291
  date: "2026-08-03",
282
292
  summary: "Nieuwe eis bovenop prompt 1 t/m 4: uitgeschakelde ladingdragers mogen niet meer kiesbaar zijn.",
283
- body: `AANVULLING op koppelprompt 1, 2, 3 en 4 (die blijven gewoon geldig).
284
-
285
- In de Rule Engine (stap Beladingindeling) staat welke ladingdragers zijn toegestaan.
286
- Staat een ladingdrager uit, dan mag die in de Verkoop-module nergens meer te kiezen
287
- zijn.
288
-
289
- Haal weg: elke eigen, vaste lijst met ladingdragers / verzendmethodes in de code,
290
- in constanten, in dropdowns of in filters.
291
-
292
- Nieuwe verplichting: haal de lijst op bij de Engine.
293
-
294
- const { data: dragers } = await supabase.rpc("logistics_allowed_load_carriers", {
295
- p_strategy_id: null, // uuid van logistics_strategies indien bekend
296
- });
297
- // dragers = {
298
- // allowed: ["trolley","pallet"],
299
- // default: "trolley",
300
- // carriers: [{ code, label, enabled, isDefault }, ...]
301
- // }
302
-
303
- Codes en namen (gebruik exact deze codes):
304
- trolley Stapelwagen
305
- danish_container Deense container
306
- euro_rolcontainer Euro-rolcontainer
307
- pallet Pallet
308
- euro_pallet Euro-pallet
309
- loose Geen ladingdrager
310
-
311
- Regels:
312
- - Toon in elk keuzeveld (order, orderregel, verzendmethode, filter) alleen de
313
- dragers met enabled === true. Uitgeschakelde dragers niet grijs tonen: weglaten.
314
- - Voorselectie = dragers.default.
315
- - Blokkeer opslaan als een order een ladingdrager heeft die niet in allowed staat,
316
- met melding: "Deze ladingdrager is uitgeschakeld in de Logistics Engine."
317
- - Cache de lijst niet langer dan één schermsessie; de instelling kan wijzigen.
318
- - Bestaande, nog niet verwerkte orders met een inmiddels uitgeschakelde drager:
319
- markeer die in het overzicht zodat een medewerker een andere drager kiest.
320
- - Stuur loadCarrierType in logistics_emit_event en logistics_buffer_decision altijd
293
+ body: `AANVULLING op koppelprompt 1, 2, 3 en 4 (die blijven gewoon geldig).
294
+
295
+ In de Rule Engine (stap Beladingindeling) staat welke ladingdragers zijn toegestaan.
296
+ Staat een ladingdrager uit, dan mag die in de Verkoop-module nergens meer te kiezen
297
+ zijn.
298
+
299
+ Haal weg: elke eigen, vaste lijst met ladingdragers / verzendmethodes in de code,
300
+ in constanten, in dropdowns of in filters.
301
+
302
+ Nieuwe verplichting: haal de lijst op bij de Engine.
303
+
304
+ const { data: dragers } = await supabase.rpc("logistics_allowed_load_carriers", {
305
+ p_strategy_id: null, // uuid van logistics_strategies indien bekend
306
+ });
307
+ // dragers = {
308
+ // allowed: ["trolley","pallet"],
309
+ // default: "trolley",
310
+ // carriers: [{ code, label, enabled, isDefault }, ...]
311
+ // }
312
+
313
+ Codes en namen (gebruik exact deze codes):
314
+ trolley Stapelwagen
315
+ danish_container Deense container
316
+ euro_rolcontainer Euro-rolcontainer
317
+ pallet Pallet
318
+ euro_pallet Euro-pallet
319
+ loose Geen ladingdrager
320
+
321
+ Regels:
322
+ - Toon in elk keuzeveld (order, orderregel, verzendmethode, filter) alleen de
323
+ dragers met enabled === true. Uitgeschakelde dragers niet grijs tonen: weglaten.
324
+ - Voorselectie = dragers.default.
325
+ - Blokkeer opslaan als een order een ladingdrager heeft die niet in allowed staat,
326
+ met melding: "Deze ladingdrager is uitgeschakeld in de Logistics Engine."
327
+ - Cache de lijst niet langer dan één schermsessie; de instelling kan wijzigen.
328
+ - Bestaande, nog niet verwerkte orders met een inmiddels uitgeschakelde drager:
329
+ markeer die in het overzicht zodat een medewerker een andere drager kiest.
330
+ - Stuur loadCarrierType in logistics_emit_event en logistics_buffer_decision altijd
321
331
  met een van bovenstaande codes mee, nooit met een eigen naam of vrije tekst.`,
322
332
  },
323
333
  ],
@@ -332,15 +342,15 @@ Regels:
332
342
  date: "2026-07-24",
333
343
  summary: "Eigen beladings- en documentlogica eruit, taken uitvoeren.",
334
344
  withSharedContract: true,
335
- body: `Haal weg: eigen beladingindeling, eigen documentmomenten, eigen controle-instellingen.
336
-
337
- Consumeer deze taaktypes:
338
- - load.compose payload: loadCarriers, fillMode (generated | manual_scan)
339
- - control.execute payload: controlMode (paper), noticeAt
340
- - print.control_note payload: printItems (control_note | control_note_and_shipping)
341
- - print.shipping_note payload: moment (with_packing_slip | with_control | after_control | after_scan)
342
- - transport.dispatch
343
-
345
+ body: `Haal weg: eigen beladingindeling, eigen documentmomenten, eigen controle-instellingen.
346
+
347
+ Consumeer deze taaktypes:
348
+ - load.compose payload: loadCarriers, fillMode (generated | manual_scan)
349
+ - control.execute payload: controlMode (paper), noticeAt
350
+ - print.control_note payload: printItems (control_note | control_note_and_shipping)
351
+ - print.shipping_note payload: moment (with_packing_slip | with_control | after_control | after_scan)
352
+ - transport.dispatch
353
+
344
354
  Wat er geprint wordt en wanneer komt volledig uit de taak-payload. Geen eigen keuzes meer.`,
345
355
  },
346
356
  {
@@ -348,28 +358,28 @@ Wat er geprint wordt en wanneer komt volledig uit de taak-payload. Geen eigen ke
348
358
  title: "Documentvrijgave komt volledig van de Engine",
349
359
  date: "2026-08-03",
350
360
  summary: "Pakbon, stickers, verzendbrief en productiebon alleen nog via Engine-taken.",
351
- body: `Deze prompt vervangt niets uit prompt 1; hij komt er bovenop.
352
-
353
- Haal weg: elke eigen regel over wanneer een pakbon, sticker, verzendbrief of
354
- productiebon wordt vrijgegeven of geprint. De Logistics Engine beslist dit nu.
355
-
356
- Nieuwe taaktypes die je moet verwerken (tabel logistics_tasks):
357
- - print.packing_slip payload: release (with_pick_release | hours_before_pick),
358
- pickStationId, pickStationTypeId, requiresApproval
359
- - print.sticker payload: idem als print.packing_slip
360
- - print.shipping_note payload: release (after_checkin | after_scan |
361
- with_packing_slip | manual_approval), pickStationId,
362
- pickStationTypeId, requiresApproval
363
- - print.production_note payload: release (on_stock_upgrade |
364
- manual_approval_after_upgrade), trigger, pickStationId,
365
- pickStationTypeId, requiresApproval
366
-
367
- Regels:
368
- - planned_start van de taak is het vrijgavemoment. Nooit eerder printen.
369
- - pickStationId verwijst naar public.pick_stations.id; pickStationTypeId naar
370
- public.pick_station_types.id. Stuur het document uitsluitend naar dat station.
371
- - requiresApproval = true: het document blijft in de wachtrij tot iemand het op
372
- het pickstation goedkeurt. Meld de goedkeuring met logistics_complete_task.
361
+ body: `Deze prompt vervangt niets uit prompt 1; hij komt er bovenop.
362
+
363
+ Haal weg: elke eigen regel over wanneer een pakbon, sticker, verzendbrief of
364
+ productiebon wordt vrijgegeven of geprint. De Logistics Engine beslist dit nu.
365
+
366
+ Nieuwe taaktypes die je moet verwerken (tabel logistics_tasks):
367
+ - print.packing_slip payload: release (with_pick_release | hours_before_pick),
368
+ pickStationId, pickStationTypeId, requiresApproval
369
+ - print.sticker payload: idem als print.packing_slip
370
+ - print.shipping_note payload: release (after_checkin | after_scan |
371
+ with_packing_slip | manual_approval), pickStationId,
372
+ pickStationTypeId, requiresApproval
373
+ - print.production_note payload: release (on_stock_upgrade |
374
+ manual_approval_after_upgrade), trigger, pickStationId,
375
+ pickStationTypeId, requiresApproval
376
+
377
+ Regels:
378
+ - planned_start van de taak is het vrijgavemoment. Nooit eerder printen.
379
+ - pickStationId verwijst naar public.pick_stations.id; pickStationTypeId naar
380
+ public.pick_station_types.id. Stuur het document uitsluitend naar dat station.
381
+ - requiresApproval = true: het document blijft in de wachtrij tot iemand het op
382
+ het pickstation goedkeurt. Meld de goedkeuring met logistics_complete_task.
373
383
  - Is er geen taak voor een document, dan wordt dat document niet geprint.`,
374
384
  },
375
385
  {
@@ -377,53 +387,53 @@ Regels:
377
387
  title: "Order- en karindeling altijd via de Logistics Engine",
378
388
  date: "2026-08-04",
379
389
  summary: "Nieuwe eis bovenop prompt 1 en 2: geen eigen indeling van orders en karren meer; stap 3 'Order picken' van de Rule Engine is leidend.",
380
- body: `Deze prompt vervangt niets uit prompt 1 en 2; hij komt er bovenop.
381
-
382
- Geconstateerd probleem: deze module deelt orders en karren nog zelf in en houdt
383
- zich daardoor niet aan stap 3 "Order picken" van de Rule Engine. Dat moet stoppen.
384
-
385
- Haal weg: elke eigen berekening van
386
- - welke orders samen op één belading/kar komen,
387
- - hoeveel orders, klanten, lagen of dozen er op een drager passen,
388
- - wanneer er met picken gestart wordt,
389
- - of er met of zonder scanner gewerkt wordt.
390
-
391
- Zo werkt het vanaf nu:
392
-
393
- 1) Vraag altijd eerst het besluit op voordat je iets indeelt:
394
- const { data: besluit } = await supabase.rpc("logistics_buffer_decision", {
395
- p_order_ref: "SO-12345",
396
- p_customer_ref: "CUST-1",
397
- p_delivery_location_ref: "LOC-1",
398
- p_checkin_at: "2026-08-01T10:00:00Z",
399
- p_pickup_at: "2026-08-01T14:00:00Z",
400
- p_load_carrier_type: "trolley",
401
- p_strategy_id: null,
402
- });
403
- // besluit = { buffer, bufferRef, matchCriteria, release, trace }
404
- Deel de order uitsluitend in bij bufferRef. Is buffer=false, dan blijft de order
405
- los staan: nooit zelf alsnog samenvoegen.
406
-
407
- 2) De feitelijke belading komt uit de taak load.compose in logistics_tasks
408
- (owner_module = "afhandeling"). De payload bevat loadCarriers, fillMode
409
- (generated | manual_scan) en de picksettings uit stap 3: pickMode,
410
- pickStartMode, pickMinutes, staff, reserveMinutes, scanner.
411
- - fillMode = generated: neem de indeling exact over zoals aangeleverd.
412
- - fillMode = manual_scan: laat de medewerker scannen en meld het resultaat
413
- terug; corrigeer de indeling niet zelf.
414
- - scanner = required: geen afronding zonder scan.
415
-
416
- 3) planned_start en planned_end van de taak zijn leidend voor het pickmoment.
417
- Herbereken die nooit zelf, ook niet bij drukte.
418
-
419
- 4) Is er nog geen load.compose taak voor een order, dan meld je dat met
420
- logistics_emit_event (order.received of order.changed) en wacht je op de taak.
421
- Zelf alvast indelen is niet toegestaan.
422
-
423
- 5) Elke taak draagt rule_version. Rond een lopende belading altijd af op de
424
- rule_version van de taak, ook als de Rule Engine intussen is gewijzigd.
425
-
426
- Meld afwijkingen terug met logistics_fail_task inclusief toelichting, zodat de
390
+ body: `Deze prompt vervangt niets uit prompt 1 en 2; hij komt er bovenop.
391
+
392
+ Geconstateerd probleem: deze module deelt orders en karren nog zelf in en houdt
393
+ zich daardoor niet aan stap 3 "Order picken" van de Rule Engine. Dat moet stoppen.
394
+
395
+ Haal weg: elke eigen berekening van
396
+ - welke orders samen op één belading/kar komen,
397
+ - hoeveel orders, klanten, lagen of dozen er op een drager passen,
398
+ - wanneer er met picken gestart wordt,
399
+ - of er met of zonder scanner gewerkt wordt.
400
+
401
+ Zo werkt het vanaf nu:
402
+
403
+ 1) Vraag altijd eerst het besluit op voordat je iets indeelt:
404
+ const { data: besluit } = await supabase.rpc("logistics_buffer_decision", {
405
+ p_order_ref: "SO-12345",
406
+ p_customer_ref: "CUST-1",
407
+ p_delivery_location_ref: "LOC-1",
408
+ p_checkin_at: "2026-08-01T10:00:00Z",
409
+ p_pickup_at: "2026-08-01T14:00:00Z",
410
+ p_load_carrier_type: "trolley",
411
+ p_strategy_id: null,
412
+ });
413
+ // besluit = { buffer, bufferRef, matchCriteria, release, trace }
414
+ Deel de order uitsluitend in bij bufferRef. Is buffer=false, dan blijft de order
415
+ los staan: nooit zelf alsnog samenvoegen.
416
+
417
+ 2) De feitelijke belading komt uit de taak load.compose in logistics_tasks
418
+ (owner_module = "afhandeling"). De payload bevat loadCarriers, fillMode
419
+ (generated | manual_scan) en de picksettings uit stap 3: pickMode,
420
+ pickStartMode, pickMinutes, staff, reserveMinutes, scanner.
421
+ - fillMode = generated: neem de indeling exact over zoals aangeleverd.
422
+ - fillMode = manual_scan: laat de medewerker scannen en meld het resultaat
423
+ terug; corrigeer de indeling niet zelf.
424
+ - scanner = required: geen afronding zonder scan.
425
+
426
+ 3) planned_start en planned_end van de taak zijn leidend voor het pickmoment.
427
+ Herbereken die nooit zelf, ook niet bij drukte.
428
+
429
+ 4) Is er nog geen load.compose taak voor een order, dan meld je dat met
430
+ logistics_emit_event (order.received of order.changed) en wacht je op de taak.
431
+ Zelf alvast indelen is niet toegestaan.
432
+
433
+ 5) Elke taak draagt rule_version. Rond een lopende belading altijd af op de
434
+ rule_version van de taak, ook als de Rule Engine intussen is gewijzigd.
435
+
436
+ Meld afwijkingen terug met logistics_fail_task inclusief toelichting, zodat de
427
437
  Engine kan herplannen. Val nooit terug op eigen logica.`,
428
438
  },
429
439
  {
@@ -431,50 +441,50 @@ Engine kan herplannen. Val nooit terug op eigen logica.`,
431
441
  title: "Karren verdelen over meerdere ophaalmomenten",
432
442
  date: "2026-08-04",
433
443
  summary: "Nieuwe eis bovenop prompt 1 t/m 4: de Engine verdeelt complete karren over de ophaalmomenten van een dag; deze module toont dat en verzet nooit zelf.",
434
- body: `LEES DIT EERST — hoe je deze prompt uitvoert
435
-
436
- Alles uit prompt 1 t/m 4 blijft gelden. Hier komt er één ding bij: een dag kan
437
- MEERDERE ophaalmomenten hebben, en de Logistics Engine bepaalt welke kar bij
438
- welk ophaalmoment hoort. Jij rekent NIETS zelf uit en verplaatst NOOIT zelf een
439
- kar op basis van eigen logica.
440
-
441
- 1) Ophaalmomenten lezen
442
- Tabel: public.logistics_pickup_moments
443
- Kolommen: id, moment_date, sequence, label, pickup_time, carrier_ref
444
- Toon per dag de ophaalmomenten op volgorde van sequence/pickup_time.
445
-
446
- 2) Karren per ophaalmoment
447
- Tabel public.order_carts heeft nieuwe kolommen:
448
- - pickup_moment_id → bij welk ophaalmoment de kar hoort
449
- - moved_from_moment_id → het ophaalmoment waar de kar oorspronkelijk stond
450
- - moved_reason → 'capacity_shift' als de Engine hem doorschoof
451
- - moved_at → wanneer dat gebeurde
452
- - pickup_locked → true = deze kar mag NIET doorgeschoven worden
453
-
454
- Groepeer de pickagenda en de leveringsplanning voortaan op pickup_moment_id,
455
- niet meer alleen op de dag. Een kar met moved_reason = 'capacity_shift' toon
456
- je met een duidelijk label "doorgeschoven vanaf <label vorig moment>".
457
-
458
- 3) Regels die je moet respecteren
459
- - Alleen COMPLETE karren wisselen van ophaalmoment. Splits nooit een kar en
460
- verplaats nooit losse orderregels naar een ander moment.
461
- - Karren uit een buffer mogen mee, maar alleen als geheel.
462
- - Wil een gebruiker een kar vastzetten op een moment, zet dan
463
- pickup_locked = true. Meer doe je niet; de Engine slaat die kar over.
464
-
465
- 4) Herberekenen
466
- Na elke wijziging die de planning raakt (nieuwe order, annulering, andere
467
- ophaaltijd, andere kar-indeling) roep je aan:
468
- await supabase.rpc("logistics_capacity_plan_day", { p_date: "2026-08-04" })
469
- De functie geeft per ophaalmoment terug: requiredMinutes, availableMinutes,
470
- feasible en movedCarts. Toon die uitkomst; verzin geen eigen berekening.
471
-
472
- 5) Tekort tonen
473
- Tabel public.logistics_capacity_alerts bevat per dag/ophaalmoment het tekort
474
- (shortage_minutes, carts_short, moved_carts, unplanned_carts). Staat er een
475
- rij met status = 'open', toon dan bij dat ophaalmoment een rode waarschuwing
476
- met het tekort en het aantal karren dat niet op tijd verwerkt kan worden.
477
-
444
+ body: `LEES DIT EERST — hoe je deze prompt uitvoert
445
+
446
+ Alles uit prompt 1 t/m 4 blijft gelden. Hier komt er één ding bij: een dag kan
447
+ MEERDERE ophaalmomenten hebben, en de Logistics Engine bepaalt welke kar bij
448
+ welk ophaalmoment hoort. Jij rekent NIETS zelf uit en verplaatst NOOIT zelf een
449
+ kar op basis van eigen logica.
450
+
451
+ 1) Ophaalmomenten lezen
452
+ Tabel: public.logistics_pickup_moments
453
+ Kolommen: id, moment_date, sequence, label, pickup_time, carrier_ref
454
+ Toon per dag de ophaalmomenten op volgorde van sequence/pickup_time.
455
+
456
+ 2) Karren per ophaalmoment
457
+ Tabel public.order_carts heeft nieuwe kolommen:
458
+ - pickup_moment_id → bij welk ophaalmoment de kar hoort
459
+ - moved_from_moment_id → het ophaalmoment waar de kar oorspronkelijk stond
460
+ - moved_reason → 'capacity_shift' als de Engine hem doorschoof
461
+ - moved_at → wanneer dat gebeurde
462
+ - pickup_locked → true = deze kar mag NIET doorgeschoven worden
463
+
464
+ Groepeer de pickagenda en de leveringsplanning voortaan op pickup_moment_id,
465
+ niet meer alleen op de dag. Een kar met moved_reason = 'capacity_shift' toon
466
+ je met een duidelijk label "doorgeschoven vanaf <label vorig moment>".
467
+
468
+ 3) Regels die je moet respecteren
469
+ - Alleen COMPLETE karren wisselen van ophaalmoment. Splits nooit een kar en
470
+ verplaats nooit losse orderregels naar een ander moment.
471
+ - Karren uit een buffer mogen mee, maar alleen als geheel.
472
+ - Wil een gebruiker een kar vastzetten op een moment, zet dan
473
+ pickup_locked = true. Meer doe je niet; de Engine slaat die kar over.
474
+
475
+ 4) Herberekenen
476
+ Na elke wijziging die de planning raakt (nieuwe order, annulering, andere
477
+ ophaaltijd, andere kar-indeling) roep je aan:
478
+ await supabase.rpc("logistics_capacity_plan_day", { p_date: "2026-08-04" })
479
+ De functie geeft per ophaalmoment terug: requiredMinutes, availableMinutes,
480
+ feasible en movedCarts. Toon die uitkomst; verzin geen eigen berekening.
481
+
482
+ 5) Tekort tonen
483
+ Tabel public.logistics_capacity_alerts bevat per dag/ophaalmoment het tekort
484
+ (shortage_minutes, carts_short, moved_carts, unplanned_carts). Staat er een
485
+ rij met status = 'open', toon dan bij dat ophaalmoment een rode waarschuwing
486
+ met het tekort en het aantal karren dat niet op tijd verwerkt kan worden.
487
+
478
488
  Val nooit terug op eigen logica. Wat de Engine teruggeeft, is leidend.`,
479
489
  },
480
490
  {
@@ -482,131 +492,131 @@ Val nooit terug op eigen logica. Wat de Engine teruggeeft, is leidend.`,
482
492
  title: "Order handmatig verzetten en ophaaltijd wijzigen",
483
493
  date: "2026-08-04",
484
494
  summary: "Nieuwe eis bovenop prompt 1 t/m 3: een medewerker mag een order handmatig naar een andere dag en/of ophaaltijd zetten, via twee vaste RPC's van de Engine.",
485
- body: `LEES DIT EERST — hoe je deze prompt uitvoert
486
-
487
- Deze prompt beschrijft één nieuwe functie in de Afhandeling-module: "Order
488
- verzetten". Je bouwt hem volledig, niet gedeeltelijk. Alles wat hieronder staat
489
- is verplicht. Er is GEEN eigen rekenwerk: de Logistics Engine levert twee kant-en-
490
- klare database-functies, jij bouwt alleen het scherm eromheen en toont wat de
491
- Engine teruggeeft.
492
-
493
- Concreet ben je klaar als dit alle vier waar is:
494
- 1. Er is een knop "Verzetten" bij een order (planning-/agenda-/leverscherm).
495
- 2. Het dialoogvenster toont een DAGKEUZE en een OPHAALTIJD (van/tot).
496
- 3. Voor het opslaan is logistics_reschedule_preview aangeroepen en wordt de
497
- uitkomst daarvan letterlijk aan de gebruiker getoond.
498
- 4. Opslaan gebeurt uitsluitend met logistics_reschedule_order.
499
-
500
- ------------------------------------------------------------------
501
- DE TWEE REGELS DIE DE GEBRUIKER WIL
502
- ------------------------------------------------------------------
503
-
504
- A. Order gaat NAAR VOREN (nieuwe ophaaldag is eerder dan de huidige)
505
- -> Het AFLEVERMOMENT (leverdatum naar de klant) blijft ONGEWIJZIGD.
506
- De order wordt dus eerder klaargemaakt en opgehaald, maar de klant krijgt
507
- hem op dezelfde dag als afgesproken. Je verandert de leverdatum NIET.
508
- Er gaat GEEN melding naar de klant.
509
-
510
- B. Order gaat NAAR ACHTEREN (nieuwe ophaaldag is later)
511
- -> Het aflevermoment schuift mee.
512
- -> De gebruiker MOET in het dialoogvenster de keuze krijgen:
513
- "Klant informeren over de nieuwe leverdatum" (aan/uit) plus een reden.
514
- Staat het aan, dan zorgt de Engine voor de klantmelding. Bouw daar zelf
515
- geen mailflow voor.
516
-
517
- C. Alleen de OPHAALTIJD wijzigen (zelfde dag, andere van/tot-tijd) mag altijd
518
- en geldt niet als verzetten: geen klantmelding, leverdatum blijft gelijk.
519
-
520
- ------------------------------------------------------------------
521
- STAP 1 — VOORVERTONING (verplicht vóór opslaan)
522
- ------------------------------------------------------------------
523
- const { data: preview } = await supabase.rpc("logistics_reschedule_preview", {
524
- p_order_ref: "SO-12345", // orders.order_number
525
- p_pickup_date: "2026-08-07", // nieuwe ophaaldag (YYYY-MM-DD), null = ongewijzigd
526
- p_pickup_from: "06:00", // nieuwe starttijd, null = ongewijzigd
527
- p_pickup_to: "09:00", // nieuwe eindtijd, null = ongewijzigd
528
- });
529
-
530
- preview bevat:
531
- {
532
- ok: true,
533
- orderRef, orderId, bufferId, planLocked,
534
- current: { pickupDate, pickupFrom, pickupTo, deliveryDate },
535
- next: { pickupDate, pickupFrom, pickupTo, deliveryDate },
536
- direction: "earlier" | "later" | "same",
537
- dayShift: -2, // aantal dagen verschil
538
- timeChanged: true,
539
- keepDeliveryMoment: true, // true bij naar voren: leverdatum blijft
540
- customerNoticeRequired: true, // true bij naar achteren: toon de klantoptie
541
- affectedTasks: 8 // aantal taken dat opnieuw wordt ingepland
542
- }
543
-
544
- Wat je met deze velden DOET in het scherm:
545
- - ok === false -> toon preview.error en blokkeer opslaan.
546
- - direction === "earlier" -> toon: "Order wordt X dag(en) eerder opgehaald. Het
547
- aflevermoment blijft {current.deliveryDate}." Verberg de klantoptie.
548
- - direction === "later" -> toon: "Order wordt X dag(en) later. Nieuwe leverdatum:
549
- {next.deliveryDate}." Toon de schakelaar "Klant informeren" (standaard AAN) en
550
- een verplicht redenveld.
551
- - direction === "same" && timeChanged -> toon alleen de nieuwe ophaaltijd.
552
- - Toon altijd affectedTasks: "X taken worden opnieuw ingepland."
553
-
554
- ------------------------------------------------------------------
555
- STAP 2 — OPSLAAN
556
- ------------------------------------------------------------------
557
- const { data: result } = await supabase.rpc("logistics_reschedule_order", {
558
- p_order_ref: "SO-12345",
559
- p_pickup_date: "2026-08-07",
560
- p_pickup_from: "06:00",
561
- p_pickup_to: "09:00",
562
- p_notify_customer: true, // alleen relevant bij direction "later"
563
- p_reason: "Aanvoer bloemen vertraagd",
564
- p_source_module: "afhandeling",
565
- p_user_id: null,
566
- });
567
- // result = preview-velden + { applied: true, rescheduleId, delayAlertId,
568
- // customerNotified, pickupMoment }
569
-
570
- De Engine doet hierbij zelf al ALLES hieronder — doe dit dus niet nogmaals:
571
- - orders.custom_fields.pickup_moment bijwerken ("YYYY-MM-DD|van|tot");
572
- - leverdatum alleen aanpassen bij naar achteren;
573
- - de order uit zijn orderbuffer halen zodat hij niet aan de oude dag vastzit;
574
- - de klantmelding aanmaken als p_notify_customer true is;
575
- - een order.changed-melding sturen en de hele takenketen (buffer, pick, controle,
576
- printen, transport) opnieuw inplannen met logistics_schedule_tasks;
577
- - het verzetten vastleggen in public.logistics_reschedules.
578
-
579
- ------------------------------------------------------------------
580
- WAT JE ABSOLUUT NIET DOET
581
- ------------------------------------------------------------------
582
- - Geen eigen UPDATE op orders, order_buffers, order_carts of logistics_tasks om
583
- een datum of tijd te verzetten. Uitsluitend via de twee RPC's hierboven.
584
- - Zelf planned_start / planned_end / due_at van taken herberekenen. Na het
585
- opslaan lees je de taken gewoon opnieuw; ze staan dan al goed.
586
- - Bij naar voren de leverdatum meeschuiven. Dat is expliciet fout.
587
- - Bij naar achteren de klant informeren zonder dat de gebruiker dat heeft
588
- aangevinkt, of een eigen mail sturen buiten de Engine om.
589
-
590
- ------------------------------------------------------------------
591
- NA HET OPSLAAN
592
- ------------------------------------------------------------------
593
- 1. Herlaad de taken van deze order uit logistics_tasks (planned_start bepaalt
594
- waar de order in de pickagenda en het leverscherm verschijnt).
595
- 2. Toon een bevestiging met de nieuwe ophaaldag, de ophaaltijd, de leverdatum en
596
- of de klant is geïnformeerd (result.customerNotified).
597
- 3. De verzetgeschiedenis kun je tonen met:
598
- supabase.from("logistics_reschedules").select("*").eq("order_ref", ref)
599
- .order("created_at", { ascending: false });
600
-
601
- ------------------------------------------------------------------
602
- CONTROLELIJST VOOR JEZELF (loop deze af voor je klaar meldt)
603
- ------------------------------------------------------------------
604
- [ ] Knop "Verzetten" bestaat bij een order.
605
- [ ] Dagkeuze én ophaaltijd van/tot zijn in te vullen.
606
- [ ] preview wordt live aangeroepen bij elke wijziging in het formulier.
607
- [ ] Naar voren: leverdatum ongewijzigd, geen klantoptie zichtbaar.
608
- [ ] Naar achteren: nieuwe leverdatum zichtbaar + klantoptie + reden.
609
- [ ] Opslaan gaat via logistics_reschedule_order, nergens een eigen UPDATE.
495
+ body: `LEES DIT EERST — hoe je deze prompt uitvoert
496
+
497
+ Deze prompt beschrijft één nieuwe functie in de Afhandeling-module: "Order
498
+ verzetten". Je bouwt hem volledig, niet gedeeltelijk. Alles wat hieronder staat
499
+ is verplicht. Er is GEEN eigen rekenwerk: de Logistics Engine levert twee kant-en-
500
+ klare database-functies, jij bouwt alleen het scherm eromheen en toont wat de
501
+ Engine teruggeeft.
502
+
503
+ Concreet ben je klaar als dit alle vier waar is:
504
+ 1. Er is een knop "Verzetten" bij een order (planning-/agenda-/leverscherm).
505
+ 2. Het dialoogvenster toont een DAGKEUZE en een OPHAALTIJD (van/tot).
506
+ 3. Voor het opslaan is logistics_reschedule_preview aangeroepen en wordt de
507
+ uitkomst daarvan letterlijk aan de gebruiker getoond.
508
+ 4. Opslaan gebeurt uitsluitend met logistics_reschedule_order.
509
+
510
+ ------------------------------------------------------------------
511
+ DE TWEE REGELS DIE DE GEBRUIKER WIL
512
+ ------------------------------------------------------------------
513
+
514
+ A. Order gaat NAAR VOREN (nieuwe ophaaldag is eerder dan de huidige)
515
+ -> Het AFLEVERMOMENT (leverdatum naar de klant) blijft ONGEWIJZIGD.
516
+ De order wordt dus eerder klaargemaakt en opgehaald, maar de klant krijgt
517
+ hem op dezelfde dag als afgesproken. Je verandert de leverdatum NIET.
518
+ Er gaat GEEN melding naar de klant.
519
+
520
+ B. Order gaat NAAR ACHTEREN (nieuwe ophaaldag is later)
521
+ -> Het aflevermoment schuift mee.
522
+ -> De gebruiker MOET in het dialoogvenster de keuze krijgen:
523
+ "Klant informeren over de nieuwe leverdatum" (aan/uit) plus een reden.
524
+ Staat het aan, dan zorgt de Engine voor de klantmelding. Bouw daar zelf
525
+ geen mailflow voor.
526
+
527
+ C. Alleen de OPHAALTIJD wijzigen (zelfde dag, andere van/tot-tijd) mag altijd
528
+ en geldt niet als verzetten: geen klantmelding, leverdatum blijft gelijk.
529
+
530
+ ------------------------------------------------------------------
531
+ STAP 1 — VOORVERTONING (verplicht vóór opslaan)
532
+ ------------------------------------------------------------------
533
+ const { data: preview } = await supabase.rpc("logistics_reschedule_preview", {
534
+ p_order_ref: "SO-12345", // orders.order_number
535
+ p_pickup_date: "2026-08-07", // nieuwe ophaaldag (YYYY-MM-DD), null = ongewijzigd
536
+ p_pickup_from: "06:00", // nieuwe starttijd, null = ongewijzigd
537
+ p_pickup_to: "09:00", // nieuwe eindtijd, null = ongewijzigd
538
+ });
539
+
540
+ preview bevat:
541
+ {
542
+ ok: true,
543
+ orderRef, orderId, bufferId, planLocked,
544
+ current: { pickupDate, pickupFrom, pickupTo, deliveryDate },
545
+ next: { pickupDate, pickupFrom, pickupTo, deliveryDate },
546
+ direction: "earlier" | "later" | "same",
547
+ dayShift: -2, // aantal dagen verschil
548
+ timeChanged: true,
549
+ keepDeliveryMoment: true, // true bij naar voren: leverdatum blijft
550
+ customerNoticeRequired: true, // true bij naar achteren: toon de klantoptie
551
+ affectedTasks: 8 // aantal taken dat opnieuw wordt ingepland
552
+ }
553
+
554
+ Wat je met deze velden DOET in het scherm:
555
+ - ok === false -> toon preview.error en blokkeer opslaan.
556
+ - direction === "earlier" -> toon: "Order wordt X dag(en) eerder opgehaald. Het
557
+ aflevermoment blijft {current.deliveryDate}." Verberg de klantoptie.
558
+ - direction === "later" -> toon: "Order wordt X dag(en) later. Nieuwe leverdatum:
559
+ {next.deliveryDate}." Toon de schakelaar "Klant informeren" (standaard AAN) en
560
+ een verplicht redenveld.
561
+ - direction === "same" && timeChanged -> toon alleen de nieuwe ophaaltijd.
562
+ - Toon altijd affectedTasks: "X taken worden opnieuw ingepland."
563
+
564
+ ------------------------------------------------------------------
565
+ STAP 2 — OPSLAAN
566
+ ------------------------------------------------------------------
567
+ const { data: result } = await supabase.rpc("logistics_reschedule_order", {
568
+ p_order_ref: "SO-12345",
569
+ p_pickup_date: "2026-08-07",
570
+ p_pickup_from: "06:00",
571
+ p_pickup_to: "09:00",
572
+ p_notify_customer: true, // alleen relevant bij direction "later"
573
+ p_reason: "Aanvoer bloemen vertraagd",
574
+ p_source_module: "afhandeling",
575
+ p_user_id: null,
576
+ });
577
+ // result = preview-velden + { applied: true, rescheduleId, delayAlertId,
578
+ // customerNotified, pickupMoment }
579
+
580
+ De Engine doet hierbij zelf al ALLES hieronder — doe dit dus niet nogmaals:
581
+ - orders.custom_fields.pickup_moment bijwerken ("YYYY-MM-DD|van|tot");
582
+ - leverdatum alleen aanpassen bij naar achteren;
583
+ - de order uit zijn orderbuffer halen zodat hij niet aan de oude dag vastzit;
584
+ - de klantmelding aanmaken als p_notify_customer true is;
585
+ - een order.changed-melding sturen en de hele takenketen (buffer, pick, controle,
586
+ printen, transport) opnieuw inplannen met logistics_schedule_tasks;
587
+ - het verzetten vastleggen in public.logistics_reschedules.
588
+
589
+ ------------------------------------------------------------------
590
+ WAT JE ABSOLUUT NIET DOET
591
+ ------------------------------------------------------------------
592
+ - Geen eigen UPDATE op orders, order_buffers, order_carts of logistics_tasks om
593
+ een datum of tijd te verzetten. Uitsluitend via de twee RPC's hierboven.
594
+ - Zelf planned_start / planned_end / due_at van taken herberekenen. Na het
595
+ opslaan lees je de taken gewoon opnieuw; ze staan dan al goed.
596
+ - Bij naar voren de leverdatum meeschuiven. Dat is expliciet fout.
597
+ - Bij naar achteren de klant informeren zonder dat de gebruiker dat heeft
598
+ aangevinkt, of een eigen mail sturen buiten de Engine om.
599
+
600
+ ------------------------------------------------------------------
601
+ NA HET OPSLAAN
602
+ ------------------------------------------------------------------
603
+ 1. Herlaad de taken van deze order uit logistics_tasks (planned_start bepaalt
604
+ waar de order in de pickagenda en het leverscherm verschijnt).
605
+ 2. Toon een bevestiging met de nieuwe ophaaldag, de ophaaltijd, de leverdatum en
606
+ of de klant is geïnformeerd (result.customerNotified).
607
+ 3. De verzetgeschiedenis kun je tonen met:
608
+ supabase.from("logistics_reschedules").select("*").eq("order_ref", ref)
609
+ .order("created_at", { ascending: false });
610
+
611
+ ------------------------------------------------------------------
612
+ CONTROLELIJST VOOR JEZELF (loop deze af voor je klaar meldt)
613
+ ------------------------------------------------------------------
614
+ [ ] Knop "Verzetten" bestaat bij een order.
615
+ [ ] Dagkeuze én ophaaltijd van/tot zijn in te vullen.
616
+ [ ] preview wordt live aangeroepen bij elke wijziging in het formulier.
617
+ [ ] Naar voren: leverdatum ongewijzigd, geen klantoptie zichtbaar.
618
+ [ ] Naar achteren: nieuwe leverdatum zichtbaar + klantoptie + reden.
619
+ [ ] Opslaan gaat via logistics_reschedule_order, nergens een eigen UPDATE.
610
620
  [ ] Na opslaan worden de taken opnieuw geladen en klopt de agenda.`,
611
621
  },
612
622
  {
@@ -614,40 +624,40 @@ CONTROLELIJST VOOR JEZELF (loop deze af voor je klaar meldt)
614
624
  title: "Capaciteitswaarschuwing tonen in de pick agenda",
615
625
  date: "2026-08-04",
616
626
  summary: "Nieuwe eis bovenop prompt 1 t/m 5: als er op een dag meer karren gepland staan dan er met het beschikbare personeel gepickt kunnen worden, moet de planning dat zichtbaar waarschuwen.",
617
- body: `LEES DIT EERST
618
-
619
- De Logistics Engine rekent per pickdag uit hoeveel picktijd alle geplande karren
620
- kosten en hoeveel picktijd er beschikbaar is (aantal medewerkers x werktijden uit
621
- "4. Pickstations"). Jij rekent NIETS zelf uit; je toont alleen wat de Engine geeft.
622
-
623
- STAP 1 — BEREKENEN (bij het openen van een week/dag)
624
- const { data: dagen } = await supabase.rpc("logistics_capacity_day_check", {
625
- p_from: "2026-08-03", // eerste dag van de getoonde periode
626
- p_to: "2026-08-09", // laatste dag
627
- });
628
-
629
- dagen is een array met per dag:
630
- { date, carts, requiredMinutes, availableMinutes, shortageMinutes, staff }
631
-
632
- STAP 2 — OPEN WAARSCHUWINGEN LEZEN
633
- const { data: alerts } = await supabase
634
- .from("logistics_capacity_alerts")
635
- .select("plan_date, required_minutes, available_minutes, shortage_minutes, carts_total, carts_short")
636
- .eq("status", "open");
637
-
638
- STAP 3 — TONEN (verplicht)
639
- - Weekoverzicht: elke dag met shortageMinutes > 0 krijgt een duidelijk zichtbare
640
- waarschuwing (amber/rood) met de tekst:
641
- "Te weinig capaciteit: {carts} karren vragen {requiredMinutes} min picktijd,
642
- beschikbaar is {availableMinutes} min ({staff} medewerkers).
643
- Circa {carts_short} karren passen niet op deze dag."
644
- - Dagdetails: dezelfde waarschuwing bovenaan het dagscherm.
645
- - Dagen zonder tekort krijgen GEEN waarschuwing.
646
- - Verzin geen eigen drempels, kleuren-logica of eigen berekening van picktijd.
647
-
648
- CONTROLELIJST
649
- [ ] logistics_capacity_day_check wordt aangeroepen bij elke periodewissel.
650
- [ ] Dagen met tekort tonen de waarschuwing in het weekoverzicht én in dagdetails.
627
+ body: `LEES DIT EERST
628
+
629
+ De Logistics Engine rekent per pickdag uit hoeveel picktijd alle geplande karren
630
+ kosten en hoeveel picktijd er beschikbaar is (aantal medewerkers x werktijden uit
631
+ "4. Pickstations"). Jij rekent NIETS zelf uit; je toont alleen wat de Engine geeft.
632
+
633
+ STAP 1 — BEREKENEN (bij het openen van een week/dag)
634
+ const { data: dagen } = await supabase.rpc("logistics_capacity_day_check", {
635
+ p_from: "2026-08-03", // eerste dag van de getoonde periode
636
+ p_to: "2026-08-09", // laatste dag
637
+ });
638
+
639
+ dagen is een array met per dag:
640
+ { date, carts, requiredMinutes, availableMinutes, shortageMinutes, staff }
641
+
642
+ STAP 2 — OPEN WAARSCHUWINGEN LEZEN
643
+ const { data: alerts } = await supabase
644
+ .from("logistics_capacity_alerts")
645
+ .select("plan_date, required_minutes, available_minutes, shortage_minutes, carts_total, carts_short")
646
+ .eq("status", "open");
647
+
648
+ STAP 3 — TONEN (verplicht)
649
+ - Weekoverzicht: elke dag met shortageMinutes > 0 krijgt een duidelijk zichtbare
650
+ waarschuwing (amber/rood) met de tekst:
651
+ "Te weinig capaciteit: {carts} karren vragen {requiredMinutes} min picktijd,
652
+ beschikbaar is {availableMinutes} min ({staff} medewerkers).
653
+ Circa {carts_short} karren passen niet op deze dag."
654
+ - Dagdetails: dezelfde waarschuwing bovenaan het dagscherm.
655
+ - Dagen zonder tekort krijgen GEEN waarschuwing.
656
+ - Verzin geen eigen drempels, kleuren-logica of eigen berekening van picktijd.
657
+
658
+ CONTROLELIJST
659
+ [ ] logistics_capacity_day_check wordt aangeroepen bij elke periodewissel.
660
+ [ ] Dagen met tekort tonen de waarschuwing in het weekoverzicht én in dagdetails.
651
661
  [ ] De getoonde cijfers komen letterlijk uit de Engine.`,
652
662
  },
653
663
  {
@@ -655,31 +665,31 @@ CONTROLELIJST
655
665
  title: "Geen capaciteitsmeldingen in Planning en leveringen",
656
666
  date: "2026-08-04",
657
667
  summary: "Nieuwe eis bovenop prompt 1 t/m 6: verwijder alle capaciteits- en foutmeldingen uit de agenda Planning en leveringen; de Engine blijft op de achtergrond controleren.",
658
- body: `AANVULLING op prompt 1 t/m 6. Deze nieuwste eis vervangt uitsluitend de
659
- zichtbare meldingen uit prompt 5 en 6 binnen het scherm "Planning en leveringen".
660
-
661
- Verwijder uit het weekoverzicht en de dagdetails van "Planning en leveringen":
662
- - de melding "Te weinig capaciteit";
663
- - aantallen tekortkomende minuten, medewerkers of karren;
664
- - rode, amberkleurige of andere fout-/waarschuwingsblokken uit
665
- public.logistics_capacity_alerts;
666
- - foutmeldingen die afkomstig zijn van logistics_capacity_day_check.
667
-
668
- De agenda toont alleen de planning, bestellingen, karren, planten en statussen.
669
- Een tekort mag de agenda niet visueel blokkeren of als foutmelding worden getoond.
670
-
671
- BELANGRIJK — alleen de zichtbaarheid vervalt:
672
- - Blijf logistics_capacity_day_check en logistics_capacity_plan_day uitvoeren waar
673
- dat nodig is voor de planning en automatische herverdeling.
674
- - Verwijder of sluit public.logistics_capacity_alerts niet.
675
- - Capaciteitsmeldingen blijven beschikbaar in het Regieoverzicht van de Logistics
676
- Engine en in het centrale Meldingencentrum.
677
- - Verberg echte technische fouten niet: een mislukte laad- of opslagactie mag nog
678
- steeds via een normale toast worden gemeld, maar niet als melding in de agenda.
679
-
680
- CONTROLELIJST
681
- [ ] In Planning en leveringen staat bij geen enkele dag een capaciteitsmelding.
682
- [ ] Het openen en wisselen van weken blijft de Engine-berekening uitvoeren.
668
+ body: `AANVULLING op prompt 1 t/m 6. Deze nieuwste eis vervangt uitsluitend de
669
+ zichtbare meldingen uit prompt 5 en 6 binnen het scherm "Planning en leveringen".
670
+
671
+ Verwijder uit het weekoverzicht en de dagdetails van "Planning en leveringen":
672
+ - de melding "Te weinig capaciteit";
673
+ - aantallen tekortkomende minuten, medewerkers of karren;
674
+ - rode, amberkleurige of andere fout-/waarschuwingsblokken uit
675
+ public.logistics_capacity_alerts;
676
+ - foutmeldingen die afkomstig zijn van logistics_capacity_day_check.
677
+
678
+ De agenda toont alleen de planning, bestellingen, karren, planten en statussen.
679
+ Een tekort mag de agenda niet visueel blokkeren of als foutmelding worden getoond.
680
+
681
+ BELANGRIJK — alleen de zichtbaarheid vervalt:
682
+ - Blijf logistics_capacity_day_check en logistics_capacity_plan_day uitvoeren waar
683
+ dat nodig is voor de planning en automatische herverdeling.
684
+ - Verwijder of sluit public.logistics_capacity_alerts niet.
685
+ - Capaciteitsmeldingen blijven beschikbaar in het Regieoverzicht van de Logistics
686
+ Engine en in het centrale Meldingencentrum.
687
+ - Verberg echte technische fouten niet: een mislukte laad- of opslagactie mag nog
688
+ steeds via een normale toast worden gemeld, maar niet als melding in de agenda.
689
+
690
+ CONTROLELIJST
691
+ [ ] In Planning en leveringen staat bij geen enkele dag een capaciteitsmelding.
692
+ [ ] Het openen en wisselen van weken blijft de Engine-berekening uitvoeren.
683
693
  [ ] Het Regieoverzicht kan de capaciteitsmeldingen nog steeds tonen.`,
684
694
  },
685
695
  {
@@ -687,28 +697,28 @@ CONTROLELIJST
687
697
  title: "Tekort in medewerkers tonen in de capaciteitswaarschuwing",
688
698
  date: "2026-08-04",
689
699
  summary: "Nieuwe eis bovenop prompt 1 t/m 7: elke capaciteitswaarschuwing vermeldt hoeveel medewerkers er tekort zijn, niet alleen minuten en karren.",
690
- body: `AANVULLING op prompt 6. De waarschuwing blijft weg uit "Planning en
691
- leveringen" (prompt 7); overal waar je hem WEL toont (Regieoverzicht,
692
- Meldingencentrum, dagdetails buiten de agenda) geldt onderstaande tekst.
693
-
694
- BEREKENING (verplicht, geen eigen variant)
695
- minutenPerMedewerker = availableMinutes / staff // staff > 0
696
- medewerkersTekort = Math.ceil(shortageMinutes / minutenPerMedewerker)
697
- Is staff 0 of availableMinutes 0, toon dan geen aantal maar de tekst
698
- "vul het gemiddeld aantal medewerkers in bij 3. Order picken".
699
-
700
- TEKST
701
- "Te weinig capaciteit: {carts} karren vragen {requiredMinutes} min picktijd,
702
- beschikbaar is {availableMinutes} min ({staff} medewerkers).
703
- Je komt {medewerkersTekort} medewerker(s) tekort — samen
704
- {staff + medewerkersTekort} nodig. Circa {carts_short} karren passen niet
705
- op deze dag."
706
-
707
- Gebruik enkelvoud "medewerker" bij 1, anders "medewerkers".
708
-
709
- CONTROLELIJST
710
- [ ] Elke zichtbare capaciteitswaarschuwing noemt het tekort in medewerkers.
711
- [ ] Het aantal komt uit de formule hierboven, niet uit een eigen aanname.
700
+ body: `AANVULLING op prompt 6. De waarschuwing blijft weg uit "Planning en
701
+ leveringen" (prompt 7); overal waar je hem WEL toont (Regieoverzicht,
702
+ Meldingencentrum, dagdetails buiten de agenda) geldt onderstaande tekst.
703
+
704
+ BEREKENING (verplicht, geen eigen variant)
705
+ minutenPerMedewerker = availableMinutes / staff // staff > 0
706
+ medewerkersTekort = Math.ceil(shortageMinutes / minutenPerMedewerker)
707
+ Is staff 0 of availableMinutes 0, toon dan geen aantal maar de tekst
708
+ "vul het gemiddeld aantal medewerkers in bij 3. Order picken".
709
+
710
+ TEKST
711
+ "Te weinig capaciteit: {carts} karren vragen {requiredMinutes} min picktijd,
712
+ beschikbaar is {availableMinutes} min ({staff} medewerkers).
713
+ Je komt {medewerkersTekort} medewerker(s) tekort — samen
714
+ {staff + medewerkersTekort} nodig. Circa {carts_short} karren passen niet
715
+ op deze dag."
716
+
717
+ Gebruik enkelvoud "medewerker" bij 1, anders "medewerkers".
718
+
719
+ CONTROLELIJST
720
+ [ ] Elke zichtbare capaciteitswaarschuwing noemt het tekort in medewerkers.
721
+ [ ] Het aantal komt uit de formule hierboven, niet uit een eigen aanname.
712
722
  [ ] Zonder ingevuld personeel toont de melding de invulhint in plaats van 0.`,
713
723
  },
714
724
  {
@@ -717,23 +727,23 @@ CONTROLELIJST
717
727
  date: "2026-08-19",
718
728
  summary: "Nieuwe orders worden binnen seconden automatisch ingedeeld door de Engine.",
719
729
  withSharedContract: false,
720
- body: `De Logistics Engine deelt nieuwe en gewijzigde orders voortaan automatisch in.
721
- Een order komt bij binnenkomst in de wachtrij public.logistics_plan_queue en wordt
722
- direct verwerkt (doorgaans binnen enkele seconden, uiterlijk binnen 2 minuten).
723
-
724
- REGELS
725
- - Deel NOOIT zelf orders in en maak geen eigen taken aan als er nog geen indeling is.
726
- - Toon de melding "wacht op een indeling van de Logistics Engine" pas als de order
727
- langer dan 2 minuten geleden is aangemaakt/gewijzigd EN er nog geen taken zijn.
728
- Binnen die marge toon je niets — dat is normale verwerkingstijd.
729
- - Ververs de weergave automatisch (realtime of polling) zodra taken verschijnen,
730
- zodat de gebruiker niets hoeft te doen.
731
- - Reserveringen (orders.is_reservation = true) en vastgezette orders
732
- (orders.plan_locked_at gevuld) tel je nooit mee in deze melding.
733
-
734
- CONTROLELIJST
735
- [ ] Geen melding binnen 2 minuten na aanmaken/wijzigen van een order.
736
- [ ] Geen eigen indeling of eigen taken als fallback.
730
+ body: `De Logistics Engine deelt nieuwe en gewijzigde orders voortaan automatisch in.
731
+ Een order komt bij binnenkomst in de wachtrij public.logistics_plan_queue en wordt
732
+ direct verwerkt (doorgaans binnen enkele seconden, uiterlijk binnen 2 minuten).
733
+
734
+ REGELS
735
+ - Deel NOOIT zelf orders in en maak geen eigen taken aan als er nog geen indeling is.
736
+ - Toon de melding "wacht op een indeling van de Logistics Engine" pas als de order
737
+ langer dan 2 minuten geleden is aangemaakt/gewijzigd EN er nog geen taken zijn.
738
+ Binnen die marge toon je niets — dat is normale verwerkingstijd.
739
+ - Ververs de weergave automatisch (realtime of polling) zodra taken verschijnen,
740
+ zodat de gebruiker niets hoeft te doen.
741
+ - Reserveringen (orders.is_reservation = true) en vastgezette orders
742
+ (orders.plan_locked_at gevuld) tel je nooit mee in deze melding.
743
+
744
+ CONTROLELIJST
745
+ [ ] Geen melding binnen 2 minuten na aanmaken/wijzigen van een order.
746
+ [ ] Geen eigen indeling of eigen taken als fallback.
737
747
  [ ] Weergave werkt bij zonder handmatig verversen.`,
738
748
  },
739
749
  ],
@@ -748,12 +758,12 @@ CONTROLELIJST
748
758
  date: "2026-07-24",
749
759
  summary: "Pick-taken tonen en terugmelden.",
750
760
  withSharedContract: true,
751
- body: `Haal weg: eigen pickvolgorde en eigen bepaling van de starttijd.
752
-
753
- Bouw:
754
- - pick.execute taken tonen, gesorteerd op planned_start.
755
- - Payload: pickMode, pickStartMode, pickMinutes, staff, reserveMinutes, scanner, loadCarriers.
756
- - Voortgang terugmelden via RPC: logistics_claim_task bij starten, logistics_complete_task bij afronden.
761
+ body: `Haal weg: eigen pickvolgorde en eigen bepaling van de starttijd.
762
+
763
+ Bouw:
764
+ - pick.execute taken tonen, gesorteerd op planned_start.
765
+ - Payload: pickMode, pickStartMode, pickMinutes, staff, reserveMinutes, scanner, loadCarriers.
766
+ - Voortgang terugmelden via RPC: logistics_claim_task bij starten, logistics_complete_task bij afronden.
757
767
  - Te-laat-dreigende belading markeren op basis van planned_end; niet zelf herrekenen.`,
758
768
  },
759
769
  {
@@ -761,26 +771,26 @@ Bouw:
761
771
  title: "Documenten per pickstation vanuit de Engine",
762
772
  date: "2026-08-03",
763
773
  summary: "Documenttaken tonen op het juiste pickstation, inclusief goedkeuring.",
764
- body: `Deze prompt vervangt niets uit prompt 1; hij komt er bovenop.
765
-
766
- Haal weg: eigen logica over welk document op welk pickstation verschijnt en
767
- wanneer. De Logistics Engine bepaalt dit volledig.
768
-
769
- Verwerk deze taaktypes uit logistics_tasks:
770
- - print.packing_slip, print.sticker, print.shipping_note, print.production_note
771
-
772
- Payload per taak:
773
- - release wanneer het document vrijkomt
774
- - pickStationId public.pick_stations.id waar het document heen moet
775
- - pickStationTypeId public.pick_station_types.id dat het document mag ontvangen
776
- - requiresApproval true = eerst handmatige goedkeuring op het pickstation
777
-
778
- Regels:
779
- - Toon een document pas vanaf planned_start van de taak.
780
- - Filter de wachtrij van een pickstation op pickStationId; is die leeg, gebruik
781
- dan pickStationTypeId. Toon nooit documenten van een ander station.
782
- - requiresApproval = true: toon een goedkeurknop; pas na goedkeuren printen en
783
- daarna logistics_complete_task aanroepen (bij weigeren logistics_fail_task).
774
+ body: `Deze prompt vervangt niets uit prompt 1; hij komt er bovenop.
775
+
776
+ Haal weg: eigen logica over welk document op welk pickstation verschijnt en
777
+ wanneer. De Logistics Engine bepaalt dit volledig.
778
+
779
+ Verwerk deze taaktypes uit logistics_tasks:
780
+ - print.packing_slip, print.sticker, print.shipping_note, print.production_note
781
+
782
+ Payload per taak:
783
+ - release wanneer het document vrijkomt
784
+ - pickStationId public.pick_stations.id waar het document heen moet
785
+ - pickStationTypeId public.pick_station_types.id dat het document mag ontvangen
786
+ - requiresApproval true = eerst handmatige goedkeuring op het pickstation
787
+
788
+ Regels:
789
+ - Toon een document pas vanaf planned_start van de taak.
790
+ - Filter de wachtrij van een pickstation op pickStationId; is die leeg, gebruik
791
+ dan pickStationTypeId. Toon nooit documenten van een ander station.
792
+ - requiresApproval = true: toon een goedkeurknop; pas na goedkeuren printen en
793
+ daarna logistics_complete_task aanroepen (bij weigeren logistics_fail_task).
784
794
  - Bepaal nooit zelf een vrijgavemoment en print nooit vooruit.`,
785
795
  },
786
796
  {
@@ -788,32 +798,32 @@ Regels:
788
798
  title: "Documenten ophalen via de Floriday-gateway",
789
799
  date: "2026-08-04",
790
800
  summary: "Geen eigen Floriday-token of florydayFetch meer; gebundeld printen per kar.",
791
- body: `Deze prompt vervangt niets uit prompt 1 en 2; hij komt er bovenop.
792
-
793
- Haal weg: eigen Floriday-tokenlogica, eigen florydayFetch, eigen keuze van
794
- staging/live en eigen HTTP-aanroepen naar de Floriday-gateway.
795
-
796
- Alle modules draaien in dezelfde shell op dezelfde database. Importeer de
797
- gateway daarom rechtstreeks en geef de service-role supabase-client van de
798
- shell mee:
799
-
800
- import {
801
- runActiveEnvironment,
802
- runPdf,
803
- runPdfBulkMerged,
804
- } from "@flowselections/floriday-authenticatie-module/gateway";
805
-
806
- Regels:
807
- - De omgeving komt altijd uit runActiveEnvironment(supabase). Nooit zelf kiezen.
808
- - Eén document (print.packing_slip, print.sticker, print.shipping_note,
809
- print.production_note): runPdf.
810
- - Trigger "kar afgemeld" of "kar-scan": bundel alle regels van die kar en roep
811
- runPdfBulkMerged aan, zodat het pickstation één printjob krijgt in plaats van
812
- losse documenten.
813
- - Toon bovenin het scherm welke koppeling actief is (label, GLN, staging/live).
814
- - Print nooit twee keer hetzelfde: controleer public.logistics_document_prints
815
- op (order_ref, document_type, trigger) voordat je print.
816
- - Trigger en pickstation blijven van de Logistics Engine; de gateway levert
801
+ body: `Deze prompt vervangt niets uit prompt 1 en 2; hij komt er bovenop.
802
+
803
+ Haal weg: eigen Floriday-tokenlogica, eigen florydayFetch, eigen keuze van
804
+ staging/live en eigen HTTP-aanroepen naar de Floriday-gateway.
805
+
806
+ Alle modules draaien in dezelfde shell op dezelfde database. Importeer de
807
+ gateway daarom rechtstreeks en geef de service-role supabase-client van de
808
+ shell mee:
809
+
810
+ import {
811
+ runActiveEnvironment,
812
+ runPdf,
813
+ runPdfBulkMerged,
814
+ } from "@flowselections/floriday-authenticatie-module/gateway";
815
+
816
+ Regels:
817
+ - De omgeving komt altijd uit runActiveEnvironment(supabase). Nooit zelf kiezen.
818
+ - Eén document (print.packing_slip, print.sticker, print.shipping_note,
819
+ print.production_note): runPdf.
820
+ - Trigger "kar afgemeld" of "kar-scan": bundel alle regels van die kar en roep
821
+ runPdfBulkMerged aan, zodat het pickstation één printjob krijgt in plaats van
822
+ losse documenten.
823
+ - Toon bovenin het scherm welke koppeling actief is (label, GLN, staging/live).
824
+ - Print nooit twee keer hetzelfde: controleer public.logistics_document_prints
825
+ op (order_ref, document_type, trigger) voordat je print.
826
+ - Trigger en pickstation blijven van de Logistics Engine; de gateway levert
817
827
  alleen het PDF-bestand.`,
818
828
  },
819
829
  {
@@ -821,29 +831,29 @@ Regels:
821
831
  title: "Strikte documentroutering per pickstation",
822
832
  date: "2026-08-04",
823
833
  summary: "Een pickstation toont uitsluitend de documentsoorten die eraan gekoppeld zijn.",
824
- body: `Deze prompt vervangt niets uit prompt 1 t/m 3; hij komt er bovenop.
825
-
826
- Probleem: op "Pickstation Orders" verschenen naast pakbonnen ook veilingbrieven
827
- en transportbrieven. Dat mag nooit.
828
-
829
- Haal weg: elke query die de wachtrij alleen op station_id filtert, en elke
830
- fallback die "overige documenten" toch ergens toont.
831
-
832
- Regels:
833
- - Filter de wachtrij ALTIJD op screen_id van het geopende scherm, niet op
834
- station_id. Elk scherm heeft via public.pick_screen_document_types precies de
835
- documenttypes die het mag tonen.
836
- - Toon nooit een document waarvan het document_type_id niet aan dat scherm is
837
- gekoppeld. Geen "rest"-categorie, geen samengevoegde stationweergave.
838
- - De koppeling documentsoort -> pickstation komt uit de Logistiek Engine
839
- (Rule Engine, stap "Pickstations"). In de database is dat opvraagbaar met
840
- select public.logistics_document_station('pakbon').
841
- - De printwachtrij public.pick_queue heeft een trigger die station_id en
842
- screen_id herschrijft volgens die instelling. Schrijf zelf nooit een
843
- station_id dat daarvan afwijkt; dat wordt overschreven of geweigerd.
844
- - Taken uit logistics_tasks dragen payload.documentType en payload.pickStationId.
845
- Gebruik die twee samen; nooit alleen het taaktype.
846
- - Standaardweergave is "documenten van vandaag". Oudere, niet-geprinte regels
834
+ body: `Deze prompt vervangt niets uit prompt 1 t/m 3; hij komt er bovenop.
835
+
836
+ Probleem: op "Pickstation Orders" verschenen naast pakbonnen ook veilingbrieven
837
+ en transportbrieven. Dat mag nooit.
838
+
839
+ Haal weg: elke query die de wachtrij alleen op station_id filtert, en elke
840
+ fallback die "overige documenten" toch ergens toont.
841
+
842
+ Regels:
843
+ - Filter de wachtrij ALTIJD op screen_id van het geopende scherm, niet op
844
+ station_id. Elk scherm heeft via public.pick_screen_document_types precies de
845
+ documenttypes die het mag tonen.
846
+ - Toon nooit een document waarvan het document_type_id niet aan dat scherm is
847
+ gekoppeld. Geen "rest"-categorie, geen samengevoegde stationweergave.
848
+ - De koppeling documentsoort -> pickstation komt uit de Logistiek Engine
849
+ (Rule Engine, stap "Pickstations"). In de database is dat opvraagbaar met
850
+ select public.logistics_document_station('pakbon').
851
+ - De printwachtrij public.pick_queue heeft een trigger die station_id en
852
+ screen_id herschrijft volgens die instelling. Schrijf zelf nooit een
853
+ station_id dat daarvan afwijkt; dat wordt overschreven of geweigerd.
854
+ - Taken uit logistics_tasks dragen payload.documentType en payload.pickStationId.
855
+ Gebruik die twee samen; nooit alleen het taaktype.
856
+ - Standaardweergave is "documenten van vandaag". Oudere, niet-geprinte regels
847
857
  horen in een apart tabblad, niet in de dagwachtrij.`,
848
858
  },
849
859
  {
@@ -851,41 +861,41 @@ Regels:
851
861
  title: "Documenten altijd renderen met de layout uit de Layout maker",
852
862
  date: "2026-08-04",
853
863
  summary: "Geen eigen pakbon-opmaak meer; de layout komt uit public.document_layouts.",
854
- body: `Deze prompt vervangt niets uit prompt 1 t/m 4; hij komt er bovenop.
855
-
856
- Probleem: het pickstation print een eigen, hardgecodeerde pakbon. De pakbon die
857
- in de module "Layout maker (pakbonnen, verzendbrieven, extra)" is ontworpen
858
- wordt genegeerd.
859
-
860
- Haal weg: elke eigen HTML/PDF-opmaak van pakbonnen, verzendbrieven, stickers,
861
- productiebonnen en controlelijsten. Ook alle vaste kolommen, logo's, marges en
862
- papierformaten die in de pickstation-code staan.
863
-
864
- Regels:
865
- - De opmaak komt ALTIJD uit public.document_layouts (+ document_layout_elements,
866
- document_layout_variables, document_layout_versions, layout_logos). Nooit een
867
- eigen sjabloon, ook niet als fallback.
868
- - Welke layout bij een documentsoort hoort, bepaal je niet zelf. Gebruik:
869
- select public.logistics_document_layout('pakbon');
870
- Dat geeft { layoutId, layoutName, layoutVersion, layoutDocumentType } van de
871
- actieve layout (voorkeur: is_standaard).
872
- - Elke regel in public.pick_queue draagt deze gegevens al mee in payload:
873
- documentType, layoutId, layoutName, layoutVersion. Gebruik payload.layoutId
874
- als die er staat; val alleen terug op de functie hierboven als hij ontbreekt.
875
- - Render met de renderer van de Layout maker, niet met eigen code. Papierformaat,
876
- oriëntatie en marges komen uit de layoutrij (papierformaat, breedte_mm,
877
- hoogte_mm, marge_*, orientatie, page_settings).
878
- - Ontbreekt er een actieve layout voor een documentsoort? Print dan niet, maar
879
- toon "Geen layout ingesteld voor <documentsoort>" met een verwijzing naar de
880
- Layout maker. Nooit stilzwijgend een eigen pakbon printen.
881
- - Floriday-documenten (prompt 3) blijven via de gateway lopen; alles wat we zelf
882
- opmaken loopt via de Layout maker.
883
- - Log bij het afronden van de taak welke layoutId en layoutVersion zijn gebruikt
884
- in het result van logistics_complete_task.
885
-
886
- CONTROLELIJST
887
- [ ] Geen enkel eigen documentsjabloon meer in de code.
888
- [ ] Pakbon in het pickstation is visueel identiek aan de preview in de Layout maker.
864
+ body: `Deze prompt vervangt niets uit prompt 1 t/m 4; hij komt er bovenop.
865
+
866
+ Probleem: het pickstation print een eigen, hardgecodeerde pakbon. De pakbon die
867
+ in de module "Layout maker (pakbonnen, verzendbrieven, extra)" is ontworpen
868
+ wordt genegeerd.
869
+
870
+ Haal weg: elke eigen HTML/PDF-opmaak van pakbonnen, verzendbrieven, stickers,
871
+ productiebonnen en controlelijsten. Ook alle vaste kolommen, logo's, marges en
872
+ papierformaten die in de pickstation-code staan.
873
+
874
+ Regels:
875
+ - De opmaak komt ALTIJD uit public.document_layouts (+ document_layout_elements,
876
+ document_layout_variables, document_layout_versions, layout_logos). Nooit een
877
+ eigen sjabloon, ook niet als fallback.
878
+ - Welke layout bij een documentsoort hoort, bepaal je niet zelf. Gebruik:
879
+ select public.logistics_document_layout('pakbon');
880
+ Dat geeft { layoutId, layoutName, layoutVersion, layoutDocumentType } van de
881
+ actieve layout (voorkeur: is_standaard).
882
+ - Elke regel in public.pick_queue draagt deze gegevens al mee in payload:
883
+ documentType, layoutId, layoutName, layoutVersion. Gebruik payload.layoutId
884
+ als die er staat; val alleen terug op de functie hierboven als hij ontbreekt.
885
+ - Render met de renderer van de Layout maker, niet met eigen code. Papierformaat,
886
+ oriëntatie en marges komen uit de layoutrij (papierformaat, breedte_mm,
887
+ hoogte_mm, marge_*, orientatie, page_settings).
888
+ - Ontbreekt er een actieve layout voor een documentsoort? Print dan niet, maar
889
+ toon "Geen layout ingesteld voor <documentsoort>" met een verwijzing naar de
890
+ Layout maker. Nooit stilzwijgend een eigen pakbon printen.
891
+ - Floriday-documenten (prompt 3) blijven via de gateway lopen; alles wat we zelf
892
+ opmaken loopt via de Layout maker.
893
+ - Log bij het afronden van de taak welke layoutId en layoutVersion zijn gebruikt
894
+ in het result van logistics_complete_task.
895
+
896
+ CONTROLELIJST
897
+ [ ] Geen enkel eigen documentsjabloon meer in de code.
898
+ [ ] Pakbon in het pickstation is visueel identiek aan de preview in de Layout maker.
889
899
  [ ] Ontbrekende layout geeft een duidelijke melding in plaats van een print.`,
890
900
  },
891
901
  {
@@ -893,50 +903,50 @@ CONTROLELIJST
893
903
  title: "Sectie-layouts uit de Layout maker correct printen",
894
904
  date: "2026-08-04",
895
905
  summary: "Een geldige pakbon in document_layouts.config mag nooit als ontbrekende layout worden afgewezen.",
896
- body: `Deze prompt vervangt niets uit prompt 1 t/m 5; hij komt er bovenop.
897
-
898
- Probleem: de gekoppelde layout "packbon hoofd" is een geldige sectie-layout.
899
- De opbouw staat in public.document_layouts.config.pakbon.secties en niet in
900
- public.document_layout_elements. Het pickstation ondersteunt nu alleen
901
- canvas-elementen en toont daardoor ten onrechte "Geen layout ingesteld".
902
-
903
- Haal weg:
904
- - elke controle die een layout als ontbrekend beschouwt wanneer
905
- document_layout_elements leeg is;
906
- - elke renderer die uitsluitend document_layout_elements ondersteunt;
907
- - de algemene MissingLayoutError voor een wel bestaande, actieve layoutrij.
908
-
909
- Regels:
910
- - Een layout ontbreekt uitsluitend als public.document_layouts geen actieve rij
911
- voor het door de Engine aangeleverde layoutId bevat. Nul canvas-elementen is
912
- geen ontbrekende layout.
913
- - Lees eerst payload.layoutId (of payload.layout_id) uit public.pick_queue.
914
- De huidige pakbonnen verwijzen daarmee naar de gekoppelde layoutrij.
915
- - Kies daarna de renderer op basis van de opgeslagen layoutvorm:
916
- 1. config.pakbon.secties is een array: gebruik dezelfde sectie-/pakbonrenderer
917
- en dezelfde datamapping als de preview van de Layout maker;
918
- 2. document_layout_elements bevat rijen: gebruik de canvasrenderer;
919
- 3. geen van beide bevat inhoud: meld "Layout heeft nog geen inhoud", niet
920
- "Geen layout ingesteld".
921
- - Importeer de renderer en bijbehorende types bij voorkeur rechtstreeks uit de
922
- Layout maker-module. Kopieer geen pakbonsjabloon naar deze module. Als de
923
- Layout maker de renderer nog niet exporteert, voeg daar named exports toe voor
924
- de sectierenderer en de order-naar-pakbon-datamapping en gebruik die hier.
925
- - Respecteer config.pakbon.secties volledig: volgorde, zichtbaar, condities,
926
- opties, karren, lagen, producten, totalen en logo. Respecteer daarnaast
927
- papierformaat, oriëntatie, marges en page_settings van document_layouts.
928
- - Vul vóór renderen de volledige ordercontext voor payload.orderId of
929
- order_number: order, klant, afleveradres, orderregels, karren, lagen,
930
- producten en fusten. De wachtrijregel alleen bevat hiervoor onvoldoende data.
931
- - Markeer de wachtrijregel pas als geprint nadat renderen is gelukt en de
932
- printdialoog is geopend. Laat de regel bij een renderfout in de wachtrij staan.
933
-
934
- CONTROLELIJST
935
- [ ] "packbon hoofd" opent vanuit Pickstation Orders zonder MissingLayoutError.
936
- [ ] De afdruk is visueel gelijk aan de preview in de Layout maker.
937
- [ ] Karren, lagen en producten van de gekozen order staan op de pakbon.
938
- [ ] Een lege layout geeft "Layout heeft nog geen inhoud".
939
- [ ] Een werkelijk ontbrekende/inactieve layoutrij geeft pas "Geen layout ingesteld".
906
+ body: `Deze prompt vervangt niets uit prompt 1 t/m 5; hij komt er bovenop.
907
+
908
+ Probleem: de gekoppelde layout "packbon hoofd" is een geldige sectie-layout.
909
+ De opbouw staat in public.document_layouts.config.pakbon.secties en niet in
910
+ public.document_layout_elements. Het pickstation ondersteunt nu alleen
911
+ canvas-elementen en toont daardoor ten onrechte "Geen layout ingesteld".
912
+
913
+ Haal weg:
914
+ - elke controle die een layout als ontbrekend beschouwt wanneer
915
+ document_layout_elements leeg is;
916
+ - elke renderer die uitsluitend document_layout_elements ondersteunt;
917
+ - de algemene MissingLayoutError voor een wel bestaande, actieve layoutrij.
918
+
919
+ Regels:
920
+ - Een layout ontbreekt uitsluitend als public.document_layouts geen actieve rij
921
+ voor het door de Engine aangeleverde layoutId bevat. Nul canvas-elementen is
922
+ geen ontbrekende layout.
923
+ - Lees eerst payload.layoutId (of payload.layout_id) uit public.pick_queue.
924
+ De huidige pakbonnen verwijzen daarmee naar de gekoppelde layoutrij.
925
+ - Kies daarna de renderer op basis van de opgeslagen layoutvorm:
926
+ 1. config.pakbon.secties is een array: gebruik dezelfde sectie-/pakbonrenderer
927
+ en dezelfde datamapping als de preview van de Layout maker;
928
+ 2. document_layout_elements bevat rijen: gebruik de canvasrenderer;
929
+ 3. geen van beide bevat inhoud: meld "Layout heeft nog geen inhoud", niet
930
+ "Geen layout ingesteld".
931
+ - Importeer de renderer en bijbehorende types bij voorkeur rechtstreeks uit de
932
+ Layout maker-module. Kopieer geen pakbonsjabloon naar deze module. Als de
933
+ Layout maker de renderer nog niet exporteert, voeg daar named exports toe voor
934
+ de sectierenderer en de order-naar-pakbon-datamapping en gebruik die hier.
935
+ - Respecteer config.pakbon.secties volledig: volgorde, zichtbaar, condities,
936
+ opties, karren, lagen, producten, totalen en logo. Respecteer daarnaast
937
+ papierformaat, oriëntatie, marges en page_settings van document_layouts.
938
+ - Vul vóór renderen de volledige ordercontext voor payload.orderId of
939
+ order_number: order, klant, afleveradres, orderregels, karren, lagen,
940
+ producten en fusten. De wachtrijregel alleen bevat hiervoor onvoldoende data.
941
+ - Markeer de wachtrijregel pas als geprint nadat renderen is gelukt en de
942
+ printdialoog is geopend. Laat de regel bij een renderfout in de wachtrij staan.
943
+
944
+ CONTROLELIJST
945
+ [ ] "packbon hoofd" opent vanuit Pickstation Orders zonder MissingLayoutError.
946
+ [ ] De afdruk is visueel gelijk aan de preview in de Layout maker.
947
+ [ ] Karren, lagen en producten van de gekozen order staan op de pakbon.
948
+ [ ] Een lege layout geeft "Layout heeft nog geen inhoud".
949
+ [ ] Een werkelijk ontbrekende/inactieve layoutrij geeft pas "Geen layout ingesteld".
940
950
  [ ] De wachtrij wordt alleen afgerond na een geslaagde render/printactie.`,
941
951
  },
942
952
  {
@@ -944,37 +954,37 @@ CONTROLELIJST
944
954
  title: "Altijd de actuele layout, behalve bij kant-en-klare PDF's",
945
955
  date: "2026-08-14",
946
956
  summary: "Layout wint boven meegestuurde HTML; klantstickers en Floriday-documenten gaan als bestaande PDF ongewijzigd naar de printer.",
947
- body: `Deze prompt vervangt niets uit prompt 1 t/m 6; hij komt er bovenop.
948
-
949
- Uitgangspunt: wat er geprint wordt is ALTIJD de actuele layout uit de Layout
950
- maker, gevuld met de echte gegevens van de order en de klant. Enige uitzondering:
951
- documenten die al als kant-en-klare PDF bestaan (klantstickers en
952
- Floriday-documenten). Die worden ongewijzigd doorgestuurd.
953
-
954
- Volgorde van beslissen bij een printopdracht in public.print_jobs:
955
- 1. Is er een kant-en-klare PDF meegegeven (pdf-veld op de opdracht / bron uit
956
- floriday_customer_sticker_pdfs_cache): die PDF ongewijzigd naar de printer.
957
- Niet opnieuw opmaken, niet renderen.
958
- 2. Is print_jobs.layout_id gevuld: die layout wint. Negeer een eventueel
959
- meegestuurde render_html en render vers uit public.document_layouts.
960
- 3. Pas als beide leeg zijn: meegestuurde render_html, en anders de actieve
961
- layout bij het documenttype.
962
-
963
- Haal weg:
964
- - elke eigen HTML-sjabloon die om de layout heen print;
965
- - elke gecachte kopie van een layout; de layout wordt per opdracht live gelezen.
966
-
967
- Regels:
968
- - layout_id en printer_id komen uit de Engine (public.pick_enqueue_print_job).
969
- Kies zelf nooit een andere layout of printer wanneer die velden gevuld zijn.
970
- - Statussen blijven binnen de enum print_job_status: wachtend, bezig, voltooid,
971
- mislukt, geannuleerd. Andere modules luisteren daarop.
972
- - Geen PDF én geen layout: markeer de opdracht als mislukt met een leesbare
973
- reden in foutmelding. Nooit blanco printen.
974
-
975
- CONTROLELIJST
976
- [ ] Layout aangepast in de Layout maker -> eerstvolgende print toont de wijziging.
977
- [ ] Klantsticker en Floriday-document komen er exact uit zoals aangeleverd.
957
+ body: `Deze prompt vervangt niets uit prompt 1 t/m 6; hij komt er bovenop.
958
+
959
+ Uitgangspunt: wat er geprint wordt is ALTIJD de actuele layout uit de Layout
960
+ maker, gevuld met de echte gegevens van de order en de klant. Enige uitzondering:
961
+ documenten die al als kant-en-klare PDF bestaan (klantstickers en
962
+ Floriday-documenten). Die worden ongewijzigd doorgestuurd.
963
+
964
+ Volgorde van beslissen bij een printopdracht in public.print_jobs:
965
+ 1. Is er een kant-en-klare PDF meegegeven (pdf-veld op de opdracht / bron uit
966
+ floriday_customer_sticker_pdfs_cache): die PDF ongewijzigd naar de printer.
967
+ Niet opnieuw opmaken, niet renderen.
968
+ 2. Is print_jobs.layout_id gevuld: die layout wint. Negeer een eventueel
969
+ meegestuurde render_html en render vers uit public.document_layouts.
970
+ 3. Pas als beide leeg zijn: meegestuurde render_html, en anders de actieve
971
+ layout bij het documenttype.
972
+
973
+ Haal weg:
974
+ - elke eigen HTML-sjabloon die om de layout heen print;
975
+ - elke gecachte kopie van een layout; de layout wordt per opdracht live gelezen.
976
+
977
+ Regels:
978
+ - layout_id en printer_id komen uit de Engine (public.pick_enqueue_print_job).
979
+ Kies zelf nooit een andere layout of printer wanneer die velden gevuld zijn.
980
+ - Statussen blijven binnen de enum print_job_status: wachtend, bezig, voltooid,
981
+ mislukt, geannuleerd. Andere modules luisteren daarop.
982
+ - Geen PDF én geen layout: markeer de opdracht als mislukt met een leesbare
983
+ reden in foutmelding. Nooit blanco printen.
984
+
985
+ CONTROLELIJST
986
+ [ ] Layout aangepast in de Layout maker -> eerstvolgende print toont de wijziging.
987
+ [ ] Klantsticker en Floriday-document komen er exact uit zoals aangeleverd.
978
988
  [ ] Geen enkele printer- of layoutnaam hardcoded in deze module.`,
979
989
  },
980
990
  ],
@@ -989,12 +999,12 @@ CONTROLELIJST
989
999
  date: "2026-07-24",
990
1000
  summary: "Scan- en controletaken uitvoeren.",
991
1001
  withSharedContract: true,
992
- body: `Haal weg: eigen aannames over wat en wanneer er gescand moet worden.
993
-
994
- Bouw:
995
- - scan.verify taken; payload reason = pick_scanner_required of kar_vullen_manual_scan.
996
- - control.execute taken met controlMode "scanner" = de scancontrole.
997
- - Scanresultaat meesturen in logistics_complete_task (p_result).
1002
+ body: `Haal weg: eigen aannames over wat en wanneer er gescand moet worden.
1003
+
1004
+ Bouw:
1005
+ - scan.verify taken; payload reason = pick_scanner_required of kar_vullen_manual_scan.
1006
+ - control.execute taken met controlMode "scanner" = de scancontrole.
1007
+ - Scanresultaat meesturen in logistics_complete_task (p_result).
998
1008
  - Bij afwijking logistics_fail_task met toelichting.`,
999
1009
  },
1000
1010
  ],
@@ -1009,11 +1019,11 @@ Bouw:
1009
1019
  date: "2026-07-24",
1010
1020
  summary: "Reserveringstaken uitvoeren en voorraadwijzigingen melden.",
1011
1021
  withSharedContract: true,
1012
- body: `Haal weg: eigen beslissing wanneer er gereserveerd of afgeboekt wordt.
1013
-
1014
- Bouw:
1015
- - stock.reserve taken uitvoeren; payload bevat loadCarriers.
1016
- - Bij relevante voorraadwijziging een gebeurtenis melden met event_type
1022
+ body: `Haal weg: eigen beslissing wanneer er gereserveerd of afgeboekt wordt.
1023
+
1024
+ Bouw:
1025
+ - stock.reserve taken uitvoeren; payload bevat loadCarriers.
1026
+ - Bij relevante voorraadwijziging een gebeurtenis melden met event_type
1017
1027
  stock.changed en dezelfde order_ref, zodat de Engine kan herplannen.`,
1018
1028
  },
1019
1029
  ],
@@ -1028,10 +1038,10 @@ Bouw:
1028
1038
  date: "2026-07-24",
1029
1039
  summary: "Strategie koppelen aan klant en klantgroep.",
1030
1040
  withSharedContract: true,
1031
- body: `Bouw:
1032
- - Per klant en per klantgroep een koppelveld naar de logistieke strategie
1033
- (logistics_strategies.id), zodat de Engine weet welke rule config geldt.
1034
- - Dit veld meegeven als strategy_id bij logistics_emit_event, of beschikbaar
1041
+ body: `Bouw:
1042
+ - Per klant en per klantgroep een koppelveld naar de logistieke strategie
1043
+ (logistics_strategies.id), zodat de Engine weet welke rule config geldt.
1044
+ - Dit veld meegeven als strategy_id bij logistics_emit_event, of beschikbaar
1035
1045
  maken zodat de Verkoop-module het kan meegeven.`,
1036
1046
  },
1037
1047
  ],
@@ -1046,18 +1056,18 @@ Bouw:
1046
1056
  date: "2026-07-31",
1047
1057
  summary: "Eerste koppelprompt: mail alleen omzetten naar order als de Engine dat toestaat.",
1048
1058
  withSharedContract: true,
1049
- body: `Haal weg: eigen aannames over of een bestelling met te weinig voorraad
1050
- toch mag worden aangemaakt.
1051
-
1052
- ${STOCK_CONTRACT}
1053
-
1054
- In de Mailbox-module concreet:
1055
- - Vóór het omzetten van een mail naar een bestelling altijd
1056
- logistics_check_order_allowed aanroepen met alle orderregels.
1057
- - allowed = false -> maak de bestelling NIET aan. Zet de mail op status
1058
- "geblokkeerd - onvoldoende voorraad" en toon per product wanted/available/missing
1059
- uit check.shortages, zodat een medewerker het handmatig kan oplossen.
1060
- - allowed = true -> bestelling normaal aanmaken en daarna de gebeurtenis melden
1059
+ body: `Haal weg: eigen aannames over of een bestelling met te weinig voorraad
1060
+ toch mag worden aangemaakt.
1061
+
1062
+ ${STOCK_CONTRACT}
1063
+
1064
+ In de Mailbox-module concreet:
1065
+ - Vóór het omzetten van een mail naar een bestelling altijd
1066
+ logistics_check_order_allowed aanroepen met alle orderregels.
1067
+ - allowed = false -> maak de bestelling NIET aan. Zet de mail op status
1068
+ "geblokkeerd - onvoldoende voorraad" en toon per product wanted/available/missing
1069
+ uit check.shortages, zodat een medewerker het handmatig kan oplossen.
1070
+ - allowed = true -> bestelling normaal aanmaken en daarna de gebeurtenis melden
1061
1071
  via logistics_emit_event (order.received) met een unieke p_idempotency_key.`,
1062
1072
  },
1063
1073
  {
@@ -1065,39 +1075,39 @@ In de Mailbox-module concreet:
1065
1075
  title: "Bufferbesluit altijd bij de Engine opvragen",
1066
1076
  date: "2026-08-02",
1067
1077
  summary: "Nieuwe eis bovenop prompt 1: geen eigen bufferlogica; bufferRef komt uit logistics_buffer_decision.",
1068
- body: `AANVULLING op koppelprompt 1 (die blijft gewoon geldig).
1069
-
1070
- Haal weg: elke eigen regel of aanname over het samenvoegen van bestellingen uit
1071
- dezelfde mail, dezelfde afzender of hetzelfde afleveradres.
1072
-
1073
- Nieuwe verplichting: vraag na de voorraadcontrole, vóór het aanmaken van de
1074
- bestelling, het bufferbesluit op bij de Engine:
1075
-
1076
- const { data: besluit } = await supabase.rpc("logistics_buffer_decision", {
1077
- p_order: {
1078
- orderRef: "MAIL-12345",
1079
- checkinAt: "2026-08-03T09:00:00Z",
1080
- deliveryDate: "2026-08-03",
1081
- customerRef: "CUST-1",
1082
- floridayCustomerRef: "FLOR-1",
1083
- bvoCustomerRef: "BVO-1",
1084
- deliveryLocationRef: "LOC-1",
1085
- carrierRef: "TRANS-1",
1086
- loadCarrierType: "trolley",
1087
- orderType: "standaard",
1088
- },
1089
- p_strategy_id: null,
1090
- });
1091
- // besluit = { buffer, reason, bufferRef, bufferKey, matchCriteria,
1092
- // closeBeforeMinutes, bufferCloseAt, release, trace }
1093
-
1094
- Regels:
1095
- - besluit.buffer === false -> bestelling los aanmaken, geen buffer toewijzen.
1096
- - besluit.buffer === true -> gebruik exact besluit.bufferRef en stuur die mee in de
1097
- payload van logistics_emit_event.
1098
- - Ontbrekende velden zorgen ervoor dat de Engine NIET combineert; vul daarom altijd
1099
- alle bovenstaande velden zo compleet mogelijk uit de mail/klantgegevens.
1100
- - Toon besluit.reason en besluit.trace in het mailscherm, zodat een medewerker ziet
1078
+ body: `AANVULLING op koppelprompt 1 (die blijft gewoon geldig).
1079
+
1080
+ Haal weg: elke eigen regel of aanname over het samenvoegen van bestellingen uit
1081
+ dezelfde mail, dezelfde afzender of hetzelfde afleveradres.
1082
+
1083
+ Nieuwe verplichting: vraag na de voorraadcontrole, vóór het aanmaken van de
1084
+ bestelling, het bufferbesluit op bij de Engine:
1085
+
1086
+ const { data: besluit } = await supabase.rpc("logistics_buffer_decision", {
1087
+ p_order: {
1088
+ orderRef: "MAIL-12345",
1089
+ checkinAt: "2026-08-03T09:00:00Z",
1090
+ deliveryDate: "2026-08-03",
1091
+ customerRef: "CUST-1",
1092
+ floridayCustomerRef: "FLOR-1",
1093
+ bvoCustomerRef: "BVO-1",
1094
+ deliveryLocationRef: "LOC-1",
1095
+ carrierRef: "TRANS-1",
1096
+ loadCarrierType: "trolley",
1097
+ orderType: "standaard",
1098
+ },
1099
+ p_strategy_id: null,
1100
+ });
1101
+ // besluit = { buffer, reason, bufferRef, bufferKey, matchCriteria,
1102
+ // closeBeforeMinutes, bufferCloseAt, release, trace }
1103
+
1104
+ Regels:
1105
+ - besluit.buffer === false -> bestelling los aanmaken, geen buffer toewijzen.
1106
+ - besluit.buffer === true -> gebruik exact besluit.bufferRef en stuur die mee in de
1107
+ payload van logistics_emit_event.
1108
+ - Ontbrekende velden zorgen ervoor dat de Engine NIET combineert; vul daarom altijd
1109
+ alle bovenstaande velden zo compleet mogelijk uit de mail/klantgegevens.
1110
+ - Toon besluit.reason en besluit.trace in het mailscherm, zodat een medewerker ziet
1101
1111
  waarom een bestelling wel of niet is samengevoegd.`,
1102
1112
  },
1103
1113
  {
@@ -1105,39 +1115,39 @@ Regels:
1105
1115
  title: "Alleen ladingdragers gebruiken die de Engine toestaat",
1106
1116
  date: "2026-08-03",
1107
1117
  summary: "Nieuwe eis bovenop prompt 1 en 2: uitgeschakelde ladingdragers mogen niet meer worden gekozen of ingelezen.",
1108
- body: `AANVULLING op koppelprompt 1 en 2 (die blijven gewoon geldig).
1109
-
1110
- In de Rule Engine (stap Beladingindeling) staat welke ladingdragers zijn toegestaan.
1111
- Staat een ladingdrager uit, dan mag de Mailbox-module die niet meer kiezen of
1112
- automatisch invullen.
1113
-
1114
- Haal weg: elke eigen lijst met ladingdragers / verzendmethodes en elke eigen
1115
- vertaling van tekst uit een mail naar een ladingdrager die niet uit de Engine komt.
1116
-
1117
- Nieuwe verplichting: haal de lijst op bij de Engine.
1118
-
1119
- const { data: dragers } = await supabase.rpc("logistics_allowed_load_carriers", {
1120
- p_strategy_id: null,
1121
- });
1122
- // dragers = { allowed: [...], default: "trolley",
1123
- // carriers: [{ code, label, enabled, isDefault }, ...] }
1124
-
1125
- Codes en namen (gebruik exact deze codes):
1126
- trolley Stapelwagen
1127
- danish_container Deense container
1128
- euro_rolcontainer Euro-rolcontainer
1129
- pallet Pallet
1130
- euro_pallet Euro-pallet
1131
- loose Geen ladingdrager
1132
-
1133
- Regels:
1134
- - In het mailscherm alleen dragers met enabled === true tonen; de rest weglaten.
1135
- - Herkent de mail een ladingdrager die niet in allowed staat, maak de bestelling dan
1136
- NIET automatisch aan. Zet de mail op "handmatig controleren - ladingdrager
1137
- uitgeschakeld" en toon welke drager in de mail stond.
1138
- - Is er niets herkend, gebruik dan dragers.default.
1139
- - Stuur loadCarrierType in logistics_check_order_allowed, logistics_buffer_decision
1140
- en logistics_emit_event altijd met een van bovenstaande codes mee.
1118
+ body: `AANVULLING op koppelprompt 1 en 2 (die blijven gewoon geldig).
1119
+
1120
+ In de Rule Engine (stap Beladingindeling) staat welke ladingdragers zijn toegestaan.
1121
+ Staat een ladingdrager uit, dan mag de Mailbox-module die niet meer kiezen of
1122
+ automatisch invullen.
1123
+
1124
+ Haal weg: elke eigen lijst met ladingdragers / verzendmethodes en elke eigen
1125
+ vertaling van tekst uit een mail naar een ladingdrager die niet uit de Engine komt.
1126
+
1127
+ Nieuwe verplichting: haal de lijst op bij de Engine.
1128
+
1129
+ const { data: dragers } = await supabase.rpc("logistics_allowed_load_carriers", {
1130
+ p_strategy_id: null,
1131
+ });
1132
+ // dragers = { allowed: [...], default: "trolley",
1133
+ // carriers: [{ code, label, enabled, isDefault }, ...] }
1134
+
1135
+ Codes en namen (gebruik exact deze codes):
1136
+ trolley Stapelwagen
1137
+ danish_container Deense container
1138
+ euro_rolcontainer Euro-rolcontainer
1139
+ pallet Pallet
1140
+ euro_pallet Euro-pallet
1141
+ loose Geen ladingdrager
1142
+
1143
+ Regels:
1144
+ - In het mailscherm alleen dragers met enabled === true tonen; de rest weglaten.
1145
+ - Herkent de mail een ladingdrager die niet in allowed staat, maak de bestelling dan
1146
+ NIET automatisch aan. Zet de mail op "handmatig controleren - ladingdrager
1147
+ uitgeschakeld" en toon welke drager in de mail stond.
1148
+ - Is er niets herkend, gebruik dan dragers.default.
1149
+ - Stuur loadCarrierType in logistics_check_order_allowed, logistics_buffer_decision
1150
+ en logistics_emit_event altijd met een van bovenstaande codes mee.
1141
1151
  - Cache de lijst niet; de instelling kan op elk moment wijzigen.`,
1142
1152
  },
1143
1153
  ],
@@ -1152,56 +1162,56 @@ Regels:
1152
1162
  date: "2026-08-04",
1153
1163
  withSharedContract: true,
1154
1164
  summary: "Deze module schrijft de dagbezetting weg en toont per dag of de Logistics Engine alle ophaalmomenten haalt.",
1155
- body: `LEES DIT EERST — hoe je deze prompt uitvoert
1156
-
1157
- Deze module is de bron van de bezetting. De Logistics Engine rekent daarmee uit
1158
- of alle karren voor ieder ophaalmoment op tijd gepickt kunnen worden. Je bouwt
1159
- twee dingen: wegschrijven van de planning, en het tonen van de waarschuwing.
1160
- Zelf rekenen doe je niet.
1161
-
1162
- 1) Dagbezetting wegschrijven
1163
- Tabel: public.logistics_staff_planning
1164
- Kolommen: plan_date (date), staff_count (aantal medewerkers),
1165
- start_time, end_time (werktijden), source_module, note.
1166
- Eén rij per dag (uniek op gebruiker + plan_date). Schrijf of update deze rij
1167
- telkens wanneer de gebruiker de bezetting of de werktijden van een dag
1168
- wijzigt:
1169
-
1170
- await supabase.from("logistics_staff_planning").upsert({
1171
- plan_date: "2026-08-04",
1172
- staff_count: 7,
1173
- start_time: "06:00",
1174
- end_time: "16:00",
1175
- source_module: "personeelsplanning",
1176
- }, { onConflict: "user_id,plan_date" })
1177
-
1178
- 2) Direct laten herrekenen
1179
- Roep na iedere wijziging aan:
1180
- await supabase.rpc("logistics_capacity_plan_day", { p_date: "2026-08-04" })
1181
- De Engine berekent dan opnieuw of alle ophaalmomenten haalbaar zijn en
1182
- verplaatst waar nodig automatisch COMPLETE karren naar een later
1183
- ophaalmoment. Jij verplaatst zelf nooit karren.
1184
-
1185
- 3) Rode waarschuwing tonen
1186
- Tabel: public.logistics_capacity_alerts, filter op plan_date en
1187
- status = 'open'. Toon op de betreffende dag een duidelijk rode melding met:
1188
- - shortage_minutes → hoeveel capaciteit tekort is (toon in uren + minuten)
1189
- - pickup_label / pickup_time → welk ophaalmoment niet gehaald wordt
1190
- - carts_short → hoeveel karren niet op tijd verwerkt kunnen worden
1191
- - moved_carts → welke karren automatisch zijn doorgeschoven
1192
- - unplanned_carts → welke karren nog steeds niet ingepland kunnen worden
1193
- Zijn er geen open rijen, toon dan géén waarschuwing (groen/neutraal).
1194
-
1195
- 4) Ophaalmomenten
1196
- Tabel public.logistics_pickup_moments bevat per dag de ophaalmomenten
1197
- (sequence, label, pickup_time). Gebruik die om de waarschuwing per
1198
- ophaalmoment te tonen. Aanmaken of wijzigen van ophaalmomenten mag alleen
1199
- als de gebruiker daar in deze module om vraagt; anders alleen lezen.
1200
-
1201
- 5) Wat je NIET doet
1202
- - Geen eigen capaciteitsberekening of eigen picktijden.
1203
- - Geen karren splitsen of verplaatsen.
1204
- - Geen eigen mail sturen bij een tekort; de Engine regelt e-mail en het
1165
+ body: `LEES DIT EERST — hoe je deze prompt uitvoert
1166
+
1167
+ Deze module is de bron van de bezetting. De Logistics Engine rekent daarmee uit
1168
+ of alle karren voor ieder ophaalmoment op tijd gepickt kunnen worden. Je bouwt
1169
+ twee dingen: wegschrijven van de planning, en het tonen van de waarschuwing.
1170
+ Zelf rekenen doe je niet.
1171
+
1172
+ 1) Dagbezetting wegschrijven
1173
+ Tabel: public.logistics_staff_planning
1174
+ Kolommen: plan_date (date), staff_count (aantal medewerkers),
1175
+ start_time, end_time (werktijden), source_module, note.
1176
+ Eén rij per dag (uniek op gebruiker + plan_date). Schrijf of update deze rij
1177
+ telkens wanneer de gebruiker de bezetting of de werktijden van een dag
1178
+ wijzigt:
1179
+
1180
+ await supabase.from("logistics_staff_planning").upsert({
1181
+ plan_date: "2026-08-04",
1182
+ staff_count: 7,
1183
+ start_time: "06:00",
1184
+ end_time: "16:00",
1185
+ source_module: "personeelsplanning",
1186
+ }, { onConflict: "user_id,plan_date" })
1187
+
1188
+ 2) Direct laten herrekenen
1189
+ Roep na iedere wijziging aan:
1190
+ await supabase.rpc("logistics_capacity_plan_day", { p_date: "2026-08-04" })
1191
+ De Engine berekent dan opnieuw of alle ophaalmomenten haalbaar zijn en
1192
+ verplaatst waar nodig automatisch COMPLETE karren naar een later
1193
+ ophaalmoment. Jij verplaatst zelf nooit karren.
1194
+
1195
+ 3) Rode waarschuwing tonen
1196
+ Tabel: public.logistics_capacity_alerts, filter op plan_date en
1197
+ status = 'open'. Toon op de betreffende dag een duidelijk rode melding met:
1198
+ - shortage_minutes → hoeveel capaciteit tekort is (toon in uren + minuten)
1199
+ - pickup_label / pickup_time → welk ophaalmoment niet gehaald wordt
1200
+ - carts_short → hoeveel karren niet op tijd verwerkt kunnen worden
1201
+ - moved_carts → welke karren automatisch zijn doorgeschoven
1202
+ - unplanned_carts → welke karren nog steeds niet ingepland kunnen worden
1203
+ Zijn er geen open rijen, toon dan géén waarschuwing (groen/neutraal).
1204
+
1205
+ 4) Ophaalmomenten
1206
+ Tabel public.logistics_pickup_moments bevat per dag de ophaalmomenten
1207
+ (sequence, label, pickup_time). Gebruik die om de waarschuwing per
1208
+ ophaalmoment te tonen. Aanmaken of wijzigen van ophaalmomenten mag alleen
1209
+ als de gebruiker daar in deze module om vraagt; anders alleen lezen.
1210
+
1211
+ 5) Wat je NIET doet
1212
+ - Geen eigen capaciteitsberekening of eigen picktijden.
1213
+ - Geen karren splitsen of verplaatsen.
1214
+ - Geen eigen mail sturen bij een tekort; de Engine regelt e-mail en het
1205
1215
  Meldingencentrum via de instellingen in de Rule Engine.`,
1206
1216
  },
1207
1217
  ],
@@ -1220,16 +1230,16 @@ export const MODULE_PROMPTS = Object.keys(MODULES)
1220
1230
  export function buildRevisionPrompt(prompt, revision) {
1221
1231
  const shared = revision.withSharedContract
1222
1232
  ? `\n## Gedeeld contract\n\n${sharedBlock(prompt.moduleKey)}\n`
1223
- : `\n(Deze prompt is een aanvulling. Het gedeelde contract uit koppelprompt 1 blijft
1233
+ : `\n(Deze prompt is een aanvulling. Het gedeelde contract uit koppelprompt 1 blijft
1224
1234
  onverkort gelden — niets terugdraaien.)\n`;
1225
- return `# Koppelprompt ${revision.number} — ${prompt.label}
1226
- ## ${revision.title} (${revision.date})
1227
-
1228
- ${revision.summary}
1229
- ${shared}
1230
- ## Wat deze module moet doen
1231
-
1232
- ${revision.body}
1235
+ return `# Koppelprompt ${revision.number} — ${prompt.label}
1236
+ ## ${revision.title} (${revision.date})
1237
+
1238
+ ${revision.summary}
1239
+ ${shared}
1240
+ ## Wat deze module moet doen
1241
+
1242
+ ${revision.body}
1233
1243
  `;
1234
1244
  }
1235
1245
  export function buildModulePrompt(prompt) {
@@ -1237,18 +1247,18 @@ export function buildModulePrompt(prompt) {
1237
1247
  }
1238
1248
  /** Alle modules, met per module de volledige promptgeschiedenis. */
1239
1249
  export function buildAllModulePrompts() {
1240
- const sections = MODULE_PROMPTS.map((p) => `# ${p.order}. ${p.label} (module: ${p.moduleKey})
1241
-
1242
- ${p.summary}
1243
-
1250
+ const sections = MODULE_PROMPTS.map((p) => `# ${p.order}. ${p.label} (module: ${p.moduleKey})
1251
+
1252
+ ${p.summary}
1253
+
1244
1254
  ${p.revisions.map((r) => buildRevisionPrompt(p, r)).join("\n---\n\n")}`).join("\n\n====================\n\n");
1245
- return `# Koppelprompts voor de aangesloten modules
1246
-
1247
- Per module staan de prompts op volgorde. Een nieuwe eis komt er altijd als
1248
- NIEUWE prompt bij; eerdere prompts blijven geldig en worden niet herschreven.
1249
- Er worden geen secrets gebruikt: alle communicatie loopt via de gedeelde
1250
- Supabase-database.
1251
-
1255
+ return `# Koppelprompts voor de aangesloten modules
1256
+
1257
+ Per module staan de prompts op volgorde. Een nieuwe eis komt er altijd als
1258
+ NIEUWE prompt bij; eerdere prompts blijven geldig en worden niet herschreven.
1259
+ Er worden geen secrets gebruikt: alle communicatie loopt via de gedeelde
1260
+ Supabase-database.
1261
+
1252
1262
  ${sections}`;
1253
1263
  }
1254
1264
  export function modulePromptFilename(moduleKey, revisionNumber) {