@flowselections/floriday-voorraad 1.0.87 → 1.0.90

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 (32) hide show
  1. package/dist-lib/components/voorraad/FloridayTakeoverPanel.d.ts.map +1 -1
  2. package/dist-lib/components/voorraad/FloridayTakeoverPanel.js +21 -8
  3. package/dist-lib/components/voorraad/InventoryStatusBadge.d.ts.map +1 -1
  4. package/dist-lib/components/voorraad/InventoryStatusBadge.js +7 -0
  5. package/dist-lib/hooks/useInventorySettings.d.ts +3 -0
  6. package/dist-lib/hooks/useInventorySettings.d.ts.map +1 -1
  7. package/dist-lib/hooks/useInventorySettings.js +1 -1
  8. package/dist-lib/hooks-entry.d.ts +1 -0
  9. package/dist-lib/hooks-entry.d.ts.map +1 -1
  10. package/dist-lib/hooks-entry.js +1 -0
  11. package/dist-lib/integrations/supabase/types.d.ts +214 -0
  12. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  13. package/dist-lib/lib/__tests__/floriday-customer-offers.test.d.ts +2 -0
  14. package/dist-lib/lib/__tests__/floriday-customer-offers.test.d.ts.map +1 -0
  15. package/dist-lib/lib/__tests__/floriday-customer-offers.test.js +36 -0
  16. package/dist-lib/lib/floriday-customer-offers.d.ts +25 -0
  17. package/dist-lib/lib/floriday-customer-offers.d.ts.map +1 -0
  18. package/dist-lib/lib/floriday-customer-offers.js +49 -0
  19. package/dist-lib/lib/floriday-takeover-core.d.ts +71 -0
  20. package/dist-lib/lib/floriday-takeover-core.d.ts.map +1 -0
  21. package/dist-lib/lib/floriday-takeover-core.js +196 -0
  22. package/dist-lib/lib/floriday-takeover.functions.d.ts +5 -49
  23. package/dist-lib/lib/floriday-takeover.functions.d.ts.map +1 -1
  24. package/dist-lib/lib/floriday-takeover.functions.js +3 -219
  25. package/dist-lib/server-hooks/floriday-follow.d.ts +3 -0
  26. package/dist-lib/server-hooks/floriday-follow.d.ts.map +1 -0
  27. package/dist-lib/server-hooks/floriday-follow.js +46 -0
  28. package/dist-lib/server-hooks/guard.d.ts.map +1 -1
  29. package/dist-lib/server-hooks/guard.js +6 -1
  30. package/package.json +16 -2
  31. package/sql/AGENTS.md +3 -0
  32. package/sql/floriday-voorraad-schema.sql +8188 -0
@@ -1,58 +1,7 @@
1
- /**
2
- * Proefrapport "Overnemen uit Floriday". Leest alleen: niets wordt geschreven
3
- * en niets gaat naar Floriday. Het daadwerkelijk overnemen gebeurt via de
4
- * databasefunctie `floriday_takeover_apply` (één transactie, terug te draaien).
5
- *
6
- * Floriday (Suppliers API 2026v1), alleen lezen via de leesroute van de
7
- * Floriday-authenticatiemodule:
8
- * - GET /trade-items/base-supply?year&week → WeeklyBaseSupply { tradeItemId, numberOfPieces, isAvailable, basePricePerPiece { currency, value } }
9
- * Daarnaast de lokaal bijgehouden Floriday-gegevens van deze koppeling:
10
- * - trade items (TradeItemBase: tradeItemId, supplierArticleCode, articleGtin, vbnProductCode, tradeItemName)
11
- * - doorlopende voorraad (GET /trade-items/{id}/continuous-stock, door de ophaaltaak bewaard)
12
- * - catalogusprijzen en aanbodregels (supply lines), incl. klantspecifieke aanbiedingen (alleen tonen)
13
- */
1
+ /** Proefrapport "Overnemen uit Floriday" (alleen lezen); de logica staat in floriday-takeover-core.ts. */
14
2
  import { createServerFn } from "@tanstack/react-start";
