@flowselections/logistiek-engine 1.0.25 → 1.0.27

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 (57) hide show
  1. package/README.md +24 -24
  2. package/dist-lib/components/ConsistentieCard.d.ts +2 -0
  3. package/dist-lib/components/ConsistentieCard.d.ts.map +1 -0
  4. package/dist-lib/components/ConsistentieCard.js +38 -0
  5. package/dist-lib/components/ModulePromptsPage.d.ts.map +1 -1
  6. package/dist-lib/components/ModulePromptsPage.js +11 -3
  7. package/dist-lib/components/RegiePage.d.ts.map +1 -1
  8. package/dist-lib/components/RegiePage.js +2 -1
  9. package/dist-lib/index.d.ts +1 -0
  10. package/dist-lib/index.d.ts.map +1 -1
  11. package/dist-lib/index.js +1 -0
  12. package/dist-lib/integrations/supabase/auth-middleware.d.ts +1048 -715
  13. package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
  14. package/dist-lib/integrations/supabase/client.server.d.ts +1048 -715
  15. package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
  16. package/dist-lib/integrations/supabase/client.server.js +1 -1
  17. package/dist-lib/integrations/supabase/moduleClient.d.ts +1048 -715
  18. package/dist-lib/integrations/supabase/moduleClient.d.ts.map +1 -1
  19. package/dist-lib/integrations/supabase/types.d.ts +1081 -768
  20. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  21. package/dist-lib/lib/dimensionData.server.d.ts +27 -0
  22. package/dist-lib/lib/dimensionData.server.d.ts.map +1 -0
  23. package/dist-lib/lib/dimensionData.server.js +157 -0
  24. package/dist-lib/lib/dimensionPlanner.d.ts +70 -0
  25. package/dist-lib/lib/dimensionPlanner.d.ts.map +1 -0
  26. package/dist-lib/lib/dimensionPlanner.js +217 -0
  27. package/dist-lib/lib/floridayDocuments.functions.d.ts +3054 -2055
  28. package/dist-lib/lib/floridayDocuments.functions.d.ts.map +1 -1
  29. package/dist-lib/lib/floridayPush.functions.d.ts +1048 -715
  30. package/dist-lib/lib/floridayPush.functions.d.ts.map +1 -1
  31. package/dist-lib/lib/loadCarrierPlanner.d.ts +3 -0
  32. package/dist-lib/lib/loadCarrierPlanner.d.ts.map +1 -1
  33. package/dist-lib/lib/loadCarrierPlanning.functions.d.ts +5105 -3465
  34. package/dist-lib/lib/loadCarrierPlanning.functions.d.ts.map +1 -1
  35. package/dist-lib/lib/loadCarrierPlanning.server.d.ts +4 -0
  36. package/dist-lib/lib/loadCarrierPlanning.server.d.ts.map +1 -1
  37. package/dist-lib/lib/loadCarrierPlanning.server.js +113 -3
  38. package/dist-lib/lib/logisticsPlanner.d.ts.map +1 -1
  39. package/dist-lib/lib/logisticsPlanner.js +35 -13
  40. package/dist-lib/lib/logisticsRecalcJob.functions.d.ts +1983 -1317
  41. package/dist-lib/lib/logisticsRecalcJob.functions.d.ts.map +1 -1
  42. package/dist-lib/lib/logisticsRecalculate.functions.d.ts +1048 -715
  43. package/dist-lib/lib/logisticsRecalculate.functions.d.ts.map +1 -1
  44. package/dist-lib/lib/modulePrompts.d.ts +19 -3
  45. package/dist-lib/lib/modulePrompts.d.ts.map +1 -1
  46. package/dist-lib/lib/modulePrompts.js +1666 -1298
  47. package/dist-lib/lib/ruleEngineSchema.d.ts.map +1 -1
  48. package/dist-lib/lib/ruleEngineSchema.js +32 -4
  49. package/dist-lib/lib/ruleEngineWizard.d.ts.map +1 -1
  50. package/dist-lib/lib/ruleEngineWizard.js +4 -1
  51. package/package.json +1 -1
  52. package/dist-lib/components/rule-engine/FloridayConnectionBanner.d.ts +0 -2
  53. package/dist-lib/components/rule-engine/FloridayConnectionBanner.d.ts.map +0 -1
  54. package/dist-lib/components/rule-engine/FloridayConnectionBanner.js +0 -41
  55. package/dist-lib/integrations/supabase/client.d.ts +0 -20293
  56. package/dist-lib/integrations/supabase/client.d.ts.map +0 -1
  57. package/dist-lib/integrations/supabase/client.js +0 -13
@@ -7,157 +7,166 @@
7
7
  // staan en stuur je alleen het nieuwe stuk door.
8
8
  // ============================================================================
9
9
  import { LOGISTICS_MODULES } from "./logisticsTasks";
10
+ /**
11
+ * Modules met een koppelprompt. Naast de modules die taken uitvoeren staat hier
12
+ * ook de Layout maker: die voert geen logistieke taken uit, maar levert wel de
13
+ * documentlayouts waarmee de Engine print.
14
+ */
15
+ export const PROMPT_MODULE_LABELS = {
16
+ ...LOGISTICS_MODULES,
17
+ layoutmaker: "Layout maker (pakbonnen, verzendbrieven, extra)",
18
+ };
10
19
  function sharedBlock(moduleKey) {
11
20
  const key = moduleKey === "<modulesleutel>" ? "<modulesleutel>" : moduleKey;
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
21
+ return `Alle communicatie met de Logistics Engine loopt uitsluitend via de gedeelde
22
+ Supabase-database. Je leest en schrijft gewoon tabellen met de bestaande
23
+ Supabase-client van deze module (ingelogde gebruiker, RLS). Geen API-key,
24
+ geen header, geen secret, geen edge function.
25
+
26
+ Verbinding — verplicht en fail-closed:
27
+ - Gebruik de Supabase-client van je EIGEN module, lazy geïnstantieerd en
28
+ uitsluitend gevoed door import.meta.env.VITE_SUPABASE_URL en
29
+ import.meta.env.VITE_SUPABASE_PUBLISHABLE_KEY. Ontbreekt er een waarde, gooi
30
+ dan een harde fout; raad nooit een verbinding.
31
+ - Hardcodeer NOOIT een Supabase project-ref, URL of anon-key — ook niet als
32
+ fallback, voorbeeld of default. De shell bepaalt via env welke database geldt.
33
+ - Importeer NOOIT uit "@/integrations/supabase/client": dat bestand wordt door
34
+ de Supabase-integratie automatisch gegenereerd en overschreven met een
35
+ vastgezette project-ref, wat in een andere shell naar de verkeerde database
36
+ schrijft. Zet je eigen client in bijvoorbeeld
37
+ "@/integrations/supabase/moduleClient" en sluit het gegenereerde bestand uit
38
+ van je library-build.
39
+
40
+ De Engine beslist alles over buffering, belading, picken, controle, printen en
41
+ transport. Deze module voert alleen nog taken uit en beslist zelf niets meer.
42
+
43
+ --- Gebeurtenis melden: RPC logistics_emit_event (idempotent) ---
44
+ const { data } = await supabase.rpc("logistics_emit_event", {
45
+ p_source_module: "${key}",
46
+ p_event_type: "order.received", // of order.changed, stock.changed
47
+ p_order_ref: "SO-12345",
48
+ p_payload: {
49
+ checkinAt: "2026-08-01T10:00:00Z",
50
+ pickupAt: "2026-08-01T14:00:00Z",
51
+ customerRef: "CUST-1",
52
+ deliveryLocationRef: "LOC-1",
53
+ bufferRef: null,
54
+ loadCarriers: [{ type: "trolley", count: 3 }],
55
+ ruleVersion: null, // optioneel: plan op een vastgelegde regelversie
56
+ },
57
+ p_strategy_id: null, // uuid van logistics_strategies indien bekend
58
+ p_shadow: true,
59
+ p_idempotency_key: "${key}:SO-12345:order.received:v1",
60
+ });
61
+ // data = { eventId, planRunId, status, duplicate }
62
+ Geef ALTIJD een p_idempotency_key mee die uniek is per gebeurtenis. Stuur je
63
+ dezelfde melding twee keer (retry, dubbele webhook), dan plant de Engine maar
64
+ één keer en krijg je duplicate: true terug.
65
+ De Engine pakt de rij direct op (status pending -> processed), plant de keten en
66
+ zet plan_run_id en error terug op dezelfde rij.
67
+
68
+ --- Taken ophalen: SELECT op public.logistics_tasks ---
69
+ const { data } = await supabase
70
+ .from("logistics_tasks")
71
+ .select("*")
72
+ .eq("owner_module", "${key}")
73
+ .eq("status", "planned")
74
+ .order("planned_start", { ascending: true });
75
+ Kolommen: order_ref, buffer_ref, task_type, sequence, depends_on,
76
+ planned_start, planned_end, due_at, status, shadow, payload, result,
77
+ rule_version, claimed_by, claimed_at, error.
78
+
79
+ Realtime kan gewoon met supabase.channel() op logistics_tasks (in useEffect,
80
+ met removeChannel in de cleanup).
81
+
82
+ --- Taak claimen: ALTIJD via RPC, nooit met een eigen UPDATE ---
83
+ const { data: taak } = await supabase.rpc("logistics_claim_task", {
84
+ p_task_id: taskId, p_worker: "station-3",
85
+ });
86
+ if (!taak) { /* al door iemand anders geclaimd */ }
87
+
88
+ // of automatisch de eerstvolgende taak van deze module:
89
+ const { data: volgende } = await supabase.rpc("logistics_claim_next_task", {
90
+ p_owner_module: "${key}", p_worker: "station-3",
91
+ });
92
+
93
+ --- Taak afmelden: ook via RPC ---
94
+ await supabase.rpc("logistics_complete_task", { p_task_id: taskId, p_result: { ... } });
95
+ await supabase.rpc("logistics_fail_task", { p_task_id: taskId, p_error: "reden" });
96
+
97
+ --- Order annuleren ---
98
+ await supabase.rpc("logistics_emit_event", {
99
+ p_source_module: "${key}",
100
+ p_event_type: "order.cancelled",
101
+ p_order_ref: "SO-12345",
102
+ p_payload: { reason: "klant belde af" },
103
+ p_idempotency_key: "${key}:SO-12345:cancel:v1",
104
+ });
105
+ Al afgeronde taken blijven staan. Annuleer nooit zelf met een eigen UPDATE.
106
+
107
+ --- Order splitsen ---
108
+ await supabase.rpc("logistics_emit_event", {
109
+ p_source_module: "${key}",
110
+ p_event_type: "order.split",
111
+ p_order_ref: "SO-12345",
112
+ p_payload: {
113
+ checkinAt: "...", pickupAt: "...",
114
+ children: [
115
+ { orderRef: "SO-12345-A", loadCarriers: [{ type: "trolley", count: 1 }] },
116
+ { orderRef: "SO-12345-B", loadCarriers: [{ type: "trolley", count: 2 }] },
117
+ ],
118
+ },
119
+ p_idempotency_key: "${key}:SO-12345:split:v1",
120
+ });
121
+
122
+ --- Spoed / prioriteit ---
123
+ await supabase.rpc("logistics_set_order_priority", {
124
+ p_order_ref: "SO-12345", p_priority: 100,
125
+ });
126
+
127
+ --- Instellingen inzien (nooit zelf beslissen) ---
128
+ public.logistics_strategies de logistieke strategieën
129
+ public.logistics_rule_configs settings (jsonb) per strategie + current_version
130
+ public.logistics_plan_runs momentopname van de instellingen per planronde
131
+ Alleen lezen, en alleen om te tonen.
132
+
133
+ Ladingdragers: trolley, danish_container, euro_rolcontainer, pallet, euro_pallet, loose
134
+ Taakstatussen: planned, claimed, in_progress, done, skipped, cancelled,
135
+ superseded, failed
136
+
137
+ Eigenaarschap: meldingen, planruns en taken hebben een kolom user_id die de
138
+ database automatisch vult met het ingelogde account.
139
+
140
+ Start in schaduwmodus (shadow: true): eigen logica blijft draaien, Engine-taken
141
+ worden alleen getoond en vergeleken. Elke taak draagt rule_version — een lopende
133
142
  order verandert nooit stilletjes mee als de Rule Engine wijzigt.`;
134
143
  }
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 });
144
+ const STOCK_CONTRACT = `De Engine bepaalt of een bestelling met producten die niet op voorraad zijn
145
+ überhaupt aangemaakt mag worden. Die keuze staat in de Rule Engine
146
+ (Order verwerking -> "Bestellingen uitleveren wanneer een product niet op
147
+ voorraad is"). Beslis dit NOOIT zelf.
148
+
149
+ Roep vóór het opslaan van een nieuwe order (of het wijzigen van de aantallen)
150
+ altijd deze RPC aan:
151
+
152
+ const { data: check } = await supabase.rpc("logistics_check_order_allowed", {
153
+ p_lines: [
154
+ { productId: "<uuid of artikelnummer of barcode of floriday trade item id>", quantity: 12 },
155
+ ],
156
+ p_strategy_id: null, // uuid van logistics_strategies indien bekend
157
+ });
158
+ // check = { allowed, allowBackorder, shortages: [{ productId, product, wanted, available, missing }], reason }
159
+
160
+ Regels:
161
+ - allowed === false -> blokkeer het aanmaken/opslaan van de order volledig en
162
+ toon per regel uit check.shortages:
163
+ "<product>: besteld <wanted>, beschikbaar <available>, tekort <missing>".
164
+ Geen omweg, geen "toch doorgaan"-knop. reason = "insufficient_stock".
165
+ - allowed === true en shortages niet leeg -> order mag wél aangemaakt worden
166
+ (uitleveren zonder voorraad staat aan); de Engine maakt zelf de melding en/of
167
+ e-mail aan. Toon de tekorten alleen ter informatie.
168
+ - Alleen de instelling opvragen (bv. live waarschuwing tijdens typen):
169
+ const { data: mag } = await supabase.rpc("logistics_backorder_allowed", { p_strategy_id: null });
161
170
  - Cache het antwoord niet: de instelling kan op elk moment wijzigen.`;
