@flowselections/floriday-voorraad 1.0.164 → 1.0.166

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 (31) hide show
  1. package/dist-lib/components/fs-ui/DataTable.d.ts.map +1 -1
  2. package/dist-lib/components/fs-ui/DataTable.js +2 -2
  3. package/dist-lib/components/voorraad/VoorraadPage.d.ts.map +1 -1
  4. package/dist-lib/components/voorraad/VoorraadPage.js +86 -6
  5. package/dist-lib/components/voorraad/artikel/ArtikelPagina.d.ts.map +1 -1
  6. package/dist-lib/components/voorraad/artikel/ArtikelPagina.js +10 -2
  7. package/dist-lib/components/voorraad/lijst/VoorraadLijstTabel.d.ts +3 -1
  8. package/dist-lib/components/voorraad/lijst/VoorraadLijstTabel.d.ts.map +1 -1
  9. package/dist-lib/components/voorraad/lijst/VoorraadLijstTabel.js +5 -2
  10. package/dist-lib/hooks/useVoorraadLijst.d.ts +4 -5
  11. package/dist-lib/hooks/useVoorraadLijst.d.ts.map +1 -1
  12. package/dist-lib/hooks/useVoorraadLijst.js +23 -5
  13. package/dist-lib/hooks-entry.d.ts +1 -0
  14. package/dist-lib/hooks-entry.d.ts.map +1 -1
  15. package/dist-lib/hooks-entry.js +1 -0
  16. package/dist-lib/integrations/supabase/types.d.ts +537 -0
  17. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  18. package/dist-lib/lib/lijst-stand.d.ts +31 -0
  19. package/dist-lib/lib/lijst-stand.d.ts.map +1 -0
  20. package/dist-lib/lib/lijst-stand.js +77 -0
  21. package/dist-lib/lib/voorraad-push-melding.d.ts +10 -0
  22. package/dist-lib/lib/voorraad-push-melding.d.ts.map +1 -0
  23. package/dist-lib/lib/voorraad-push-melding.js +11 -0
  24. package/dist-lib/server-hooks/floriday-mutaties.d.ts +3 -0
  25. package/dist-lib/server-hooks/floriday-mutaties.d.ts.map +1 -0
  26. package/dist-lib/server-hooks/floriday-mutaties.js +70 -0
  27. package/dist-lib/server-hooks/guard.d.ts.map +1 -1
  28. package/dist-lib/server-hooks/guard.js +1 -1
  29. package/package.json +22 -1
  30. package/sql/AGENTS.md +2 -0
  31. package/sql/floriday-voorraad-schema.sql +1454 -902