15
3
  import { requireSupabaseAuth } from "../integrations/supabase/auth-middleware.js";
16
- import { fetchState } from "./floriday-fetch-status.js";
17
- function offerView(id, d) {
18
- const pr = d?.pricePerPiece ?? d?.price ?? null;
19
- const price = typeof pr === "number" ? pr : pr?.value != null ? Number(pr.value) : null;
20
- const orgs = d?.customerOrganizationIds ?? (d?.customerOrganizationId ? [d.customerOrganizationId] : []);
21
- return {
22
- id,
23
- customer: d?.customerOrganizationName ?? (Array.isArray(orgs) && orgs.length ? orgs.join(", ") : null),
24
- price: Number.isFinite(price) ? price : null,
25
- currency: pr?.currency ?? d?.currency ?? null,
26
- pieces: d?.numberOfPieces != null ? Number(d.numberOfPieces) : d?.availableNumberOfPieces != null ? Number(d.availableNumberOfPieces) : null,
27
- from: d?.period?.startDateTime ?? d?.startDateTime ?? d?.validFrom ?? null,
28
- to: d?.period?.endDateTime ?? d?.endDateTime ?? d?.validTo ?? null,
29
- };
30
- }
31
- function offerTradeItems(d) {
32
- const ids = new Set();
33
- if (d?.tradeItemId)
34
- ids.add(String(d.tradeItemId).toLowerCase());
35
- for (const l of Array.isArray(d?.lines) ? d.lines : Array.isArray(d?.tradeItems) ? d.tradeItems : []) {
36
- if (l?.tradeItemId)
37
- ids.add(String(l.tradeItemId).toLowerCase());
38
- }
39
- return [...ids];
40
- }
41
- function isoWeek(d) {
42
- const t = new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate()));
43
- const day = t.getUTCDay() || 7;
44
- t.setUTCDate(t.getUTCDate() + 4 - day);
45
- const y = t.getUTCFullYear();
46
- const w = Math.ceil(((t.getTime() - Date.UTC(y, 0, 1)) / 86400000 + 1) / 7);
47
- return { year: y, week: w };
48
- }
49
- function nameOf(data) {
50
- const n = data?.tradeItemName;
51
- if (typeof n === "string")
52
- return n;
53
- return String(n?.nl ?? n?.en ?? Object.values(n ?? {})[0] ?? "").trim();
54
- }
55
- const norm = (v) => String(v ?? "").trim().toLowerCase();
4
+ import { buildTakeoverPreview } from "./floriday-takeover-core.js";
56
5
  export const getFloridayTakeoverPreview = createServerFn({ method: "POST" })
57
6
  .middleware([requireSupabaseAuth])