162
171
  const MODULES = {
163
172
  verkoop: {
@@ -170,16 +179,16 @@ const MODULES = {
170
179
  date: "2026-07-24",
171
180
  summary: "Orderbufferlogica eruit, events en taken via Supabase.",
172
181
  withSharedContract: true,
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.
182
+ body: `Haal weg: alle eigen orderbufferlogica, eigen samenvoegregels en alle
183
+ fetch-calls naar een Engine-URL plus eventuele secrets.
184
+
185
+ Bouw:
186
+ - Bij nieuwe/gewijzigde order een gebeurtenis melden via logistics_emit_event
187
+ (event_type order.received of order.changed).
188
+ - Taken van type buffer.close lezen uit logistics_tasks (owner_module "verkoop"),
189
+ uitvoeren en afmelden via logistics_claim_task + logistics_complete_task.
190
+ - Buffer-samenstelling tonen zoals die in payload staat (matchCriteria, release).
191
+ - release "manual" -> knop Vrijgeven. release "none" -> alleen tonen, niets doen.
183
192
  - In schaduwmodus: Engine-buffersluittijd naast de eigen berekening tonen.`,
184
193
  },
185
194
  {
@@ -187,16 +196,16 @@ Bouw:
187
196
  title: "Voorraadcontrole vóór het aanmaken van een order",
188
197
  date: "2026-07-31",
189
198
  summary: "Nieuwe eis bovenop prompt 1: order blokkeren als een product niet op voorraad is.",
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.
199
+ body: `AANVULLING op koppelprompt 1 (die blijft gewoon geldig, niets terugdraaien).
200
+
201
+ Nieuwe verplichting: vóór het aanmaken of wijzigen van een order eerst de
202
+ voorraadcontrole van de Engine aanroepen.
203
+
204
+ ${STOCK_CONTRACT}
205
+
206
+ In de Verkoop-module concreet:
207
+ - Blokkeer de opslaan-knop van de order zolang allowed false is.
208
+ - Toon de tekorten in het orderscherm, per regel bij het product.
200
209
  - Doe de controle ook opnieuw bij het wijzigen van aantallen of regels.`,
201
210
  },
202
211
  {
@@ -204,51 +213,51 @@ In de Verkoop-module concreet:
204
213
  title: "Bufferen mag alleen als de Engine dat zegt",
205
214
  date: "2026-07-31",
206
215
  summary: "Nieuwe eis bovenop prompt 1 en 2: geen eigen bufferbesluit meer, altijd eerst logistics_buffer_decision aanroepen.",
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
216
+ body: `AANVULLING op koppelprompt 1 en 2 (die blijven gewoon geldig).
217
+
218
+ Probleem: er stonden orders in een orderbuffer terwijl bufferen in de Rule Engine
219
+ UIT stond. De Verkoop-module beslist dat dus nog zelf. Dat moet eruit.
220
+
221
+ Haal weg: elke eigen regel, instelling of code die bepaalt of een order in een
222
+ orderbuffer / gecombineerde order terechtkomt, inclusief automatisch aanmaken of
223
+ toewijzen van een buffer bij het opslaan van een order.
224
+
225
+ Nieuwe verplichting: vraag het per order aan de Engine.
226
+
227
+ const { data: besluit } = await supabase.rpc("logistics_buffer_decision", {
228
+ p_order: {
229
+ orderRef: "SO-12345",
230
+ checkinAt: "2026-08-01T10:00:00Z",
231
+ customerRef: "CUST-1",
232
+ floridayCustomerRef: "FLOR-1",
233
+ bvoCustomerRef: "BVO-1",
234
+ deliveryLocationRef: "LOC-1",
235
+ deliveryDate: "2026-08-01",
236
+ carrierRef: "TRANS-1",
237
+ loadCarrierType: "trolley",
238
+ orderType: "standaard",
239
+ },
240
+ p_strategy_id: null, // uuid van logistics_strategies indien bekend
241
+ });
242
+ // besluit = { buffer, reason, bufferRef, bufferKey, matchCriteria,
243
+ // closeBeforeMinutes, bufferCloseAt, release }
244
+
245
+ Regels:
246
+ - besluit.buffer === false (reason "buffering_disabled") -> de order gaat LOS door.
247
+ Geen buffer aanmaken, geen bestaande buffer toewijzen, geen buffer tonen.
248
+ - besluit.buffer === true -> gebruik exact besluit.bufferRef als buffersleutel.
249
+ Orders met dezelfde bufferRef horen in dezelfde gecombineerde order. Bepaal die
250
+ groepering nooit zelf op eigen criteria.
251
+ - Toon buffersluittijd uit besluit.bufferCloseAt, en de vrijgave uit
252
+ besluit.release ("auto" | "manual" | "none"). Bij "manual" een knop Vrijgeven,
253
+ bij "none" alleen tonen.
254
+ - Cache het antwoord niet: de instelling kan op elk moment wijzigen. Vraag het
255
+ opnieuw bij elke wijziging van de order (aanmeldtijd, klant, locatie, aantallen).
256
+ - Stuur bufferRef mee in de payload van logistics_emit_event (veld bufferRef),
257
+ zodat de Engine en de Verkoop-module dezelfde buffer bedoelen.
258
+
259
+ Eenmalige opschoning: haal bij bestaande, nog niet afgehandelde orders de
260
+ bufferkoppeling weg zodra logistics_buffer_decision voor die order buffer=false
252
261
  teruggeeft. Al verwerkte/verzonden orders laat je met rust.`,
253
262
  },
254
263
  {
@@ -256,33 +265,33 @@ teruggeeft. Al verwerkte/verzonden orders laat je met rust.`,
256
265
  title: "Correctie bufferbesluit: nieuwe velden en uitleg",
257
266
  date: "2026-08-02",
258
267
  summary: "Nieuwe eis bovenop prompt 3: logistics_buffer_decision is gecorrigeerd, stuur alle matchvelden mee en gebruik de uitleg (trace).",
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
268
+ body: `AANVULLING op koppelprompt 1, 2 en 3 (die blijven gewoon geldig).
269
+
270
+ Wat er is gewijzigd aan de Engine-kant:
271
+ - logistics_buffer_decision las de Rule Engine-instellingen fout uit en negeerde de
272
+ aangevinkte criteria. Dat is opgelost: de gekozen criteria worden nu echt vergeleken.
273
+ - De buffersleutel bevat niet langer het exacte sluitmoment. Orders met dezelfde
274
+ gegevens maar een iets andere aanmeldtijd komen nu wél in dezelfde buffer.
275
+ - De functie heeft een derde parameter p_user_id (laat die weg of op null staan).
276
+ - Het antwoord bevat nu ook een veld trace: per criterium waarom het klopt of niet.
277
+
278
+ Wat de Verkoop-module moet doen:
279
+ 1. Stuur bij elke aanroep ALLE velden mee die als criterium gekozen kunnen zijn.
280
+ Ontbreekt een waarde van een aangevinkt criterium, dan combineert de Engine de
281
+ order bewust NIET (reason "buffering_enabled_missing_data").
282
+ Verplichte velden in p_order:
283
+ orderRef, checkinAt, deliveryDate, customerRef, floridayCustomerRef,
284
+ bvoCustomerRef, deliveryLocationRef, carrierRef, loadCarrierType, orderType
285
+ deliveryLocationRef moet een stabiele sleutel zijn van het afleveradres
286
+ (locatie-id of genormaliseerd adres), niet een vrije tekst die per order verschilt.
287
+ 2. Toon in het orderscherm de reden uit besluit.reason en, bij twijfel, de regels uit
288
+ besluit.trace ("waarom zit deze order niet bij die andere order?").
289
+ 3. Vraag het besluit opnieuw op zodra een van bovenstaande velden wijzigt, en
290
+ verplaats de order dan naar de nieuwe bufferRef.
291
+ 4. Bepaal nooit zelf of twee orders bij elkaar horen; vergelijk alleen bufferRef.
292
+
293
+ Eenmalige opschoning: haal voor alle nog niet verwerkte orders opnieuw het besluit op
294
+ en werk de bufferkoppeling bij. Orders die volgens de Engine samen horen, moeten daarna
286
295
  dezelfde bufferRef hebben.`,
287
296
  },
288
297
  {
@@ -290,44 +299,44 @@ dezelfde bufferRef hebben.`,
290
299
  title: "Alleen ladingdragers tonen die de Engine toestaat",
291
300
  date: "2026-08-03",
292
301
  summary: "Nieuwe eis bovenop prompt 1 t/m 4: uitgeschakelde ladingdragers mogen niet meer kiesbaar zijn.",
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
302
+ body: `AANVULLING op koppelprompt 1, 2, 3 en 4 (die blijven gewoon geldig).
303
+
304
+ In de Rule Engine (stap Beladingindeling) staat welke ladingdragers zijn toegestaan.
305
+ Staat een ladingdrager uit, dan mag die in de Verkoop-module nergens meer te kiezen
306
+ zijn.
307
+
308
+ Haal weg: elke eigen, vaste lijst met ladingdragers / verzendmethodes in de code,
309
+ in constanten, in dropdowns of in filters.
310
+
311
+ Nieuwe verplichting: haal de lijst op bij de Engine.
312
+
313
+ const { data: dragers } = await supabase.rpc("logistics_allowed_load_carriers", {
314
+ p_strategy_id: null, // uuid van logistics_strategies indien bekend
315
+ });
316
+ // dragers = {
317
+ // allowed: ["trolley","pallet"],
318
+ // default: "trolley",
319
+ // carriers: [{ code, label, enabled, isDefault }, ...]
320
+ // }
321
+
322
+ Codes en namen (gebruik exact deze codes):
323
+ trolley Stapelwagen
324
+ danish_container Deense container
325
+ euro_rolcontainer Euro-rolcontainer
326
+ pallet Pallet
327
+ euro_pallet Euro-pallet
328
+ loose Geen ladingdrager
329
+
330
+ Regels:
331
+ - Toon in elk keuzeveld (order, orderregel, verzendmethode, filter) alleen de
332
+ dragers met enabled === true. Uitgeschakelde dragers niet grijs tonen: weglaten.
333
+ - Voorselectie = dragers.default.
334
+ - Blokkeer opslaan als een order een ladingdrager heeft die niet in allowed staat,
335
+ met melding: "Deze ladingdrager is uitgeschakeld in de Logistics Engine."
336
+ - Cache de lijst niet langer dan één schermsessie; de instelling kan wijzigen.
337
+ - Bestaande, nog niet verwerkte orders met een inmiddels uitgeschakelde drager:
338
+ markeer die in het overzicht zodat een medewerker een andere drager kiest.
339
+ - Stuur loadCarrierType in logistics_emit_event en logistics_buffer_decision altijd
331
340
  met een van bovenstaande codes mee, nooit met een eigen naam of vrije tekst.`,
332
341
  },
333
342
  {
@@ -335,55 +344,55 @@ Regels:
335
344
  title: "Karindeling: eigen planner eruit, Engine-planner erin",
336
345
  date: "2026-08-27",
337
346
  summary: "Nieuwe eis bovenop prompt 1 t/m 5: /api/public/cron/replan-carts schrijft nog eigen karren met algorithm_version 1 en valt terug op 1×1×1.",
338
- body: `AANVULLING op koppelprompt 1 t/m 5 (die blijven gewoon geldig).
339
-
340
- GEMETEN PROBLEEM (Mathot productie, 27-08-2026)
341
- public.order_carts bevat 11.560 rijen met algorithm_version = 1, nog steeds
342
- groeiend (laatste 13:12:59). Zetten we cron 113 (elke minuut naar
343
- /api/public/cron/replan-carts) uit, dan stopt de aanwas onmiddellijk. De
344
- schrijver is dus de Verkoop-module.
345
-
346
- Bewijs in de Verkoop-module:
347
- - src/lib/cart-replan.functions.ts, regel ~178-190: INSERT in order_carts met
348
- algorithm_version: CART_PLANNER_VERSION.
349
- - src/lib/cart-planner.ts, regel 14: export const CART_PLANNER_VERSION = 1.
350
- - src/lib/cart-replan.functions.ts, regel ~208: INSERT in order_cart_items.
351
- - src/lib/create-order.functions.ts, regel ~1196 + ~1203 en ~1296 + ~1313:
352
- INSERTs die algorithm_version en source helemaal niet zetten, dus de
353
- kolomdefaults ('auto', 1) gebruiken.
354
- - Dit pad rekent zélf (planCarts uit src/lib/cart-planner.ts). Het roept de
355
- gedeelde Engine-planner (loadCarrierPlanning / planByFloridayPacking,
356
- LOAD_CARRIER_PLANNER_VERSION = 3) NIET aan.
357
- - Terugval bij ontbrekende belading: in de mapping naar PlannerInputItem staat
358
- overal \`?? 1\` (plants_per_crate, crates_per_layer, layers_per_cart). Bij een
359
- order zonder bekende belading levert dat stilzwijgend 1×1×1 op — precies wat de
360
- beslisregel van 26-08 verbiedt. De guard (B1/B2/B3) zit niet in dit pad.
361
-
362
- WAT ER MOET GEBEUREN — voorkeur A, minimaal B.
363
-
364
- A (voorkeur). Laat /api/public/cron/replan-carts niet meer zelf rekenen.
365
- Roep de gedeelde planner van de Logistics Engine aan
366
- (@flowselections/logistiek-engine → loadCarrierPlanning /
367
- planByFloridayPacking) en schrijf uitsluitend het resultaat weg. Verwijder
368
- src/lib/cart-planner.ts als schrijfpad. Reden: één rekenhart, dus geen derde
369
- afwijkende indeling naast TS en SQL.
370
-
371
- B (alleen als A nu te groot is, en dan als tussenstap):
372
- - Zet in ELKE INSERT in order_carts expliciet
373
- algorithm_version: LOAD_CARRIER_PLANNER_VERSION (nu 3) en source
374
- ('auto' | 'manual' | 'floriday'). Ook in create-order.functions.ts regel
375
- ~1196 en ~1296. Vertrouw nooit op de kolomdefault.
376
- - Neem dezelfde guard over als de Engine: is de belading van een orderregel
377
- onbekend (geen plants_per_crate / crates_per_layer / layers_per_cart op de
378
- regel én niet op het product), dan wordt die regel NIET ingedeeld. Geen
379
- \`?? 1\`, nooit terugvallen op 1×1×1. Meld het als blokkade op de order,
380
- zodat een medewerker de belading invult.
381
- - Verwijder alle bestaande rijen met algorithm_version < 3 pas nadat A of B
382
- live is; anders komen ze binnen een minuut terug.
383
-
384
- NIET DOEN
385
- - De kolomdefaults van order_carts niet weghalen en algorithm_version niet
386
- NOT NULL maken; dat kan pas als alle schrijfpaden de kolom zetten.
347
+ body: `AANVULLING op koppelprompt 1 t/m 5 (die blijven gewoon geldig).
348
+
349
+ GEMETEN PROBLEEM (Mathot productie, 27-08-2026)
350
+ public.order_carts bevat 11.560 rijen met algorithm_version = 1, nog steeds
351
+ groeiend (laatste 13:12:59). Zetten we cron 113 (elke minuut naar
352
+ /api/public/cron/replan-carts) uit, dan stopt de aanwas onmiddellijk. De
353
+ schrijver is dus de Verkoop-module.
354
+
355
+ Bewijs in de Verkoop-module:
356
+ - src/lib/cart-replan.functions.ts, regel ~178-190: INSERT in order_carts met
357
+ algorithm_version: CART_PLANNER_VERSION.
358
+ - src/lib/cart-planner.ts, regel 14: export const CART_PLANNER_VERSION = 1.
359
+ - src/lib/cart-replan.functions.ts, regel ~208: INSERT in order_cart_items.
360
+ - src/lib/create-order.functions.ts, regel ~1196 + ~1203 en ~1296 + ~1313:
361
+ INSERTs die algorithm_version en source helemaal niet zetten, dus de
362
+ kolomdefaults ('auto', 1) gebruiken.
363
+ - Dit pad rekent zélf (planCarts uit src/lib/cart-planner.ts). Het roept de
364
+ gedeelde Engine-planner (loadCarrierPlanning / planByFloridayPacking,
365
+ LOAD_CARRIER_PLANNER_VERSION = 3) NIET aan.
366
+ - Terugval bij ontbrekende belading: in de mapping naar PlannerInputItem staat
367
+ overal \`?? 1\` (plants_per_crate, crates_per_layer, layers_per_cart). Bij een
368
+ order zonder bekende belading levert dat stilzwijgend 1×1×1 op — precies wat de
369
+ beslisregel van 26-08 verbiedt. De guard (B1/B2/B3) zit niet in dit pad.
370
+
371
+ WAT ER MOET GEBEUREN — voorkeur A, minimaal B.
372
+
373
+ A (voorkeur). Laat /api/public/cron/replan-carts niet meer zelf rekenen.
374
+ Roep de gedeelde planner van de Logistics Engine aan
375
+ (@flowselections/logistiek-engine → loadCarrierPlanning /
376
+ planByFloridayPacking) en schrijf uitsluitend het resultaat weg. Verwijder
377
+ src/lib/cart-planner.ts als schrijfpad. Reden: één rekenhart, dus geen derde
378
+ afwijkende indeling naast TS en SQL.
379
+
380
+ B (alleen als A nu te groot is, en dan als tussenstap):
381
+ - Zet in ELKE INSERT in order_carts expliciet
382
+ algorithm_version: LOAD_CARRIER_PLANNER_VERSION (nu 3) en source
383
+ ('auto' | 'manual' | 'floriday'). Ook in create-order.functions.ts regel
384
+ ~1196 en ~1296. Vertrouw nooit op de kolomdefault.
385
+ - Neem dezelfde guard over als de Engine: is de belading van een orderregel
386
+ onbekend (geen plants_per_crate / crates_per_layer / layers_per_cart op de
387
+ regel én niet op het product), dan wordt die regel NIET ingedeeld. Geen
388
+ \`?? 1\`, nooit terugvallen op 1×1×1. Meld het als blokkade op de order,
389
+ zodat een medewerker de belading invult.
390
+ - Verwijder alle bestaande rijen met algorithm_version < 3 pas nadat A of B
391
+ live is; anders komen ze binnen een minuut terug.
392
+
393
+ NIET DOEN
394
+ - De kolomdefaults van order_carts niet weghalen en algorithm_version niet
395
+ NOT NULL maken; dat kan pas als alle schrijfpaden de kolom zetten.
387
396
  - De beslisregel van 26-08 (B1/B2/B3) niet wijzigen — alleen overnemen.`,
388
397
  },
389
398
  {
@@ -391,42 +400,102 @@ NIET DOEN
391
400
  title: "Orderoverzicht uit de Engine: één waarheid voor alle modules",
392
401
  date: "2026-09-01",
393
402
  summary: "De verkooplijst toont karren, planten en pickdag uit logistics_order_overview, zodat Verkoop, Afhandeling en Pick exact dezelfde cijfers laten zien.",
394
- body: `AANVULLING op koppelprompt 1 t/m 6 (die blijven geldig).
395
-
396
- WAAROM
397
- Elke module telde tot nu toe zelf karren: de pick agenda telde payload-velden van
398
- taken (3517 karren), Controle karren telde cart_numbers (27) en Verkoop telde
399
- order_carts. Drie tellingen, drie antwoorden. De Engine heeft nu één index:
400
- public.logistics_cart_index. Alle schermen lezen daaruit.
401
-
402
- BOUW DIT
403
- - Haal de orderlijst op met:
404
-
405
- const { data, error } = await supabase.rpc("logistics_order_overview", {
406
- p_from: null, p_to: null,
407
- });
408
- if (error) throw new Error(error.message);
409
- const orders = Array.isArray(data) ? data : [];
410
-
411
- - Kolommen: order_uuid, order_number, customer_name, order_source,
412
- delivery_date, status, pick_date, cart_count, plant_count, shifted,
413
- printed_at.
414
- - Toon cart_count en plant_count exact zoals ze binnenkomen; niet zelf
415
- hertellen uit order_carts of order_items.
416
- - pick_date is de pickdag van de Engine; die ligt nooit in het verleden.
417
- - shifted = true toont het label "te laat aangemeld".
418
- - printed_at gevuld = de order is al uitgeprint.
419
-
420
- NIET DOEN
421
- - Geen eigen count op order_carts / order_cart_items voor weergave.
422
- - Geen eigen pickdagberekening uit delivery_date.
423
- - Geen datum, project-ref of Supabase-URL hardcoden.
424
-
425
- CONTROLELIJST
426
- [ ] Karrenaantal in Verkoop is gelijk aan dat in de pick agenda en in Controle.
427
- [ ] Een gemengde kar telt bij elke deelnemende order mee, maar per dag één keer.
403
+ body: `AANVULLING op koppelprompt 1 t/m 6 (die blijven geldig).
404
+
405
+ WAAROM
406
+ Elke module telde tot nu toe zelf karren: de pick agenda telde payload-velden van
407
+ taken (3517 karren), Controle karren telde cart_numbers (27) en Verkoop telde
408
+ order_carts. Drie tellingen, drie antwoorden. De Engine heeft nu één index:
409
+ public.logistics_cart_index. Alle schermen lezen daaruit.
410
+
411
+ BOUW DIT
412
+ - Haal de orderlijst op met:
413
+
414
+ const { data, error } = await supabase.rpc("logistics_order_overview", {
415
+ p_from: null, p_to: null,
416
+ });
417
+ if (error) throw new Error(error.message);
418
+ const orders = Array.isArray(data) ? data : [];
419
+
420
+ - Kolommen: order_uuid, order_number, customer_name, order_source,
421
+ delivery_date, status, pick_date, cart_count, plant_count, shifted,
422
+ printed_at.
423
+ - Toon cart_count en plant_count exact zoals ze binnenkomen; niet zelf
424
+ hertellen uit order_carts of order_items.
425
+ - pick_date is de pickdag van de Engine; die ligt nooit in het verleden.
426
+ - shifted = true toont het label "te laat aangemeld".
427
+ - printed_at gevuld = de order is al uitgeprint.
428
+
429
+ NIET DOEN
430
+ - Geen eigen count op order_carts / order_cart_items voor weergave.
431
+ - Geen eigen pickdagberekening uit delivery_date.
432
+ - Geen datum, project-ref of Supabase-URL hardcoden.
433
+
434
+ CONTROLELIJST
435
+ [ ] Karrenaantal in Verkoop is gelijk aan dat in de pick agenda en in Controle.
436
+ [ ] Een gemengde kar telt bij elke deelnemende order mee, maar per dag één keer.
428
437
  [ ] Verwijderde orders verdwijnen uit de lijst zonder losse taken achter te laten.`,
429
438
  },
439
+ {
440
+ number: 8,
441
+ title: "Statusvelden en leverdatum in het orderoverzicht",
442
+ date: "2026-09-01",
443
+ summary: "logistics_order_overview blijft de enige lijst; de karindex levert nu ook aangemeld/geprint/gecontroleerd en de leverdatum.",
444
+ body: `AANVULLING op prompt 1 t/m 7 (die blijven geldig).
445
+
446
+ WAT ER VERANDERT
447
+ public.logistics_cart_index geeft per (order, kar) nu ook:
448
+ delivery_date, is_announced, is_printed, is_controlled.
449
+ Daarop draaien logistics_pick_agenda, logistics_delivery_agenda,
450
+ control_get_todays_carts, logistics_order_overview en het Picking Station.
451
+
452
+ BOUW DIT
453
+ - Blijf de orderlijst uitsluitend uit logistics_order_overview halen.
454
+ - Wil je voortgang per order tonen, lees dan de statusvelden uit de index of
455
+ uit logistics_delivery_agenda; bereken zelf niets bij.
456
+ - Een filter op status (bijv. "alleen nog niet geprint") mag regels verbergen,
457
+ maar nooit het totaal aan karren veranderen.
458
+
459
+ CONTROLE OP AFWIJKING
460
+ const { data } = await supabase.rpc("logistics_consistency_check");
461
+ Velden: agenda_carts, control_carts, raw_carts, delivery_carts, station_carts,
462
+ sales_carts, announced_carts, orphan_tasks, ok.
463
+ Is ok === false, toon een discrete melding en corrigeer niets zelf.
464
+
465
+ CONTROLELIJST
466
+ [ ] Verkoop, pick agenda, lever agenda, controle en picking station tonen hetzelfde aantal.
467
+ [ ] Geen eigen kartelling meer in de verkoopcode.`,
468
+ },
469
+ {
470
+ number: 9,
471
+ title: "Stamgegevens voor indelen op afmetingen",
472
+ date: "2026-09-03",
473
+ summary: "Producten krijgen een fusttype en een planthoogte, zodat de Engine op fysieke afmetingen kan indelen.",
474
+ body: `AANVULLING op prompt 1 t/m 8 (die blijven geldig).
475
+
476
+ WAT ER VERANDERT
477
+ De Logistiek Engine kent één globale instelling "Methode voor karrenindeling":
478
+ "belading" (huidig gedrag) of "afmetingen". Bij "afmetingen" rekent de Engine
479
+ met echte maten en verzint hij niets.
480
+
481
+ WAT JIJ BOUWT
482
+ 1. In het productbeheer twee velden tonen en opslaan op public.products:
483
+ - fust_type_id -> keuzelijst uit public.fust_types (naam tonen)
484
+ - plant_height_cm -> hoogte van de plant boven het fust, in centimeters
485
+ Beide mogen leeg blijven; ze zijn alleen nodig bij methode "afmetingen".
486
+ 2. Toon per product een discrete markering "afmetingen onvolledig" wanneer
487
+ fust_type_id of plant_height_cm leeg is. Geen standaardwaarde invullen.
488
+ 3. Verzin nooit maten en kopieer ze niet van een ander product.
489
+
490
+ WAT JE NIET DOET
491
+ - Geen keuze per order voor de indelingsmethode: die staat alleen in de Engine.
492
+ - Geen eigen karrenberekening; de Engine blijft de enige bron.
493
+
494
+ CONTROLELIJST
495
+ [ ] Fusttype en planthoogte zijn per product in te vullen en op te slaan.
496
+ [ ] Producten met ontbrekende maten zijn herkenbaar in de lijst.
497
+ [ ] Geen standaardwaardes of gekopieerde maten.`,
498
+ },
430
499
  ],
431
500
  },
432
501
  afhandeling: {
@@ -439,15 +508,15 @@ CONTROLELIJST
439
508
  date: "2026-07-24",
440
509
  summary: "Eigen beladings- en documentlogica eruit, taken uitvoeren.",
441
510
  withSharedContract: true,
442
- body: `Haal weg: eigen beladingindeling, eigen documentmomenten, eigen controle-instellingen.
443
-
444
- Consumeer deze taaktypes:
445
- - load.compose payload: loadCarriers, fillMode (generated | manual_scan)
446
- - control.execute payload: controlMode (paper), noticeAt
447
- - print.control_note payload: printItems (control_note | control_note_and_shipping)
448
- - print.shipping_note payload: moment (with_packing_slip | with_control | after_control | after_scan)
449
- - transport.dispatch
450
-
511
+ body: `Haal weg: eigen beladingindeling, eigen documentmomenten, eigen controle-instellingen.
512
+
513
+ Consumeer deze taaktypes:
514
+ - load.compose payload: loadCarriers, fillMode (generated | manual_scan)
515
+ - control.execute payload: controlMode (paper), noticeAt
516
+ - print.control_note payload: printItems (control_note | control_note_and_shipping)
517
+ - print.shipping_note payload: moment (with_packing_slip | with_control | after_control | after_scan)
518
+ - transport.dispatch
519
+
451
520
  Wat er geprint wordt en wanneer komt volledig uit de taak-payload. Geen eigen keuzes meer.`,
452
521
  },
453
522
  {
@@ -455,28 +524,28 @@ Wat er geprint wordt en wanneer komt volledig uit de taak-payload. Geen eigen ke
455
524
  title: "Documentvrijgave komt volledig van de Engine",
456
525
  date: "2026-08-03",
457
526
  summary: "Pakbon, stickers, verzendbrief en productiebon alleen nog via Engine-taken.",
458
- body: `Deze prompt vervangt niets uit prompt 1; hij komt er bovenop.
459
-
460
- Haal weg: elke eigen regel over wanneer een pakbon, sticker, verzendbrief of
461
- productiebon wordt vrijgegeven of geprint. De Logistics Engine beslist dit nu.
462
-
463
- Nieuwe taaktypes die je moet verwerken (tabel logistics_tasks):
464
- - print.packing_slip payload: release (with_pick_release | hours_before_pick),
465
- pickStationId, pickStationTypeId, requiresApproval
466
- - print.sticker payload: idem als print.packing_slip
467
- - print.shipping_note payload: release (after_checkin | after_scan |
468
- with_packing_slip | manual_approval), pickStationId,
469
- pickStationTypeId, requiresApproval
470
- - print.production_note payload: release (on_stock_upgrade |
471
- manual_approval_after_upgrade), trigger, pickStationId,
472
- pickStationTypeId, requiresApproval
473
-
474
- Regels:
475
- - planned_start van de taak is het vrijgavemoment. Nooit eerder printen.
476
- - pickStationId verwijst naar public.pick_stations.id; pickStationTypeId naar
477
- public.pick_station_types.id. Stuur het document uitsluitend naar dat station.
478
- - requiresApproval = true: het document blijft in de wachtrij tot iemand het op
479
- het pickstation goedkeurt. Meld de goedkeuring met logistics_complete_task.
527
+ body: `Deze prompt vervangt niets uit prompt 1; hij komt er bovenop.
528
+
529
+ Haal weg: elke eigen regel over wanneer een pakbon, sticker, verzendbrief of
530
+ productiebon wordt vrijgegeven of geprint. De Logistics Engine beslist dit nu.
531
+
532
+ Nieuwe taaktypes die je moet verwerken (tabel logistics_tasks):
533
+ - print.packing_slip payload: release (with_pick_release | hours_before_pick),
534
+ pickStationId, pickStationTypeId, requiresApproval
535
+ - print.sticker payload: idem als print.packing_slip
536
+ - print.shipping_note payload: release (after_checkin | after_scan |
537
+ with_packing_slip | manual_approval), pickStationId,
538
+ pickStationTypeId, requiresApproval
539
+ - print.production_note payload: release (on_stock_upgrade |
540
+ manual_approval_after_upgrade), trigger, pickStationId,
541
+ pickStationTypeId, requiresApproval
542
+
543
+ Regels:
544
+ - planned_start van de taak is het vrijgavemoment. Nooit eerder printen.
545
+ - pickStationId verwijst naar public.pick_stations.id; pickStationTypeId naar
546
+ public.pick_station_types.id. Stuur het document uitsluitend naar dat station.
547
+ - requiresApproval = true: het document blijft in de wachtrij tot iemand het op
548
+ het pickstation goedkeurt. Meld de goedkeuring met logistics_complete_task.
480
549
  - Is er geen taak voor een document, dan wordt dat document niet geprint.`,
481
550
  },
482
551
  {
@@ -484,53 +553,53 @@ Regels:
484
553
  title: "Order- en karindeling altijd via de Logistics Engine",
485
554
  date: "2026-08-04",
486
555
  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.",
487
- body: `Deze prompt vervangt niets uit prompt 1 en 2; hij komt er bovenop.
488
-
489
- Geconstateerd probleem: deze module deelt orders en karren nog zelf in en houdt
490
- zich daardoor niet aan stap 3 "Order picken" van de Rule Engine. Dat moet stoppen.
491
-
492
- Haal weg: elke eigen berekening van
493
- - welke orders samen op één belading/kar komen,
494
- - hoeveel orders, klanten, lagen of dozen er op een drager passen,
495
- - wanneer er met picken gestart wordt,
496
- - of er met of zonder scanner gewerkt wordt.
497
-
498
- Zo werkt het vanaf nu:
499
-
500
- 1) Vraag altijd eerst het besluit op voordat je iets indeelt:
501
- const { data: besluit } = await supabase.rpc("logistics_buffer_decision", {
502
- p_order_ref: "SO-12345",
503
- p_customer_ref: "CUST-1",
504
- p_delivery_location_ref: "LOC-1",
505
- p_checkin_at: "2026-08-01T10:00:00Z",
506
- p_pickup_at: "2026-08-01T14:00:00Z",
507
- p_load_carrier_type: "trolley",
508
- p_strategy_id: null,
509
- });
510
- // besluit = { buffer, bufferRef, matchCriteria, release, trace }
511
- Deel de order uitsluitend in bij bufferRef. Is buffer=false, dan blijft de order
512
- los staan: nooit zelf alsnog samenvoegen.
513
-
514
- 2) De feitelijke belading komt uit de taak load.compose in logistics_tasks
515
- (owner_module = "afhandeling"). De payload bevat loadCarriers, fillMode
516
- (generated | manual_scan) en de picksettings uit stap 3: pickMode,
517
- pickStartMode, pickMinutes, staff, reserveMinutes, scanner.
518
- - fillMode = generated: neem de indeling exact over zoals aangeleverd.
519
- - fillMode = manual_scan: laat de medewerker scannen en meld het resultaat
520
- terug; corrigeer de indeling niet zelf.
521
- - scanner = required: geen afronding zonder scan.
522
-
523
- 3) planned_start en planned_end van de taak zijn leidend voor het pickmoment.
524
- Herbereken die nooit zelf, ook niet bij drukte.
525
-
526
- 4) Is er nog geen load.compose taak voor een order, dan meld je dat met
527
- logistics_emit_event (order.received of order.changed) en wacht je op de taak.
528
- Zelf alvast indelen is niet toegestaan.
529
-
530
- 5) Elke taak draagt rule_version. Rond een lopende belading altijd af op de
531
- rule_version van de taak, ook als de Rule Engine intussen is gewijzigd.
532
-
533
- Meld afwijkingen terug met logistics_fail_task inclusief toelichting, zodat de
556
+ body: `Deze prompt vervangt niets uit prompt 1 en 2; hij komt er bovenop.
557
+
558
+ Geconstateerd probleem: deze module deelt orders en karren nog zelf in en houdt
559
+ zich daardoor niet aan stap 3 "Order picken" van de Rule Engine. Dat moet stoppen.
560
+
561
+ Haal weg: elke eigen berekening van
562
+ - welke orders samen op één belading/kar komen,
563
+ - hoeveel orders, klanten, lagen of dozen er op een drager passen,
564
+ - wanneer er met picken gestart wordt,
565
+ - of er met of zonder scanner gewerkt wordt.
566
+
567
+ Zo werkt het vanaf nu:
568
+
569
+ 1) Vraag altijd eerst het besluit op voordat je iets indeelt:
570
+ const { data: besluit } = await supabase.rpc("logistics_buffer_decision", {
571
+ p_order_ref: "SO-12345",
572
+ p_customer_ref: "CUST-1",
573
+ p_delivery_location_ref: "LOC-1",
574
+ p_checkin_at: "2026-08-01T10:00:00Z",
575
+ p_pickup_at: "2026-08-01T14:00:00Z",
576
+ p_load_carrier_type: "trolley",
577
+ p_strategy_id: null,
578
+ });
579
+ // besluit = { buffer, bufferRef, matchCriteria, release, trace }
580
+ Deel de order uitsluitend in bij bufferRef. Is buffer=false, dan blijft de order
581
+ los staan: nooit zelf alsnog samenvoegen.
582
+
583
+ 2) De feitelijke belading komt uit de taak load.compose in logistics_tasks
584
+ (owner_module = "afhandeling"). De payload bevat loadCarriers, fillMode
585
+ (generated | manual_scan) en de picksettings uit stap 3: pickMode,
586
+ pickStartMode, pickMinutes, staff, reserveMinutes, scanner.
587
+ - fillMode = generated: neem de indeling exact over zoals aangeleverd.
588
+ - fillMode = manual_scan: laat de medewerker scannen en meld het resultaat
589
+ terug; corrigeer de indeling niet zelf.
590
+ - scanner = required: geen afronding zonder scan.
591
+
592
+ 3) planned_start en planned_end van de taak zijn leidend voor het pickmoment.
593
+ Herbereken die nooit zelf, ook niet bij drukte.
594
+
595
+ 4) Is er nog geen load.compose taak voor een order, dan meld je dat met
596
+ logistics_emit_event (order.received of order.changed) en wacht je op de taak.
597
+ Zelf alvast indelen is niet toegestaan.
598
+
599
+ 5) Elke taak draagt rule_version. Rond een lopende belading altijd af op de
600
+ rule_version van de taak, ook als de Rule Engine intussen is gewijzigd.
601
+
602
+ Meld afwijkingen terug met logistics_fail_task inclusief toelichting, zodat de
534
603
  Engine kan herplannen. Val nooit terug op eigen logica.`,