@@ -0,0 +1,31 @@
1
+ import type { LijstFilter, LijstSort } from "./voorraad-lijst.js";
2
+ /** Stand van de Voorraad-lijst, bewaard op de stap in de browsergeschiedenis vlak vóór het openen van een artikel. */
3
+ export interface LijstStand {
4
+ v: 1;
5
+ userId: string;
6
+ ts: number;
7
+ zoek: string;
8
+ filter: LijstFilter;
9
+ sort: LijstSort;
10
+ oplopend: boolean;
11
+ pagina: number;
12
+ grootte: number;
13
+ scrollTop: number;
14
+ artikelId: string | null;
15
+ }
16
+ export declare const LIJST_STAND_MAX_MS: number;
17
+ export declare function maakLijstStand(s: Omit<LijstStand, "v">): LijstStand;
18
+ /** Alleen een stand van dezelfde gebruiker, jonger dan 30 minuten en met geldige waarden; anders null (= standaardstand). */
19
+ export declare function geldigeLijstStand(raw: unknown, userId: string | null | undefined, nu: number): LijstStand | null;
20
+ /** Een bewaarde pagina die niet meer bestaat wordt de laatste pagina die er wel is. */
21
+ export declare function begrensPagina(pagina: number, totaal: number, grootte: number): number;
22
+ /** Dichtstbijzijnde scrollbare ouder (shell-main of venster). */
23
+ export declare function scrollOuder(el: Element | null): HTMLElement | null;
24
+ export declare function leesScroll(el: Element | null): number;
25
+ export declare function zetScroll(el: Element | null, top: number): void;
26
+ /** Kleine geheugencache van lijstantwoorden, per gebruiker en vraag; 30 minuten, max. 5 items. */
27
+ export declare function maakLijstCache<T>(max?: number, maxMs?: number): {
28
+ get(key: string, nu: number): T | null;
29
+ set(key: string, data: T, nu: number): void;
30
+ };
31
+ //# sourceMappingURL=lijst-stand.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lijst-stand.d.ts","sourceRoot":"","sources":["../../src/lib/lijst-stand.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAE/D,sHAAsH;AACtH,MAAM,WAAW,UAAU;IACzB,CAAC,EAAE,CAAC,CAAC;IACL,MAAM,EAAE,MAAM,CAAC;IACf,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,WAAW,CAAC;IACpB,IAAI,EAAE,SAAS,CAAC;IAChB,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED,eAAO,MAAM,kBAAkB,QAAiB,CAAC;AAKjD,wBAAgB,cAAc,CAAC,CAAC,EAAE,IAAI,CAAC,UAAU,EAAE,GAAG,CAAC,GAAG,UAAU,CAEnE;AAED,6HAA6H;AAC7H,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAAE,EAAE,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAYhH;AAED,uFAAuF;AACvF,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAGrF;AAED,iEAAiE;AACjE,wBAAgB,WAAW,CAAC,EAAE,EAAE,OAAO,GAAG,IAAI,GAAG,WAAW,GAAG,IAAI,CAQlE;AACD,wBAAgB,UAAU,CAAC,EAAE,EAAE,OAAO,GAAG,IAAI,GAAG,MAAM,CAGrD;AACD,wBAAgB,SAAS,CAAC,EAAE,EAAE,OAAO,GAAG,IAAI,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAG/D;AAED,kGAAkG;AAClG,wBAAgB,cAAc,CAAC,CAAC,EAAE,GAAG,SAAI,EAAE,KAAK,SAAqB;aAGxD,MAAM,MAAM,MAAM,GAAG,CAAC,GAAG,IAAI;aAM7B,MAAM,QAAQ,CAAC,MAAM,MAAM;EAMvC"}
@@ -0,0 +1,77 @@
1
+ export const LIJST_STAND_MAX_MS = 30 * 60 * 1000;
2
+ const FILTERS = ["alles", "onder_minimum", "verpakking", "niet_gekoppeld", "verwijderd_floriday", "prijs", "twijfel", "gearchiveerd"];
3
+ const SORTS = ["naam", "artikelnummer", "liggend", "gereserveerd", "vrij", "inkomend", "prijs"];
4
+ const MAX_GROOTTE = 500;
5
+ export function maakLijstStand(s) {
6
+ return { v: 1, ...s };
7
+ }
8
+ /** Alleen een stand van dezelfde gebruiker, jonger dan 30 minuten en met geldige waarden; anders null (= standaardstand). */
9
+ export function geldigeLijstStand(raw, userId, nu) {
10
+ if (!raw || typeof raw !== "object" || !userId)
11
+ return null;
12
+ const s = raw;
13
+ if (s.v !== 1 || s.userId !== userId)
14
+ return null;
15
+ if (typeof s.ts !== "number" || nu - s.ts > LIJST_STAND_MAX_MS || s.ts > nu + 60000)
16
+ return null;
17
+ if (typeof s.zoek !== "string" || !FILTERS.includes(s.filter) || !SORTS.includes(s.sort))
18
+ return null;
19
+ if (typeof s.oplopend !== "boolean")
20
+ return null;
21
+ if (!Number.isInteger(s.pagina) || s.pagina < 1)
22
+ return null;
23
+ if (!Number.isInteger(s.grootte) || s.grootte < 1 || s.grootte > MAX_GROOTTE)
24
+ return null;
25
+ const scrollTop = typeof s.scrollTop === "number" && s.scrollTop >= 0 ? s.scrollTop : 0;
26
+ const artikelId = typeof s.artikelId === "string" ? s.artikelId : null;
27
+ return { v: 1, userId, ts: s.ts, zoek: s.zoek, filter: s.filter, sort: s.sort, oplopend: s.oplopend, pagina: s.pagina, grootte: s.grootte, scrollTop, artikelId };
28
+ }
29
+ /** Een bewaarde pagina die niet meer bestaat wordt de laatste pagina die er wel is. */
30
+ export function begrensPagina(pagina, totaal, grootte) {
31
+ const laatste = Math.max(1, Math.ceil(Math.max(0, totaal) / Math.max(1, grootte)));
32
+ return Math.min(Math.max(1, pagina), laatste);
33
+ }
34
+ /** Dichtstbijzijnde scrollbare ouder (shell-main of venster). */
35
+ export function scrollOuder(el) {
36
+ let n = el?.parentElement ?? null;
37
+ while (n) {
38
+ const o = getComputedStyle(n).overflowY;
39
+ if ((o === "auto" || o === "scroll") && n.scrollHeight > n.clientHeight)
40
+ return n;
41
+ n = n.parentElement;
42
+ }
43
+ return null;
44
+ }
45
+ export function leesScroll(el) {
46
+ const o = scrollOuder(el);
47
+ return o ? o.scrollTop : window.scrollY;
48
+ }
49
+ export function zetScroll(el, top) {
50
+ const o = scrollOuder(el);
51
+ if (o)
52
+ o.scrollTop = top;
53
+ else
54
+ window.scrollTo({ top });
55
+ }
56
+ /** Kleine geheugencache van lijstantwoorden, per gebruiker en vraag; 30 minuten, max. 5 items. */
57
+ export function maakLijstCache(max = 5, maxMs = LIJST_STAND_MAX_MS) {
58
+ const m = new Map();
59
+ return {
60
+ get(key, nu) {
61
+ const e = m.get(key);
62
+ if (!e)
63
+ return null;
64
+ if (nu - e.ts > maxMs) {
65
+ m.delete(key);
66
+ return null;
67
+ }
68
+ return e.data;
69
+ },
70
+ set(key, data, nu) {
71
+ m.delete(key);
72
+ m.set(key, { ts: nu, data });
73
+ while (m.size > max)
74
+ m.delete(m.keys().next().value);
75
+ },
76
+ };
77
+ }
@@ -0,0 +1,10 @@
1
+ /** Meldingen op de Voorraadpagina uit `voorraad_integriteit_status()` (push stilgevallen, mislukte mutaties). */
2
+ export type PushStatus = {
3
+ wacht?: boolean;
4
+ minuten?: number;
5
+ tekst?: string | null;
6
+ tekst_mutaties?: string | null;
7
+ };
8
+ /** Hooguit twee korte regels; alleen tonen wat er echt aan de hand is. */
9
+ export declare function pushMeldingen(push: PushStatus | null | undefined): string[];
10
+ //# sourceMappingURL=voorraad-push-melding.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"voorraad-push-melding.d.ts","sourceRoot":"","sources":["../../src/lib/voorraad-push-melding.ts"],"names":[],"mappings":"AAAA,iHAAiH;AACjH,MAAM,MAAM,UAAU,GAAG;IACvB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC,CAAC;AAEF,0EAA0E;AAC1E,wBAAgB,aAAa,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,EAAE,CAM3E"}
@@ -0,0 +1,11 @@
1
+ /** Hooguit twee korte regels; alleen tonen wat er echt aan de hand is. */
2
+ export function pushMeldingen(push) {
3
+ if (!push)
4
+ return [];
5
+ const out = [];
6
+ if (push.wacht && (push.minuten ?? 0) > 30 && push.tekst)
7
+ out.push(push.tekst);
8
+ if (push.tekst_mutaties)
9
+ out.push(push.tekst_mutaties);
10
+ return out;
11
+ }
@@ -0,0 +1,3 @@
1
+ /** Shell-handler voor /api/public/hooks/floriday-mutaties. */
2
+ export declare function handleFloridayMutaties(request: Request): Promise<Response>;
3
+ //# sourceMappingURL=floriday-mutaties.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"floriday-mutaties.d.ts","sourceRoot":"","sources":["../../src/server-hooks/floriday-mutaties.ts"],"names":[],"mappings":"AAmEA,8DAA8D;AAC9D,wBAAsB,sBAAsB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAEhF"}
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Floriday-leidende artikelen: eigen verkopen gaan als mutatie naar Floriday.
3
+ *
4
+ * Elke minuut:
5
+ * 1. `voorraad_floriday_mutaties_ronde()` werkt de status bij (verzonden → bevestigd na een
6
+ * ophaalronde ná het versturen) en geeft de artikelen waarvan we de stand moeten lezen;
7
+ * 2. de actuele weekstand van die artikelen wordt uit Floriday gelezen en vastgelegd
8
+ * (`voorraad_floriday_stand_vastleggen`);
9
+ * 3. `voorraad_floriday_mutaties_klaarzetten_alle()` zet open mutaties klaar als
10
+ * stand én orders vers zijn (< 2 minuten); anders wacht het (niets wordt geblokkeerd).
11
+ * Er wordt nooit een absoluut aantal verstuurd dat niet op een verse Floriday-stand rust.
12
+ * Schakelaar: voorraad_hook_enabled.floriday_mutaties (standaard aan).
13
+ */
14
+ import { runGuardedHook } from "./guard.js";
15
+ function isoWeek(d) {
16
+ // Week in Europe/Amsterdam (Floriday rekent in lokale weken).
17
+ const parts = new Intl.DateTimeFormat("en-CA", { timeZone: "Europe/Amsterdam", year: "numeric", month: "2-digit", day: "2-digit" })
18
+ .format(d).split("-").map(Number);
19
+ const t = new Date(Date.UTC(parts[0], parts[1] - 1, parts[2]));
20
+ const dag = t.getUTCDay() || 7;
21
+ t.setUTCDate(t.getUTCDate() + 4 - dag);
22
+ const jan1 = new Date(Date.UTC(t.getUTCFullYear(), 0, 1));
23
+ return { year: t.getUTCFullYear(), week: Math.ceil(((t.getTime() - jan1.getTime()) / 86400000 + 1) / 7) };
24
+ }
25
+ async function handle() {
26
+ const json = (b, status = 200) => new Response(JSON.stringify(b), { status, headers: { "Content-Type": "application/json" } });
27
+ const { getAdminClient } = await import("../lib/supabase-admin.server.js");
28
+ const admin = getAdminClient();
29
+ const ronde = await admin.rpc("voorraad_floriday_mutaties_ronde");
30
+ if (ronde.error)
31
+ return json({ error: ronde.error.message }, 500);
32
+ const teLezen = ronde.data?.te_lezen ?? [];
33
+ const conn = ronde.data?.connection_id ?? null;
34
+ let gelezen = 0;
35
+ let leesFout = null;
36
+ if (teLezen.length && conn) {
37
+ const { readBaseSupplyWeeks } = await import("../lib/base-supply-reader.server.js");
38
+ const wk = isoWeek(new Date());
39
+ const out = await readBaseSupplyWeeks([wk]);
40
+ const res = out.results[0];
41
+ if (!res?.ok) {
42
+ leesFout = res?.error ?? out.error ?? "Floriday-stand niet gelezen";
43
+ console.error("[voorraad] floriday-mutaties: stand lezen mislukt:", leesFout);
44
+ }
45
+ else {
46
+ const wanted = new Set(teLezen.map((r) => String(r.trade_item_id).toLowerCase()));
47
+ const rows = res.rows
48
+ .filter((r) => wanted.has(r.tradeItemId.toLowerCase()))
49
+ .map((r) => ({ trade_item_id: r.tradeItemId, number_of_pieces: r.isAvailable ? r.numberOfPieces : 0 }));
50
+ // Artikel zonder weekaanbod = 0 stuks in Floriday (volledig gelezen week).
51
+ for (const id of wanted)
52
+ if (!rows.some((r) => r.trade_item_id.toLowerCase() === id))
53
+ rows.push({ trade_item_id: id, number_of_pieces: 0 });
54
+ const st = await admin.rpc("voorraad_floriday_stand_vastleggen", {
55
+ p_connection_id: conn, p_jaar: wk.year, p_week: wk.week, p_rows: rows,
56
+ });
57
+ if (st.error)
58
+ return json({ error: st.error.message }, 500);
59
+ gelezen = st.data ?? 0;
60
+ }
61
+ }
62
+ const kz = await admin.rpc("voorraad_floriday_mutaties_klaarzetten_alle");
63
+ if (kz.error)
64
+ return json({ error: kz.error.message }, 500);
65
+ return json({ ronde: { verzonden: ronde.data?.verzonden, bevestigd: ronde.data?.bevestigd, mislukt: ronde.data?.mislukt }, gelezen, leesFout, klaargezet: kz.data });
66
+ }
67
+ /** Shell-handler voor /api/public/hooks/floriday-mutaties. */
68
+ export async function handleFloridayMutaties(request) {
69
+ return runGuardedHook(request, "floriday_mutaties", () => handle(), "floriday-mutaties");
70
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"guard.d.ts","sourceRoot":"","sources":["../../src/server-hooks/guard.ts"],"names":[],"mappings":"AAgBA;;;;;GAKG;AACH,eAAO,MAAM,yBAAyB,aAAuD,CAAC;AAE9F,oFAAoF;AACpF,eAAO,MAAM,UAAU,aAAmD,CAAC;AAsC3E,wBAAsB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAchE;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":"AAgBA;;;;;GAKG;AACH,eAAO,MAAM,yBAAyB,aAAuD,CAAC;AAE9F,oFAAoF;AACpF,eAAO,MAAM,UAAU,aAAwE,CAAC;AAsChG,wBAAsB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAchE;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"}
@@ -21,7 +21,7 @@ const CORS = {
21
21
  */
22
22
  export const DEFAULT_ON_IF_ERP_LEADING = new Set(["floriday_base_supply", "logistics_tasks"]);
23
23
  /** Standaard aan zonder voorwaarde (alleen lezen uit Floriday, verstuurt niets). */
24
- export const DEFAULT_ON = new Set(["follow_floriday", "floriday_versies"]);
24
+ export const DEFAULT_ON = new Set(["follow_floriday", "floriday_versies", "floriday_mutaties"]);
25
25
  async function hasErpLeadingArticle(admin) {
26
26
  const lead = await admin
27
27
  .from("floriday_inventory_settings")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flowselections/floriday-voorraad",
3
- "version": "1.0.164",
3
+ "version": "1.0.166",
4
4
  "sideEffects": false,
5
5
  "type": "module",
6
6
  "main": "./dist-lib/index.js",
@@ -184,6 +184,15 @@
184
184
  "POST",
185
185
  "OPTIONS"
186
186
  ]
187
+ },
188
+ {
189
+ "routePath": "/api/public/hooks/floriday-mutaties",
190
+ "import": "@flowselections/floriday-voorraad/hooks",
191
+ "handler": "handleFloridayMutaties",
192
+ "methods": [
193
+ "POST",
194
+ "OPTIONS"
195
+ ]
187
196
  }
