@flowselections/floriday-voorraad 1.0.133 → 1.0.135

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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=release-134.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"release-134.test.d.ts","sourceRoot":"","sources":["../../../src/lib/__tests__/release-134.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,50 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { contentIdempotencyKey } from "../idempotency-key.js";
3
+ import { compareAfterUpdate, verifyDifferenceMessage } from "../publish-verify.js";
4
+ import { publishOutcome } from "../publish-status.js";
5
+ import { translatePublishError } from "../publish-errors.js";
6
+ const id = "ea7776dd-cdee-46e3-ab2a-ffd970bd8d7b";
7
+ describe("1.0.134 idempotentiesleutel per inhoud", () => {
8
+ it("naam wijzigen geeft een nieuwe sleutel (nieuwe PUT)", async () => {
9
+ const a = await contentIdempotencyKey("inventory:trade-item:update", id, { tradeItemName: "TEST QA artikel 28-10" });
10
+ const b = await contentIdempotencyKey("inventory:trade-item:update", id, { tradeItemName: "TEST QA artikel 28-10 gewijzigd" });
11
+ expect(a).not.toBe(b);
12
+ });
13
+ it("dezelfde wijziging nog eens geeft dezelfde sleutel (geen tweede PUT)", async () => {
14
+ const a = await contentIdempotencyKey("inventory:trade-item:update", id, { tradeItemName: "X", vbnProductCode: 34407 });
15
+ const b = await contentIdempotencyKey("inventory:trade-item:update", id, { vbnProductCode: 34407, tradeItemName: "X" });
16
+ expect(a).toBe(b);
17
+ });
18
+ it("sleutel bevat geen vaste versie meer", async () => {
19
+ const k = await contentIdempotencyKey("inventory:trade-item:update", id, {});
20
+ expect(k).not.toMatch(/:v1$/);
21
+ expect(k).toMatch(new RegExp(`^inventory:trade-item:update:${id}:[0-9a-f]{64}$`));
22
+ });
23
+ });
24
+ describe("1.0.134 controle na update", () => {
25
+ const item = {
26
+ tradeItemName: "TEST QA artikel 28-10",
27
+ vbnProductCode: 34407,
28
+ packingConfigurations: [{ primary: true, piecesPerPackage: 12, packagesPerLayer: 5, layersPerLoadCarrier: 8, package: { vbnPackageCode: 412 }, loadCarrier: "DANISH_TROLLEY" }],
29
+ };
30
+ it("naamafwijking is een verschil met beide waarden", () => {
31
+ const d = compareAfterUpdate({ name: "TEST QA artikel 28-10 gewijzigd", vbnCode: 34407 }, item);
32
+ expect(d).toHaveLength(1);
33
+ expect(verifyDifferenceMessage(d)).toContain("«TEST QA artikel 28-10»");
34
+ expect(verifyDifferenceMessage(d)).toContain("«TEST QA artikel 28-10 gewijzigd»");
35
+ });
36
+ it("verpakkingsafwijking telt", () => {
37
+ const d = compareAfterUpdate({ packing: { piecesPerPackage: 6, packagesPerLayer: 5, layersPerLoadCarrier: 8, vbnPackageCode: 412, loadCarrier: "DANISH_TROLLEY" } }, item);
38
+ expect(d.map((x) => x.field)).toEqual(["piecesPerPackage"]);
39
+ });
40
+ it("gelijk = geen verschil", () => {
41
+ expect(compareAfterUpdate({ name: "TEST QA artikel 28-10", vbnCode: "34407" }, item)).toEqual([]);
42
+ });
43
+ it("afwijking na update = job mislukt", () => {
44
+ expect(publishOutcome([
45
+ { step_key: "trade-item", status: "done" },
46
+ { step_key: "verify", status: "failed", error_code: "VERIFY_MISMATCH" },
47
+ ])).toBe("failed");
48
+ expect(translatePublishError(new Error("VERIFY_MISMATCH:Naam wijkt af")).code).toBe("VERIFY_MISMATCH");
49
+ });
50
+ });
@@ -1 +1 @@
1
- {"version":3,"file":"floriday-warehouse.functions.d.ts","sourceRoot":"","sources":["../../src/lib/floriday-warehouse.functions.ts"],"names":[],"mappings":"AAWA,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,gBAAgB,CAAC;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAoCD,eAAO,MAAM,gBAAgB;;;;;;;;;KAgB1B,CAAC;AAEJ,eAAO,MAAM,iBAAiB;;;;0CAER,cAAc,qFAoBjC,CAAC;AAEJ,eAAO,MAAM,iBAAiB;;;;0CAER,cAAc,qFAqBjC,CAAC"}
1
+ {"version":3,"file":"floriday-warehouse.functions.d.ts","sourceRoot":"","sources":["../../src/lib/floriday-warehouse.functions.ts"],"names":[],"mappings":"AAWA,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,gBAAgB,CAAC;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAoCD,eAAO,MAAM,gBAAgB;;;;;;;;;KAgB1B,CAAC;AAEJ,eAAO,MAAM,iBAAiB;;;;0CAER,cAAc,qFAqBjC,CAAC;AAEJ,eAAO,MAAM,iBAAiB;;;;0CAER,cAAc,qFAsBjC,CAAC"}
@@ -62,6 +62,7 @@ export const createWarehouseFn = createServerFn({ method: "POST" })
62
62
  .handler(async ({ data, context }) => safe("createWarehouse", async () => {
63
63
  const { resolveFloridayEnvironment } = await import("./floriday-env.server.js");
64
64
  const { gatewayWrite } = await import("./floriday-gateway.server.js");
65
+ const { contentIdempotencyKey } = await import("./idempotency-key.js");
65
66
  const environment = await resolveFloridayEnvironment(context.supabase);
66
67
  const body = toWarehouseBody(data);
67
68
  const res = await gatewayWrite({
@@ -71,7 +72,7 @@ export const createWarehouseFn = createServerFn({ method: "POST" })
71
72
  method: "POST",
72
73
  path: `/suppliers-api-2026v1/warehouses`,
73
74
  referenceId: data.warehouseId,
74
- idempotencyKey: `inventory:warehouse:create:${data.warehouseId}`,
75
+ idempotencyKey: await contentIdempotencyKey("inventory:warehouse:create", data.warehouseId, body),
75
76
  body,
76
77
  validateEnums: DEFAULT_VALIDATE_ENUMS,
77
78
  });
@@ -83,6 +84,7 @@ export const updateWarehouseFn = createServerFn({ method: "POST" })
83
84
  .handler(async ({ data, context }) => safe("updateWarehouse", async () => {
84
85
  const { resolveFloridayEnvironment } = await import("./floriday-env.server.js");
85
86
  const { gatewayWrite } = await import("./floriday-gateway.server.js");
87
+ const { contentIdempotencyKey } = await import("./idempotency-key.js");
86
88
  const environment = await resolveFloridayEnvironment(context.supabase);
87
89
  const body = toWarehouseBody(data);
88
90
  delete body.warehouseId;
@@ -93,7 +95,7 @@ export const updateWarehouseFn = createServerFn({ method: "POST" })
93
95
  method: "PUT",
94
96
  path: `/suppliers-api-2026v1/warehouses/${data.warehouseId}`,
95
97
  referenceId: data.warehouseId,
96
- idempotencyKey: `inventory:warehouse:update:${data.warehouseId}`,
98
+ idempotencyKey: await contentIdempotencyKey("inventory:warehouse:update", data.warehouseId, body),
97
99
  body,
98
100
  validateEnums: DEFAULT_VALIDATE_ENUMS,
99
101
  });
@@ -1 +1 @@
1
- {"version":3,"file":"floriday-writes.functions.d.ts","sourceRoot":"","sources":["../../src/lib/floriday-writes.functions.ts"],"names":[],"mappings":"AA4BA,KAAK,UAAU,GAAG;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC,CAAC;AAEF,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,GAAG,CAAC;IACV,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;CAChB;AAyBD,wBAAgB,6BAA6B,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAInE;AAwED,eAAO,MAAM,gBAAgB;;;;0CAEP,UAAU,sCAS7B,CAAC;AAEJ,eAAO,MAAM,iBAAiB;;;;0CAER,UAAU,sCAS7B,CAAC"}
1
+ {"version":3,"file":"floriday-writes.functions.d.ts","sourceRoot":"","sources":["../../src/lib/floriday-writes.functions.ts"],"names":[],"mappings":"AA+BA,KAAK,UAAU,GAAG;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC,CAAC;AAEF,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,GAAG,CAAC;IACV,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;CAChB;AAyBD,wBAAgB,6BAA6B,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAInE;AAsED,eAAO,MAAM,gBAAgB;;;;0CAEP,UAAU,sCAS7B,CAAC;AAEJ,eAAO,MAAM,iBAAiB;;;;0CAER,UAAU,sCAS7B,CAAC"}
@@ -9,12 +9,15 @@
9
9
  * `PUT /trade-items/{id}` dus één idempotency-guard werkt op een retry
10
10
  * direct na een create.
11
11
  * - method = "PUT" voor beide (de tradeItemId is client-side bekend).
12
- * - idempotencyKey expliciet meegestuurd op basis van tradeItemId+formVersion,
13
- * zodat we niet afhankelijk zijn van body-hashing aan de gateway-kant.
12
+ * - idempotencyKey = tradeItemId + sha256 van de genormaliseerde body: een
13
+ * herhaling van dezelfde wijziging blijft idempotent, een nieuwe wijziging
14
+ * krijgt een nieuwe sleutel (een vaste "v1"-sleutel liet de gateway het
15
+ * oude antwoord teruggeven zonder nieuwe PUT).
14
16
  */
15
17
  import { createServerFn } from "@tanstack/react-start";
16
18
  import { requireSupabaseAuth } from "../integrations/supabase/auth-middleware.js";
17
19
  import { normalizeErrors } from "./normalize-errors-middleware.js";
20
+ import { contentIdempotencyKey } from "./idempotency-key.js";
18
21
  const DEFAULT_VALIDATE_ENUMS = [
19
22
  { field: "currency", listName: "Currency" },
20
23
  { field: "salesUnit", listName: "SalesUnit" },
@@ -78,8 +81,7 @@ async function safe(label, fn) {
78
81
  * (tradeItemId is deel van de body, niet van het pad).
79
82
  * Zie swagger operationId "AddTradeItem".
80
83
  */
81
- function buildCreatePayload(environment, data) {
82
- const formVersion = data.formVersion ?? "v1";
84
+ async function buildCreatePayload(environment, data) {
83
85
  // `warehouseId` is lokaal nodig voor voorraad/aanbod, maar is géén veld op
84
86
  // Floriday TradeItemBase. Niet meesturen naar /trade-items.
85
87
  const body = { ...data.values };
@@ -91,7 +93,7 @@ function buildCreatePayload(environment, data) {
91
93
  method: "POST",
92
94
  path: `/suppliers-api-2026v1/trade-items`,
93
95
  referenceId: data.tradeItemId,
94
- idempotencyKey: `inventory:trade-item:create:${data.tradeItemId}:${formVersion}`,
96
+ idempotencyKey: await contentIdempotencyKey("inventory:trade-item:create", data.tradeItemId, body),
95
97
  body,
96
98
  validateEnums: DEFAULT_VALIDATE_ENUMS,
97
99
  };
@@ -100,8 +102,7 @@ function buildCreatePayload(environment, data) {
100
102
  * UPDATE = PUT /suppliers-api-2026v1/trade-items/{tradeItemId} met TradeItemUpdate-body
101
103
  * (tradeItemId hoort NIET in de body). operationId "EditTradeItem".
102
104
  */
103
- function buildUpdatePayload(environment, data) {
104
- const formVersion = data.formVersion ?? "v1";
105
+ async function buildUpdatePayload(environment, data) {
105
106
  // Verwijder velden die niet in TradeItemUpdate horen (Floriday geeft 400 op
106
107
  // onbekende velden bij strict-parsing van sommige properties).
107
108
  const body = { ...data.values };
@@ -115,7 +116,7 @@ function buildUpdatePayload(environment, data) {
115
116
  method: "PUT",
116
117
  path: `/suppliers-api-2026v1/trade-items/${data.tradeItemId}`,
117
118
  referenceId: data.tradeItemId,
118
- idempotencyKey: `inventory:trade-item:update:${data.tradeItemId}:${formVersion}`,
119
+ idempotencyKey: await contentIdempotencyKey("inventory:trade-item:update", data.tradeItemId, body),
119
120
  body,
120
121
  validateEnums: DEFAULT_VALIDATE_ENUMS,
121
122
  };
@@ -127,7 +128,7 @@ export const publishTradeItem = createServerFn({ method: "POST" })
127
128
  const { resolveFloridayEnvironment } = await import("./floriday-env.server.js");
128
129
  const { gatewayWrite } = await import("./floriday-gateway.server.js");
129
130
  const environment = await resolveFloridayEnvironment(context.supabase);
130
- const res = await gatewayWrite(buildCreatePayload(environment, data));
131
+ const res = await gatewayWrite(await buildCreatePayload(environment, data));
131
132
  return res;
132
133
  }));
133
134
  export const updateTradeItemFn = createServerFn({ method: "POST" })
@@ -137,6 +138,6 @@ export const updateTradeItemFn = createServerFn({ method: "POST" })
137
138
  const { resolveFloridayEnvironment } = await import("./floriday-env.server.js");
138
139
  const { gatewayWrite } = await import("./floriday-gateway.server.js");
139
140
  const environment = await resolveFloridayEnvironment(context.supabase);
140
- const res = await gatewayWrite(buildUpdatePayload(environment, data));
141
+ const res = await gatewayWrite(await buildUpdatePayload(environment, data));
141
142
  return res;
142
143
  }));
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Idempotentiesleutel per inhoud: dezelfde wijziging → dezelfde sleutel (geen
3
+ * tweede schrijfactie), een andere wijziging → een nieuwe sleutel.
4
+ * Werkt op de server-runtime en in de browser (Web Crypto).
5
+ */
6
+ /** Stabiele weergave: sleutels gesorteerd, undefined weggelaten, strings getrimd. */
7
+ export declare function normalizeForKey(value: unknown): unknown;
8
+ export declare function sha256Hex(text: string): Promise<string>;
9
+ /** `${prefix}:${id}:${sha256(genormaliseerde body)}` */
10
+ export declare function contentIdempotencyKey(prefix: string, id: string, body: unknown): Promise<string>;
11
+ //# sourceMappingURL=idempotency-key.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"idempotency-key.d.ts","sourceRoot":"","sources":["../../src/lib/idempotency-key.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,qFAAqF;AACrF,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAmBvD;AAED,wBAAsB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAK7D;AAED,wDAAwD;AACxD,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,MAAM,EACd,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,OAAO,GACZ,OAAO,CAAC,MAAM,CAAC,CAGjB"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Idempotentiesleutel per inhoud: dezelfde wijziging → dezelfde sleutel (geen
3
+ * tweede schrijfactie), een andere wijziging → een nieuwe sleutel.
4
+ * Werkt op de server-runtime en in de browser (Web Crypto).
5
+ */
6
+ /** Stabiele weergave: sleutels gesorteerd, undefined weggelaten, strings getrimd. */
7
+ export function normalizeForKey(value) {
8
+ if (value === undefined)
9
+ return undefined;
10
+ if (value === null)
11
+ return null;
12
+ if (typeof value === "string")
13
+ return value.trim();
14
+ if (Array.isArray(value)) {
15
+ return value.map((v) => {
16
+ const n = normalizeForKey(v);
17
+ return n === undefined ? null : n;
18
+ });
19
+ }
20
+ if (typeof value === "object") {
21
+ const out = {};
22
+ for (const k of Object.keys(value).sort()) {
23
+ const n = normalizeForKey(value[k]);
24
+ if (n !== undefined)
25
+ out[k] = n;
26
+ }
27
+ return out;
28
+ }
29
+ return value;
30
+ }
31
+ export async function sha256Hex(text) {
32
+ const buf = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(text));
33
+ return Array.from(new Uint8Array(buf))
34
+ .map((b) => b.toString(16).padStart(2, "0"))
35
+ .join("");
36
+ }
37
+ /** `${prefix}:${id}:${sha256(genormaliseerde body)}` */
38
+ export async function contentIdempotencyKey(prefix, id, body) {
39
+ const hash = await sha256Hex(JSON.stringify(normalizeForKey(body) ?? null));
40
+ return `${prefix}:${id}:${hash}`;
41
+ }
@@ -6,7 +6,7 @@
6
6
  * Woorden als API, gateway, Floriday-intern jargon en modulenamen komen hier
7
7
  * bewust niet in voor.
8
8
  */
9
- export type PublishErrorCode = "MISSING_CHARACTERISTICS" | "NO_CONNECTION" | "WRONG_ENVIRONMENT" | "NOT_LOGGED_IN" | "INVALID_VALUE" | "DUPLICATE" | "NOT_FOUND" | "TEMPORARY" | "TIMEOUT" | "RATE_LIMIT" | "GATE_BLOCKED" | "UNKNOWN";
9
+ export type PublishErrorCode = "VERIFY_MISMATCH" | "MISSING_CHARACTERISTICS" | "NO_CONNECTION" | "WRONG_ENVIRONMENT" | "NOT_LOGGED_IN" | "INVALID_VALUE" | "DUPLICATE" | "NOT_FOUND" | "TEMPORARY" | "TIMEOUT" | "RATE_LIMIT" | "GATE_BLOCKED" | "UNKNOWN";
10
10
  export interface PublishErrorAction {
11
11
  label: string;
12
12
  /** Interne route waar de gebruiker het kan oplossen. */
@@ -1 +1 @@
1
- {"version":3,"file":"publish-errors.d.ts","sourceRoot":"","sources":["../../src/lib/publish-errors.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,MAAM,gBAAgB,GACxB,yBAAyB,GACzB,eAAe,GACf,mBAAmB,GACnB,eAAe,GACf,eAAe,GACf,WAAW,GACX,WAAW,GACX,WAAW,GACX,SAAS,GACT,YAAY,GACZ,cAAc,GACd,SAAS,CAAC;AAEd,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,wDAAwD;IACxD,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,2EAA2E;IAC3E,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,gBAAgB,CAAC;IACvB,8BAA8B;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,yCAAyC;IACzC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,kBAAkB,CAAC;IAC5B,iDAAiD;IACjD,SAAS,EAAE,OAAO,CAAC;IACnB,2DAA2D;IAC3D,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,uDAAuD;IACvD,MAAM,EAAE,MAAM,CAAC;CAChB;AAiBD,8EAA8E;AAC9E,wBAAgB,aAAa,CAC3B,KAAK,EAAE,MAAM,EAAE,EACf,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC9B,MAAM,CAIR;AAED,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,OAAO,EACd,IAAI,CAAC,EAAE;IAAE,oBAAoB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,GACvD,oBAAoB,CAwKtB;AAgBD,wBAAgB,iBAAiB,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAGxD;AAaD;;;GAGG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAK9C;AAED,QAAA,MAAM,aAAa,oFAAqF,CAAC;AACzG,KAAK,WAAW,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAAC;AAElD,6EAA6E;AAC7E,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,MAAM,GACV;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,GAAG,EAAE,MAAM,EAAE,CAAA;CAAE,GAAG,IAAI,CAkBrE"}
1
+ {"version":3,"file":"publish-errors.d.ts","sourceRoot":"","sources":["../../src/lib/publish-errors.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,MAAM,gBAAgB,GACxB,iBAAiB,GACjB,yBAAyB,GACzB,eAAe,GACf,mBAAmB,GACnB,eAAe,GACf,eAAe,GACf,WAAW,GACX,WAAW,GACX,WAAW,GACX,SAAS,GACT,YAAY,GACZ,cAAc,GACd,SAAS,CAAC;AAEd,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,wDAAwD;IACxD,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,2EAA2E;IAC3E,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,gBAAgB,CAAC;IACvB,8BAA8B;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,yCAAyC;IACzC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,kBAAkB,CAAC;IAC5B,iDAAiD;IACjD,SAAS,EAAE,OAAO,CAAC;IACnB,2DAA2D;IAC3D,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,uDAAuD;IACvD,MAAM,EAAE,MAAM,CAAC;CAChB;AAiBD,8EAA8E;AAC9E,wBAAgB,aAAa,CAC3B,KAAK,EAAE,MAAM,EAAE,EACf,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC9B,MAAM,CAIR;AAED,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,OAAO,EACd,IAAI,CAAC,EAAE;IAAE,oBAAoB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,GACvD,oBAAoB,CAmLtB;AAgBD,wBAAgB,iBAAiB,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAGxD;AAaD;;;GAGG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAK9C;AAED,QAAA,MAAM,aAAa,oFAAqF,CAAC;AACzG,KAAK,WAAW,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAAC;AAElD,6EAA6E;AAC7E,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,MAAM,GACV;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,GAAG,EAAE,MAAM,EAAE,CAAA;CAAE,GAAG,IAAI,CAkBrE"}
@@ -36,6 +36,16 @@ export function translatePublishError(error, opts) {
36
36
  : JSON.stringify(error ?? {});
37
37
  const raw = detail ?? "";
38
38
  const lower = raw.toLowerCase();
39
+ // Controle na update: afwijking tussen Floriday en ERP (zie publish-verify).
40
+ if (raw.startsWith("VERIFY_MISMATCH:")) {
41
+ return {
42
+ code: "VERIFY_MISMATCH",
43
+ message: raw.slice("VERIFY_MISMATCH:".length),
44
+ hint: "Publiceer opnieuw; blijft het verschil, controleer de Floriday-koppeling.",
45
+ retryable: false,
46
+ detail: raw,
47
+ };
48
+ }
39
49
  const missingCodes = extractMissingCodes(raw);
40
50
  if (missingCodes.length > 0) {
41
51
  const readable = describeCodes(missingCodes, opts?.characteristicLabels);
@@ -143,7 +143,7 @@ function sanitizeResult(value) {
143
143
  return { value: String(value) };
144
144
  }
145
145
  export async function finishPublishJob(jobId, patch) {
146
- const { data } = await steps().select("status, step_key").eq("job_id", jobId);
146
+ const { data } = await steps().select("status, step_key, error_code").eq("job_id", jobId);
147
147
  const rows = (data ?? []);
148
148
  const { publishOutcome } = await import("./publish-status.js");
149
149
  const status = publishOutcome(rows);
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * Regels voor de publicatiestatus van een artikel (1.0.130).
3
3
  * - Een artikel met een Floriday-id telt als gepubliceerd.
4
- * - Een mislukte controle is een waarschuwing, geen mislukte publicatie.
4
+ * - Een mislukte controle is een waarschuwing, geen mislukte publicatie —
5
+ * behalve een afwijking na een update (VERIFY_MISMATCH): dan is de job mislukt.
5
6
  * - Wijzigingen gaan naar Floriday als er een Floriday-id is, tenzij er een
6
7
  * reden is waarom dat niet kan; die reden wordt dan getoond.
7
8
  */
@@ -9,6 +10,7 @@ export declare function isPublishedToFloriday(...ids: Array<string | null | unde
9
10
  export type StepOutcome = {
10
11
  step_key: string;
11
12
  status: string;
13
+ error_code?: string | null;
12
14
  };
13
15
  export type PublishOutcome = "failed" | "succeeded_with_warnings" | "succeeded";
14
16
  /** Alleen de artikelstap bepaalt of de publicatie mislukt is. */
@@ -1 +1 @@
1
- {"version":3,"file":"publish-status.d.ts","sourceRoot":"","sources":["../../src/lib/publish-status.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,wBAAgB,qBAAqB,CAAC,GAAG,GAAG,EAAE,KAAK,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,GAAG,OAAO,CAEvF;AAED,MAAM,MAAM,WAAW,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAC/D,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,yBAAyB,GAAG,WAAW,CAAC;AAEhF,iEAAiE;AACjE,wBAAgB,cAAc,CAAC,KAAK,EAAE,WAAW,EAAE,GAAG,cAAc,CAInE;AAED,MAAM,WAAW,UAAU;IACzB,eAAe,EAAE,OAAO,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,uEAAuE;AACvE,wBAAgB,cAAc,CAAC,KAAK,EAAE;IACpC,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IACtC,aAAa,EAAE,OAAO,GAAG,IAAI,GAAG,SAAS,CAAC;IAC1C,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B,GAAG,UAAU,CAWb"}
1
+ {"version":3,"file":"publish-status.d.ts","sourceRoot":"","sources":["../../src/lib/publish-status.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,wBAAgB,qBAAqB,CAAC,GAAG,GAAG,EAAE,KAAK,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,GAAG,OAAO,CAEvF;AAED,MAAM,MAAM,WAAW,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC;AAC3F,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,yBAAyB,GAAG,WAAW,CAAC;AAEhF,iEAAiE;AACjE,wBAAgB,cAAc,CAAC,KAAK,EAAE,WAAW,EAAE,GAAG,cAAc,CAKnE;AAED,MAAM,WAAW,UAAU;IACzB,eAAe,EAAE,OAAO,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,uEAAuE;AACvE,wBAAgB,cAAc,CAAC,KAAK,EAAE;IACpC,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IACtC,aAAa,EAAE,OAAO,GAAG,IAAI,GAAG,SAAS,CAAC;IAC1C,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B,GAAG,UAAU,CAWb"}
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * Regels voor de publicatiestatus van een artikel (1.0.130).
3
3
  * - Een artikel met een Floriday-id telt als gepubliceerd.
4
- * - Een mislukte controle is een waarschuwing, geen mislukte publicatie.
4
+ * - Een mislukte controle is een waarschuwing, geen mislukte publicatie —
5
+ * behalve een afwijking na een update (VERIFY_MISMATCH): dan is de job mislukt.
5
6
  * - Wijzigingen gaan naar Floriday als er een Floriday-id is, tenzij er een
6
7
  * reden is waarom dat niet kan; die reden wordt dan getoond.
7
8
  */
@@ -13,6 +14,8 @@ export function publishOutcome(steps) {
13
14
  const failed = steps.filter((s) => s.status === "failed");
14
15
  if (failed.some((s) => s.step_key === "trade-item"))
15
16
  return "failed";
17
+ if (failed.some((s) => s.step_key === "verify" && s.error_code === "VERIFY_MISMATCH"))
18
+ return "failed";
16
19
  return failed.length ? "succeeded_with_warnings" : "succeeded";
17
20
  }
18
21
  /** Wat het bewerkscherm belooft over het versturen van wijzigingen. */
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Controle na een update naar Floriday: naam, VBN-code en hoofdverpakking moeten
3
+ * gelijk zijn aan wat het ERP verstuurde. Een afwijking = de update is niet
4
+ * (volledig) aangekomen; de job moet dan mislukken en het artikel blijft "dirty".
5
+ */
6
+ export interface VerifyExpected {
7
+ name?: string | null;
8
+ vbnCode?: string | number | null;
9
+ packing?: {
10
+ piecesPerPackage?: number | null;
11
+ packagesPerLayer?: number | null;
12
+ layersPerLoadCarrier?: number | null;
13
+ vbnPackageCode?: number | string | null;
14
+ loadCarrier?: string | number | null;
15
+ } | null;
16
+ }
17
+ export interface VerifyDifference {
18
+ field: string;
19
+ label: string;
20
+ floriday: string;
21
+ erp: string;
22
+ }
23
+ export declare function compareAfterUpdate(expected: VerifyExpected, item: any): VerifyDifference[];
24
+ export declare function verifyDifferenceMessage(diffs: VerifyDifference[]): string;
25
+ //# sourceMappingURL=publish-verify.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"publish-verify.d.ts","sourceRoot":"","sources":["../../src/lib/publish-verify.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IACjC,OAAO,CAAC,EAAE;QACR,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACjC,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACjC,oBAAoB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACrC,cAAc,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;QACxC,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;KACtC,GAAG,IAAI,CAAC;CACV;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;CACb;AAgBD,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,cAAc,EAAE,IAAI,EAAE,GAAG,GAAG,gBAAgB,EAAE,CAwB1F;AAED,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,gBAAgB,EAAE,GAAG,MAAM,CAKzE"}
@@ -0,0 +1,44 @@
1
+ const txt = (v) => (v === null || v === undefined ? "" : String(v).trim());
2
+ const shown = (v) => (v === "" ? "(leeg)" : v);
3
+ function remotePrimary(item) {
4
+ const list = Array.isArray(item?.packingConfigurations) ? item.packingConfigurations : [];
5
+ return list.find((c) => c && (c.primary === true || c.isPrimary === true)) ?? null;
6
+ }
7
+ function remoteLoadCarrier(p) {
8
+ const lc = p.loadCarrier;
9
+ if (lc && typeof lc === "object")
10
+ return txt(lc.loadCarrierType ?? lc.type ?? lc.code);
11
+ return txt(lc);
12
+ }
13
+ export function compareAfterUpdate(expected, item) {
14
+ const out = [];
15
+ const add = (field, label, floriday, erp) => {
16
+ const f = txt(floriday);
17
+ const e = txt(erp);
18
+ if (e === "")
19
+ return; // ERP stuurde niets: niets te vergelijken
20
+ if (f !== e)
21
+ out.push({ field, label, floriday: shown(f), erp: shown(e) });
22
+ };
23
+ add("name", "Naam", item?.tradeItemName ?? item?.name, expected.name);
24
+ add("vbnCode", "VBN-code", item?.vbnProductCode ?? item?.vbnCode, expected.vbnCode);
25
+ const exp = expected.packing;
26
+ if (exp) {
27
+ const p = remotePrimary(item);
28
+ if (!p) {
29
+ out.push({ field: "packing", label: "Hoofdverpakking", floriday: "(geen)", erp: "ingesteld" });
30
+ }
31
+ else {
32
+ add("piecesPerPackage", "Stuks per fust", p.piecesPerPackage, exp.piecesPerPackage);
33
+ add("packagesPerLayer", "Fusten per laag", p.packagesPerLayer, exp.packagesPerLayer);
34
+ add("layersPerLoadCarrier", "Lagen per ladingdrager", p.layersPerLoadCarrier, exp.layersPerLoadCarrier);
35
+ add("vbnPackageCode", "Fust", p.package?.vbnPackageCode ?? p.vbnPackageCode, exp.vbnPackageCode);
36
+ add("loadCarrier", "Ladingdrager", remoteLoadCarrier(p), exp.loadCarrier);
37
+ }
38
+ }
39
+ return out;
40
+ }
41
+ export function verifyDifferenceMessage(diffs) {
42
+ return ("Floriday heeft de wijziging niet (volledig) overgenomen: " +
43
+ diffs.map((d) => `${d.label} in Floriday: «${d.floriday}», in het ERP: «${d.erp}»`).join("; "));
44
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flowselections/floriday-voorraad",
3
- "version": "1.0.133",
3
+ "version": "1.0.135",
4
4
  "sideEffects": false,
5
5
  "type": "module",
6
6
  "main": "./dist-lib/index.js",
@@ -1,4 +1,4 @@
1
- -- @flowselections/floriday-voorraad 1.0.132 – volledig schema (idempotent, geen gegevens).
1
+ -- @flowselections/floriday-voorraad 1.0.134 – volledig schema (idempotent, geen gegevens).
2
2
  -- Gegenereerd uit de database met scripts/generate-voorraad-schema.py. Niet met de hand wijzigen.
3
3
  -- Volgorde 20. Ontbrekende tabellen van andere modules worden overgeslagen met een NOTICE; draai opnieuw na die module.
4
4
  -- De statuscontrole van floriday_continuous_stock_queue is van Voorraad en wordt hier opnieuw gemaakt.
@@ -5181,12 +5181,11 @@ DECLARE k text;
5181
5181
  BEGIN
5182
5182
  PERFORM public.voorraad_vereis('ingelogd', 'recompute_reserved_for_order');
5183
5183
  FOR k IN
5184
- SELECT DISTINCT p.trade_item_key
5184
+ SELECT DISTINCT public.voorraad_orderregel_sleutel(oi.product_id::text)
5185
5185
  FROM logistics_active.order_items oi
5186
- JOIN public.products p ON p.id = oi.product_id
5187
- WHERE oi.order_id = p_order_id AND p.trade_item_key IS NOT NULL
5186
+ WHERE oi.order_id = p_order_id AND oi.product_id IS NOT NULL
5188
5187
  LOOP
5189
- PERFORM public.recompute_reserved_stock(k);
5188
+ IF k IS NOT NULL THEN PERFORM public.recompute_reserved_stock(k); END IF;
5190
5189
  END LOOP;
5191
5190
  END $function$
5192
5191
  ;
@@ -5210,16 +5209,15 @@ DECLARE
5210
5209
  v_products uuid[];
5211
5210
  v_pid uuid;
5212
5211
  v_leads boolean;
5212
+ v_refs text[];
5213
5213
  BEGIN
5214
5214
  PERFORM public.voorraad_vereis('ingelogd', 'recompute_reserved_stock');
5215
5215
  IF p_trade_item_key IS NULL OR btrim(p_trade_item_key) = '' THEN RETURN; END IF;
5216
5216
  IF current_setting('app.recomputing_reserved', true) = 'on' THEN RETURN; END IF;
5217
5217
 
5218
- -- B1: nieuwe of losse sleutel zonder orderregels, toewijzing of set-/groepsrelatie en reservering 0: niets te doen.
5219
5218
  v_key := public.logistics_reserved_key(p_trade_item_key);
5220
5219
  IF public.logistics_reserved_fast_skip(p_trade_item_key, v_key) THEN RETURN; END IF;
5221
5220
 
5222
- -- Set of groepshoofdproduct: gericht herberekenen op de onderdelen en leden (M7).
5223
5221
  SELECT array_agg(p.id) INTO v_products
5224
5222
  FROM public.products p
5225
5223
  WHERE (p.trade_item_key = btrim(p_trade_item_key)
@@ -5247,8 +5245,6 @@ BEGIN
5247
5245
  RETURN;
5248
5246
  END IF;
5249
5247
 
5250
- -- Sleutel hoort bij een groep of samenstelling: de toewijzing hangt van andere
5251
- -- regels af, dus volledig herberekenen.
5252
5248
  IF v_key LIKE 'group:%'
5253
5249
  OR EXISTS (SELECT 1 FROM public.logistics_reservation_allocations a
5254
5250
  WHERE a.trade_item_key = v_key AND a.source = 'group')
@@ -5265,18 +5261,28 @@ BEGIN
5265
5261
  RETURN;
5266
5262
  END IF;
5267
5263
 
5264
+ SELECT array_agg(DISTINCT r) INTO v_refs FROM (
5265
+ SELECT p.id::text AS r FROM public.products p
5266
+ WHERE p.trade_item_key = v_key OR p.floriday_trade_item_id = v_key OR p.id::text = v_key
5267
+ OR 'product:' || p.id::text = v_key
5268
+ UNION
5269
+ SELECT btrim(p.floriday_trade_item_id) FROM public.products p
5270
+ WHERE (p.trade_item_key = v_key OR p.floriday_trade_item_id = v_key OR 'product:' || p.id::text = v_key)
5271
+ AND NULLIF(btrim(COALESCE(p.floriday_trade_item_id,'')),'') IS NOT NULL
5272
+ UNION SELECT v_key
5273
+ ) x WHERE r IS NOT NULL;
5274
+
5268
5275
  v_leads := public.voorraad_floriday_leads_key(v_key);
5269
5276
  SELECT CEIL(COALESCE(SUM(COALESCE(oi.quantity, 0)), 0))::integer INTO v_sum
5270
5277
  FROM logistics_active.order_items oi
5271
5278
  JOIN public.orders o ON o.id = oi.order_id
5272
- JOIN public.products p ON p.id = oi.product_id
5273
- WHERE COALESCE(NULLIF(btrim(COALESCE(p.floriday_trade_item_id, '')), ''),
5274
- NULLIF(btrim(COALESCE(p.trade_item_key, '')), ''), p.id::text) = v_key
5279
+ WHERE oi.product_id::text = ANY(v_refs)
5280
+ AND public.voorraad_orderregel_sleutel(oi.product_id::text) = v_key
5275
5281
  AND o.status = 'Nog te picken' AND NOT public.logistics_reserved_order_excluded(o.id)
5276
5282
  AND NOT (public.normalize_order_source(o.order_source) = 'FO' AND v_leads)
5277
- AND NOT EXISTS (SELECT 1 FROM public.product_bom_items b WHERE b.parent_product_id = p.id)
5283
+ AND NOT EXISTS (SELECT 1 FROM public.product_bom_items b WHERE b.parent_product_id = oi.product_id)
5278
5284
  AND NOT EXISTS (SELECT 1 FROM public.product_groups pg
5279
- WHERE pg.is_active = true AND (pg.id = p.id OR pg.head_product_id = p.id))
5285
+ WHERE pg.is_active = true AND (pg.id = oi.product_id OR pg.head_product_id = oi.product_id))
5280
5286
  AND NOT EXISTS (
5281
5287
  SELECT 1 FROM public.stock_mutation_log sml
5282
5288
  WHERE sml.order_item_id = oi.id
@@ -6752,7 +6758,7 @@ AS $function$
6752
6758
  DECLARE v_key text; v_old_key text;
6753
6759
  BEGIN
6754
6760
  IF (TG_OP = 'INSERT') THEN
6755
- SELECT p.trade_item_key INTO v_key FROM public.products p WHERE p.id = NEW.product_id;
6761
+ v_key := public.voorraad_orderregel_sleutel(NEW.product_id::text);
6756
6762
  PERFORM public.recompute_reserved_stock(v_key);
6757
6763
  RETURN NEW;
6758
6764
  ELSIF (TG_OP = 'UPDATE') THEN
@@ -6763,15 +6769,15 @@ BEGIN
6763
6769
  AND NEW.source_line_key IS NOT DISTINCT FROM OLD.source_line_key THEN
6764
6770
  RETURN NEW;
6765
6771
  END IF;
6766
- SELECT p.trade_item_key INTO v_key FROM public.products p WHERE p.id = NEW.product_id;
6767
- SELECT p.trade_item_key INTO v_old_key FROM public.products p WHERE p.id = OLD.product_id;
6772
+ v_key := public.voorraad_orderregel_sleutel(NEW.product_id::text);
6773
+ v_old_key := public.voorraad_orderregel_sleutel(OLD.product_id::text);
6768
6774
  PERFORM public.recompute_reserved_stock(v_key);
6769
6775
  IF v_old_key IS DISTINCT FROM v_key THEN
6770
6776
  PERFORM public.recompute_reserved_stock(v_old_key);
6771
6777
  END IF;
6772
6778
  RETURN NEW;
6773
6779
  ELSIF (TG_OP = 'DELETE') THEN
6774
- SELECT p.trade_item_key INTO v_old_key FROM public.products p WHERE p.id = OLD.product_id;
6780
+ v_old_key := public.voorraad_orderregel_sleutel(OLD.product_id::text);
6775
6781
  PERFORM public.recompute_reserved_stock(v_old_key);
6776
6782
  RETURN OLD;
6777
6783
  END IF;
@@ -9252,6 +9258,28 @@ GRANT EXECUTE ON FUNCTION voorraad_order_geannuleerd(text,timestamp with time zo
9252
9258
  REVOKE EXECUTE ON FUNCTION voorraad_order_geannuleerd(text,timestamp with time zone,timestamp with time zone) FROM anon;
9253
9259
  GRANT EXECUTE ON FUNCTION voorraad_order_geannuleerd(text,timestamp with time zone,timestamp with time zone) TO authenticated;
9254
9260
  GRANT EXECUTE ON FUNCTION voorraad_order_geannuleerd(text,timestamp with time zone,timestamp with time zone) TO service_role;
9261
+ CREATE OR REPLACE FUNCTION public.voorraad_orderregel_sleutel(p_ref text)
9262
+ RETURNS text
9263
+ LANGUAGE plpgsql
9264
+ STABLE SECURITY DEFINER
9265
+ SET search_path TO 'public'
9266
+ AS $function$
9267
+ DECLARE v_key text;
9268
+ BEGIN
9269
+ PERFORM public.voorraad_vereis('ingelogd', 'voorraad_orderregel_sleutel');
9270
+ IF p_ref IS NULL OR btrim(p_ref) = '' THEN RETURN NULL; END IF;
9271
+ BEGIN
9272
+ SELECT c.trade_item_key INTO v_key FROM public.voorraad_canoniek_artikel(p_ref) c;
9273
+ EXCEPTION WHEN others THEN
9274
+ RETURN NULL;
9275
+ END;
9276
+ RETURN v_key;
9277
+ END $function$
9278
+ ;
9279
+ REVOKE ALL ON FUNCTION voorraad_orderregel_sleutel(text) FROM PUBLIC;
9280
+ REVOKE ALL ON FUNCTION voorraad_orderregel_sleutel(text) FROM anon;
9281
+ GRANT EXECUTE ON FUNCTION voorraad_orderregel_sleutel(text) TO authenticated;
9282
+ GRANT EXECUTE ON FUNCTION voorraad_orderregel_sleutel(text) TO service_role;
9255
9283
  CREATE OR REPLACE FUNCTION public.voorraad_packing_from_floriday(p_product_id uuid, p_uitvoeren boolean)
9256
9284
  RETURNS jsonb
9257
9285
  LANGUAGE plpgsql