535
604
  },
536
605
  {
@@ -538,50 +607,50 @@ Engine kan herplannen. Val nooit terug op eigen logica.`,
538
607
  title: "Karren verdelen over meerdere ophaalmomenten",
539
608
  date: "2026-08-04",
540
609
  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.",
541
- body: `LEES DIT EERST — hoe je deze prompt uitvoert
542
-
543
- Alles uit prompt 1 t/m 4 blijft gelden. Hier komt er één ding bij: een dag kan
544
- MEERDERE ophaalmomenten hebben, en de Logistics Engine bepaalt welke kar bij
545
- welk ophaalmoment hoort. Jij rekent NIETS zelf uit en verplaatst NOOIT zelf een
546
- kar op basis van eigen logica.
547
-
548
- 1) Ophaalmomenten lezen
549
- Tabel: public.logistics_pickup_moments
550
- Kolommen: id, moment_date, sequence, label, pickup_time, carrier_ref
551
- Toon per dag de ophaalmomenten op volgorde van sequence/pickup_time.
552
-
553
- 2) Karren per ophaalmoment
554
- Tabel public.order_carts heeft nieuwe kolommen:
555
- - pickup_moment_id → bij welk ophaalmoment de kar hoort
556
- - moved_from_moment_id → het ophaalmoment waar de kar oorspronkelijk stond
557
- - moved_reason → 'capacity_shift' als de Engine hem doorschoof
558
- - moved_at → wanneer dat gebeurde
559
- - pickup_locked → true = deze kar mag NIET doorgeschoven worden
560
-
561
- Groepeer de pickagenda en de leveringsplanning voortaan op pickup_moment_id,
562
- niet meer alleen op de dag. Een kar met moved_reason = 'capacity_shift' toon
563
- je met een duidelijk label "doorgeschoven vanaf <label vorig moment>".
564
-
565
- 3) Regels die je moet respecteren
566
- - Alleen COMPLETE karren wisselen van ophaalmoment. Splits nooit een kar en
567
- verplaats nooit losse orderregels naar een ander moment.
568
- - Karren uit een buffer mogen mee, maar alleen als geheel.
569
- - Wil een gebruiker een kar vastzetten op een moment, zet dan
570
- pickup_locked = true. Meer doe je niet; de Engine slaat die kar over.
571
-
572
- 4) Herberekenen
573
- Na elke wijziging die de planning raakt (nieuwe order, annulering, andere
574
- ophaaltijd, andere kar-indeling) roep je aan:
575
- await supabase.rpc("logistics_capacity_plan_day", { p_date: "2026-08-04" })
576
- De functie geeft per ophaalmoment terug: requiredMinutes, availableMinutes,
577
- feasible en movedCarts. Toon die uitkomst; verzin geen eigen berekening.
578
-
579
- 5) Tekort tonen
580
- Tabel public.logistics_capacity_alerts bevat per dag/ophaalmoment het tekort
581
- (shortage_minutes, carts_short, moved_carts, unplanned_carts). Staat er een
582
- rij met status = 'open', toon dan bij dat ophaalmoment een rode waarschuwing
583
- met het tekort en het aantal karren dat niet op tijd verwerkt kan worden.
584
-
610
+ body: `LEES DIT EERST — hoe je deze prompt uitvoert
611
+
612
+ Alles uit prompt 1 t/m 4 blijft gelden. Hier komt er één ding bij: een dag kan
613
+ MEERDERE ophaalmomenten hebben, en de Logistics Engine bepaalt welke kar bij
614
+ welk ophaalmoment hoort. Jij rekent NIETS zelf uit en verplaatst NOOIT zelf een
615
+ kar op basis van eigen logica.
616
+
617
+ 1) Ophaalmomenten lezen
618
+ Tabel: public.logistics_pickup_moments
619
+ Kolommen: id, moment_date, sequence, label, pickup_time, carrier_ref
620
+ Toon per dag de ophaalmomenten op volgorde van sequence/pickup_time.
621
+
622
+ 2) Karren per ophaalmoment
623
+ Tabel public.order_carts heeft nieuwe kolommen:
624
+ - pickup_moment_id → bij welk ophaalmoment de kar hoort
625
+ - moved_from_moment_id → het ophaalmoment waar de kar oorspronkelijk stond
626
+ - moved_reason → 'capacity_shift' als de Engine hem doorschoof
627
+ - moved_at → wanneer dat gebeurde
628
+ - pickup_locked → true = deze kar mag NIET doorgeschoven worden
629
+
630
+ Groepeer de pickagenda en de leveringsplanning voortaan op pickup_moment_id,
631
+ niet meer alleen op de dag. Een kar met moved_reason = 'capacity_shift' toon
632
+ je met een duidelijk label "doorgeschoven vanaf <label vorig moment>".
633
+
634
+ 3) Regels die je moet respecteren
635
+ - Alleen COMPLETE karren wisselen van ophaalmoment. Splits nooit een kar en
636
+ verplaats nooit losse orderregels naar een ander moment.
637
+ - Karren uit een buffer mogen mee, maar alleen als geheel.
638
+ - Wil een gebruiker een kar vastzetten op een moment, zet dan
639
+ pickup_locked = true. Meer doe je niet; de Engine slaat die kar over.
640
+
641
+ 4) Herberekenen
642
+ Na elke wijziging die de planning raakt (nieuwe order, annulering, andere
643
+ ophaaltijd, andere kar-indeling) roep je aan:
644
+ await supabase.rpc("logistics_capacity_plan_day", { p_date: "2026-08-04" })
645
+ De functie geeft per ophaalmoment terug: requiredMinutes, availableMinutes,
646
+ feasible en movedCarts. Toon die uitkomst; verzin geen eigen berekening.
647
+
648
+ 5) Tekort tonen
649
+ Tabel public.logistics_capacity_alerts bevat per dag/ophaalmoment het tekort
650
+ (shortage_minutes, carts_short, moved_carts, unplanned_carts). Staat er een
651
+ rij met status = 'open', toon dan bij dat ophaalmoment een rode waarschuwing
652
+ met het tekort en het aantal karren dat niet op tijd verwerkt kan worden.
653
+
585
654
  Val nooit terug op eigen logica. Wat de Engine teruggeeft, is leidend.`,
586
655
  },
587
656
  {
@@ -589,131 +658,131 @@ Val nooit terug op eigen logica. Wat de Engine teruggeeft, is leidend.`,
589
658
  title: "Order handmatig verzetten en ophaaltijd wijzigen",
590
659
  date: "2026-08-04",
591
660
  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.",
592
- body: `LEES DIT EERST — hoe je deze prompt uitvoert
593
-
594
- Deze prompt beschrijft één nieuwe functie in de Afhandeling-module: "Order
595
- verzetten". Je bouwt hem volledig, niet gedeeltelijk. Alles wat hieronder staat
596
- is verplicht. Er is GEEN eigen rekenwerk: de Logistics Engine levert twee kant-en-
597
- klare database-functies, jij bouwt alleen het scherm eromheen en toont wat de
598
- Engine teruggeeft.
599
-
600
- Concreet ben je klaar als dit alle vier waar is:
601
- 1. Er is een knop "Verzetten" bij een order (planning-/agenda-/leverscherm).
602
- 2. Het dialoogvenster toont een DAGKEUZE en een OPHAALTIJD (van/tot).
603
- 3. Voor het opslaan is logistics_reschedule_preview aangeroepen en wordt de
604
- uitkomst daarvan letterlijk aan de gebruiker getoond.
605
- 4. Opslaan gebeurt uitsluitend met logistics_reschedule_order.
606
-
607
- ------------------------------------------------------------------
608
- DE TWEE REGELS DIE DE GEBRUIKER WIL
609
- ------------------------------------------------------------------
610
-
611
- A. Order gaat NAAR VOREN (nieuwe ophaaldag is eerder dan de huidige)
612
- -> Het AFLEVERMOMENT (leverdatum naar de klant) blijft ONGEWIJZIGD.
613
- De order wordt dus eerder klaargemaakt en opgehaald, maar de klant krijgt
614
- hem op dezelfde dag als afgesproken. Je verandert de leverdatum NIET.
615
- Er gaat GEEN melding naar de klant.
616
-
617
- B. Order gaat NAAR ACHTEREN (nieuwe ophaaldag is later)
618
- -> Het aflevermoment schuift mee.
619
- -> De gebruiker MOET in het dialoogvenster de keuze krijgen:
620
- "Klant informeren over de nieuwe leverdatum" (aan/uit) plus een reden.
621
- Staat het aan, dan zorgt de Engine voor de klantmelding. Bouw daar zelf
622
- geen mailflow voor.
623
-
624
- C. Alleen de OPHAALTIJD wijzigen (zelfde dag, andere van/tot-tijd) mag altijd
625
- en geldt niet als verzetten: geen klantmelding, leverdatum blijft gelijk.
626
-
627
- ------------------------------------------------------------------
628
- STAP 1 — VOORVERTONING (verplicht vóór opslaan)
629
- ------------------------------------------------------------------
630
- const { data: preview } = await supabase.rpc("logistics_reschedule_preview", {
631
- p_order_ref: "SO-12345", // orders.order_number
632
- p_pickup_date: "2026-08-07", // nieuwe ophaaldag (YYYY-MM-DD), null = ongewijzigd
633
- p_pickup_from: "06:00", // nieuwe starttijd, null = ongewijzigd
634
- p_pickup_to: "09:00", // nieuwe eindtijd, null = ongewijzigd
635
- });
636
-
637
- preview bevat:
638
- {
639
- ok: true,
640
- orderRef, orderId, bufferId, planLocked,
641
- current: { pickupDate, pickupFrom, pickupTo, deliveryDate },
642
- next: { pickupDate, pickupFrom, pickupTo, deliveryDate },
643
- direction: "earlier" | "later" | "same",
644
- dayShift: -2, // aantal dagen verschil
645
- timeChanged: true,
646
- keepDeliveryMoment: true, // true bij naar voren: leverdatum blijft
647
- customerNoticeRequired: true, // true bij naar achteren: toon de klantoptie
648
- affectedTasks: 8 // aantal taken dat opnieuw wordt ingepland
649
- }
650
-
651
- Wat je met deze velden DOET in het scherm:
652
- - ok === false -> toon preview.error en blokkeer opslaan.
653
- - direction === "earlier" -> toon: "Order wordt X dag(en) eerder opgehaald. Het
654
- aflevermoment blijft {current.deliveryDate}." Verberg de klantoptie.
655
- - direction === "later" -> toon: "Order wordt X dag(en) later. Nieuwe leverdatum:
656
- {next.deliveryDate}." Toon de schakelaar "Klant informeren" (standaard AAN) en
657
- een verplicht redenveld.
658
- - direction === "same" && timeChanged -> toon alleen de nieuwe ophaaltijd.
659
- - Toon altijd affectedTasks: "X taken worden opnieuw ingepland."
660
-
661
- ------------------------------------------------------------------
662
- STAP 2 — OPSLAAN
663
- ------------------------------------------------------------------
664
- const { data: result } = await supabase.rpc("logistics_reschedule_order", {
665
- p_order_ref: "SO-12345",
666
- p_pickup_date: "2026-08-07",
667
- p_pickup_from: "06:00",
668
- p_pickup_to: "09:00",
669
- p_notify_customer: true, // alleen relevant bij direction "later"
670
- p_reason: "Aanvoer bloemen vertraagd",
671
- p_source_module: "afhandeling",
672
- p_user_id: null,
673
- });
674
- // result = preview-velden + { applied: true, rescheduleId, delayAlertId,
675
- // customerNotified, pickupMoment }
676
-
677
- De Engine doet hierbij zelf al ALLES hieronder — doe dit dus niet nogmaals:
678
- - orders.custom_fields.pickup_moment bijwerken ("YYYY-MM-DD|van|tot");
679
- - leverdatum alleen aanpassen bij naar achteren;
680
- - de order uit zijn orderbuffer halen zodat hij niet aan de oude dag vastzit;
681
- - de klantmelding aanmaken als p_notify_customer true is;
682
- - een order.changed-melding sturen en de hele takenketen (buffer, pick, controle,
683
- printen, transport) opnieuw inplannen met logistics_schedule_tasks;
684
- - het verzetten vastleggen in public.logistics_reschedules.
685
-
686
- ------------------------------------------------------------------
687
- WAT JE ABSOLUUT NIET DOET
688
- ------------------------------------------------------------------
689
- - Geen eigen UPDATE op orders, order_buffers, order_carts of logistics_tasks om
690
- een datum of tijd te verzetten. Uitsluitend via de twee RPC's hierboven.
691
- - Zelf planned_start / planned_end / due_at van taken herberekenen. Na het
692
- opslaan lees je de taken gewoon opnieuw; ze staan dan al goed.
693
- - Bij naar voren de leverdatum meeschuiven. Dat is expliciet fout.
694
- - Bij naar achteren de klant informeren zonder dat de gebruiker dat heeft
695
- aangevinkt, of een eigen mail sturen buiten de Engine om.
696
-
697
- ------------------------------------------------------------------
698
- NA HET OPSLAAN
699
- ------------------------------------------------------------------
700
- 1. Herlaad de taken van deze order uit logistics_tasks (planned_start bepaalt
701
- waar de order in de pickagenda en het leverscherm verschijnt).
702
- 2. Toon een bevestiging met de nieuwe ophaaldag, de ophaaltijd, de leverdatum en
703
- of de klant is geïnformeerd (result.customerNotified).
704
- 3. De verzetgeschiedenis kun je tonen met:
705
- supabase.from("logistics_reschedules").select("*").eq("order_ref", ref)
706
- .order("created_at", { ascending: false });
707
-
708
- ------------------------------------------------------------------
709
- CONTROLELIJST VOOR JEZELF (loop deze af voor je klaar meldt)
710
- ------------------------------------------------------------------
711
- [ ] Knop "Verzetten" bestaat bij een order.
712
- [ ] Dagkeuze én ophaaltijd van/tot zijn in te vullen.
713
- [ ] preview wordt live aangeroepen bij elke wijziging in het formulier.
714
- [ ] Naar voren: leverdatum ongewijzigd, geen klantoptie zichtbaar.
715
- [ ] Naar achteren: nieuwe leverdatum zichtbaar + klantoptie + reden.
716
- [ ] Opslaan gaat via logistics_reschedule_order, nergens een eigen UPDATE.
661
+ body: `LEES DIT EERST — hoe je deze prompt uitvoert
662
+
663
+ Deze prompt beschrijft één nieuwe functie in de Afhandeling-module: "Order
664
+ verzetten". Je bouwt hem volledig, niet gedeeltelijk. Alles wat hieronder staat
665
+ is verplicht. Er is GEEN eigen rekenwerk: de Logistics Engine levert twee kant-en-
666
+ klare database-functies, jij bouwt alleen het scherm eromheen en toont wat de
667
+ Engine teruggeeft.
668
+
669
+ Concreet ben je klaar als dit alle vier waar is:
670
+ 1. Er is een knop "Verzetten" bij een order (planning-/agenda-/leverscherm).
671
+ 2. Het dialoogvenster toont een DAGKEUZE en een OPHAALTIJD (van/tot).
672
+ 3. Voor het opslaan is logistics_reschedule_preview aangeroepen en wordt de
673
+ uitkomst daarvan letterlijk aan de gebruiker getoond.
674
+ 4. Opslaan gebeurt uitsluitend met logistics_reschedule_order.
675
+
676
+ ------------------------------------------------------------------
677
+ DE TWEE REGELS DIE DE GEBRUIKER WIL
678
+ ------------------------------------------------------------------
679
+
680
+ A. Order gaat NAAR VOREN (nieuwe ophaaldag is eerder dan de huidige)
681
+ -> Het AFLEVERMOMENT (leverdatum naar de klant) blijft ONGEWIJZIGD.
682
+ De order wordt dus eerder klaargemaakt en opgehaald, maar de klant krijgt
683
+ hem op dezelfde dag als afgesproken. Je verandert de leverdatum NIET.
684
+ Er gaat GEEN melding naar de klant.
685
+
686
+ B. Order gaat NAAR ACHTEREN (nieuwe ophaaldag is later)
687
+ -> Het aflevermoment schuift mee.
688
+ -> De gebruiker MOET in het dialoogvenster de keuze krijgen:
689
+ "Klant informeren over de nieuwe leverdatum" (aan/uit) plus een reden.
690
+ Staat het aan, dan zorgt de Engine voor de klantmelding. Bouw daar zelf
691
+ geen mailflow voor.
692
+
693
+ C. Alleen de OPHAALTIJD wijzigen (zelfde dag, andere van/tot-tijd) mag altijd
694
+ en geldt niet als verzetten: geen klantmelding, leverdatum blijft gelijk.
695
+
696
+ ------------------------------------------------------------------
697
+ STAP 1 — VOORVERTONING (verplicht vóór opslaan)
698
+ ------------------------------------------------------------------
699
+ const { data: preview } = await supabase.rpc("logistics_reschedule_preview", {
700
+ p_order_ref: "SO-12345", // orders.order_number
701
+ p_pickup_date: "2026-08-07", // nieuwe ophaaldag (YYYY-MM-DD), null = ongewijzigd
702
+ p_pickup_from: "06:00", // nieuwe starttijd, null = ongewijzigd
703
+ p_pickup_to: "09:00", // nieuwe eindtijd, null = ongewijzigd
704
+ });
705
+
706
+ preview bevat:
707
+ {
708
+ ok: true,
709
+ orderRef, orderId, bufferId, planLocked,
710
+ current: { pickupDate, pickupFrom, pickupTo, deliveryDate },
711
+ next: { pickupDate, pickupFrom, pickupTo, deliveryDate },
712
+ direction: "earlier" | "later" | "same",
713
+ dayShift: -2, // aantal dagen verschil
714
+ timeChanged: true,
715
+ keepDeliveryMoment: true, // true bij naar voren: leverdatum blijft
716
+ customerNoticeRequired: true, // true bij naar achteren: toon de klantoptie
717
+ affectedTasks: 8 // aantal taken dat opnieuw wordt ingepland
718
+ }
719
+
720
+ Wat je met deze velden DOET in het scherm:
721
+ - ok === false -> toon preview.error en blokkeer opslaan.
722
+ - direction === "earlier" -> toon: "Order wordt X dag(en) eerder opgehaald. Het
723
+ aflevermoment blijft {current.deliveryDate}." Verberg de klantoptie.
724
+ - direction === "later" -> toon: "Order wordt X dag(en) later. Nieuwe leverdatum:
725
+ {next.deliveryDate}." Toon de schakelaar "Klant informeren" (standaard AAN) en
726
+ een verplicht redenveld.
727
+ - direction === "same" && timeChanged -> toon alleen de nieuwe ophaaltijd.
728
+ - Toon altijd affectedTasks: "X taken worden opnieuw ingepland."
729
+
730
+ ------------------------------------------------------------------
731
+ STAP 2 — OPSLAAN
732
+ ------------------------------------------------------------------
733
+ const { data: result } = await supabase.rpc("logistics_reschedule_order", {
734
+ p_order_ref: "SO-12345",
735
+ p_pickup_date: "2026-08-07",
736
+ p_pickup_from: "06:00",
737
+ p_pickup_to: "09:00",
738
+ p_notify_customer: true, // alleen relevant bij direction "later"
739
+ p_reason: "Aanvoer bloemen vertraagd",
740
+ p_source_module: "afhandeling",
741
+ p_user_id: null,
742
+ });
743
+ // result = preview-velden + { applied: true, rescheduleId, delayAlertId,
744
+ // customerNotified, pickupMoment }
745
+
746
+ De Engine doet hierbij zelf al ALLES hieronder — doe dit dus niet nogmaals:
747
+ - orders.custom_fields.pickup_moment bijwerken ("YYYY-MM-DD|van|tot");
748
+ - leverdatum alleen aanpassen bij naar achteren;
749
+ - de order uit zijn orderbuffer halen zodat hij niet aan de oude dag vastzit;
750
+ - de klantmelding aanmaken als p_notify_customer true is;
751
+ - een order.changed-melding sturen en de hele takenketen (buffer, pick, controle,
752
+ printen, transport) opnieuw inplannen met logistics_schedule_tasks;
753
+ - het verzetten vastleggen in public.logistics_reschedules.
754
+
755
+ ------------------------------------------------------------------
756
+ WAT JE ABSOLUUT NIET DOET
757
+ ------------------------------------------------------------------
758
+ - Geen eigen UPDATE op orders, order_buffers, order_carts of logistics_tasks om
759
+ een datum of tijd te verzetten. Uitsluitend via de twee RPC's hierboven.
760
+ - Zelf planned_start / planned_end / due_at van taken herberekenen. Na het
761
+ opslaan lees je de taken gewoon opnieuw; ze staan dan al goed.
762
+ - Bij naar voren de leverdatum meeschuiven. Dat is expliciet fout.
763
+ - Bij naar achteren de klant informeren zonder dat de gebruiker dat heeft
764
+ aangevinkt, of een eigen mail sturen buiten de Engine om.
765
+
766
+ ------------------------------------------------------------------
767
+ NA HET OPSLAAN
768
+ ------------------------------------------------------------------
769
+ 1. Herlaad de taken van deze order uit logistics_tasks (planned_start bepaalt
770
+ waar de order in de pickagenda en het leverscherm verschijnt).
771
+ 2. Toon een bevestiging met de nieuwe ophaaldag, de ophaaltijd, de leverdatum en
772
+ of de klant is geïnformeerd (result.customerNotified).
773
+ 3. De verzetgeschiedenis kun je tonen met:
774
+ supabase.from("logistics_reschedules").select("*").eq("order_ref", ref)
775
+ .order("created_at", { ascending: false });
776
+
777
+ ------------------------------------------------------------------
778
+ CONTROLELIJST VOOR JEZELF (loop deze af voor je klaar meldt)
779
+ ------------------------------------------------------------------
780
+ [ ] Knop "Verzetten" bestaat bij een order.
781
+ [ ] Dagkeuze én ophaaltijd van/tot zijn in te vullen.
782
+ [ ] preview wordt live aangeroepen bij elke wijziging in het formulier.
783
+ [ ] Naar voren: leverdatum ongewijzigd, geen klantoptie zichtbaar.
784
+ [ ] Naar achteren: nieuwe leverdatum zichtbaar + klantoptie + reden.
785
+ [ ] Opslaan gaat via logistics_reschedule_order, nergens een eigen UPDATE.
717
786
  [ ] Na opslaan worden de taken opnieuw geladen en klopt de agenda.`,
718
787
  },
