@flowselections/floriday-voorraad 1.0.84 → 1.0.86

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.
@@ -0,0 +1,49 @@
1
+ export type TakeoverRow = {
2
+ tradeItemId: string;
3
+ name: string;
4
+ articleCode: string | null;
5
+ gtin: string | null;
6
+ vbnCode: string | null;
7
+ productId: string | null;
8
+ productName: string | null;
9
+ action: "bijwerken" | "aanmaken" | "niet";
10
+ reason: string | null;
11
+ suggestion: string | null;
12
+ erpStock: number | null;
13
+ erpPrice: number | null;
14
+ floridayStock: number | null;
15
+ stockSource: string | null;
16
+ supplyLinePrice: number | null;
17
+ catalogPrice: number | null;
18
+ floridayPrice: number | null;
19
+ priceSource: string | null;
20
+ priceDiffers: boolean;
21
+ forms: {
22
+ continuous: boolean;
23
+ continuousAvailable: boolean | null;
24
+ weekly: boolean;
25
+ weeks: Array<{
26
+ year: number;
27
+ week: number;
28
+ pieces: number;
29
+ available: boolean;
30
+ }>;
31
+ supply_lines: number;
32
+ customer_offers: number;
33
+ };
34
+ };
35
+ export declare const getFloridayTakeoverPreview: import("@tanstack/start-client-core").OptionalFetcher<readonly [import("@tanstack/start-client-core").FunctionMiddlewareAfterServer<{}, unknown, undefined, {
36
+ supabase: import("../integrations/supabase/auth-middleware.js").AuthedSupabaseClient;
37
+ userId: string;
38
+ claims: import("@supabase/auth-js").JwtPayload;
39
+ }, undefined, undefined, undefined>], undefined, Promise<{
40
+ connectionId: string;
41
+ environment: any;
42
+ weeks: {
43
+ year: number;
44
+ week: number;
45
+ }[];
46
+ weekError: string | null;
47
+ rows: TakeoverRow[];
48
+ }>>;
49
+ //# sourceMappingURL=floriday-takeover.functions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"floriday-takeover.functions.d.ts","sourceRoot":"","sources":["../../src/lib/floriday-takeover.functions.ts"],"names":[],"mappings":"AAgBA,MAAM,MAAM,WAAW,GAAG;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,MAAM,EAAE,WAAW,GAAG,UAAU,GAAG,MAAM,CAAC;IAC1C,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,YAAY,EAAE,OAAO,CAAC;IACtB,KAAK,EAAE;QACL,UAAU,EAAE,OAAO,CAAC;QACpB,mBAAmB,EAAE,OAAO,GAAG,IAAI,CAAC;QACpC,MAAM,EAAE,OAAO,CAAC;QAChB,KAAK,EAAE,KAAK,CAAC;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAC;YAAC,MAAM,EAAE,MAAM,CAAC;YAAC,SAAS,EAAE,OAAO,CAAA;SAAE,CAAC,CAAC;QACjF,YAAY,EAAE,MAAM,CAAC;QACrB,eAAe,EAAE,MAAM,CAAC;KACzB,CAAC;CACH,CAAC;AAmBF,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;GA8InC,CAAC"}
@@ -0,0 +1,185 @@
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
+ */
14
+ import { createServerFn } from "@tanstack/react-start";
15
+ import { requireSupabaseAuth } from "../integrations/supabase/auth-middleware.js";
16
+ function isoWeek(d) {
17
+ const t = new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate()));
18
+ const day = t.getUTCDay() || 7;
19
+ t.setUTCDate(t.getUTCDate() + 4 - day);
20
+ const y = t.getUTCFullYear();
21
+ const w = Math.ceil(((t.getTime() - Date.UTC(y, 0, 1)) / 86400000 + 1) / 7);
22
+ return { year: y, week: w };
23
+ }
24
+ function nameOf(data) {
25
+ const n = data?.tradeItemName;
26
+ if (typeof n === "string")
27
+ return n;
28
+ return String(n?.nl ?? n?.en ?? Object.values(n ?? {})[0] ?? "").trim();
29
+ }
30
+ const norm = (v) => String(v ?? "").trim().toLowerCase();
31
+ export const getFloridayTakeoverPreview = createServerFn({ method: "POST" })
32
+ .middleware([requireSupabaseAuth])
33
+ .handler(async ({ context }) => {
34
+ const { data: canWrite } = await context.supabase.rpc("voorraad_can_write");
35
+ if (canWrite !== true)
36
+ throw new Error("Je hebt geen rechten om voorraad over te nemen.");
37
+ const { resolveActiveFloridayConnection } = await import("./floriday-env.server.js");
38
+ const conn = await resolveActiveFloridayConnection(context.supabase);
39
+ const { supabaseAdmin } = await import("../integrations/supabase/client.server.js");
40
+ const admin = supabaseAdmin;
41
+ const [items, products, stock, cont, catalog, lines] = await Promise.all([
42
+ admin.from("floriday_trade_items_cache").select("floriday_id, data").eq("connection_id", conn.id).eq("is_deleted", false).limit(5000),
43
+ admin.from("products").select("id, product, barcode, article_number, floriday_trade_item_id, sale_price").limit(10000),
44
+ admin.from("trade_item_stock").select("trade_item_key, physical_stock").eq("connection_id", conn.id).limit(20000),
45
+ admin.from("floriday_continuous_stock_cache").select("trade_item_id, number_of_pieces").eq("connection_id", conn.id).limit(5000),
46
+ admin.from("floriday_catalog_prices_cache").select("trade_item_id, price, updated_at").eq("connection_id", conn.id).eq("is_deleted", false).order("updated_at", { ascending: false }).limit(20000),
47
+ 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),
48
+ ]);
49
+ for (const r of [items, products, stock, cont, catalog, lines])
50
+ if (r.error)
51
+ throw new Error(r.error.message);
52
+ // Weekaanbod: huidige en twee komende weken, live gelezen (1 per seconde).
53
+ const now = new Date();
54
+ const weeks = [0, 1, 2].map((i) => isoWeek(new Date(now.getTime() + i * 7 * 86400000)));
55
+ let weekError = null;
56
+ const weekly = new Map();
57
+ try {
58
+ const { readBaseSupplyWeeks } = await import("./base-supply-reader.server.js");
59
+ const out = await readBaseSupplyWeeks(weeks);
60
+ if (out.error)
61
+ weekError = out.error;
62
+ for (const w of out.results) {
63
+ if (!w.ok) {
64
+ weekError = weekError ?? w.error;
65
+ continue;
66
+ }
67
+ for (const r of w.rows) {
68
+ const arr = weekly.get(r.tradeItemId) ?? [];
69
+ arr.push({ year: w.year, week: w.week, pieces: r.numberOfPieces, available: r.isAvailable, price: r.basePricePerPiece });
70
+ weekly.set(r.tradeItemId, arr);
71
+ }
72
+ }
73
+ }
74
+ catch (e) {
75
+ weekError = e?.message ?? String(e);
76
+ }
77
+ const byFid = new Map();
78
+ const unlinked = [];
79
+ for (const p of products.data ?? []) {
80
+ if (p.floriday_trade_item_id)
81
+ byFid.set(String(p.floriday_trade_item_id).toLowerCase(), p);
82
+ else
83
+ unlinked.push(p);
84
+ }
85
+ const stockBy = new Map();
86
+ for (const s of stock.data ?? [])
87
+ stockBy.set(String(s.trade_item_key), Number(s.physical_stock ?? 0));
88
+ const contBy = new Map();
89
+ for (const c of cont.data ?? [])
90
+ contBy.set(String(c.trade_item_id), Number(c.number_of_pieces ?? 0));
91
+ const catBy = new Map();
92
+ for (const c of catalog.data ?? []) {
93
+ const k = String(c.trade_item_id);
94
+ if (!catBy.has(k) && Number(c.price) > 0)
95
+ catBy.set(k, Number(c.price));
96
+ }
97
+ const lineBy = new Map();
98
+ for (const l of lines.data ?? []) {
99
+ const k = String(l.trade_item_id);
100
+ const cur = lineBy.get(k) ?? { price: null, count: 0, customer: 0 };
101
+ const isCustomer = /customer/i.test(String(l.supply_type ?? ""));
102
+ if (isCustomer)
103
+ cur.customer++;
104
+ else {
105
+ cur.count++;
106
+ if (cur.price == null && Number(l.price_per_piece) > 0)
107
+ cur.price = Number(l.price_per_piece);
108
+ }
109
+ lineBy.set(k, cur);
110
+ }
111
+ const cur = weeks[0];
112
+ const rows = (items.data ?? []).map((it) => {
113
+ const fid = String(it.floriday_id).toLowerCase();
114
+ const d = it.data ?? {};
115
+ const p = byFid.get(fid) ?? null;
116
+ const w = (weekly.get(fid) ?? []).sort((a, b) => a.year - b.year || a.week - b.week);
117
+ const contRaw = contBy.has(fid) ? contBy.get(fid) : null;
118
+ const line = lineBy.get(fid) ?? { price: null, count: 0, customer: 0 };
119
+ // Aanbodvorm uit Floriday zelf: staat er een doorlopende-voorraadrecord, dan is dat de vorm; anders weekaanbod huidige week.
120
+ let floridayStock = null;
121
+ let stockSource = null;
122
+ if (contRaw != null) {
123
+ floridayStock = contRaw;
124
+ stockSource = "Doorlopende voorraad";
125
+ }
126
+ else {
127
+ const now = w.find((x) => x.year === cur.year && x.week === cur.week);
128
+ if (now) {
129
+ floridayStock = now.pieces;
130
+ stockSource = `Weekaanbod week ${now.week}`;
131
+ }
132
+ }
133
+ // Prijs: aanbodregel gaat voor op catalogusprijs.
134
+ const weekPrice = w.find((x) => x.price != null && x.price > 0)?.price ?? null;
135
+ const supplyLinePrice = line.price ?? weekPrice;
136
+ const catalogPrice = catBy.get(fid) ?? null;
137
+ const floridayPrice = supplyLinePrice ?? catalogPrice;
138
+ const priceSource = supplyLinePrice != null ? "Aanbodregel" : catalogPrice != null ? "Catalogusprijs" : null;
139
+ let suggestion = null;
140
+ if (!p) {
141
+ const gtin = norm(d.articleGtin), code = norm(d.supplierArticleCode), nm = norm(nameOf(d));
142
+ const hit = unlinked.find((u) => (gtin && norm(u.barcode) === gtin) || (code && norm(u.article_number) === code) || (nm && norm(u.product) === nm));
143
+ if (hit)
144
+ suggestion = `Lijkt op bestaand artikel "${hit.product}" (niet automatisch gekoppeld)`;
145
+ }
146
+ const erpStock = p
147
+ ? stockBy.get(fid) ?? stockBy.get(`product:${p.id}`) ?? stockBy.get(String(p.id)) ?? null
148
+ : null;
149
+ const name = nameOf(d) || String(d.supplierArticleCode ?? fid);
150
+ let action = p ? "bijwerken" : "aanmaken";
151
+ let reason = null;
152
+ if (floridayStock == null && floridayPrice == null) {
153
+ action = p ? "niet" : "aanmaken";
154
+ reason = "Geen aantal en geen prijs gevonden in Floriday";
155
+ }
156
+ return {
157
+ tradeItemId: fid, name,
158
+ articleCode: d.supplierArticleCode ?? null,
159
+ gtin: d.articleGtin ?? null,
160
+ vbnCode: d.vbnProductCode != null ? String(d.vbnProductCode) : null,
161
+ productId: p?.id ?? null, productName: p?.product ?? null,
162
+ action, reason, suggestion,
163
+ erpStock, erpPrice: p?.sale_price != null ? Number(p.sale_price) : null,
164
+ floridayStock, stockSource,
165
+ supplyLinePrice, catalogPrice, floridayPrice, priceSource,
166
+ priceDiffers: supplyLinePrice != null && catalogPrice != null && Math.abs(supplyLinePrice - catalogPrice) > 0.0001,
167
+ forms: {
168
+ continuous: contRaw != null && contRaw > 0,
169
+ continuousAvailable: contRaw == null ? null : contRaw > 0,
170
+ weekly: w.length > 0,
171
+ weeks: w.map(({ year, week, pieces, available }) => ({ year, week, pieces, available })),
172
+ supply_lines: line.count,
173
+ customer_offers: line.customer,
174
+ },
175
+ };
176
+ });
177
+ rows.sort((a, b) => a.name.localeCompare(b.name, "nl"));
178
+ return {
179
+ connectionId: conn.id,
180
+ environment: conn.environment ?? null,
181
+ weeks,
182
+ weekError,
183
+ rows,
184
+ };
185
+ });
@@ -1 +1 @@
1
- {"version":3,"file":"floriday-stock-reconcile.d.ts","sourceRoot":"","sources":["../../src/server-hooks/floriday-stock-reconcile.ts"],"names":[],"mappings":"AAwPA,wHAAwH;AACxH,wBAAsB,4BAA4B,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAEtF"}
1
+ {"version":3,"file":"floriday-stock-reconcile.d.ts","sourceRoot":"","sources":["../../src/server-hooks/floriday-stock-reconcile.ts"],"names":[],"mappings":"AA2PA,wHAAwH;AACxH,wBAAsB,4BAA4B,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAEtF"}
@@ -117,7 +117,7 @@ async function handle(request) {
117
117
  }