58
7
  .handler(async ({ context }) => {
@@ -62,170 +11,5 @@ export const getFloridayTakeoverPreview = createServerFn({ method: "POST" })
62
11
  const { resolveActiveFloridayConnection } = await import("./floriday-env.server.js");
63
12
  const conn = await resolveActiveFloridayConnection(context.supabase);
64
13
  const { supabaseAdmin } = await import("../integrations/supabase/client.server.js");
65
- const admin = supabaseAdmin;
66
- const nowIso = new Date().toISOString();
67
- const [items, products, stock, cont, catalog, lines, syncRows, offers] = await Promise.all([
68
- admin.from("floriday_trade_items_cache").select("floriday_id, data").eq("connection_id", conn.id).eq("is_deleted", false).limit(5000),
69
- admin.from("products").select("id, product, barcode, article_number, floriday_trade_item_id, sale_price").limit(10000),
70
- admin.from("trade_item_stock").select("trade_item_key, physical_stock").eq("connection_id", conn.id).limit(20000),
71
- admin.from("floriday_continuous_stock_cache").select("trade_item_id, number_of_pieces").eq("connection_id", conn.id).limit(5000),
72
- admin.from("floriday_catalog_prices_cache").select("trade_item_id, price, updated_at").eq("connection_id", conn.id).eq("is_deleted", false).or(`valid_to.is.null,valid_to.gte.${nowIso}`).order("updated_at", { ascending: false }).limit(20000),
73
- admin.from("trade_item_supply_lines").select("trade_item_id, supply_type, price_per_piece, updated_at").eq("connection_id", conn.id).order("updated_at", { ascending: false }).limit(20000),
74
- admin.from("floriday_sync_state").select("entity_type, backfill_target_sequence, backfill_done_at, last_success_at").eq("connection_id", conn.id).in("entity_type", ["trade-items", "catalog-prices", "customer-offers"]),
75
- admin.from("floriday_customer_offers_cache").select("floriday_id, data").eq("connection_id", conn.id).eq("is_deleted", false).limit(5000),
76
- ]);
77
- for (const r of [items, products, stock, cont, catalog, lines, syncRows, offers])
78
- if (r.error)
79
- throw new Error(r.error.message);
80
- // Weekaanbod: huidige en twee komende weken, live gelezen (1 per seconde).
81
- const now = new Date();
82
- const weeks = [0, 1, 2].map((i) => isoWeek(new Date(now.getTime() + i * 7 * 86400000)));
83
- let weekError = null;
84
- const weekly = new Map();
85
- try {
86
- const { readBaseSupplyWeeks } = await import("./base-supply-reader.server.js");
87
- const out = await readBaseSupplyWeeks(weeks);
88
- if (out.error)
89
- weekError = out.error;
90
- for (const w of out.results) {
91
- if (!w.ok) {
92
- weekError = weekError ?? w.error;
93
- continue;
94
- }
95
- for (const r of w.rows) {
96
- const arr = weekly.get(r.tradeItemId) ?? [];
97
- arr.push({ year: w.year, week: w.week, pieces: r.numberOfPieces, available: r.isAvailable, price: r.basePricePerPiece });
98
- weekly.set(r.tradeItemId, arr);
99
- }
100
- }
101
- }
102
- catch (e) {
103
- weekError = e?.message ?? String(e);
104
- }
105
- const byFid = new Map();
106
- const unlinked = [];
107
- for (const p of products.data ?? []) {
108
- if (p.floriday_trade_item_id)
109
- byFid.set(String(p.floriday_trade_item_id).toLowerCase(), p);
110
- else
111
- unlinked.push(p);
112
- }
113
- const stockBy = new Map();
114
- for (const s of stock.data ?? [])
115
- stockBy.set(String(s.trade_item_key), Number(s.physical_stock ?? 0));
116
- const contBy = new Map();
117
- for (const c of cont.data ?? [])
118
- contBy.set(String(c.trade_item_id), Number(c.number_of_pieces ?? 0));
119
- const catBy = new Map();
120
- for (const c of catalog.data ?? []) {
121
- const k = String(c.trade_item_id);
122
- if (!catBy.has(k) && Number(c.price) > 0)
123
- catBy.set(k, Number(c.price));
124
- }
125
- const lineBy = new Map();
126
- for (const l of lines.data ?? []) {
127
- const k = String(l.trade_item_id);
128
- const cur = lineBy.get(k) ?? { price: null, count: 0, customer: 0 };
129
- const isCustomer = /customer/i.test(String(l.supply_type ?? ""));
130
- if (isCustomer)
131
- cur.customer++;
132
- else {
133
- cur.count++;
134
- if (cur.price == null && Number(l.price_per_piece) > 0)
135
- cur.price = Number(l.price_per_piece);
136
- }
137
- lineBy.set(k, cur);
138
- }
139
- const syncBy = new Map();
140
- for (const r of syncRows.data ?? [])
141
- syncBy.set(String(r.entity_type), r);
142
- const offersBy = new Map();
143
- for (const o of offers.data ?? []) {
144
- for (const tid of offerTradeItems(o.data)) {
145
- const arr = offersBy.get(tid) ?? [];
146
- arr.push(offerView(String(o.floriday_id), o.data));
147
- offersBy.set(tid, arr);
148
- }
149
- }
150
- const fetchStatus = {
151
- tradeItems: fetchState(syncBy.get("trade-items") ?? null, (items.data ?? []).length),
152
- catalogPrices: fetchState(syncBy.get("catalog-prices") ?? null, (catalog.data ?? []).length),
153
- customerOffers: fetchState(syncBy.get("customer-offers") ?? null, (offers.data ?? []).length),
154
- };
155
- const cur = weeks[0];
156
- const rows = (items.data ?? []).map((it) => {
157
- const fid = String(it.floriday_id).toLowerCase();
158
- const d = it.data ?? {};
159
- const p = byFid.get(fid) ?? null;
160
- const w = (weekly.get(fid) ?? []).sort((a, b) => a.year - b.year || a.week - b.week);
161
- const contRaw = contBy.has(fid) ? contBy.get(fid) : null;
162
- const line = lineBy.get(fid) ?? { price: null, count: 0, customer: 0 };
163
- // Aanbodvorm uit Floriday zelf: staat er een doorlopende-voorraadrecord, dan is dat de vorm; anders weekaanbod huidige week.
164
- let floridayStock = null;
165
- let stockSource = null;
166
- if (contRaw != null) {
167
- floridayStock = contRaw;
168
- stockSource = "Doorlopende voorraad";
169
- }
170
- else {
171
- const now = w.find((x) => x.year === cur.year && x.week === cur.week);
172
- if (now) {
173
- floridayStock = now.pieces;
174
- stockSource = `Weekaanbod week ${now.week}`;
175
- }
176
- }
177
- // Prijs: aanbodregel gaat voor op catalogusprijs.
178
- const weekPrice = w.find((x) => x.price != null && x.price > 0)?.price ?? null;
179
- const supplyLinePrice = line.price ?? weekPrice;
180
- const catalogPrice = catBy.get(fid) ?? null;
181
- const floridayPrice = supplyLinePrice ?? catalogPrice;
182
- const priceSource = supplyLinePrice != null ? "Aanbodregel" : catalogPrice != null ? "Catalogusprijs" : null;
183
- let suggestion = null;
184
- if (!p) {
185
- const gtin = norm(d.articleGtin), code = norm(d.supplierArticleCode), nm = norm(nameOf(d));
186
- const hit = unlinked.find((u) => (gtin && norm(u.barcode) === gtin) || (code && norm(u.article_number) === code) || (nm && norm(u.product) === nm));
187
- if (hit)
188
- suggestion = `Lijkt op bestaand artikel "${hit.product}" (niet automatisch gekoppeld)`;
189
- }
190
- const erpStock = p
191
- ? stockBy.get(fid) ?? stockBy.get(`product:${p.id}`) ?? stockBy.get(String(p.id)) ?? null
192
- : null;
193
- const name = nameOf(d) || String(d.supplierArticleCode ?? fid);
194
- let action = p ? "bijwerken" : "aanmaken";
195
- let reason = null;
196
- if (floridayStock == null && floridayPrice == null) {
197
- action = p ? "niet" : "aanmaken";
198
- reason = "Geen aantal en geen prijs gevonden in Floriday";
199
- }
200
- return {
201
- tradeItemId: fid, name,
202
- articleCode: d.supplierArticleCode ?? null,
203
- gtin: d.articleGtin ?? null,
204
- vbnCode: d.vbnProductCode != null ? String(d.vbnProductCode) : null,
205
- productId: p?.id ?? null, productName: p?.product ?? null,
206
- action, reason, suggestion,
207
- erpStock, erpPrice: p?.sale_price != null ? Number(p.sale_price) : null,
208
- floridayStock, stockSource,
209
- supplyLinePrice, catalogPrice, floridayPrice, priceSource,
210
- priceDiffers: supplyLinePrice != null && catalogPrice != null && Math.abs(supplyLinePrice - catalogPrice) > 0.0001,
211
- forms: {
212
- continuous: contRaw != null && contRaw > 0,
213
- continuousAvailable: contRaw == null ? null : contRaw > 0,
214
- weekly: w.length > 0,
215
- weeks: w.map(({ year, week, pieces, available }) => ({ year, week, pieces, available })),
216
- supply_lines: line.count,
217
- customer_offers: (offersBy.get(fid) ?? []).length || line.customer,
218
- },
219
- customerOffers: offersBy.get(fid) ?? [],
220
- };
221
- });
222
- rows.sort((a, b) => a.name.localeCompare(b.name, "nl"));
223
- return {
224
- connectionId: conn.id,
225
- environment: conn.environment ?? null,
226
- weeks,
227
- weekError,
228
- fetchStatus,
229
- rows,
230
- };
14
+ return buildTakeoverPreview(supabaseAdmin, conn);
231
15
  });