719
788
  {
@@ -721,40 +790,40 @@ CONTROLELIJST VOOR JEZELF (loop deze af voor je klaar meldt)
721
790
  title: "Capaciteitswaarschuwing tonen in de pick agenda",
722
791
  date: "2026-08-04",
723
792
  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.",
724
- body: `LEES DIT EERST
725
-
726
- De Logistics Engine rekent per pickdag uit hoeveel picktijd alle geplande karren
727
- kosten en hoeveel picktijd er beschikbaar is (aantal medewerkers x werktijden uit
728
- "4. Pickstations"). Jij rekent NIETS zelf uit; je toont alleen wat de Engine geeft.
729
-
730
- STAP 1 — BEREKENEN (bij het openen van een week/dag)
731
- const { data: dagen } = await supabase.rpc("logistics_capacity_day_check", {
732
- p_from: "2026-08-03", // eerste dag van de getoonde periode
733
- p_to: "2026-08-09", // laatste dag
734
- });
735
-
736
- dagen is een array met per dag:
737
- { date, carts, requiredMinutes, availableMinutes, shortageMinutes, staff }
738
-
739
- STAP 2 — OPEN WAARSCHUWINGEN LEZEN
740
- const { data: alerts } = await supabase
741
- .from("logistics_capacity_alerts")
742
- .select("plan_date, required_minutes, available_minutes, shortage_minutes, carts_total, carts_short")
743
- .eq("status", "open");
744
-
745
- STAP 3 — TONEN (verplicht)
746
- - Weekoverzicht: elke dag met shortageMinutes > 0 krijgt een duidelijk zichtbare
747
- waarschuwing (amber/rood) met de tekst:
748
- "Te weinig capaciteit: {carts} karren vragen {requiredMinutes} min picktijd,
749
- beschikbaar is {availableMinutes} min ({staff} medewerkers).
750
- Circa {carts_short} karren passen niet op deze dag."
751
- - Dagdetails: dezelfde waarschuwing bovenaan het dagscherm.
752
- - Dagen zonder tekort krijgen GEEN waarschuwing.
753
- - Verzin geen eigen drempels, kleuren-logica of eigen berekening van picktijd.
754
-
755
- CONTROLELIJST
756
- [ ] logistics_capacity_day_check wordt aangeroepen bij elke periodewissel.
757
- [ ] Dagen met tekort tonen de waarschuwing in het weekoverzicht én in dagdetails.
793
+ body: `LEES DIT EERST
794
+
795
+ De Logistics Engine rekent per pickdag uit hoeveel picktijd alle geplande karren
796
+ kosten en hoeveel picktijd er beschikbaar is (aantal medewerkers x werktijden uit
797
+ "4. Pickstations"). Jij rekent NIETS zelf uit; je toont alleen wat de Engine geeft.
798
+
799
+ STAP 1 — BEREKENEN (bij het openen van een week/dag)
800
+ const { data: dagen } = await supabase.rpc("logistics_capacity_day_check", {
801
+ p_from: "2026-08-03", // eerste dag van de getoonde periode
802
+ p_to: "2026-08-09", // laatste dag
803
+ });
804
+
805
+ dagen is een array met per dag:
806
+ { date, carts, requiredMinutes, availableMinutes, shortageMinutes, staff }
807
+
808
+ STAP 2 — OPEN WAARSCHUWINGEN LEZEN
809
+ const { data: alerts } = await supabase
810
+ .from("logistics_capacity_alerts")
811
+ .select("plan_date, required_minutes, available_minutes, shortage_minutes, carts_total, carts_short")
812
+ .eq("status", "open");
813
+
814
+ STAP 3 — TONEN (verplicht)
815
+ - Weekoverzicht: elke dag met shortageMinutes > 0 krijgt een duidelijk zichtbare
816
+ waarschuwing (amber/rood) met de tekst:
817
+ "Te weinig capaciteit: {carts} karren vragen {requiredMinutes} min picktijd,
818
+ beschikbaar is {availableMinutes} min ({staff} medewerkers).
819
+ Circa {carts_short} karren passen niet op deze dag."
820
+ - Dagdetails: dezelfde waarschuwing bovenaan het dagscherm.
821
+ - Dagen zonder tekort krijgen GEEN waarschuwing.
822
+ - Verzin geen eigen drempels, kleuren-logica of eigen berekening van picktijd.
823
+
824
+ CONTROLELIJST
825
+ [ ] logistics_capacity_day_check wordt aangeroepen bij elke periodewissel.
826
+ [ ] Dagen met tekort tonen de waarschuwing in het weekoverzicht én in dagdetails.
758
827
  [ ] De getoonde cijfers komen letterlijk uit de Engine.`,
759
828
  },
760
829
  {
@@ -762,31 +831,31 @@ CONTROLELIJST
762
831
  title: "Geen capaciteitsmeldingen in Planning en leveringen",
763
832
  date: "2026-08-04",
764
833
  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.",
765
- body: `AANVULLING op prompt 1 t/m 6. Deze nieuwste eis vervangt uitsluitend de
766
- zichtbare meldingen uit prompt 5 en 6 binnen het scherm "Planning en leveringen".
767
-
768
- Verwijder uit het weekoverzicht en de dagdetails van "Planning en leveringen":
769
- - de melding "Te weinig capaciteit";
770
- - aantallen tekortkomende minuten, medewerkers of karren;
771
- - rode, amberkleurige of andere fout-/waarschuwingsblokken uit
772
- public.logistics_capacity_alerts;
773
- - foutmeldingen die afkomstig zijn van logistics_capacity_day_check.
774
-
775
- De agenda toont alleen de planning, bestellingen, karren, planten en statussen.
776
- Een tekort mag de agenda niet visueel blokkeren of als foutmelding worden getoond.
777
-
778
- BELANGRIJK — alleen de zichtbaarheid vervalt:
779
- - Blijf logistics_capacity_day_check en logistics_capacity_plan_day uitvoeren waar
780
- dat nodig is voor de planning en automatische herverdeling.
781
- - Verwijder of sluit public.logistics_capacity_alerts niet.
782
- - Capaciteitsmeldingen blijven beschikbaar in het Regieoverzicht van de Logistics
783
- Engine en in het centrale Meldingencentrum.
784
- - Verberg echte technische fouten niet: een mislukte laad- of opslagactie mag nog
785
- steeds via een normale toast worden gemeld, maar niet als melding in de agenda.
786
-
787
- CONTROLELIJST
788
- [ ] In Planning en leveringen staat bij geen enkele dag een capaciteitsmelding.
789
- [ ] Het openen en wisselen van weken blijft de Engine-berekening uitvoeren.
834
+ body: `AANVULLING op prompt 1 t/m 6. Deze nieuwste eis vervangt uitsluitend de
835
+ zichtbare meldingen uit prompt 5 en 6 binnen het scherm "Planning en leveringen".
836
+
837
+ Verwijder uit het weekoverzicht en de dagdetails van "Planning en leveringen":
838
+ - de melding "Te weinig capaciteit";
839
+ - aantallen tekortkomende minuten, medewerkers of karren;
840
+ - rode, amberkleurige of andere fout-/waarschuwingsblokken uit
841
+ public.logistics_capacity_alerts;
842
+ - foutmeldingen die afkomstig zijn van logistics_capacity_day_check.
843
+
844
+ De agenda toont alleen de planning, bestellingen, karren, planten en statussen.
845
+ Een tekort mag de agenda niet visueel blokkeren of als foutmelding worden getoond.
846
+
847
+ BELANGRIJK — alleen de zichtbaarheid vervalt:
848
+ - Blijf logistics_capacity_day_check en logistics_capacity_plan_day uitvoeren waar
849
+ dat nodig is voor de planning en automatische herverdeling.
850
+ - Verwijder of sluit public.logistics_capacity_alerts niet.
851
+ - Capaciteitsmeldingen blijven beschikbaar in het Regieoverzicht van de Logistics
852
+ Engine en in het centrale Meldingencentrum.
853
+ - Verberg echte technische fouten niet: een mislukte laad- of opslagactie mag nog
854
+ steeds via een normale toast worden gemeld, maar niet als melding in de agenda.
855
+
856
+ CONTROLELIJST
857
+ [ ] In Planning en leveringen staat bij geen enkele dag een capaciteitsmelding.
858
+ [ ] Het openen en wisselen van weken blijft de Engine-berekening uitvoeren.
790
859
  [ ] Het Regieoverzicht kan de capaciteitsmeldingen nog steeds tonen.`,
791
860
  },
792
861
  {
@@ -794,28 +863,28 @@ CONTROLELIJST
794
863
  title: "Tekort in medewerkers tonen in de capaciteitswaarschuwing",
795
864
  date: "2026-08-04",
796
865
  summary: "Nieuwe eis bovenop prompt 1 t/m 7: elke capaciteitswaarschuwing vermeldt hoeveel medewerkers er tekort zijn, niet alleen minuten en karren.",
797
- body: `AANVULLING op prompt 6. De waarschuwing blijft weg uit "Planning en
798
- leveringen" (prompt 7); overal waar je hem WEL toont (Regieoverzicht,
799
- Meldingencentrum, dagdetails buiten de agenda) geldt onderstaande tekst.
800
-
801
- BEREKENING (verplicht, geen eigen variant)
802
- minutenPerMedewerker = availableMinutes / staff // staff > 0
803
- medewerkersTekort = Math.ceil(shortageMinutes / minutenPerMedewerker)
804
- Is staff 0 of availableMinutes 0, toon dan geen aantal maar de tekst
805
- "vul het gemiddeld aantal medewerkers in bij 3. Order picken".
806
-
807
- TEKST
808
- "Te weinig capaciteit: {carts} karren vragen {requiredMinutes} min picktijd,
809
- beschikbaar is {availableMinutes} min ({staff} medewerkers).
810
- Je komt {medewerkersTekort} medewerker(s) tekort — samen
811
- {staff + medewerkersTekort} nodig. Circa {carts_short} karren passen niet
812
- op deze dag."
813
-
814
- Gebruik enkelvoud "medewerker" bij 1, anders "medewerkers".
815
-
816
- CONTROLELIJST
817
- [ ] Elke zichtbare capaciteitswaarschuwing noemt het tekort in medewerkers.
818
- [ ] Het aantal komt uit de formule hierboven, niet uit een eigen aanname.
866
+ body: `AANVULLING op prompt 6. De waarschuwing blijft weg uit "Planning en
867
+ leveringen" (prompt 7); overal waar je hem WEL toont (Regieoverzicht,
868
+ Meldingencentrum, dagdetails buiten de agenda) geldt onderstaande tekst.
869
+
870
+ BEREKENING (verplicht, geen eigen variant)
871
+ minutenPerMedewerker = availableMinutes / staff // staff > 0
872
+ medewerkersTekort = Math.ceil(shortageMinutes / minutenPerMedewerker)
873
+ Is staff 0 of availableMinutes 0, toon dan geen aantal maar de tekst
874
+ "vul het gemiddeld aantal medewerkers in bij 3. Order picken".
875
+
876
+ TEKST
877
+ "Te weinig capaciteit: {carts} karren vragen {requiredMinutes} min picktijd,
878
+ beschikbaar is {availableMinutes} min ({staff} medewerkers).
879
+ Je komt {medewerkersTekort} medewerker(s) tekort — samen
880
+ {staff + medewerkersTekort} nodig. Circa {carts_short} karren passen niet
881
+ op deze dag."
882
+
883
+ Gebruik enkelvoud "medewerker" bij 1, anders "medewerkers".
884
+
885
+ CONTROLELIJST
886
+ [ ] Elke zichtbare capaciteitswaarschuwing noemt het tekort in medewerkers.
887
+ [ ] Het aantal komt uit de formule hierboven, niet uit een eigen aanname.
819
888
  [ ] Zonder ingevuld personeel toont de melding de invulhint in plaats van 0.`,
820
889
  },
821
890
  {
@@ -824,23 +893,23 @@ CONTROLELIJST
824
893
  date: "2026-08-19",
825
894
  summary: "Nieuwe orders worden binnen seconden automatisch ingedeeld door de Engine.",
826
895
  withSharedContract: false,
827
- body: `De Logistics Engine deelt nieuwe en gewijzigde orders voortaan automatisch in.
828
- Een order komt bij binnenkomst in de wachtrij public.logistics_plan_queue en wordt
829
- direct verwerkt (doorgaans binnen enkele seconden, uiterlijk binnen 2 minuten).
830
-
831
- REGELS
832
- - Deel NOOIT zelf orders in en maak geen eigen taken aan als er nog geen indeling is.
833
- - Toon de melding "wacht op een indeling van de Logistics Engine" pas als de order
834
- langer dan 2 minuten geleden is aangemaakt/gewijzigd EN er nog geen taken zijn.
835
- Binnen die marge toon je niets — dat is normale verwerkingstijd.
836
- - Ververs de weergave automatisch (realtime of polling) zodra taken verschijnen,
837
- zodat de gebruiker niets hoeft te doen.
838
- - Reserveringen (orders.is_reservation = true) en vastgezette orders
839
- (orders.plan_locked_at gevuld) tel je nooit mee in deze melding.
840
-
841
- CONTROLELIJST
842
- [ ] Geen melding binnen 2 minuten na aanmaken/wijzigen van een order.
843
- [ ] Geen eigen indeling of eigen taken als fallback.
896
+ body: `De Logistics Engine deelt nieuwe en gewijzigde orders voortaan automatisch in.
897
+ Een order komt bij binnenkomst in de wachtrij public.logistics_plan_queue en wordt
898
+ direct verwerkt (doorgaans binnen enkele seconden, uiterlijk binnen 2 minuten).
899
+
900
+ REGELS
901
+ - Deel NOOIT zelf orders in en maak geen eigen taken aan als er nog geen indeling is.
902
+ - Toon de melding "wacht op een indeling van de Logistics Engine" pas als de order
903
+ langer dan 2 minuten geleden is aangemaakt/gewijzigd EN er nog geen taken zijn.
904
+ Binnen die marge toon je niets — dat is normale verwerkingstijd.
905
+ - Ververs de weergave automatisch (realtime of polling) zodra taken verschijnen,
906
+ zodat de gebruiker niets hoeft te doen.
907
+ - Reserveringen (orders.is_reservation = true) en vastgezette orders
908
+ (orders.plan_locked_at gevuld) tel je nooit mee in deze melding.
909
+
910
+ CONTROLELIJST
911
+ [ ] Geen melding binnen 2 minuten na aanmaken/wijzigen van een order.
912
+ [ ] Geen eigen indeling of eigen taken als fallback.
844
913
  [ ] Weergave werkt bij zonder handmatig verversen.`,
845
914
  },
846
915
  {
@@ -848,72 +917,72 @@ CONTROLELIJST
848
917
  title: "Instellingen opschonen: planning en printen komen van de Engine/Layout maker",
849
918
  date: "2026-08-27",
850
919
  summary: "Nieuwe eis bovenop prompt 1 t/m 9: de settingscard 'Afhandeling — Planning' wordt opgeruimd.",
851
- body: `AANVULLING op prompt 1 t/m 9 (die blijven gewoon geldig).
852
-
853
- De settingscard "Afhandeling — Planning" bevat een aantal velden die inmiddels
854
- worden bepaald door de Logistics Engine of de Layout maker. Deze module mag die
855
- velden niet meer zelf uitlezen of tonen.
856
-
857
- Haal weg uit de settingscard en bijbehorende schermen:
858
-
859
- 1. "Aantal pickdagen (werkdagen vóór ophaaldag)"
860
- 2. "Extra pickdagen bij stickers"
861
- 3. "Orderpickdagen (werkdagen)"
862
- 4. "Pickverdeling over werkdagen"
863
- 5. "Automatische Floriday-verwerking pauzeren"
864
- 6. "Afleverbrief automatisch printen"
865
- 7. "Werkplek (voor printen)"
866
-
867
- Waarom deze velden overbodig zijn:
868
- - Aantal pickdagen, extra pickdagen, orderpickdagen en pickverdeling worden
869
- volledig bepaald door de Logistics Engine (stap 3 "Order picken" en
870
- ophaalmomenten). De Engine plant planned_start en planned_end per taak.
871
- - "Automatische Floriday-verwerking pauzeren" heeft geen lezer meer in deze
872
- module.
873
- - "Afleverbrief automatisch printen" is geen printerkeuze maar een trigger.
874
- De Logistics Engine neemt die trigger over: Afhandeling maakt zelf geen
875
- printopdrachten meer aan voor afleverbrieven, maar consumeert
876
- print.shipping_note-taken uit public.logistics_tasks. De Engine bepaalt of
877
- en wanneer een afleverbrief wordt vrijgegeven voor printen.
878
- - "Werkplek (voor printen)" is overbodig omdat locatiegebonden printen het
879
- domein is van de Logistics Engine (via print_plek_id in de taak-payload).
880
- Zonder werkplek en zonder print_plek_id valt de Layout maker terug op de
881
- standaardlayout of standaardregels voor documenttype "afleverbrief".
882
-
883
- Printer-beslisvolgorde in de Layout maker:
884
-
885
- 1. printer_id op de opdracht (expliciet meegegeven)
886
- 2. print_plek_id -> regel van de printplek
887
- 3. werkplek -> printplek van die computer, dan werkplek-uitzondering
888
- 4. printer gekoppeld aan de (standaard)layout van het documenttype
889
- 5. standaardregel: documenttype + papierformaat
890
- 6. standaardregel: documenttype alleen
891
-
892
- Zonder werkplek en zonder print_plek_id slaat een afleverbrief-print stap 1-3
893
- over en komt hij op de printer van de standaardlayout of de standaardregels
894
- uit. Wil men locatiegebonden printen, dan moet dat via de Logistics Engine
895
- (print_plek_id) of een expliciete printer_id op de opdracht.
896
-
897
- Wat blijft staan:
898
- - "Aanmeldmail vervoerders" (+ minuten vóór aanmelddeadline en e-mail-doeleinde)
899
- blijft bestaan. Dat is eigen functionaliteit van de Afhandeling-module.
900
-
901
- Voorwaarde:
902
- Er moet minstens één printer voor documenttype "afleverbrief" zijn ingesteld
903
- in de Layout maker: hetzij aan de standaardlayout, hetzij via
904
- Layouts en printers -> Standaardregels. Ontbreekt die, dan mislukt de
905
- automatische print met de leesbare foutmelding:
906
- "Geen printer voor afleverbrief — stel dit in bij Layouts en printers → Routering".
907
-
908
- Database:
909
- - Droppen geen kolommen of rijen. De kolommen in afhandeling_settings blijven
910
- staan, maar worden niet meer gelezen of getoond.
911
-
912
- CONTROLELIJST
913
- [ ] Alle zeven velden zijn verwijderd uit de settingscard.
914
- [ ] Aanmeldmail-instellingen zijn nog zichtbaar en werken nog.
915
- [ ] Er worden geen eigen planningsberekeningen meer gedaan; alles komt uit public.logistics_tasks.
916
- [ ] Afleverbrieven worden alleen nog aangemaakt via print.shipping_note-taken, niet spontaan.
920
+ body: `AANVULLING op prompt 1 t/m 9 (die blijven gewoon geldig).
921
+
922
+ De settingscard "Afhandeling — Planning" bevat een aantal velden die inmiddels
923
+ worden bepaald door de Logistics Engine of de Layout maker. Deze module mag die
924
+ velden niet meer zelf uitlezen of tonen.
925
+
926
+ Haal weg uit de settingscard en bijbehorende schermen:
927
+
928
+ 1. "Aantal pickdagen (werkdagen vóór ophaaldag)"
929
+ 2. "Extra pickdagen bij stickers"
930
+ 3. "Orderpickdagen (werkdagen)"
931
+ 4. "Pickverdeling over werkdagen"
932
+ 5. "Automatische Floriday-verwerking pauzeren"
933
+ 6. "Afleverbrief automatisch printen"
934
+ 7. "Werkplek (voor printen)"
935
+
936
+ Waarom deze velden overbodig zijn:
937
+ - Aantal pickdagen, extra pickdagen, orderpickdagen en pickverdeling worden
938
+ volledig bepaald door de Logistics Engine (stap 3 "Order picken" en
939
+ ophaalmomenten). De Engine plant planned_start en planned_end per taak.
940
+ - "Automatische Floriday-verwerking pauzeren" heeft geen lezer meer in deze
941
+ module.
942
+ - "Afleverbrief automatisch printen" is geen printerkeuze maar een trigger.
943
+ De Logistics Engine neemt die trigger over: Afhandeling maakt zelf geen
944
+ printopdrachten meer aan voor afleverbrieven, maar consumeert
945
+ print.shipping_note-taken uit public.logistics_tasks. De Engine bepaalt of
946
+ en wanneer een afleverbrief wordt vrijgegeven voor printen.
947
+ - "Werkplek (voor printen)" is overbodig omdat locatiegebonden printen het
948
+ domein is van de Logistics Engine (via print_plek_id in de taak-payload).
949
+ Zonder werkplek en zonder print_plek_id valt de Layout maker terug op de
950
+ standaardlayout of standaardregels voor documenttype "afleverbrief".
951
+
952
+ Printer-beslisvolgorde in de Layout maker:
953
+
954
+ 1. printer_id op de opdracht (expliciet meegegeven)
955
+ 2. print_plek_id -> regel van de printplek
956
+ 3. werkplek -> printplek van die computer, dan werkplek-uitzondering
957
+ 4. printer gekoppeld aan de (standaard)layout van het documenttype
958
+ 5. standaardregel: documenttype + papierformaat
959
+ 6. standaardregel: documenttype alleen
960
+
961
+ Zonder werkplek en zonder print_plek_id slaat een afleverbrief-print stap 1-3
962
+ over en komt hij op de printer van de standaardlayout of de standaardregels
963
+ uit. Wil men locatiegebonden printen, dan moet dat via de Logistics Engine
964
+ (print_plek_id) of een expliciete printer_id op de opdracht.
965
+
966
+ Wat blijft staan:
967
+ - "Aanmeldmail vervoerders" (+ minuten vóór aanmelddeadline en e-mail-doeleinde)
968
+ blijft bestaan. Dat is eigen functionaliteit van de Afhandeling-module.
969
+
970
+ Voorwaarde:
971
+ Er moet minstens één printer voor documenttype "afleverbrief" zijn ingesteld
972
+ in de Layout maker: hetzij aan de standaardlayout, hetzij via
973
+ Layouts en printers -> Standaardregels. Ontbreekt die, dan mislukt de
974
+ automatische print met de leesbare foutmelding:
975
+ "Geen printer voor afleverbrief — stel dit in bij Layouts en printers → Routering".
976
+
977
+ Database:
978
+ - Droppen geen kolommen of rijen. De kolommen in afhandeling_settings blijven
979
+ staan, maar worden niet meer gelezen of getoond.
980
+
981
+ CONTROLELIJST
982
+ [ ] Alle zeven velden zijn verwijderd uit de settingscard.
983
+ [ ] Aanmeldmail-instellingen zijn nog zichtbaar en werken nog.
984
+ [ ] Er worden geen eigen planningsberekeningen meer gedaan; alles komt uit public.logistics_tasks.
985
+ [ ] Afleverbrieven worden alleen nog aangemaakt via print.shipping_note-taken, niet spontaan.
917
986
  [ ] De kolommen in afhandeling_settings zijn niet gedropt.`,
918
987
  },
919
988
  {
@@ -921,51 +990,51 @@ CONTROLELIJST
921
990
  title: "Controle karren rechtstreeks uit de agenda van de Engine",
922
991
  date: "2026-09-01",
923
992
  summary: "De controlepagina toont alle karren van de pickagenda van vandaag, inclusief gemengde, ongeprinte en nog niet ingedeelde karren.",
924
- body: `AANVULLING op prompt 1 t/m 10 (die blijven geldig).
925
-
926
- GEMETEN PROBLEEM
927
- Het scherm "Controle karren" blijft leeg doordat cartControl.functions.ts zelf
928
- alleen logistics_tasks van type load.compose leest, met owner_module =
929
- "afhandeling" en een beperkte statuslijst. De pickagenda wordt echter bepaald
930
- door pick.execute. Daardoor kan de agenda wel orders tonen terwijl de controlelijst
931
- 0 karren toont.
932
-
933
- BOUW DIT
934
- - Verwijder loadEngineCartsForToday en de lokale datum-/taakfilter als bron voor
935
- de controlelijst.
936
- - Laat getTodaysControlCarts rechtstreeks deze RPC aanroepen:
937
-
938
- const { data, error } = await supabase.rpc("control_get_todays_carts");
939
- if (error) throw new Error(error.message);
940
-
941
- - Map iedere RPC-rij naar ControlCart. Gebruik cart_number_id als stabiele sleutel,
942
- order_id, order_source, cart_index, pick_date, status, printed_at en
943
- printed_today letterlijk uit het resultaat.
944
- - De RPC is de enige bron voor welke karren vandaag zichtbaar zijn. Voeg in de
945
- frontend geen extra filter toe op load.compose, owner_module, taskstatus,
946
- orderstatus of printedCount.
947
- - De RPC bevat bewust:
948
- 1. alle pick.execute-taken met planned_start vandaag in Europe/Amsterdam;
949
- 2. alle orders die vandaag zijn aangemeld of uitgeprint;
950
- 3. normale én gemengde karren via order_cart_items -> order_items;
951
- 4. tijdelijke regels voor agenda-orders die nog geen order_carts hebben.
952
- - Gebruik printed_today voor de kolom "Uitgeprint". Een kar hoeft niet geprint te
953
- zijn om zichtbaar en goed te keuren te zijn.
954
- - Ververs bij wijzigingen in logistics_tasks, pick_queue, print_jobs, order_carts,
955
- order_cart_items en cart_numbers, plus de bestaande polling.
956
- - Gebruik dezelfde RPC-uitkomst ook als basis voor submitControlSession, zodat de
957
- getoonde en afgemelde verzameling nooit van elkaar afwijkt.
958
-
959
- NIET DOEN
960
- - Niet terugvallen op loadTodaysPickstationOrders als de RPC nul regels geeft.
961
- - Niet zelf kar-aantallen herberekenen.
962
- - Geen project-ref, Supabase-URL of datum hardcoden.
963
-
964
- CONTROLELIJST
965
- [ ] Een ongeprinte kar op de pickagenda van vandaag staat in Controle karren.
966
- [ ] Een vandaag geprinte kar met een oudere pickdatum blijft zichtbaar.
967
- [ ] Een gemengde kar verschijnt zonder dubbele regels.
968
- [ ] De teller en tabel gebruiken exact dezelfde RPC-data.
993
+ body: `AANVULLING op prompt 1 t/m 10 (die blijven geldig).
994
+
995
+ GEMETEN PROBLEEM
996
+ Het scherm "Controle karren" blijft leeg doordat cartControl.functions.ts zelf
997
+ alleen logistics_tasks van type load.compose leest, met owner_module =
998
+ "afhandeling" en een beperkte statuslijst. De pickagenda wordt echter bepaald
999
+ door pick.execute. Daardoor kan de agenda wel orders tonen terwijl de controlelijst
1000
+ 0 karren toont.
1001
+
1002
+ BOUW DIT
1003
+ - Verwijder loadEngineCartsForToday en de lokale datum-/taakfilter als bron voor
1004
+ de controlelijst.
1005
+ - Laat getTodaysControlCarts rechtstreeks deze RPC aanroepen:
1006
+
1007
+ const { data, error } = await supabase.rpc("control_get_todays_carts");
1008
+ if (error) throw new Error(error.message);
1009
+
1010
+ - Map iedere RPC-rij naar ControlCart. Gebruik cart_number_id als stabiele sleutel,
1011
+ order_id, order_source, cart_index, pick_date, status, printed_at en
1012
+ printed_today letterlijk uit het resultaat.
1013
+ - De RPC is de enige bron voor welke karren vandaag zichtbaar zijn. Voeg in de
1014
+ frontend geen extra filter toe op load.compose, owner_module, taskstatus,
1015
+ orderstatus of printedCount.
1016
+ - De RPC bevat bewust:
1017
+ 1. alle pick.execute-taken met planned_start vandaag in Europe/Amsterdam;
1018
+ 2. alle orders die vandaag zijn aangemeld of uitgeprint;
1019
+ 3. normale én gemengde karren via order_cart_items -> order_items;
1020
+ 4. tijdelijke regels voor agenda-orders die nog geen order_carts hebben.
1021
+ - Gebruik printed_today voor de kolom "Uitgeprint". Een kar hoeft niet geprint te
1022
+ zijn om zichtbaar en goed te keuren te zijn.
1023
+ - Ververs bij wijzigingen in logistics_tasks, pick_queue, print_jobs, order_carts,
1024
+ order_cart_items en cart_numbers, plus de bestaande polling.
1025
+ - Gebruik dezelfde RPC-uitkomst ook als basis voor submitControlSession, zodat de
1026
+ getoonde en afgemelde verzameling nooit van elkaar afwijkt.
1027
+
1028
+ NIET DOEN
1029
+ - Niet terugvallen op loadTodaysPickstationOrders als de RPC nul regels geeft.
1030
+ - Niet zelf kar-aantallen herberekenen.
1031
+ - Geen project-ref, Supabase-URL of datum hardcoden.
1032
+
1033
+ CONTROLELIJST
1034
+ [ ] Een ongeprinte kar op de pickagenda van vandaag staat in Controle karren.
1035
+ [ ] Een vandaag geprinte kar met een oudere pickdatum blijft zichtbaar.
1036
+ [ ] Een gemengde kar verschijnt zonder dubbele regels.
1037
+ [ ] De teller en tabel gebruiken exact dezelfde RPC-data.
969
1038
  [ ] Goedkeuren verwerkt exact de zichtbare verzameling.`,
970
1039
  },