188
197
  ],
189
198
  "crons": [
@@ -240,6 +249,18 @@
240
249
  "schedule": "37 3 * * *",
241
250
  "kind": "sql",
242
251
  "sql": "SELECT public.cleanup_voorraad_module_history();"
252
+ },
253
+ {
254
+ "name": "voorraad-floriday-mutaties",
255
+ "kind": "http",
256
+ "path": "/api/public/hooks/floriday-mutaties",
257
+ "schedule": "* * * * *"
258
+ },
259
+ {
260
+ "name": "voorraad-integriteit-check",
261
+ "schedule": "7 * * * *",
262
+ "kind": "sql",
263
+ "sql": "SELECT public.voorraad_integriteit_check();"
243
264
  }
244
265
  ],
245
266
  "install": {
package/sql/AGENTS.md CHANGED
@@ -18,3 +18,5 @@
18
18
  - Custom fields and tray sticker save only via `voorraad_eigen_velden_opslaan` / `voorraad_traysticker_opslaan` (compare against the state at opening), because direct table writes overwrote concurrent edits.
19
19
  - Replaced/expired Floriday trade item versions are recorded only by `voorraad_floriday_versies_vastleggen` (server-only, from a complete error-free per-id read round, blocked above the max share with a message, source recorded); they never become visible articles (alias to the successor or an archived article with stock mode off), and historic shipped lines of such versions never get recalculated fust (`voorraad_fust_historisch_vervallen`), because old versions doubled the catalogue and re-linking history must not book anything.
20
20
  - New visible articles from Floriday are created only after a successful version round for that connection (`voorraad_floriday_versieronde_geslaagd`); rounds run only automatically (portions via `_volgende`/`_portie`), an item failing 3 consecutive rounds is set aside as unknown (never marked, never created), and only a whole failed round produces a user notice, because users must never need a manual step and one bad item must not block forever.
21
+ - Floriday-leading own sales reach Floriday only as mutations (`voorraad_floriday_mutaties`, target per order line/reference, set only by `voorraad_floriday_mutatie_naar`) on top of a Floriday stand read < 2 min ago with orders synced < 2 min ago; `bevestigd` only after a stand read later than the send, because Floriday writes are absolute and stale bases double-count.
22
+ - Trigger functions shared by `orders` and `order_items` use nested IF per table, never one expression mixing `TG_TABLE_NAME` with `OLD/NEW.order_id`, because PL/pgSQL fails on the missing field and the exception handler silently swallowed it.