118
118
  const { data: settings, error: setErr } = await admin
119
119
  .from("floriday_inventory_settings")
120
- .select("id, connection_id, product_id, floriday_trade_item_id, desired_quantity, desired_availability, stock_mode, sync_status")
120
+ .select("id, connection_id, product_id, floriday_trade_item_id, desired_quantity, desired_availability, stock_mode, sync_status, erp_leading")
121
121
  .eq("connection_id", connection.id)
122
122
  .not("floriday_trade_item_id", "is", null);
123
123
  if (setErr)
@@ -180,6 +180,10 @@ async function handle(request) {
180
180
  .eq("id", s.id);
181
181
  if (status === "DRIFT") {
182
182
  drift++;
183
+ // Alleen melden, tenzij het ERP leidend is (erp_leading true, of NULL =
184
+ // artikel van vóór 1.0.84). Floriday-leidende artikelen nooit corrigeren.
185
+ if (s.erp_leading === false)
186
+ continue;
183
187
  await admin.rpc("floriday_recompute_inventory", {
184
188
  p_connection_id: s.connection_id,
185
189
  p_product_id: s.product_id,
@@ -1 +1 @@
1
- {"version":3,"file":"guard.d.ts","sourceRoot":"","sources":["../../src/server-hooks/guard.ts"],"names":[],"mappings":"AAuCA,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":"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"}
@@ -13,8 +13,31 @@ const CORS = {
13
13
  "Access-Control-Allow-Methods": "POST, OPTIONS",
14
14
  "Access-Control-Allow-Headers": "content-type, apikey, x-api-key, authorization",
15
15
  };
16
- /** Standaard als de instelling (nog) niet bestaat. Gelijk aan de migratie. */
17
- const DEFAULT_ON = new Set(["floriday_base_supply", "logistics_tasks"]);
16
+ /**
17
+ * Deze verwerkers staan zonder expliciete instelling alleen aan zolang er
18
+ * minstens één ERP-leidend artikel is (erp_leading = true, of een artikel van
19
+ * vóór 1.0.84 met een andere modus dan Uit). Anders uit: een Shell die alles
20
+ * uit Floriday overneemt, verstuurt dan niets.
21
+ */
22
+ const DEFAULT_ON_IF_ERP_LEADING = new Set(["floriday_base_supply", "logistics_tasks"]);
23
+ async function hasErpLeadingArticle(admin) {
24
+ const lead = await admin
25
+ .from("floriday_inventory_settings")
26
+ .select("id", { count: "exact", head: true })
27
+ .eq("erp_leading", true);
28
+ if (lead.error)
29
+ return false; // fail closed
30
+ if ((lead.count ?? 0) > 0)
31
+ return true;
32
+ const legacy = await admin
33
+ .from("floriday_inventory_settings")
34
+ .select("id", { count: "exact", head: true })
35
+ .is("erp_leading", null)
36
+ .neq("stock_mode", "DISABLED");
37
+ if (legacy.error)
38
+ return false;
39
+ return (legacy.count ?? 0) > 0;
40
+ }
18
41
  function json(body, status = 200) {
19
42
  return new Response(JSON.stringify(body), {
20
43
  status,
@@ -40,7 +63,7 @@ export async function hookEnabled(name) {
40
63
  .eq("key", `voorraad_hook_enabled.${name}`)
41
64
  .maybeSingle();
42
65
  if (!data)
43
- return DEFAULT_ON.has(name);
66
+ return DEFAULT_ON_IF_ERP_LEADING.has(name) && (await hasErpLeadingArticle(admin));
44
67
  return String(data.value).trim().toLowerCase() === "true";
45
68
  }
46
69
  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.84",
3
+ "version": "1.0.86",
4
4
  "sideEffects": false,
5
5
  "type": "module",
6
6
  "main": "./dist-lib/index.js",