971
1040
  {
@@ -973,50 +1042,50 @@ CONTROLELIJST
973
1042
  title: "Pick agenda: nooit een pickdag in het verleden",
974
1043
  date: "2026-09-01",
975
1044
  summary: "De weekagenda leest de pickdagen uit de Engine; orders met een verstreken pickmoment staan automatisch op vandaag.",
976
- body: `AANVULLING op prompt 1 t/m 11 (die blijven geldig).
977
-
978
- GEMETEN PROBLEEM
979
- De weekagenda toont orders op maandag 31 aug terwijl het dinsdag 1 sep is. In
980
- public.logistics_tasks staat geen enkele open pick.execute-taak in het verleden:
981
- de Engine klemt planned_start al op nu. De agenda rekent dus nog zelf een pickdag
982
- uit (delivery_date minus pick_days), en die kan in het verleden liggen.
983
-
984
- BOUW DIT
985
- - Verwijder de eigen pickdagberekening uit de weekagenda (delivery_date,
986
- pick_days_before_transport, max_workdays_earlier_pick, pick_weekdays).
987
- - Haal de agenda op met:
988
-
989
- const { data, error } = await supabase.rpc("logistics_pick_agenda", {
990
- p_from: weekStartIso, // yyyy-mm-dd
991
- p_to: weekEndIso,
992
- });
993
- if (error) throw new Error(error.message);
994
- const dagen = Array.isArray(data) ? data : [];
995
-
996
- - Kolommen per dag: pick_date, order_count, cart_count, plant_count,
997
- in_preparation, ready, shifted_count, orders (jsonb array met taskId, orderRef,
998
- status, carts, plants, shifted).
999
- - Vul de tegels "bestellingen", "karren" en "planten" met order_count, cart_count
1000
- en plant_count. "In voorbereiding" = in_preparation, "Verzendklaar" = ready.
1001
- - Toon bij shifted_count > 0 op die dag het label "te laat aangemeld" (zelfde
1002
- betekenis als in de Regie-pagina van de Engine).
1003
- - Dagen zonder rij tonen simpelweg nullen; niet zelf aanvullen met eigen data.
1004
- - Ververs bij realtime-wijzigingen op logistics_tasks.
1005
-
1006
- REGEL
1007
- Een order kan nooit in het verleden gepickt worden. Elke open pick.execute-taak
1008
- met een planning vóór vandaag wordt door de Engine (en nogmaals door deze RPC)
1009
- op vandaag getoond. Bouw dus geen eigen "inhaal"- of "achterstand"-dag.
1010
-
1011
- NIET DOEN
1012
- - Geen eigen datumafronding, werkdagenlogica of tijdzoneconversie meer.
1013
- - Geen orders uit de agenda filteren op status buiten wat de RPC teruggeeft.
1014
- - Geen datum, project-ref of Supabase-URL hardcoden.
1015
-
1016
- CONTROLELIJST
1017
- [ ] De agenda toont geen enkele dag vóór vandaag met openstaande orders.
1018
- [ ] Een gisteren aangemelde, nog niet gepickte order staat vandaag in de agenda.
1019
- [ ] Tegels boven de agenda tellen exact op met de dagregels van de RPC.
1045
+ body: `AANVULLING op prompt 1 t/m 11 (die blijven geldig).
1046
+
1047
+ GEMETEN PROBLEEM
1048
+ De weekagenda toont orders op maandag 31 aug terwijl het dinsdag 1 sep is. In
1049
+ public.logistics_tasks staat geen enkele open pick.execute-taak in het verleden:
1050
+ de Engine klemt planned_start al op nu. De agenda rekent dus nog zelf een pickdag
1051
+ uit (delivery_date minus pick_days), en die kan in het verleden liggen.
1052
+
1053
+ BOUW DIT
1054
+ - Verwijder de eigen pickdagberekening uit de weekagenda (delivery_date,
1055
+ pick_days_before_transport, max_workdays_earlier_pick, pick_weekdays).
1056
+ - Haal de agenda op met:
1057
+
1058
+ const { data, error } = await supabase.rpc("logistics_pick_agenda", {
1059
+ p_from: weekStartIso, // yyyy-mm-dd
1060
+ p_to: weekEndIso,
1061
+ });
1062
+ if (error) throw new Error(error.message);
1063
+ const dagen = Array.isArray(data) ? data : [];
1064
+
1065
+ - Kolommen per dag: pick_date, order_count, cart_count, plant_count,
1066
+ in_preparation, ready, shifted_count, orders (jsonb array met taskId, orderRef,
1067
+ status, carts, plants, shifted).
1068
+ - Vul de tegels "bestellingen", "karren" en "planten" met order_count, cart_count
1069
+ en plant_count. "In voorbereiding" = in_preparation, "Verzendklaar" = ready.
1070
+ - Toon bij shifted_count > 0 op die dag het label "te laat aangemeld" (zelfde
1071
+ betekenis als in de Regie-pagina van de Engine).
1072
+ - Dagen zonder rij tonen simpelweg nullen; niet zelf aanvullen met eigen data.
1073
+ - Ververs bij realtime-wijzigingen op logistics_tasks.
1074
+
1075
+ REGEL
1076
+ Een order kan nooit in het verleden gepickt worden. Elke open pick.execute-taak
1077
+ met een planning vóór vandaag wordt door de Engine (en nogmaals door deze RPC)
1078
+ op vandaag getoond. Bouw dus geen eigen "inhaal"- of "achterstand"-dag.
1079
+
1080
+ NIET DOEN
1081
+ - Geen eigen datumafronding, werkdagenlogica of tijdzoneconversie meer.
1082
+ - Geen orders uit de agenda filteren op status buiten wat de RPC teruggeeft.
1083
+ - Geen datum, project-ref of Supabase-URL hardcoden.
1084
+
1085
+ CONTROLELIJST
1086
+ [ ] De agenda toont geen enkele dag vóór vandaag met openstaande orders.
1087
+ [ ] Een gisteren aangemelde, nog niet gepickte order staat vandaag in de agenda.
1088
+ [ ] Tegels boven de agenda tellen exact op met de dagregels van de RPC.
1020
1089
  [ ] Verschoven orders krijgen het label "te laat aangemeld".`,
1021
1090
  },
1022
1091
  {
@@ -1024,42 +1093,85 @@ CONTROLELIJST
1024
1093
  title: "Karrentelling: alles uit de gedeelde karindex",
1025
1094
  date: "2026-09-01",
1026
1095
  summary: "Agenda en Controle karren tellen karren uit logistics_cart_index; payload-velden zoals cartCount worden niet meer gebruikt.",
1027
- body: `AANVULLING op prompt 1 t/m 12 (die blijven geldig).
1028
-
1029
- GEMETEN PROBLEEM
1030
- De pick agenda toonde 3517 karren voor vandaag terwijl Controle karren er 27
1031
- toonde. Oorzaak: de agenda telde payload->>'cartCount' van taken op (dubbeltelling
1032
- over meerdere taken per order), Controle telde cart_numbers. Beide waren fout.
1033
-
1034
- WAT DE ENGINE NU LEVERT
1035
- public.logistics_cart_index(p_from, p_to) — één rij per (order, kar):
1036
- pick_date, order_uuid, order_number, order_source, cart_id, cart_index,
1037
- cart_label, is_shared, task_status, shifted, printed_at.
1038
- Daarop draaien logistics_pick_agenda en control_get_todays_carts.
1039
-
1040
- BOUW DIT
1041
- - Gebruik uitsluitend cart_count uit logistics_pick_agenda voor de dagtegels.
1042
- Een gemengde kar telt per dag één keer, ook als meerdere orders erin zitten.
1043
- - In een orderregel toont "carts" uit de orders-array het aantal karren waarin
1044
- die order zit (gedeelde karren tellen daar wél mee).
1045
- - Gebruik voor de controlelijst alleen control_get_todays_carts; die geeft nu
1046
- cart_id als cart_number_id terug, ook voor karren zonder karnummer.
1047
- - Verwijder elke lezing van payload->>'cartCount' en elke eigen count op
1048
- order_carts.
1049
-
1050
- CONTROLE OP AFWIJKING
1051
- Roep bij het laden van de agenda eenmalig aan:
1052
-
1053
- const { data } = await supabase.rpc("logistics_consistency_check");
1054
-
1055
- Is ok === false, toon dan een discrete waarschuwing "aantallen worden opnieuw
1056
- uitgelijnd" en niets meer. Niet zelf corrigeren.
1057
-
1058
- CONTROLELIJST
1059
- [ ] Agenda-karren van vandaag = karren in Controle karren.
1060
- [ ] Geen enkel scherm leest nog cartCount uit een taak-payload.
1096
+ body: `AANVULLING op prompt 1 t/m 12 (die blijven geldig).
1097
+
1098
+ GEMETEN PROBLEEM
1099
+ De pick agenda toonde 3517 karren voor vandaag terwijl Controle karren er 27
1100
+ toonde. Oorzaak: de agenda telde payload->>'cartCount' van taken op (dubbeltelling
1101
+ over meerdere taken per order), Controle telde cart_numbers. Beide waren fout.
1102
+
1103
+ WAT DE ENGINE NU LEVERT
1104
+ public.logistics_cart_index(p_from, p_to) — één rij per (order, kar):
1105
+ pick_date, order_uuid, order_number, order_source, cart_id, cart_index,
1106
+ cart_label, is_shared, task_status, shifted, printed_at.
1107
+ Daarop draaien logistics_pick_agenda en control_get_todays_carts.
1108
+
1109
+ BOUW DIT
1110
+ - Gebruik uitsluitend cart_count uit logistics_pick_agenda voor de dagtegels.
1111
+ Een gemengde kar telt per dag één keer, ook als meerdere orders erin zitten.
1112
+ - In een orderregel toont "carts" uit de orders-array het aantal karren waarin
1113
+ die order zit (gedeelde karren tellen daar wél mee).
1114
+ - Gebruik voor de controlelijst alleen control_get_todays_carts; die geeft nu
1115
+ cart_id als cart_number_id terug, ook voor karren zonder karnummer.
1116
+ - Verwijder elke lezing van payload->>'cartCount' en elke eigen count op
1117
+ order_carts.
1118
+
1119
+ CONTROLE OP AFWIJKING
1120
+ Roep bij het laden van de agenda eenmalig aan:
1121
+
1122
+ const { data } = await supabase.rpc("logistics_consistency_check");
1123
+
1124
+ Is ok === false, toon dan een discrete waarschuwing "aantallen worden opnieuw
1125
+ uitgelijnd" en niets meer. Niet zelf corrigeren.
1126
+
1127
+ CONTROLELIJST
1128
+ [ ] Agenda-karren van vandaag = karren in Controle karren.
1129
+ [ ] Geen enkel scherm leest nog cartCount uit een taak-payload.
1061
1130
  [ ] Orders van verwijderde bestellingen verschijnen nergens meer.`,
1062
1131
  },
1132
+ {
1133
+ number: 14,
1134
+ title: "Lever agenda uit dezelfde karindex als de pick agenda",
1135
+ date: "2026-09-01",
1136
+ summary: "De lever agenda leest logistics_delivery_agenda; geen eigen telling meer op orders of order_carts.",
1137
+ body: `AANVULLING op prompt 1 t/m 13 (die blijven geldig).
1138
+
1139
+ WAAROM
1140
+ Pick agenda, Controle karren en Verkoop tellen al uit dezelfde karindex. De
1141
+ lever agenda telde nog zelf. Daardoor konden dezelfde orders op twee schermen
1142
+ een ander aantal karren tonen.
1143
+
1144
+ WAT DE ENGINE LEVERT
1145
+ public.logistics_delivery_agenda(p_from date, p_to date) — één rij per leverdag:
1146
+ delivery_date, order_count, cart_count, plant_count, announced_count,
1147
+ printed_count, controlled_count, orders (jsonb array met order_uuid,
1148
+ order_number, pick_date, cart_count, plant_count, announced, printed,
1149
+ controlled).
1150
+
1151
+ Zelfde telbron als logistics_pick_agenda; alleen gegroepeerd op leverdatum in
1152
+ plaats van pickdag.
1153
+
1154
+ BOUW DIT
1155
+ - Haal de lever agenda op met:
1156
+
1157
+ const { data } = await supabase.rpc("logistics_delivery_agenda", {
1158
+ p_from: vanDatum, p_to: totDatum,
1159
+ });
1160
+ const dagen = Array.isArray(data) ? data : [];
1161
+
1162
+ - Toon cart_count en plant_count exact zoals teruggegeven; niets optellen of
1163
+ omrekenen.
1164
+ - Statusfilters (aangemeld / geprint / gecontroleerd) filteren regels; ze mogen
1165
+ het totaal nooit herberekenen.
1166
+
1167
+ NIET DOEN
1168
+ - Geen eigen query op orders, order_carts of de Floriday-cache voor aantallen.
1169
+ - Geen datums, project-refs of URL's hardcoden.
1170
+
1171
+ CONTROLELIJST
1172
+ [ ] Een order toont in de lever agenda hetzelfde aantal karren als in de pick agenda.
1173
+ [ ] Geen eigen kartelling meer in de leveringencode.`,
1174
+ },
1063
1175
  ],
1064
1176
  },
1065
1177
  pickschermen: {
@@ -1072,12 +1184,12 @@ CONTROLELIJST
1072
1184
  date: "2026-07-24",
1073
1185
  summary: "Pick-taken tonen en terugmelden.",
1074
1186
  withSharedContract: true,
1075
- body: `Haal weg: eigen pickvolgorde en eigen bepaling van de starttijd.
1076
-
1077
- Bouw:
1078
- - pick.execute taken tonen, gesorteerd op planned_start.
1079
- - Payload: pickMode, pickStartMode, pickMinutes, staff, reserveMinutes, scanner, loadCarriers.
1080
- - Voortgang terugmelden via RPC: logistics_claim_task bij starten, logistics_complete_task bij afronden.
1187
+ body: `Haal weg: eigen pickvolgorde en eigen bepaling van de starttijd.
1188
+
1189
+ Bouw:
1190
+ - pick.execute taken tonen, gesorteerd op planned_start.
1191
+ - Payload: pickMode, pickStartMode, pickMinutes, staff, reserveMinutes, scanner, loadCarriers.
1192
+ - Voortgang terugmelden via RPC: logistics_claim_task bij starten, logistics_complete_task bij afronden.
1081
1193
  - Te-laat-dreigende belading markeren op basis van planned_end; niet zelf herrekenen.`,
1082
1194
  },
1083
1195
  {
@@ -1085,26 +1197,26 @@ Bouw:
1085
1197
  title: "Documenten per pickstation vanuit de Engine",
1086
1198
  date: "2026-08-03",
1087
1199
  summary: "Documenttaken tonen op het juiste pickstation, inclusief goedkeuring.",
1088
- body: `Deze prompt vervangt niets uit prompt 1; hij komt er bovenop.
1089
-
1090
- Haal weg: eigen logica over welk document op welk pickstation verschijnt en
1091
- wanneer. De Logistics Engine bepaalt dit volledig.
1092
-
1093
- Verwerk deze taaktypes uit logistics_tasks:
1094
- - print.packing_slip, print.sticker, print.shipping_note, print.production_note
1095
-
1096
- Payload per taak:
1097
- - release wanneer het document vrijkomt
1098
- - pickStationId public.pick_stations.id waar het document heen moet
1099
- - pickStationTypeId public.pick_station_types.id dat het document mag ontvangen
1100
- - requiresApproval true = eerst handmatige goedkeuring op het pickstation
1101
-
1102
- Regels:
1103
- - Toon een document pas vanaf planned_start van de taak.
1104
- - Filter de wachtrij van een pickstation op pickStationId; is die leeg, gebruik
1105
- dan pickStationTypeId. Toon nooit documenten van een ander station.
1106
- - requiresApproval = true: toon een goedkeurknop; pas na goedkeuren printen en
1107
- daarna logistics_complete_task aanroepen (bij weigeren logistics_fail_task).
1200
+ body: `Deze prompt vervangt niets uit prompt 1; hij komt er bovenop.
1201
+
1202
+ Haal weg: eigen logica over welk document op welk pickstation verschijnt en
1203
+ wanneer. De Logistics Engine bepaalt dit volledig.
1204
+
1205
+ Verwerk deze taaktypes uit logistics_tasks:
1206
+ - print.packing_slip, print.sticker, print.shipping_note, print.production_note
1207
+
1208
+ Payload per taak:
1209
+ - release wanneer het document vrijkomt
1210
+ - pickStationId public.pick_stations.id waar het document heen moet
1211
+ - pickStationTypeId public.pick_station_types.id dat het document mag ontvangen
1212
+ - requiresApproval true = eerst handmatige goedkeuring op het pickstation
1213
+
1214
+ Regels:
1215
+ - Toon een document pas vanaf planned_start van de taak.
1216
+ - Filter de wachtrij van een pickstation op pickStationId; is die leeg, gebruik
1217
+ dan pickStationTypeId. Toon nooit documenten van een ander station.
1218
+ - requiresApproval = true: toon een goedkeurknop; pas na goedkeuren printen en
1219
+ daarna logistics_complete_task aanroepen (bij weigeren logistics_fail_task).
1108
1220
  - Bepaal nooit zelf een vrijgavemoment en print nooit vooruit.`,
1109
1221
  },