@@ -0,0 +1,3 @@
1
+ /** Shell-handler voor /api/public/hooks/floriday-follow. */
2
+ export declare function handleFloridayFollow(request: Request): Promise<Response>;
3
+ //# sourceMappingURL=floriday-follow.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"floriday-follow.d.ts","sourceRoot":"","sources":["../../src/server-hooks/floriday-follow.ts"],"names":[],"mappings":"AA4CA,4DAA4D;AAC5D,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAE9E"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * "Volgt Floriday": elk uur (na de Floriday-synchronisatie) nemen Floriday-leidende
3
+ * artikelen (erp_leading = false) het actuele aantal, de prijs en de aanbodvorm over,
4
+ * met dezelfde bronkeuze als het proefrapport "Overnemen uit Floriday".
5
+ * Er gaat niets naar Floriday en er ontstaat geen wachtrijregel (wachtrijbewaking).
6
+ * Schakelaar: voorraad_hook_enabled.follow_floriday (standaard aan).
7
+ */
8
+ import { runGuardedHook } from "./guard.js";
9
+ async function handle() {
10
+ const json = (b, status = 200) => new Response(JSON.stringify(b), { status, headers: { "Content-Type": "application/json" } });
11
+ const { getAdminClient } = await import("../lib/supabase-admin.server.js");
12
+ const admin = getAdminClient();
13
+ const { resolveActiveFloridayConnection } = await import("../lib/floriday-env.server.js");
14
+ const conn = await resolveActiveFloridayConnection(admin);
15
+ const { data: follow, error: fErr } = await admin
16
+ .from("floriday_inventory_settings")
17
+ .select("floriday_trade_item_id")
18
+ .eq("connection_id", conn.id)
19
+ .eq("erp_leading", false)
20
+ .limit(10000);
21
+ if (fErr)
22
+ return json({ error: fErr.message }, 500);
23
+ const wanted = new Set((follow ?? []).map((r) => String(r.floriday_trade_item_id ?? "").toLowerCase()).filter(Boolean));
24
+ if (wanted.size === 0)
25
+ return json({ idle: true });
26
+ const { buildTakeoverPreview } = await import("../lib/floriday-takeover-core.js");
27
+ const preview = await buildTakeoverPreview(admin, conn);
28
+ if (preview.fetchStatus.tradeItems !== "compleet")
29
+ return json({ waiting: true, reason: "Artikelen worden nog opgehaald" });
30
+ const items = preview.rows
31
+ .filter((r) => r.productId && wanted.has(r.tradeItemId))
32
+ .map((r) => ({
33
+ trade_item_id: r.tradeItemId,
34
+ stock: r.floridayStock,
35
+ price: r.floridayPrice,
36
+ offer_forms: { ...r.forms, customer_offer: r.customerOffers.length > 0 },
37
+ }));
38
+ const { data, error } = await admin.rpc("floriday_follow_apply", { p_connection_id: conn.id, p_items: items });
39
+ if (error)
40
+ return json({ error: error.message }, 500);
41
+ return json({ ...data, candidates: items.length, weekError: preview.weekError });
42
+ }
43
+ /** Shell-handler voor /api/public/hooks/floriday-follow. */
44
+ export async function handleFloridayFollow(request) {
45
+ return runGuardedHook(request, "follow_floriday", () => handle(), "floriday-follow");
46
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"guard.d.ts","sourceRoot":"","sources":["../../src/server-hooks/guard.ts"],"names":[],"mappings":"AA4DA,wBAAsB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAUhE;AAED,wBAAsB,cAAc,CAClC,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,EAC/C,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,QAAQ,CAAC,CAUnB"}
1
+ {"version":3,"file":"guard.d.ts","sourceRoot":"","sources":["../../src/server-hooks/guard.ts"],"names":[],"mappings":"AA+DA,wBAAsB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAahE;AAED,wBAAsB,cAAc,CAClC,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,EAC/C,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,QAAQ,CAAC,CAUnB"}
@@ -20,6 +20,8 @@ const CORS = {
20
20
  * uit Floriday overneemt, verstuurt dan niets.
21
21
  */
22
22
  const DEFAULT_ON_IF_ERP_LEADING = new Set(["floriday_base_supply", "logistics_tasks"]);
23
+ /** Standaard aan zonder voorwaarde (alleen lezen uit Floriday, verstuurt niets). */
24
+ const DEFAULT_ON = new Set(["follow_floriday"]);
23
25
  async function hasErpLeadingArticle(admin) {
24
26
  const lead = await admin
25
27
  .from("floriday_inventory_settings")
@@ -62,8 +64,11 @@ export async function hookEnabled(name) {
62
64
  .select("value")
63
65
  .eq("key", `voorraad_hook_enabled.${name}`)
64
66
  .maybeSingle();
65
- if (!data)
67
+ if (!data) {
68
+ if (DEFAULT_ON.has(name))
69
+ return true;
66
70
  return DEFAULT_ON_IF_ERP_LEADING.has(name) && (await hasErpLeadingArticle(admin));
71
+ }
67
72
  return String(data.value).trim().toLowerCase() === "true";
68
73
  }
69
74
  export async function runGuardedHook(request, name, handle, label) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flowselections/floriday-voorraad",
3
- "version": "1.0.87",
3
+ "version": "1.0.90",
4
4
  "sideEffects": false,
5
5
  "type": "module",
6
6
  "main": "./dist-lib/index.js",
@@ -17,7 +17,8 @@
17
17
  },
18
18
  "files": [
19
19
  "dist-lib",
20
- "public"
20
+ "public",
21
+ "sql"
21
22
  ],
22
23
  "publishConfig": {
23
24
  "access": "public"
@@ -76,6 +77,10 @@
76
77
  "private": false,
77
78
  "flowselections": {
78
79
  "moduleId": "Voorraad",
80
+ "schema": {
81
+ "file": "sql/floriday-voorraad-schema.sql",
82
+ "order": 20
83
+ },
79
84
  "pages": [
80
85
  {
81
86
  "export": "VoorraadPage",
@@ -150,6 +155,15 @@
150
155
  "OPTIONS"
151
156
  ]
152
157
  },
158
+ {
159
+ "routePath": "/api/public/hooks/floriday-follow",
160
+ "import": "@flowselections/floriday-voorraad/hooks",
161
+ "handler": "handleFloridayFollow",
162
+ "methods": [
163
+ "POST",
164
+ "OPTIONS"
165
+ ]
166
+ },
153
167
  {
154
168
  "routePath": "/api/public/hooks/logistics-tasks-run",
155
169
  "import": "@flowselections/floriday-voorraad/hooks",
package/sql/AGENTS.md ADDED
@@ -0,0 +1,3 @@
1
+ - `floriday-voorraad-schema.sql` is generated from the database by `scripts/generate-voorraad-schema.py` (via `voorraad_schema_ddl`, service role only); regenerate after every migration and never hand-edit, because a new Shell gets its schema only from this file.
2
+ - Voorraad owns `floriday_continuous_stock_queue_status_check`; the schema recreates it with all statuses, because other modules once recreated it without ours.
3
+ - Schema-update order 20 (`flowselections.schema.order`); the generator keeps only own objects and wraps statements that touch other modules' tables in a to_regclass guard with a NOTICE, because Floriday-auth (30) and the Engine (60) run after Voorraad.