1110
1222
  {
@@ -1112,32 +1224,32 @@ Regels:
1112
1224
  title: "Documenten ophalen via de Floriday-gateway",
1113
1225
  date: "2026-08-04",
1114
1226
  summary: "Geen eigen Floriday-token of florydayFetch meer; gebundeld printen per kar.",
1115
- body: `Deze prompt vervangt niets uit prompt 1 en 2; hij komt er bovenop.
1116
-
1117
- Haal weg: eigen Floriday-tokenlogica, eigen florydayFetch, eigen keuze van
1118
- staging/live en eigen HTTP-aanroepen naar de Floriday-gateway.
1119
-
1120
- Alle modules draaien in dezelfde shell op dezelfde database. Importeer de
1121
- gateway daarom rechtstreeks en geef de service-role supabase-client van de
1122
- shell mee:
1123
-
1124
- import {
1125
- runActiveEnvironment,
1126
- runPdf,
1127
- runPdfBulkMerged,
1128
- } from "@flowselections/floriday-authenticatie-module/gateway";
1129
-
1130
- Regels:
1131
- - De omgeving komt altijd uit runActiveEnvironment(supabase). Nooit zelf kiezen.
1132
- - Eén document (print.packing_slip, print.sticker, print.shipping_note,
1133
- print.production_note): runPdf.
1134
- - Trigger "kar afgemeld" of "kar-scan": bundel alle regels van die kar en roep
1135
- runPdfBulkMerged aan, zodat het pickstation één printjob krijgt in plaats van
1136
- losse documenten.
1137
- - Toon bovenin het scherm welke koppeling actief is (label, GLN, staging/live).
1138
- - Print nooit twee keer hetzelfde: controleer public.logistics_document_prints
1139
- op (order_ref, document_type, trigger) voordat je print.
1140
- - Trigger en pickstation blijven van de Logistics Engine; de gateway levert
1227
+ body: `Deze prompt vervangt niets uit prompt 1 en 2; hij komt er bovenop.
1228
+
1229
+ Haal weg: eigen Floriday-tokenlogica, eigen florydayFetch, eigen keuze van
1230
+ staging/live en eigen HTTP-aanroepen naar de Floriday-gateway.
1231
+
1232
+ Alle modules draaien in dezelfde shell op dezelfde database. Importeer de
1233
+ gateway daarom rechtstreeks en geef de service-role supabase-client van de
1234
+ shell mee:
1235
+
1236
+ import {
1237
+ runActiveEnvironment,
1238
+ runPdf,
1239
+ runPdfBulkMerged,
1240
+ } from "@flowselections/floriday-authenticatie-module/gateway";
1241
+
1242
+ Regels:
1243
+ - De omgeving komt altijd uit runActiveEnvironment(supabase). Nooit zelf kiezen.
1244
+ - Eén document (print.packing_slip, print.sticker, print.shipping_note,
1245
+ print.production_note): runPdf.
1246
+ - Trigger "kar afgemeld" of "kar-scan": bundel alle regels van die kar en roep
1247
+ runPdfBulkMerged aan, zodat het pickstation één printjob krijgt in plaats van
1248
+ losse documenten.
1249
+ - Toon bovenin het scherm welke koppeling actief is (label, GLN, staging/live).
1250
+ - Print nooit twee keer hetzelfde: controleer public.logistics_document_prints
1251
+ op (order_ref, document_type, trigger) voordat je print.
1252
+ - Trigger en pickstation blijven van de Logistics Engine; de gateway levert
1141
1253
  alleen het PDF-bestand.`,
1142
1254
  },
1143
1255
  {
@@ -1145,29 +1257,29 @@ Regels:
1145
1257
  title: "Strikte documentroutering per pickstation",
1146
1258
  date: "2026-08-04",
1147
1259
  summary: "Een pickstation toont uitsluitend de documentsoorten die eraan gekoppeld zijn.",
1148
- body: `Deze prompt vervangt niets uit prompt 1 t/m 3; hij komt er bovenop.
1149
-
1150
- Probleem: op "Pickstation Orders" verschenen naast pakbonnen ook veilingbrieven
1151
- en transportbrieven. Dat mag nooit.
1152
-
1153
- Haal weg: elke query die de wachtrij alleen op station_id filtert, en elke
1154
- fallback die "overige documenten" toch ergens toont.
1155
-
1156
- Regels:
1157
- - Filter de wachtrij ALTIJD op screen_id van het geopende scherm, niet op
1158
- station_id. Elk scherm heeft via public.pick_screen_document_types precies de
1159
- documenttypes die het mag tonen.
1160
- - Toon nooit een document waarvan het document_type_id niet aan dat scherm is
1161
- gekoppeld. Geen "rest"-categorie, geen samengevoegde stationweergave.
1162
- - De koppeling documentsoort -> pickstation komt uit de Logistiek Engine
1163
- (Rule Engine, stap "Pickstations"). In de database is dat opvraagbaar met
1164
- select public.logistics_document_station('pakbon').
1165
- - De printwachtrij public.pick_queue heeft een trigger die station_id en
1166
- screen_id herschrijft volgens die instelling. Schrijf zelf nooit een
1167
- station_id dat daarvan afwijkt; dat wordt overschreven of geweigerd.
1168
- - Taken uit logistics_tasks dragen payload.documentType en payload.pickStationId.
1169
- Gebruik die twee samen; nooit alleen het taaktype.
1170
- - Standaardweergave is "documenten van vandaag". Oudere, niet-geprinte regels
1260
+ body: `Deze prompt vervangt niets uit prompt 1 t/m 3; hij komt er bovenop.
1261
+
1262
+ Probleem: op "Pickstation Orders" verschenen naast pakbonnen ook veilingbrieven
1263
+ en transportbrieven. Dat mag nooit.
1264
+
1265
+ Haal weg: elke query die de wachtrij alleen op station_id filtert, en elke
1266
+ fallback die "overige documenten" toch ergens toont.
1267
+
1268
+ Regels:
1269
+ - Filter de wachtrij ALTIJD op screen_id van het geopende scherm, niet op
1270
+ station_id. Elk scherm heeft via public.pick_screen_document_types precies de
1271
+ documenttypes die het mag tonen.
1272
+ - Toon nooit een document waarvan het document_type_id niet aan dat scherm is
1273
+ gekoppeld. Geen "rest"-categorie, geen samengevoegde stationweergave.
1274
+ - De koppeling documentsoort -> pickstation komt uit de Logistiek Engine
1275
+ (Rule Engine, stap "Pickstations"). In de database is dat opvraagbaar met
1276
+ select public.logistics_document_station('pakbon').
1277
+ - De printwachtrij public.pick_queue heeft een trigger die station_id en
1278
+ screen_id herschrijft volgens die instelling. Schrijf zelf nooit een
1279
+ station_id dat daarvan afwijkt; dat wordt overschreven of geweigerd.
1280
+ - Taken uit logistics_tasks dragen payload.documentType en payload.pickStationId.
1281
+ Gebruik die twee samen; nooit alleen het taaktype.
1282
+ - Standaardweergave is "documenten van vandaag". Oudere, niet-geprinte regels
1171
1283
  horen in een apart tabblad, niet in de dagwachtrij.`,
1172
1284
  },
1173
1285
  {
@@ -1175,41 +1287,41 @@ Regels:
1175
1287
  title: "Documenten altijd renderen met de layout uit de Layout maker",
1176
1288
  date: "2026-08-04",
1177
1289
  summary: "Geen eigen pakbon-opmaak meer; de layout komt uit public.document_layouts.",
1178
- body: `Deze prompt vervangt niets uit prompt 1 t/m 4; hij komt er bovenop.
1179
-
1180
- Probleem: het pickstation print een eigen, hardgecodeerde pakbon. De pakbon die
1181
- in de module "Layout maker (pakbonnen, verzendbrieven, extra)" is ontworpen
1182
- wordt genegeerd.
1183
-
1184
- Haal weg: elke eigen HTML/PDF-opmaak van pakbonnen, verzendbrieven, stickers,
1185
- productiebonnen en controlelijsten. Ook alle vaste kolommen, logo's, marges en
1186
- papierformaten die in de pickstation-code staan.
1187
-
1188
- Regels:
1189
- - De opmaak komt ALTIJD uit public.document_layouts (+ document_layout_elements,
1190
- document_layout_variables, document_layout_versions, layout_logos). Nooit een
1191
- eigen sjabloon, ook niet als fallback.
1192
- - Welke layout bij een documentsoort hoort, bepaal je niet zelf. Gebruik:
1193
- select public.logistics_document_layout('pakbon');
1194
- Dat geeft { layoutId, layoutName, layoutVersion, layoutDocumentType } van de
1195
- actieve layout (voorkeur: is_standaard).
1196
- - Elke regel in public.pick_queue draagt deze gegevens al mee in payload:
1197
- documentType, layoutId, layoutName, layoutVersion. Gebruik payload.layoutId
1198
- als die er staat; val alleen terug op de functie hierboven als hij ontbreekt.
1199
- - Render met de renderer van de Layout maker, niet met eigen code. Papierformaat,
1200
- oriëntatie en marges komen uit de layoutrij (papierformaat, breedte_mm,
1201
- hoogte_mm, marge_*, orientatie, page_settings).
1202
- - Ontbreekt er een actieve layout voor een documentsoort? Print dan niet, maar
1203
- toon "Geen layout ingesteld voor <documentsoort>" met een verwijzing naar de
1204
- Layout maker. Nooit stilzwijgend een eigen pakbon printen.
1205
- - Floriday-documenten (prompt 3) blijven via de gateway lopen; alles wat we zelf
1206
- opmaken loopt via de Layout maker.
1207
- - Log bij het afronden van de taak welke layoutId en layoutVersion zijn gebruikt
1208
- in het result van logistics_complete_task.
1209
-
1210
- CONTROLELIJST
1211
- [ ] Geen enkel eigen documentsjabloon meer in de code.
1212
- [ ] Pakbon in het pickstation is visueel identiek aan de preview in de Layout maker.
1290
+ body: `Deze prompt vervangt niets uit prompt 1 t/m 4; hij komt er bovenop.
1291
+
1292
+ Probleem: het pickstation print een eigen, hardgecodeerde pakbon. De pakbon die
1293
+ in de module "Layout maker (pakbonnen, verzendbrieven, extra)" is ontworpen
1294
+ wordt genegeerd.
1295
+
1296
+ Haal weg: elke eigen HTML/PDF-opmaak van pakbonnen, verzendbrieven, stickers,
1297
+ productiebonnen en controlelijsten. Ook alle vaste kolommen, logo's, marges en
1298
+ papierformaten die in de pickstation-code staan.
1299
+
1300
+ Regels:
1301
+ - De opmaak komt ALTIJD uit public.document_layouts (+ document_layout_elements,
1302
+ document_layout_variables, document_layout_versions, layout_logos). Nooit een
1303
+ eigen sjabloon, ook niet als fallback.
1304
+ - Welke layout bij een documentsoort hoort, bepaal je niet zelf. Gebruik:
1305
+ select public.logistics_document_layout('pakbon');
1306
+ Dat geeft { layoutId, layoutName, layoutVersion, layoutDocumentType } van de
1307
+ actieve layout (voorkeur: is_standaard).
1308
+ - Elke regel in public.pick_queue draagt deze gegevens al mee in payload:
1309
+ documentType, layoutId, layoutName, layoutVersion. Gebruik payload.layoutId
1310
+ als die er staat; val alleen terug op de functie hierboven als hij ontbreekt.
1311
+ - Render met de renderer van de Layout maker, niet met eigen code. Papierformaat,
1312
+ oriëntatie en marges komen uit de layoutrij (papierformaat, breedte_mm,
1313
+ hoogte_mm, marge_*, orientatie, page_settings).
1314
+ - Ontbreekt er een actieve layout voor een documentsoort? Print dan niet, maar
1315
+ toon "Geen layout ingesteld voor <documentsoort>" met een verwijzing naar de
1316
+ Layout maker. Nooit stilzwijgend een eigen pakbon printen.
1317
+ - Floriday-documenten (prompt 3) blijven via de gateway lopen; alles wat we zelf
1318
+ opmaken loopt via de Layout maker.
1319
+ - Log bij het afronden van de taak welke layoutId en layoutVersion zijn gebruikt
1320
+ in het result van logistics_complete_task.
1321
+
1322
+ CONTROLELIJST
1323
+ [ ] Geen enkel eigen documentsjabloon meer in de code.
1324
+ [ ] Pakbon in het pickstation is visueel identiek aan de preview in de Layout maker.
1213
1325
  [ ] Ontbrekende layout geeft een duidelijke melding in plaats van een print.`,
1214
1326
  },
1215
1327
  {
@@ -1217,50 +1329,50 @@ CONTROLELIJST
1217
1329
  title: "Sectie-layouts uit de Layout maker correct printen",
1218
1330
  date: "2026-08-04",
1219
1331
  summary: "Een geldige pakbon in document_layouts.config mag nooit als ontbrekende layout worden afgewezen.",
1220
- body: `Deze prompt vervangt niets uit prompt 1 t/m 5; hij komt er bovenop.
1221
-
1222
- Probleem: de gekoppelde layout "packbon hoofd" is een geldige sectie-layout.
1223
- De opbouw staat in public.document_layouts.config.pakbon.secties en niet in
1224
- public.document_layout_elements. Het pickstation ondersteunt nu alleen
1225
- canvas-elementen en toont daardoor ten onrechte "Geen layout ingesteld".
1226
-
1227
- Haal weg:
1228
- - elke controle die een layout als ontbrekend beschouwt wanneer
1229
- document_layout_elements leeg is;
1230
- - elke renderer die uitsluitend document_layout_elements ondersteunt;
1231
- - de algemene MissingLayoutError voor een wel bestaande, actieve layoutrij.
1232
-
1233
- Regels:
1234
- - Een layout ontbreekt uitsluitend als public.document_layouts geen actieve rij
1235
- voor het door de Engine aangeleverde layoutId bevat. Nul canvas-elementen is
1236
- geen ontbrekende layout.
1237
- - Lees eerst payload.layoutId (of payload.layout_id) uit public.pick_queue.
1238
- De huidige pakbonnen verwijzen daarmee naar de gekoppelde layoutrij.
1239
- - Kies daarna de renderer op basis van de opgeslagen layoutvorm:
1240
- 1. config.pakbon.secties is een array: gebruik dezelfde sectie-/pakbonrenderer
1241
- en dezelfde datamapping als de preview van de Layout maker;
1242
- 2. document_layout_elements bevat rijen: gebruik de canvasrenderer;
1243
- 3. geen van beide bevat inhoud: meld "Layout heeft nog geen inhoud", niet
1244
- "Geen layout ingesteld".
1245
- - Importeer de renderer en bijbehorende types bij voorkeur rechtstreeks uit de
1246
- Layout maker-module. Kopieer geen pakbonsjabloon naar deze module. Als de
1247
- Layout maker de renderer nog niet exporteert, voeg daar named exports toe voor
1248
- de sectierenderer en de order-naar-pakbon-datamapping en gebruik die hier.
1249
- - Respecteer config.pakbon.secties volledig: volgorde, zichtbaar, condities,
1250
- opties, karren, lagen, producten, totalen en logo. Respecteer daarnaast
1251
- papierformaat, oriëntatie, marges en page_settings van document_layouts.
1252
- - Vul vóór renderen de volledige ordercontext voor payload.orderId of
1253
- order_number: order, klant, afleveradres, orderregels, karren, lagen,
1254
- producten en fusten. De wachtrijregel alleen bevat hiervoor onvoldoende data.
1255
- - Markeer de wachtrijregel pas als geprint nadat renderen is gelukt en de
1256
- printdialoog is geopend. Laat de regel bij een renderfout in de wachtrij staan.
1257
-
1258
- CONTROLELIJST
1259
- [ ] "packbon hoofd" opent vanuit Pickstation Orders zonder MissingLayoutError.
1260
- [ ] De afdruk is visueel gelijk aan de preview in de Layout maker.
1261
- [ ] Karren, lagen en producten van de gekozen order staan op de pakbon.
1262
- [ ] Een lege layout geeft "Layout heeft nog geen inhoud".
1263
- [ ] Een werkelijk ontbrekende/inactieve layoutrij geeft pas "Geen layout ingesteld".
1332
+ body: `Deze prompt vervangt niets uit prompt 1 t/m 5; hij komt er bovenop.
1333
+
1334
+ Probleem: de gekoppelde layout "packbon hoofd" is een geldige sectie-layout.
1335
+ De opbouw staat in public.document_layouts.config.pakbon.secties en niet in
1336
+ public.document_layout_elements. Het pickstation ondersteunt nu alleen
1337
+ canvas-elementen en toont daardoor ten onrechte "Geen layout ingesteld".
1338
+
1339
+ Haal weg:
1340
+ - elke controle die een layout als ontbrekend beschouwt wanneer
1341
+ document_layout_elements leeg is;
1342
+ - elke renderer die uitsluitend document_layout_elements ondersteunt;
1343
+ - de algemene MissingLayoutError voor een wel bestaande, actieve layoutrij.
1344
+
1345
+ Regels:
1346
+ - Een layout ontbreekt uitsluitend als public.document_layouts geen actieve rij
1347
+ voor het door de Engine aangeleverde layoutId bevat. Nul canvas-elementen is
1348
+ geen ontbrekende layout.
1349
+ - Lees eerst payload.layoutId (of payload.layout_id) uit public.pick_queue.
1350
+ De huidige pakbonnen verwijzen daarmee naar de gekoppelde layoutrij.
1351
+ - Kies daarna de renderer op basis van de opgeslagen layoutvorm:
1352
+ 1. config.pakbon.secties is een array: gebruik dezelfde sectie-/pakbonrenderer
1353
+ en dezelfde datamapping als de preview van de Layout maker;
1354
+ 2. document_layout_elements bevat rijen: gebruik de canvasrenderer;
1355
+ 3. geen van beide bevat inhoud: meld "Layout heeft nog geen inhoud", niet
1356
+ "Geen layout ingesteld".
1357
+ - Importeer de renderer en bijbehorende types bij voorkeur rechtstreeks uit de
1358
+ Layout maker-module. Kopieer geen pakbonsjabloon naar deze module. Als de
1359
+ Layout maker de renderer nog niet exporteert, voeg daar named exports toe voor
1360
+ de sectierenderer en de order-naar-pakbon-datamapping en gebruik die hier.
1361
+ - Respecteer config.pakbon.secties volledig: volgorde, zichtbaar, condities,
1362
+ opties, karren, lagen, producten, totalen en logo. Respecteer daarnaast
1363
+ papierformaat, oriëntatie, marges en page_settings van document_layouts.
1364
+ - Vul vóór renderen de volledige ordercontext voor payload.orderId of
1365
+ order_number: order, klant, afleveradres, orderregels, karren, lagen,
1366
+ producten en fusten. De wachtrijregel alleen bevat hiervoor onvoldoende data.
1367
+ - Markeer de wachtrijregel pas als geprint nadat renderen is gelukt en de
1368
+ printdialoog is geopend. Laat de regel bij een renderfout in de wachtrij staan.
1369
+
1370
+ CONTROLELIJST
1371
+ [ ] "packbon hoofd" opent vanuit Pickstation Orders zonder MissingLayoutError.
1372
+ [ ] De afdruk is visueel gelijk aan de preview in de Layout maker.
1373
+ [ ] Karren, lagen en producten van de gekozen order staan op de pakbon.
1374
+ [ ] Een lege layout geeft "Layout heeft nog geen inhoud".
1375
+ [ ] Een werkelijk ontbrekende/inactieve layoutrij geeft pas "Geen layout ingesteld".
1264
1376
  [ ] De wachtrij wordt alleen afgerond na een geslaagde render/printactie.`,
1265
1377
  },
1266
1378
  {
@@ -1268,37 +1380,37 @@ CONTROLELIJST
1268
1380
  title: "Altijd de actuele layout, behalve bij kant-en-klare PDF's",
1269
1381
  date: "2026-08-14",
1270
1382
  summary: "Layout wint boven meegestuurde HTML; klantstickers en Floriday-documenten gaan als bestaande PDF ongewijzigd naar de printer.",
1271
- body: `Deze prompt vervangt niets uit prompt 1 t/m 6; hij komt er bovenop.
1272
-
1273
- Uitgangspunt: wat er geprint wordt is ALTIJD de actuele layout uit de Layout
1274
- maker, gevuld met de echte gegevens van de order en de klant. Enige uitzondering:
1275
- documenten die al als kant-en-klare PDF bestaan (klantstickers en
1276
- Floriday-documenten). Die worden ongewijzigd doorgestuurd.
1277
-
1278
- Volgorde van beslissen bij een printopdracht in public.print_jobs:
1279
- 1. Is er een kant-en-klare PDF meegegeven (pdf-veld op de opdracht / bron uit
1280
- floriday_customer_sticker_pdfs_cache): die PDF ongewijzigd naar de printer.
1281
- Niet opnieuw opmaken, niet renderen.
1282
- 2. Is print_jobs.layout_id gevuld: die layout wint. Negeer een eventueel
1283
- meegestuurde render_html en render vers uit public.document_layouts.
1284
- 3. Pas als beide leeg zijn: meegestuurde render_html, en anders de actieve
1285
- layout bij het documenttype.
1286
-
1287
- Haal weg:
1288
- - elke eigen HTML-sjabloon die om de layout heen print;
1289
- - elke gecachte kopie van een layout; de layout wordt per opdracht live gelezen.
1290
-
1291
- Regels:
1292
- - layout_id en printer_id komen uit de Engine (public.pick_enqueue_print_job).
1293
- Kies zelf nooit een andere layout of printer wanneer die velden gevuld zijn.
1294
- - Statussen blijven binnen de enum print_job_status: wachtend, bezig, voltooid,
1295
- mislukt, geannuleerd. Andere modules luisteren daarop.
1296
- - Geen PDF én geen layout: markeer de opdracht als mislukt met een leesbare
1297
- reden in foutmelding. Nooit blanco printen.
1298
-
1299
- CONTROLELIJST
1300
- [ ] Layout aangepast in de Layout maker -> eerstvolgende print toont de wijziging.
1301
- [ ] Klantsticker en Floriday-document komen er exact uit zoals aangeleverd.
1383
+ body: `Deze prompt vervangt niets uit prompt 1 t/m 6; hij komt er bovenop.
1384
+
1385
+ Uitgangspunt: wat er geprint wordt is ALTIJD de actuele layout uit de Layout
1386
+ maker, gevuld met de echte gegevens van de order en de klant. Enige uitzondering:
1387
+ documenten die al als kant-en-klare PDF bestaan (klantstickers en
1388
+ Floriday-documenten). Die worden ongewijzigd doorgestuurd.
1389
+
1390
+ Volgorde van beslissen bij een printopdracht in public.print_jobs:
1391
+ 1. Is er een kant-en-klare PDF meegegeven (pdf-veld op de opdracht / bron uit
1392
+ floriday_customer_sticker_pdfs_cache): die PDF ongewijzigd naar de printer.
1393
+ Niet opnieuw opmaken, niet renderen.
1394
+ 2. Is print_jobs.layout_id gevuld: die layout wint. Negeer een eventueel
1395
+ meegestuurde render_html en render vers uit public.document_layouts.
1396
+ 3. Pas als beide leeg zijn: meegestuurde render_html, en anders de actieve
1397
+ layout bij het documenttype.
1398
+
1399
+ Haal weg:
1400
+ - elke eigen HTML-sjabloon die om de layout heen print;
1401
+ - elke gecachte kopie van een layout; de layout wordt per opdracht live gelezen.
1402
+
1403
+ Regels:
1404
+ - layout_id en printer_id komen uit de Engine (public.pick_enqueue_print_job).
1405
+ Kies zelf nooit een andere layout of printer wanneer die velden gevuld zijn.
1406
+ - Statussen blijven binnen de enum print_job_status: wachtend, bezig, voltooid,
1407
+ mislukt, geannuleerd. Andere modules luisteren daarop.
1408
+ - Geen PDF én geen layout: markeer de opdracht als mislukt met een leesbare
1409
+ reden in foutmelding. Nooit blanco printen.
1410
+
1411
+ CONTROLELIJST
1412
+ [ ] Layout aangepast in de Layout maker -> eerstvolgende print toont de wijziging.
1413
+ [ ] Klantsticker en Floriday-document komen er exact uit zoals aangeleverd.
1302
1414
  [ ] Geen enkele printer- of layoutnaam hardcoded in deze module.`,
1303
1415
  },
1304
1416
  {
@@ -1306,38 +1418,38 @@ CONTROLELIJST
1306
1418
  title: "Pickstation koppelen aan een printplek",
1307
1419
  date: "2026-08-25",
1308
1420
  summary: "Elk pickstation krijgt een printplek uit de Layout maker; de Engine stuurt die printplek mee en kiest zelf geen printer meer.",
1309
- body: `Deze prompt vervangt niets uit prompt 1 t/m 7; hij komt er bovenop.
1310
-
1311
- Wat er verandert in de Logistiek Engine: public.pick_enqueue_print_job kiest
1312
- geen printer meer. De opdracht krijgt de printplek van het pickstation mee
1313
- (top-level in payload.print_plek_id en in payload.data.print_plek_id) en gaat
1314
- altijd als status 'wachtend' de wachtrij in. De Layout maker bepaalt daarna de
1315
- printer: printplek -> bedrijfsbrede standaardregel, en meldt zelf de reden als
1316
- dat niet lukt.
1317
-
1318
- Wat deze module moet doen:
1319
- - Beheer per pickstation de koppeling naar een printplek in
1320
- public.print_plek_koppelingen met bron = 'pickstation', bron_id =
1321
- public.pick_stations.id als tekst, print_plek_id = de gekozen rij uit
1322
- public.print_plekken. Eén actieve koppeling per pickstation.
1323
- - Toon bij het aanmaken en wijzigen van een pickstation een keuzelijst met
1324
- actieve printplekken uit public.print_plekken (naam + omschrijving).
1325
- Verwijder je een pickstation, verwijder dan ook de koppelrij.
1326
- - Hardcode nooit een printplek-, printer- of stationsnaam of -id; alles komt
1327
- uit de database.
1328
- - Geef _printer_id alleen mee aan pick_enqueue_print_job wanneer een gebruiker
1329
- bewust handmatig een printer kiest (bijvoorbeeld opnieuw printen). Anders
1330
- leeg laten.
1331
- - Een pickstation zonder printplek: toon een duidelijke waarschuwing
1332
- ("Nog geen printplek gekoppeld — de opdracht blijft in de wachtrij staan")
1333
- in plaats van zelf een printer te kiezen.
1334
- - Blijf de status van public.print_jobs volgen (wachtend, bezig, voltooid,
1335
- mislukt, geannuleerd) en toon foutmelding van de Layout maker met een knop
1336
- "opnieuw printen".
1337
-
1338
- CONTROLELIJST
1339
- [ ] Elk pickstation heeft een rij in print_plek_koppelingen.
1340
- [ ] Nieuwe printopdrachten bevatten payload.print_plek_id.
1421
+ body: `Deze prompt vervangt niets uit prompt 1 t/m 7; hij komt er bovenop.
1422
+
1423
+ Wat er verandert in de Logistiek Engine: public.pick_enqueue_print_job kiest
1424
+ geen printer meer. De opdracht krijgt de printplek van het pickstation mee
1425
+ (top-level in payload.print_plek_id en in payload.data.print_plek_id) en gaat
1426
+ altijd als status 'wachtend' de wachtrij in. De Layout maker bepaalt daarna de
1427
+ printer: printplek -> bedrijfsbrede standaardregel, en meldt zelf de reden als
1428
+ dat niet lukt.
1429
+
1430
+ Wat deze module moet doen:
1431
+ - Beheer per pickstation de koppeling naar een printplek in
1432
+ public.print_plek_koppelingen met bron = 'pickstation', bron_id =
1433
+ public.pick_stations.id als tekst, print_plek_id = de gekozen rij uit
1434
+ public.print_plekken. Eén actieve koppeling per pickstation.
1435
+ - Toon bij het aanmaken en wijzigen van een pickstation een keuzelijst met
1436
+ actieve printplekken uit public.print_plekken (naam + omschrijving).
1437
+ Verwijder je een pickstation, verwijder dan ook de koppelrij.
1438
+ - Hardcode nooit een printplek-, printer- of stationsnaam of -id; alles komt
1439
+ uit de database.
1440
+ - Geef _printer_id alleen mee aan pick_enqueue_print_job wanneer een gebruiker
1441
+ bewust handmatig een printer kiest (bijvoorbeeld opnieuw printen). Anders
1442
+ leeg laten.
1443
+ - Een pickstation zonder printplek: toon een duidelijke waarschuwing
1444
+ ("Nog geen printplek gekoppeld — de opdracht blijft in de wachtrij staan")
1445
+ in plaats van zelf een printer te kiezen.
1446
+ - Blijf de status van public.print_jobs volgen (wachtend, bezig, voltooid,
1447
+ mislukt, geannuleerd) en toon foutmelding van de Layout maker met een knop
1448
+ "opnieuw printen".
1449
+
1450
+ CONTROLELIJST
1451
+ [ ] Elk pickstation heeft een rij in print_plek_koppelingen.
1452
+ [ ] Nieuwe printopdrachten bevatten payload.print_plek_id.
1341
1453
  [ ] Deze module kiest nergens zelf een printer.`,
1342
1454
  },
1343
1455
  {
@@ -1345,31 +1457,203 @@ CONTROLELIJST
1345
1457
  title: "Layout per ordersoort uit de Engine, anders de actieve layout",
1346
1458
  date: "2026-08-26",
1347
1459
  summary: "De Engine stuurt per werkwijze (ordersoort) een layout mee; is dat veld leeg, dan wint de actieve layout uit de Layout maker.",
1348
- body: `Deze prompt vervangt niets uit prompt 1 t/m 8; hij komt er bovenop.
1349
-
1350
- Wat er verandert in de Logistiek Engine: per werkwijze (ordersoort) kan een
1351
- gebruiker voor pakbon, sticker, verzendbrief en productiebon kiezen welke
1352
- layout gebruikt wordt. Kiest hij "Automatisch", dan blijft print_jobs.layout_id
1353
- leeg. Dat is een geldige situatie, geen fout.
1354
-
1355
- Vaste volgorde bij het bepalen van wat er geprint wordt:
1356
- 1. Kant-en-klare PDF (pdf_url / pdf_base64, bijvoorbeeld Floriday- of
1357
- klantstickers): ongewijzigd printen, layout negeren.
1358
- 2. print_jobs.layout_id gevuld: exact die layout renderen.
1359
- 3. Layout_id leeg: de layout die bij dat documenttype op actief staat.
1360
- 4. Geen van alle: opdracht op mislukt met de leesbare reden
1361
- "Maak een layout aan en zet die op actief."
1362
-
1363
- Regels:
1364
- - Kies nooit zelf een andere layout wanneer layout_id gevuld is.
1365
- - Verander de meegestuurde printplek niet; de printer komt uit de Layout maker.
1366
- - Hardcode geen layout-, printer- of documenttypenamen.
1367
-
1368
- CONTROLELIJST
1369
- [ ] Order met meegestuurde layout gebruikt exact die layout.
1370
- [ ] Order zonder layout gebruikt de actieve layout van dat documenttype.
1460
+ body: `Deze prompt vervangt niets uit prompt 1 t/m 8; hij komt er bovenop.
1461
+
1462
+ Wat er verandert in de Logistiek Engine: per werkwijze (ordersoort) kan een
1463
+ gebruiker voor pakbon, sticker, verzendbrief en productiebon kiezen welke
1464
+ layout gebruikt wordt. Kiest hij "Automatisch", dan blijft print_jobs.layout_id
1465
+ leeg. Dat is een geldige situatie, geen fout.
1466
+
1467
+ Vaste volgorde bij het bepalen van wat er geprint wordt:
1468
+ 1. Kant-en-klare PDF (pdf_url / pdf_base64, bijvoorbeeld Floriday- of
1469
+ klantstickers): ongewijzigd printen, layout negeren.
1470
+ 2. print_jobs.layout_id gevuld: exact die layout renderen.
1471
+ 3. Layout_id leeg: de layout die bij dat documenttype op actief staat.
1472
+ 4. Geen van alle: opdracht op mislukt met de leesbare reden
1473
+ "Maak een layout aan en zet die op actief."
1474
+
1475
+ Regels:
1476
+ - Kies nooit zelf een andere layout wanneer layout_id gevuld is.
1477
+ - Verander de meegestuurde printplek niet; de printer komt uit de Layout maker.
1478
+ - Hardcode geen layout-, printer- of documenttypenamen.
1479
+
1480
+ CONTROLELIJST
1481
+ [ ] Order met meegestuurde layout gebruikt exact die layout.
1482
+ [ ] Order zonder layout gebruikt de actieve layout van dat documenttype.
1371
1483
  [ ] Floriday-PDF wordt ongewijzigd geprint.`,
1372
1484
  },
1485
+ {
1486
+ number: 10,
1487
+ title: "Karren en orders uit de gedeelde karindex van de Engine",
1488
+ date: "2026-09-01",
1489
+ summary: "pickstation_get_orders_raw geeft nu een veld 'engine' met de canonieke karlijst; die is leidend boven de eigen berekening.",
1490
+ body: `AANVULLING op prompt 1 t/m 9 (die blijven geldig).
1491
+
1492
+ GEMETEN PROBLEEM
1493
+ Het Picking Station bouwde zijn lijst zelf op uit floriday_salesorders_cache en
1494
+ kwam voor vandaag op 2 karren, terwijl de Engine er 38 gepland had staan. Twee
1495
+ schermen, twee verhalen.
1496
+
1497
+ WAT ER VERANDERT
1498
+ public.pickstation_get_orders_raw() geeft dezelfde velden als voorheen
1499
+ (floriday, manual, overrides, print_counts) plus twee nieuwe:
1500
+
1501
+ engine array met één object per (order, kar):
1502
+ pick_date, delivery_date, order_uuid, order_number,
1503
+ order_source, cart_id, cart_index, cart_label, is_shared,
1504
+ task_status, shifted, announced, printed, controlled
1505
+ engine_window { from, to, today } — het venster en de dag volgens de Engine
1506
+
1507
+ BOUW DIT
1508
+ - Is 'engine' gevuld, gebruik die array als enige bron voor de orderlijst en de
1509
+ karrentelling. Geen eigen indeling of telling meer uit de Floriday-cache.
1510
+ - Gebruik engine_window.today als "vandaag"; niet de lokale browserklok.
1511
+ - Statusvelden gebruiken om te filteren of te kleuren:
1512
+ announced = staat in de pickwachtrij, printed = al geprint,
1513
+ controlled = al gecontroleerd. Een filter verandert nooit het totaal.
1514
+ - Gedeelde kar (is_shared true) telt per dag één keer, maar hoort bij elke
1515
+ order waarin hij zit.
1516
+
1517
+ NIET DOEN
1518
+ - Geen eigen kartelling uit salesorders, order_carts of taak-payloads.
1519
+ - Geen eigen datumvenster of tijdzoneconversie.
1520
+ - Geen URL's, project-refs of sleutels hardcoden.
1521
+
1522
+ CONTROLELIJST
1523
+ [ ] Picking Station, Pick agenda en Controle karren tonen voor vandaag hetzelfde aantal karren.
1524
+ [ ] Een geplande maar nog niet aangemelde kar is zichtbaar met status "gepland".
1525
+ [ ] Geen enkele query meer rechtstreeks op floriday_salesorders_cache voor aantallen.`,
1526
+ },
1527
+ {
1528
+ number: 11,
1529
+ title: "Volle kar of deelorder: pickeenheid en pakbonvariant uit de taak",
1530
+ date: "2026-09-03",
1531
+ summary: "De Engine kent nu de werkwijze \"kleinste telt\" en zet pickUnit en slipVariant in de taak-payload; het pickscherm rekent niets zelf uit.",
1532
+ body: `AANVULLING op prompt 1 t/m 10 (die blijven geldig).
1533
+
1534
+ WAT ER VERANDERT
1535
+ In de Rule Engine kan als pickmethode "Picken per kar of order, kleinste telt"
1536
+ gekozen worden: past een order binnen één kar, dan is de order de pickeenheid;
1537
+ gaat hij over meerdere karren, dan wordt er per kar gepickt.
1538
+
1539
+ De Engine zet dit in de payload van pick.execute en print.packing_slip:
1540
+
1541
+ pickMode de ingestelde werkwijze (order | cart | smallest | buffer)
1542
+ pickUnit de werkelijke eenheid van deze opdracht: "order" of "cart"
1543
+ totalCarriers aantal ladingdragers van de order
1544
+ slipVariant "full_cart" of "partial_order"
1545
+
1546
+ BOUW DIT
1547
+ - Toon en groepeer pickopdrachten op basis van pickUnit, niet op pickMode.
1548
+ - Stuur slipVariant ongewijzigd mee bij het aanvragen of printen van de pakbon.
1549
+ - De layoutkeuze gebeurt in de database (logistics_document_layout_v2); het
1550
+ pickscherm kiest zelf geen layout en geen documenttype.
1551
+
1552
+ NIET DOEN
1553
+ - Niet zelf bepalen of een order een volle kar is.
1554
+ - Geen eigen kartelling; de karindex uit prompt 10 blijft de bron.
1555
+ - Geen URL's, project-refs, sleutels of klantnamen hardcoden.
1556
+
1557
+ CONTROLELIJST
1558
+ [ ] Order binnen één kar levert één pickopdracht per order op.
1559
+ [ ] Order over meerdere karren levert een opdracht per kar op.
1560
+ [ ] De pakbon van een deelorder gebruikt de deelorder-layout als die ingesteld is.`,
1561
+ },
1562
+ {
1563
+ number: 12,
1564
+ title: "Eén order in beeld, picken per kar: \u201cKar X van Y\u201d en een pakbon per kar",
1565
+ date: "2026-09-03",
1566
+ summary: "Nieuwe eis bovenop prompt 1 t/m 11: de order blijft één geheel in het scherm, maar het picken en de pakbonnen lopen per kar via logistics_cart_slip_plan.",
1567
+ body: `AANVULLING op prompt 1 t/m 11 (die blijven geldig).
1568
+
1569
+ WAT ER VERANDERT
1570
+ Bij "kleinste telt" blijft een order in het Pick Station één order, ook als hij
1571
+ over meerdere karren gaat. Tijdens het picken moet zichtbaar zijn met welke kar
1572
+ de medewerker bezig is en welke regels bij die kar horen.
1573
+
1574
+ BRON — verplicht
1575
+ const { data } = await supabase.rpc("logistics_cart_slip_plan", { p_order_id });
1576
+
1577
+ Levert per order:
1578
+ cartTotal aantal karren van de order
1579
+ carts[].cartId unieke identificatie van de kar
1580
+ carts[].cartIndex / cartTotal voor "Kar 2 van 3"
1581
+ carts[].cartLabel kant-en-klare tekst "Kar 2 van 3"
1582
+ carts[].isFullCart volle kar of deelkar
1583
+ carts[].slipVariant "full_cart" of "partial_order"
1584
+ carts[].barcode vaste barcode van die kar
1585
+ carts[].items[] producten en aantallen VAN DIE KAR
1586
+ carts[].isShared gemengde kar (meerdere orders)
1587
+
1588
+ BOUW DIT
1589
+ - Toon de order als één regel/kaart met daarin de karren uit carts[].
1590
+ - Toon per kar de cartLabel en uitsluitend carts[].items van die kar.
1591
+ - Een kar kan afzonderlijk afgerond worden; de order is pas klaar als alle
1592
+ karren klaar zijn.
1593
+ - Bij printen van de pakbon stuur je cart_id mee in de payload van de
1594
+ wachtrijregel (veld "cartId" of "cart_id"). De database bepaalt daarmee
1595
+ automatisch de juiste pakbonvariant, "Kar X van Y" en de barcode.
1596
+
1597
+ NIET DOEN
1598
+ - Niet zelf verdelen over karren of aantallen per kar uitrekenen.
1599
+ - Niet zelf de layout of de pakbonvariant kiezen.
1600
+ - Geen URL's, project-refs, sleutels of klantnamen hardcoden.
1601
+
1602
+ CONTROLELIJST
1603
+ [ ] Een order van 3 karren staat als één order in beeld, met 3 karren eronder.
1604
+ [ ] Elke kar toont alleen zijn eigen producten en aantallen.
1605
+ [ ] Er komt één pakbon per kar; de laatste, niet-volle kar krijgt de deelkar-layout.`,
1606
+ },
1607
+ {
1608
+ number: 13,
1609
+ title: "Pakbon scannen levert de juiste verzendbrief",
1610
+ date: "2026-09-03",
1611
+ summary: "Nieuwe eis bovenop prompt 1 t/m 12: scan de barcode van de pakbon en laat de Engine de bijbehorende verzendbrief klaarzetten.",
1612
+ body: `AANVULLING op prompt 1 t/m 12 (die blijven geldig).
1613
+
1614
+ WERKWIJZE
1615
+ Picken -> pakbon printen -> barcode van de pakbon scannen -> verzendbrief.
1616
+
1617
+ STAP 1 — opzoeken (leest alleen, maakt niets aan)
1618
+ const { data } = await supabase.rpc("logistics_barcode_lookup", { p_barcode });
1619
+ Antwoord bij succes: ok true, orderId, order.orderNumber, order.customerName,
1620
+ cart (met cartLabel, items, slipVariant) en alreadyPrinted/printCount.
1621
+ Antwoord bij een probleem: ok false met errorCode
1622
+ (leeg | ongeldig | onbekend | geen_order) en een kant-en-klare Nederlandse tekst
1623
+ in "error". Toon die tekst letterlijk; verzin geen eigen melding.
1624
+
1625
+ STAP 2 — bevestigen en aanmaken
1626
+ Laat de gevonden order en kar eerst ter controle zien. Pas na een expliciete
1627
+ bevestiging:
1628
+ const { data } = await supabase.rpc("logistics_shipping_note_from_barcode", {
1629
+ p_barcode, p_allow_reprint: false, p_station_id: <pickstation of null>
1630
+ });
1631
+ Is er al eerder een verzendbrief voor deze kar gemaakt, dan komt errorCode
1632
+ "al_geprint" terug. Alleen als de medewerker daarna bewust kiest voor opnieuw
1633
+ printen roep je dezelfde RPC aan met p_allow_reprint: true.
1634
+
1635
+ De RPC zet zelf een printopdracht klaar (print_jobs, status wachtend) met de
1636
+ juiste layout en printplek. Ontbreekt de layout, dan staat dat leesbaar in
1637
+ "warning" en blijft de job wachten — toon die waarschuwing.
1638
+
1639
+ NIET DOEN
1640
+ - Nooit een verzendbrief aanmaken zonder bevestigde lookup.
1641
+ - Niet zelf print_jobs vullen of zelf een layout kiezen.
1642
+ - Geen tweede verzendbrief bij een dubbele scan zonder bewuste keuze.
1643
+ - Geen URL's, project-refs, sleutels of klantnamen hardcoden.
1644
+
1645
+ LAYOUT MAKER
1646
+ Op de pakbon-template hoort een barcode-element met als waarde het veld
1647
+ "barcode" uit de meegeleverde printgegevens (ook beschikbaar als
1648
+ payload.data.cart.barcode). Positie en weergave bepaal je in de template zelf.
1649
+ Er zijn twee pakbonvarianten: volledige kar en resterende order / deelkar.
1650
+
1651
+ CONTROLELIJST
1652
+ [ ] Scannen van een pakbon toont eerst order, kar en aantallen.
1653
+ [ ] Na bevestigen staat er een verzendbrief klaar bij de juiste printer.
1654
+ [ ] Een onbekende barcode geeft de melding uit de RPC en maakt niets aan.
1655
+ [ ] Een tweede scan waarschuwt en print pas na een bewuste keuze opnieuw.`,
1656
+ },
1373
1657
  ],
1374
1658
  },
1375
1659
  scanners: {
@@ -1382,12 +1666,12 @@ CONTROLELIJST
1382
1666
  date: "2026-07-24",
1383
1667
  summary: "Scan- en controletaken uitvoeren.",
1384
1668
  withSharedContract: true,
1385
- body: `Haal weg: eigen aannames over wat en wanneer er gescand moet worden.
1386
-
1387
- Bouw:
1388
- - scan.verify taken; payload reason = pick_scanner_required of kar_vullen_manual_scan.
1389
- - control.execute taken met controlMode "scanner" = de scancontrole.
1390
- - Scanresultaat meesturen in logistics_complete_task (p_result).
1669
+ body: `Haal weg: eigen aannames over wat en wanneer er gescand moet worden.
1670
+
1671
+ Bouw:
1672
+ - scan.verify taken; payload reason = pick_scanner_required of kar_vullen_manual_scan.
1673
+ - control.execute taken met controlMode "scanner" = de scancontrole.
1674
+ - Scanresultaat meesturen in logistics_complete_task (p_result).
1391
1675
  - Bij afwijking logistics_fail_task met toelichting.`,
1392
1676
  },
1393
1677
  ],
@@ -1402,11 +1686,11 @@ Bouw:
1402
1686
  date: "2026-07-24",
1403
1687
  summary: "Reserveringstaken uitvoeren en voorraadwijzigingen melden.",
1404
1688
  withSharedContract: true,
1405
- body: `Haal weg: eigen beslissing wanneer er gereserveerd of afgeboekt wordt.
1406
-
1407
- Bouw:
1408
- - stock.reserve taken uitvoeren; payload bevat loadCarriers.
1409
- - Bij relevante voorraadwijziging een gebeurtenis melden met event_type
1689
+ body: `Haal weg: eigen beslissing wanneer er gereserveerd of afgeboekt wordt.
1690
+
1691
+ Bouw:
1692
+ - stock.reserve taken uitvoeren; payload bevat loadCarriers.
1693
+ - Bij relevante voorraadwijziging een gebeurtenis melden met event_type
1410
1694
  stock.changed en dezelfde order_ref, zodat de Engine kan herplannen.`,
1411
1695
  },
1412
1696
  ],
@@ -1421,10 +1705,10 @@ Bouw:
1421
1705
  date: "2026-07-24",
1422
1706
  summary: "Strategie koppelen aan klant en klantgroep.",
1423
1707
  withSharedContract: true,
1424
- body: `Bouw:
1425
- - Per klant en per klantgroep een koppelveld naar de logistieke strategie
1426
- (logistics_strategies.id), zodat de Engine weet welke rule config geldt.
1427
- - Dit veld meegeven als strategy_id bij logistics_emit_event, of beschikbaar
1708
+ body: `Bouw:
1709
+ - Per klant en per klantgroep een koppelveld naar de logistieke strategie
1710
+ (logistics_strategies.id), zodat de Engine weet welke rule config geldt.
1711
+ - Dit veld meegeven als strategy_id bij logistics_emit_event, of beschikbaar
1428
1712
  maken zodat de Verkoop-module het kan meegeven.`,
1429
1713
  },
1430
1714
  ],
@@ -1439,18 +1723,18 @@ Bouw:
1439
1723
  date: "2026-07-31",
1440
1724
  summary: "Eerste koppelprompt: mail alleen omzetten naar order als de Engine dat toestaat.",
1441
1725
  withSharedContract: true,
1442
- body: `Haal weg: eigen aannames over of een bestelling met te weinig voorraad
1443
- toch mag worden aangemaakt.
1444
-
1445
- ${STOCK_CONTRACT}
1446
-
1447
- In de Mailbox-module concreet:
1448
- - Vóór het omzetten van een mail naar een bestelling altijd
1449
- logistics_check_order_allowed aanroepen met alle orderregels.
1450
- - allowed = false -> maak de bestelling NIET aan. Zet de mail op status
1451
- "geblokkeerd - onvoldoende voorraad" en toon per product wanted/available/missing
1452
- uit check.shortages, zodat een medewerker het handmatig kan oplossen.
1453
- - allowed = true -> bestelling normaal aanmaken en daarna de gebeurtenis melden
1726
+ body: `Haal weg: eigen aannames over of een bestelling met te weinig voorraad
1727
+ toch mag worden aangemaakt.
1728
+
1729
+ ${STOCK_CONTRACT}
1730
+
1731
+ In de Mailbox-module concreet:
1732
+ - Vóór het omzetten van een mail naar een bestelling altijd
1733
+ logistics_check_order_allowed aanroepen met alle orderregels.
1734
+ - allowed = false -> maak de bestelling NIET aan. Zet de mail op status
1735
+ "geblokkeerd - onvoldoende voorraad" en toon per product wanted/available/missing
1736
+ uit check.shortages, zodat een medewerker het handmatig kan oplossen.
1737
+ - allowed = true -> bestelling normaal aanmaken en daarna de gebeurtenis melden
1454
1738
  via logistics_emit_event (order.received) met een unieke p_idempotency_key.`,
1455
1739
  },
1456
1740
  {
@@ -1458,39 +1742,39 @@ In de Mailbox-module concreet:
1458
1742
  title: "Bufferbesluit altijd bij de Engine opvragen",
1459
1743
  date: "2026-08-02",
1460
1744
  summary: "Nieuwe eis bovenop prompt 1: geen eigen bufferlogica; bufferRef komt uit logistics_buffer_decision.",
1461
- body: `AANVULLING op koppelprompt 1 (die blijft gewoon geldig).
1462
-
1463
- Haal weg: elke eigen regel of aanname over het samenvoegen van bestellingen uit
1464
- dezelfde mail, dezelfde afzender of hetzelfde afleveradres.
1465
-
1466
- Nieuwe verplichting: vraag na de voorraadcontrole, vóór het aanmaken van de
1467
- bestelling, het bufferbesluit op bij de Engine:
1468
-
1469
- const { data: besluit } = await supabase.rpc("logistics_buffer_decision", {
1470
- p_order: {
1471
- orderRef: "MAIL-12345",
1472
- checkinAt: "2026-08-03T09:00:00Z",
1473
- deliveryDate: "2026-08-03",
1474
- customerRef: "CUST-1",
1475
- floridayCustomerRef: "FLOR-1",
1476
- bvoCustomerRef: "BVO-1",
1477
- deliveryLocationRef: "LOC-1",
1478
- carrierRef: "TRANS-1",
1479
- loadCarrierType: "trolley",
1480
- orderType: "standaard",
1481
- },
1482
- p_strategy_id: null,
1483
- });
1484
- // besluit = { buffer, reason, bufferRef, bufferKey, matchCriteria,
1485
- // closeBeforeMinutes, bufferCloseAt, release, trace }
1486
-
1487
- Regels:
1488
- - besluit.buffer === false -> bestelling los aanmaken, geen buffer toewijzen.
1489
- - besluit.buffer === true -> gebruik exact besluit.bufferRef en stuur die mee in de
1490
- payload van logistics_emit_event.
1491
- - Ontbrekende velden zorgen ervoor dat de Engine NIET combineert; vul daarom altijd
1492
- alle bovenstaande velden zo compleet mogelijk uit de mail/klantgegevens.
1493
- - Toon besluit.reason en besluit.trace in het mailscherm, zodat een medewerker ziet
1745
+ body: `AANVULLING op koppelprompt 1 (die blijft gewoon geldig).
1746
+
1747
+ Haal weg: elke eigen regel of aanname over het samenvoegen van bestellingen uit
1748
+ dezelfde mail, dezelfde afzender of hetzelfde afleveradres.
1749
+
1750
+ Nieuwe verplichting: vraag na de voorraadcontrole, vóór het aanmaken van de
1751
+ bestelling, het bufferbesluit op bij de Engine:
1752
+
1753
+ const { data: besluit } = await supabase.rpc("logistics_buffer_decision", {
1754
+ p_order: {
1755
+ orderRef: "MAIL-12345",
1756
+ checkinAt: "2026-08-03T09:00:00Z",
1757
+ deliveryDate: "2026-08-03",
1758
+ customerRef: "CUST-1",
1759
+ floridayCustomerRef: "FLOR-1",
1760
+ bvoCustomerRef: "BVO-1",
1761
+ deliveryLocationRef: "LOC-1",
1762
+ carrierRef: "TRANS-1",
1763
+ loadCarrierType: "trolley",
1764
+ orderType: "standaard",
1765
+ },
1766
+ p_strategy_id: null,
1767
+ });
1768
+ // besluit = { buffer, reason, bufferRef, bufferKey, matchCriteria,
1769
+ // closeBeforeMinutes, bufferCloseAt, release, trace }
1770
+
1771
+ Regels:
1772
+ - besluit.buffer === false -> bestelling los aanmaken, geen buffer toewijzen.
1773
+ - besluit.buffer === true -> gebruik exact besluit.bufferRef en stuur die mee in de
1774
+ payload van logistics_emit_event.
1775
+ - Ontbrekende velden zorgen ervoor dat de Engine NIET combineert; vul daarom altijd
1776
+ alle bovenstaande velden zo compleet mogelijk uit de mail/klantgegevens.
1777
+ - Toon besluit.reason en besluit.trace in het mailscherm, zodat een medewerker ziet
1494
1778
  waarom een bestelling wel of niet is samengevoegd.`,
1495
1779
  },
1496
1780
  {
@@ -1498,39 +1782,39 @@ Regels:
1498
1782
  title: "Alleen ladingdragers gebruiken die de Engine toestaat",
1499
1783
  date: "2026-08-03",
1500
1784
  summary: "Nieuwe eis bovenop prompt 1 en 2: uitgeschakelde ladingdragers mogen niet meer worden gekozen of ingelezen.",
1501
- body: `AANVULLING op koppelprompt 1 en 2 (die blijven gewoon geldig).
1502
-
1503
- In de Rule Engine (stap Beladingindeling) staat welke ladingdragers zijn toegestaan.
1504
- Staat een ladingdrager uit, dan mag de Mailbox-module die niet meer kiezen of
1505
- automatisch invullen.
1506
-
1507
- Haal weg: elke eigen lijst met ladingdragers / verzendmethodes en elke eigen
1508
- vertaling van tekst uit een mail naar een ladingdrager die niet uit de Engine komt.
1509
-
1510
- Nieuwe verplichting: haal de lijst op bij de Engine.
1511
-
1512
- const { data: dragers } = await supabase.rpc("logistics_allowed_load_carriers", {
1513
- p_strategy_id: null,
1514
- });
1515
- // dragers = { allowed: [...], default: "trolley",
1516
- // carriers: [{ code, label, enabled, isDefault }, ...] }
1517
-
1518
- Codes en namen (gebruik exact deze codes):
1519
- trolley Stapelwagen
1520
- danish_container Deense container
1521
- euro_rolcontainer Euro-rolcontainer
1522
- pallet Pallet
1523
- euro_pallet Euro-pallet
1524
- loose Geen ladingdrager
1525
-
1526
- Regels:
1527
- - In het mailscherm alleen dragers met enabled === true tonen; de rest weglaten.
1528
- - Herkent de mail een ladingdrager die niet in allowed staat, maak de bestelling dan
1529
- NIET automatisch aan. Zet de mail op "handmatig controleren - ladingdrager
1530
- uitgeschakeld" en toon welke drager in de mail stond.
1531
- - Is er niets herkend, gebruik dan dragers.default.
1532
- - Stuur loadCarrierType in logistics_check_order_allowed, logistics_buffer_decision
1533
- en logistics_emit_event altijd met een van bovenstaande codes mee.
1785
+ body: `AANVULLING op koppelprompt 1 en 2 (die blijven gewoon geldig).
1786
+
1787
+ In de Rule Engine (stap Beladingindeling) staat welke ladingdragers zijn toegestaan.
1788
+ Staat een ladingdrager uit, dan mag de Mailbox-module die niet meer kiezen of
1789
+ automatisch invullen.
1790
+
1791
+ Haal weg: elke eigen lijst met ladingdragers / verzendmethodes en elke eigen
1792
+ vertaling van tekst uit een mail naar een ladingdrager die niet uit de Engine komt.
1793
+
1794
+ Nieuwe verplichting: haal de lijst op bij de Engine.
1795
+
1796
+ const { data: dragers } = await supabase.rpc("logistics_allowed_load_carriers", {
1797
+ p_strategy_id: null,
1798
+ });
1799
+ // dragers = { allowed: [...], default: "trolley",
1800
+ // carriers: [{ code, label, enabled, isDefault }, ...] }
1801
+
1802
+ Codes en namen (gebruik exact deze codes):
1803
+ trolley Stapelwagen
1804
+ danish_container Deense container
1805
+ euro_rolcontainer Euro-rolcontainer
1806
+ pallet Pallet
1807
+ euro_pallet Euro-pallet
1808
+ loose Geen ladingdrager
1809
+
1810
+ Regels:
1811
+ - In het mailscherm alleen dragers met enabled === true tonen; de rest weglaten.
1812
+ - Herkent de mail een ladingdrager die niet in allowed staat, maak de bestelling dan
1813
+ NIET automatisch aan. Zet de mail op "handmatig controleren - ladingdrager
1814
+ uitgeschakeld" en toon welke drager in de mail stond.
1815
+ - Is er niets herkend, gebruik dan dragers.default.
1816
+ - Stuur loadCarrierType in logistics_check_order_allowed, logistics_buffer_decision
1817
+ en logistics_emit_event altijd met een van bovenstaande codes mee.
1534
1818
  - Cache de lijst niet; de instelling kan op elk moment wijzigen.`,
1535
1819
  },
1536
1820
  ],
@@ -1545,65 +1829,149 @@ Regels:
1545
1829
  date: "2026-08-04",
1546
1830
  withSharedContract: true,
1547
1831
  summary: "Deze module schrijft de dagbezetting weg en toont per dag of de Logistics Engine alle ophaalmomenten haalt.",
1548
- body: `LEES DIT EERST — hoe je deze prompt uitvoert
1549
-
1550
- Deze module is de bron van de bezetting. De Logistics Engine rekent daarmee uit
1551
- of alle karren voor ieder ophaalmoment op tijd gepickt kunnen worden. Je bouwt
1552
- twee dingen: wegschrijven van de planning, en het tonen van de waarschuwing.
1553
- Zelf rekenen doe je niet.
1554
-
1555
- 1) Dagbezetting wegschrijven
1556
- Tabel: public.logistics_staff_planning
1557
- Kolommen: plan_date (date), staff_count (aantal medewerkers),
1558
- start_time, end_time (werktijden), source_module, note.
1559
- Eén rij per dag (uniek op gebruiker + plan_date). Schrijf of update deze rij
1560
- telkens wanneer de gebruiker de bezetting of de werktijden van een dag
1561
- wijzigt:
1562
-
1563
- await supabase.from("logistics_staff_planning").upsert({
1564
- plan_date: "2026-08-04",
1565
- staff_count: 7,
1566
- start_time: "06:00",
1567
- end_time: "16:00",
1568
- source_module: "personeelsplanning",
1569
- }, { onConflict: "user_id,plan_date" })
1570
-
1571
- 2) Direct laten herrekenen
1572
- Roep na iedere wijziging aan:
1573
- await supabase.rpc("logistics_capacity_plan_day", { p_date: "2026-08-04" })
1574
- De Engine berekent dan opnieuw of alle ophaalmomenten haalbaar zijn en
1575
- verplaatst waar nodig automatisch COMPLETE karren naar een later
1576
- ophaalmoment. Jij verplaatst zelf nooit karren.
1577
-
1578
- 3) Rode waarschuwing tonen
1579
- Tabel: public.logistics_capacity_alerts, filter op plan_date en
1580
- status = 'open'. Toon op de betreffende dag een duidelijk rode melding met:
1581
- - shortage_minutes → hoeveel capaciteit tekort is (toon in uren + minuten)
1582
- - pickup_label / pickup_time → welk ophaalmoment niet gehaald wordt
1583
- - carts_short → hoeveel karren niet op tijd verwerkt kunnen worden
1584
- - moved_carts → welke karren automatisch zijn doorgeschoven
1585
- - unplanned_carts → welke karren nog steeds niet ingepland kunnen worden
1586
- Zijn er geen open rijen, toon dan géén waarschuwing (groen/neutraal).
1587
-
1588
- 4) Ophaalmomenten
1589
- Tabel public.logistics_pickup_moments bevat per dag de ophaalmomenten
1590
- (sequence, label, pickup_time). Gebruik die om de waarschuwing per
1591
- ophaalmoment te tonen. Aanmaken of wijzigen van ophaalmomenten mag alleen
1592
- als de gebruiker daar in deze module om vraagt; anders alleen lezen.
1593
-
1594
- 5) Wat je NIET doet
1595
- - Geen eigen capaciteitsberekening of eigen picktijden.
1596
- - Geen karren splitsen of verplaatsen.
1597
- - Geen eigen mail sturen bij een tekort; de Engine regelt e-mail en het
1832
+ body: `LEES DIT EERST — hoe je deze prompt uitvoert
1833
+
1834
+ Deze module is de bron van de bezetting. De Logistics Engine rekent daarmee uit
1835
+ of alle karren voor ieder ophaalmoment op tijd gepickt kunnen worden. Je bouwt
1836
+ twee dingen: wegschrijven van de planning, en het tonen van de waarschuwing.
1837
+ Zelf rekenen doe je niet.
1838
+
1839
+ 1) Dagbezetting wegschrijven
1840
+ Tabel: public.logistics_staff_planning
1841
+ Kolommen: plan_date (date), staff_count (aantal medewerkers),
1842
+ start_time, end_time (werktijden), source_module, note.
1843
+ Eén rij per dag (uniek op gebruiker + plan_date). Schrijf of update deze rij
1844
+ telkens wanneer de gebruiker de bezetting of de werktijden van een dag
1845
+ wijzigt:
1846
+
1847
+ await supabase.from("logistics_staff_planning").upsert({
1848
+ plan_date: "2026-08-04",
1849
+ staff_count: 7,
1850
+ start_time: "06:00",
1851
+ end_time: "16:00",
1852
+ source_module: "personeelsplanning",
1853
+ }, { onConflict: "user_id,plan_date" })
1854
+
1855
+ 2) Direct laten herrekenen
1856
+ Roep na iedere wijziging aan:
1857
+ await supabase.rpc("logistics_capacity_plan_day", { p_date: "2026-08-04" })
1858
+ De Engine berekent dan opnieuw of alle ophaalmomenten haalbaar zijn en
1859
+ verplaatst waar nodig automatisch COMPLETE karren naar een later
1860
+ ophaalmoment. Jij verplaatst zelf nooit karren.
1861
+
1862
+ 3) Rode waarschuwing tonen
1863
+ Tabel: public.logistics_capacity_alerts, filter op plan_date en
1864
+ status = 'open'. Toon op de betreffende dag een duidelijk rode melding met:
1865
+ - shortage_minutes → hoeveel capaciteit tekort is (toon in uren + minuten)
1866
+ - pickup_label / pickup_time → welk ophaalmoment niet gehaald wordt
1867
+ - carts_short → hoeveel karren niet op tijd verwerkt kunnen worden
1868
+ - moved_carts → welke karren automatisch zijn doorgeschoven
1869
+ - unplanned_carts → welke karren nog steeds niet ingepland kunnen worden
1870
+ Zijn er geen open rijen, toon dan géén waarschuwing (groen/neutraal).
1871
+
1872
+ 4) Ophaalmomenten
1873
+ Tabel public.logistics_pickup_moments bevat per dag de ophaalmomenten
1874
+ (sequence, label, pickup_time). Gebruik die om de waarschuwing per
1875
+ ophaalmoment te tonen. Aanmaken of wijzigen van ophaalmomenten mag alleen
1876
+ als de gebruiker daar in deze module om vraagt; anders alleen lezen.
1877
+
1878
+ 5) Wat je NIET doet
1879
+ - Geen eigen capaciteitsberekening of eigen picktijden.
1880
+ - Geen karren splitsen of verplaatsen.
1881
+ - Geen eigen mail sturen bij een tekort; de Engine regelt e-mail en het
1598
1882
  Meldingencentrum via de instellingen in de Rule Engine.`,
1599
1883
  },
1600
1884
  ],
1601
1885
  },
1886
+ layoutmaker: {
1887
+ order: 9,
1888
+ summary: "Levert de documentlayouts. Kiest zelf geen layout en geen printer: de Engine bepaalt welke layout en welke printplek bij een document horen.",
1889
+ revisions: [
1890
+ {
1891
+ number: 1,
1892
+ title: "De Engine kiest de layout, de Layout maker maakt hem",
1893
+ date: "2026-09-03",
1894
+ withSharedContract: true,
1895
+ summary: "Vaste rolverdeling: de Engine bepaalt documentsoort, variant, layout en printplek; de Layout maker rendert en print.",
1896
+ body: `ROLVERDELING
1897
+ De Logistics Engine beslist WELK document, in WELKE variant, met WELKE layout,
1898
+ op WELKE printplek gedrukt wordt. De Layout maker maakt en rendert de layouts.
1899
+
1900
+ WAT DE ENGINE AANLEVERT
1901
+ Een printopdracht komt binnen als rij in public.print_jobs met status
1902
+ "wachtend". In de payload staan onder meer:
1903
+
1904
+ document_code pakbon | verzendbrief | afleverbrief | veilingbrief | sticker |
1905
+ controlelijst | productiebon
1906
+ layout_id de door de Engine gekozen layout (mag je NIET overrulen)
1907
+ layout_variant "full_cart" of "partial_order" bij een pakbon
1908
+ print_plek_id de plek waar het document uit moet komen
1909
+ is_herprint true bij handmatig opnieuw printen
1910
+ data de gegevens van het document
1911
+
1912
+ BOUW DIT
1913
+ - Neem layout_id, print_plek_id en document_code over zoals ze binnenkomen.
1914
+ - Kun je niet renderen, zet de job dan op "mislukt" met een leesbare
1915
+ Nederlandse foutmelding in het foutveld; verzin geen vervangende layout.
1916
+ - Ontbreekt layout_id, laat de job dan op "wachtend" staan met de melding die de
1917
+ Engine heeft meegegeven; de gebruiker moet de layout in de Rule Engine kiezen.
1918
+
1919
+ NIET DOEN
1920
+ - Nooit zelf de actieve layout pakken als de Engine er één meestuurt.
1921
+ - Nooit zelf een printer of printplek kiezen als print_plek_id gevuld is.
1922
+ - Geen URL's, project-refs, sleutels of klantnamen hardcoden; alles uit env
1923
+ en uit de database.
1924
+
1925
+ CONTROLELIJST
1926
+ [ ] Een job met layout_id gebruikt exact die layout.
1927
+ [ ] Een job zonder layout blijft wachten met een leesbare melding.
1928
+ [ ] Herprints gebruiken dezelfde layout als de oorspronkelijke print.`,
1929
+ },
1930
+ {
1931
+ number: 2,
1932
+ title: "Twee pakbonvarianten en een barcode op de pakbon",
1933
+ date: "2026-09-03",
1934
+ summary: "Nieuwe eis bovenop prompt 1: aparte templates voor een volle kar en een deelkar, plus een barcode-element dat de verzendbrief-scan mogelijk maakt.",
1935
+ body: `AANVULLING op prompt 1 (die blijft geldig).
1936
+
1937
+ TWEE PAKBONVARIANTEN
1938
+ De Engine kent twee pakbon-instellingen:
1939
+ "Pakbon volledige kar" → layout_variant "full_cart"
1940
+ "Pakbon resterende order / deelkar" → layout_variant "partial_order"
1941
+ De Engine stuurt de bijbehorende layout_id mee. De Layout maker hoeft alleen te
1942
+ zorgen dat beide layouts als documenttype "pakbon" te kiezen zijn. Toon
1943
+ layout_variant desgewenst in de voorvertoning, maar kies er niets mee.
1944
+
1945
+ BARCODE-ELEMENT OP DE PAKBON
1946
+ Voeg aan de pakbon-template een barcode-element toe waarvan de gebruiker zelf
1947
+ positie, formaat en weergave bepaalt. De waarde komt UIT de printgegevens:
1948
+
1949
+ payload.barcode de vaste barcode van deze kar
1950
+ payload.data.cart.barcode zelfde waarde, in het karblok
1951
+
1952
+ Genereer die waarde nooit zelf en tel er niets bij op: de barcode hoort bij
1953
+ precies één kar van precies één order en blijft gelijk bij een herprint. De
1954
+ verzendbrief wordt later gemaakt door deze barcode te scannen.
1955
+
1956
+ KARGEGEVENS DIE JE KUNT AFDRUKKEN
1957
+ cart_label bijvoorbeeld "Kar 2 van 3"
1958
+ cart_index / cart_total
1959
+ cart_items producten en aantallen VAN DIE KAR
1960
+ cart_id interne identificatie
1961
+
1962
+ CONTROLELIJST
1963
+ [ ] Beide pakbonvarianten zijn als aparte layout aan te maken en te kiezen.
1964
+ [ ] De barcode op de afdruk is scanbaar en gelijk aan payload.barcode.
1965
+ [ ] Een herprint levert dezelfde barcode op.
1966
+ [ ] "Kar X van Y" en de karregels staan op de juiste pakbon.`,
1967
+ },
1968
+ ],
1969
+ },
1602
1970
  };
1603
1971
  export const MODULE_PROMPTS = Object.keys(MODULES)
1604
1972
  .map((moduleKey) => ({
1605
1973
  moduleKey,
1606
- label: LOGISTICS_MODULES[moduleKey],
1974
+ label: PROMPT_MODULE_LABELS[moduleKey],
1607
1975
  order: MODULES[moduleKey].order,
1608
1976
  summary: MODULES[moduleKey].summary,
1609
1977
  revisions: [...MODULES[moduleKey].revisions].sort((a, b) => a.number - b.number),
@@ -1613,16 +1981,16 @@ export const MODULE_PROMPTS = Object.keys(MODULES)
1613
1981
  export function buildRevisionPrompt(prompt, revision) {
1614
1982
  const shared = revision.withSharedContract
1615
1983
  ? `\n## Gedeeld contract\n\n${sharedBlock(prompt.moduleKey)}\n`
1616
- : `\n(Deze prompt is een aanvulling. Het gedeelde contract uit koppelprompt 1 blijft
1984
+ : `\n(Deze prompt is een aanvulling. Het gedeelde contract uit koppelprompt 1 blijft
1617
1985
  onverkort gelden — niets terugdraaien.)\n`;
1618
- return `# Koppelprompt ${revision.number} — ${prompt.label}
1619
- ## ${revision.title} (${revision.date})
1620
-
1621
- ${revision.summary}
1622
- ${shared}
1623
- ## Wat deze module moet doen
1624
-
1625
- ${revision.body}
1986
+ return `# Koppelprompt ${revision.number} — ${prompt.label}
1987
+ ## ${revision.title} (${revision.date})
1988
+
1989
+ ${revision.summary}
1990
+ ${shared}
1991
+ ## Wat deze module moet doen
1992
+
1993
+ ${revision.body}
1626
1994
  `;
1627
1995
  }
1628
1996
  export function buildModulePrompt(prompt) {
@@ -1630,18 +1998,18 @@ export function buildModulePrompt(prompt) {
1630
1998
  }
1631
1999
  /** Alle modules, met per module de volledige promptgeschiedenis. */
1632
2000
  export function buildAllModulePrompts() {
1633
- const sections = MODULE_PROMPTS.map((p) => `# ${p.order}. ${p.label} (module: ${p.moduleKey})
1634
-
1635
- ${p.summary}
1636
-
2001
+ const sections = MODULE_PROMPTS.map((p) => `# ${p.order}. ${p.label} (module: ${p.moduleKey})
2002
+
2003
+ ${p.summary}
2004
+
1637
2005
  ${p.revisions.map((r) => buildRevisionPrompt(p, r)).join("\n---\n\n")}`).join("\n\n====================\n\n");
1638
- return `# Koppelprompts voor de aangesloten modules
1639
-
1640
- Per module staan de prompts op volgorde. Een nieuwe eis komt er altijd als
1641
- NIEUWE prompt bij; eerdere prompts blijven geldig en worden niet herschreven.
1642
- Er worden geen secrets gebruikt: alle communicatie loopt via de gedeelde
1643
- Supabase-database.
1644
-
2006
+ return `# Koppelprompts voor de aangesloten modules
2007
+
2008
+ Per module staan de prompts op volgorde. Een nieuwe eis komt er altijd als
2009
+ NIEUWE prompt bij; eerdere prompts blijven geldig en worden niet herschreven.
2010
+ Er worden geen secrets gebruikt: alle communicatie loopt via de gedeelde
2011
+ Supabase-database.
2012
+
1645
2013
  ${sections}`;
1646
2014
  }
1647
2015
  export function modulePromptFilename(moduleKey, revisionNumber) {