@flowselections/floriday-voorraad 1.0.12 → 1.0.14

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 (123) hide show
  1. package/dist-lib/assets/floriday-logo.png.asset.json +11 -0
  2. package/dist-lib/components/voorraad/ArtikelWizard.d.ts +5 -0
  3. package/dist-lib/components/voorraad/ArtikelWizard.d.ts.map +1 -0
  4. package/dist-lib/components/voorraad/ArtikelWizard.js +1229 -0
  5. package/dist-lib/components/voorraad/ConnectionBadge.d.ts +6 -0
  6. package/dist-lib/components/voorraad/ConnectionBadge.d.ts.map +1 -0
  7. package/dist-lib/components/voorraad/ConnectionBadge.js +12 -0
  8. package/dist-lib/components/voorraad/EditArtikelPage.d.ts +4 -0
  9. package/dist-lib/components/voorraad/EditArtikelPage.d.ts.map +1 -0
  10. package/dist-lib/components/voorraad/EditArtikelPage.js +51 -0
  11. package/dist-lib/components/voorraad/NieuwArtikelPage.d.ts +2 -0
  12. package/dist-lib/components/voorraad/NieuwArtikelPage.d.ts.map +1 -0
  13. package/dist-lib/components/voorraad/NieuwArtikelPage.js +5 -0
  14. package/dist-lib/components/voorraad/StockBulkButtons.d.ts +7 -0
  15. package/dist-lib/components/voorraad/StockBulkButtons.d.ts.map +1 -0
  16. package/dist-lib/components/voorraad/StockBulkButtons.js +356 -0
  17. package/dist-lib/components/voorraad/ValidationOverviewTable.d.ts +7 -0
  18. package/dist-lib/components/voorraad/ValidationOverviewTable.d.ts.map +1 -0
  19. package/dist-lib/components/voorraad/ValidationOverviewTable.js +87 -0
  20. package/dist-lib/components/voorraad/ValidationPage.d.ts +2 -0
  21. package/dist-lib/components/voorraad/ValidationPage.d.ts.map +1 -0
  22. package/dist-lib/components/voorraad/ValidationPage.js +12 -0
  23. package/dist-lib/components/voorraad/VoorraadDashboard.d.ts +6 -0
  24. package/dist-lib/components/voorraad/VoorraadDashboard.d.ts.map +1 -0
  25. package/dist-lib/components/voorraad/VoorraadDashboard.js +42 -0
  26. package/dist-lib/components/voorraad/VoorraadPage.d.ts.map +1 -1
  27. package/dist-lib/components/voorraad/VoorraadPage.js +78 -11
  28. package/dist-lib/components/voorraad/VoorraadPagination.d.ts +10 -0
  29. package/dist-lib/components/voorraad/VoorraadPagination.d.ts.map +1 -0
  30. package/dist-lib/components/voorraad/VoorraadPagination.js +12 -0
  31. package/dist-lib/components/voorraad/VoorraadStockCard.d.ts +8 -0
  32. package/dist-lib/components/voorraad/VoorraadStockCard.d.ts.map +1 -0
  33. package/dist-lib/components/voorraad/VoorraadStockCard.js +33 -0
  34. package/dist-lib/components/voorraad/VoorraadTable.d.ts.map +1 -1
  35. package/dist-lib/components/voorraad/VoorraadTable.js +21 -2
  36. package/dist-lib/hooks/useFloriday.d.ts +55 -0
  37. package/dist-lib/hooks/useFloriday.d.ts.map +1 -0
  38. package/dist-lib/hooks/useFloriday.js +298 -0
  39. package/dist-lib/hooks/useFloridayConnection.d.ts +9 -4
  40. package/dist-lib/hooks/useFloridayConnection.d.ts.map +1 -1
  41. package/dist-lib/hooks/useFloridayConnection.js +23 -23
  42. package/dist-lib/hooks/useFlorydayCustomers.d.ts +17 -0
  43. package/dist-lib/hooks/useFlorydayCustomers.d.ts.map +1 -0
  44. package/dist-lib/hooks/useFlorydayCustomers.js +65 -0
  45. package/dist-lib/hooks/useLocalTradeItems.d.ts +38 -0
  46. package/dist-lib/hooks/useLocalTradeItems.d.ts.map +1 -0
  47. package/dist-lib/hooks/useLocalTradeItems.js +144 -0
  48. package/dist-lib/hooks/useTradeItemSpec.d.ts +42 -0
  49. package/dist-lib/hooks/useTradeItemSpec.d.ts.map +1 -0
  50. package/dist-lib/hooks/useTradeItemSpec.js +448 -0
  51. package/dist-lib/hooks/useTradeItemStock.d.ts +19 -0
  52. package/dist-lib/hooks/useTradeItemStock.d.ts.map +1 -0
  53. package/dist-lib/hooks/useTradeItemStock.js +78 -0
  54. package/dist-lib/hooks/useVbnRegulatoryFeatures.d.ts +3 -0
  55. package/dist-lib/hooks/useVbnRegulatoryFeatures.d.ts.map +1 -0
  56. package/dist-lib/hooks/useVbnRegulatoryFeatures.js +19 -0
  57. package/dist-lib/hooks/useVoorraadData.d.ts +6 -0
  58. package/dist-lib/hooks/useVoorraadData.d.ts.map +1 -1
  59. package/dist-lib/hooks/useVoorraadData.js +65 -10
  60. package/dist-lib/index.d.ts +4 -0
  61. package/dist-lib/index.d.ts.map +1 -1
  62. package/dist-lib/index.js +6 -5
  63. package/dist-lib/integrations/supabase/auth-middleware.d.ts +9085 -1645
  64. package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
  65. package/dist-lib/integrations/supabase/client.d.ts +9085 -1645
  66. package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
  67. package/dist-lib/integrations/supabase/client.server.d.ts +9085 -1645
  68. package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
  69. package/dist-lib/integrations/supabase/types.d.ts +9298 -1655
  70. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  71. package/dist-lib/integrations/supabase/types.js +20 -1
  72. package/dist-lib/lib/__tests__/floricode-required-features.test.d.ts +2 -0
  73. package/dist-lib/lib/__tests__/floricode-required-features.test.d.ts.map +1 -0
  74. package/dist-lib/lib/__tests__/floricode-required-features.test.js +186 -0
  75. package/dist-lib/lib/floricode-client.d.ts +15 -1
  76. package/dist-lib/lib/floricode-client.d.ts.map +1 -1
  77. package/dist-lib/lib/floricode-client.js +42 -4
  78. package/dist-lib/lib/floricode-required-features.functions.d.ts +10026 -0
  79. package/dist-lib/lib/floricode-required-features.functions.d.ts.map +1 -0
  80. package/dist-lib/lib/floricode-required-features.functions.js +170 -0
  81. package/dist-lib/lib/floriday-client.d.ts +45 -19
  82. package/dist-lib/lib/floriday-client.d.ts.map +1 -1
  83. package/dist-lib/lib/floriday-client.js +236 -38
  84. package/dist-lib/lib/floriday-env.d.ts +29 -0
  85. package/dist-lib/lib/floriday-env.d.ts.map +1 -0
  86. package/dist-lib/lib/floriday-env.js +42 -0
  87. package/dist-lib/lib/floriday-env.server.d.ts +15 -0
  88. package/dist-lib/lib/floriday-env.server.d.ts.map +1 -0
  89. package/dist-lib/lib/floriday-env.server.js +13 -0
  90. package/dist-lib/lib/floriday-field-sources.d.ts +18 -0
  91. package/dist-lib/lib/floriday-field-sources.d.ts.map +1 -0
  92. package/dist-lib/lib/floriday-field-sources.js +45 -0
  93. package/dist-lib/lib/floriday-gateway-entities.d.ts +44 -0
  94. package/dist-lib/lib/floriday-gateway-entities.d.ts.map +1 -0
  95. package/dist-lib/lib/floriday-gateway-entities.js +51 -0
  96. package/dist-lib/lib/floriday-gateway.functions.d.ts +39999 -0
  97. package/dist-lib/lib/floriday-gateway.functions.d.ts.map +1 -0
  98. package/dist-lib/lib/floriday-gateway.functions.js +68 -0
  99. package/dist-lib/lib/floriday-gateway.server.d.ts +122 -0
  100. package/dist-lib/lib/floriday-gateway.server.d.ts.map +1 -0
  101. package/dist-lib/lib/floriday-gateway.server.js +230 -0
  102. package/dist-lib/lib/floriday-import-validator.d.ts +26 -0
  103. package/dist-lib/lib/floriday-import-validator.d.ts.map +1 -0
  104. package/dist-lib/lib/floriday-import-validator.js +41 -0
  105. package/dist-lib/lib/floriday-payload.d.ts +47 -3
  106. package/dist-lib/lib/floriday-payload.d.ts.map +1 -1
  107. package/dist-lib/lib/floriday-payload.js +179 -7
  108. package/dist-lib/lib/floriday-schema-to-zod.d.ts +9 -0
  109. package/dist-lib/lib/floriday-schema-to-zod.d.ts.map +1 -0
  110. package/dist-lib/lib/floriday-schema-to-zod.js +53 -0
  111. package/dist-lib/lib/floriday-writes.functions.d.ts +19998 -0
  112. package/dist-lib/lib/floriday-writes.functions.d.ts.map +1 -0
  113. package/dist-lib/lib/floriday-writes.functions.js +74 -0
  114. package/dist-lib/lib/image-resize.d.ts +43 -0
  115. package/dist-lib/lib/image-resize.d.ts.map +1 -0
  116. package/dist-lib/lib/image-resize.js +212 -0
  117. package/dist-lib/lib/normalize-errors-middleware.d.ts +2 -0
  118. package/dist-lib/lib/normalize-errors-middleware.d.ts.map +1 -0
  119. package/dist-lib/lib/normalize-errors-middleware.js +24 -0
  120. package/dist-lib/lib/wizard-step-fields.d.ts +15 -0
  121. package/dist-lib/lib/wizard-step-fields.d.ts.map +1 -0
  122. package/dist-lib/lib/wizard-step-fields.js +51 -0
  123. package/package.json +29 -14
@@ -0,0 +1,42 @@
1
+ import type { ProductGroup } from "../lib/floriday-payload";
2
+ import type { FloridaySchema } from "./useFloriday";
3
+ export interface SpecCharacteristicValue {
4
+ code: string;
5
+ label: string;
6
+ }
7
+ export interface SpecCharacteristic {
8
+ code: string;
9
+ label: string;
10
+ required: boolean;
11
+ /** Naam van de VBN-codelijst voor /gateway/code-list (meestal == code). */
12
+ listName: string;
13
+ /** Toegestane waarden uit vbn-characteristic-value (leeg = onbekend). */
14
+ allowedValues: SpecCharacteristicValue[];
15
+ }
16
+ export interface TradeItemSpec {
17
+ vbnCode: string;
18
+ productGroup: ProductGroup | null;
19
+ characteristics: SpecCharacteristic[];
20
+ /** Verplichte karakteristiek-codes, voor snelle lookup. */
21
+ requiredCharacteristicCodes: Set<string>;
22
+ /** false = niet betrouwbaar publiceren; gateway/schema leverde geen VBN-classificatie. */
23
+ canDetermineCharacteristics: boolean;
24
+ characteristicIssue?: string;
25
+ /** True wanneer karakteristieken komen uit fallback (vbn-characteristic), niet uit VBN-payload. */
26
+ characteristicsAreFallback: boolean;
27
+ /** Linnaeus product_group_id voor de resolved VBN-code (8-cijferig), indien beschikbaar. */
28
+ productGroupId: string | null;
29
+ requiresBotanicalName: boolean;
30
+ requiresCountryOfOrigin: boolean;
31
+ }
32
+ export interface RegulatoryFeatureInput {
33
+ code: string;
34
+ label: string;
35
+ regulation: "required" | "advised" | "allowed";
36
+ values: {
37
+ code: string;
38
+ label: string;
39
+ }[];
40
+ }
41
+ export declare function useTradeItemSpec(schema: FloridaySchema | undefined, vbnCode: string, productGroup: ProductGroup | null, regulatoryFeatures?: RegulatoryFeatureInput[] | null): TradeItemSpec;
42
+ //# sourceMappingURL=useTradeItemSpec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useTradeItemSpec.d.ts","sourceRoot":"","sources":["../../src/hooks/useTradeItemSpec.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,KAAK,EAAE,cAAc,EAAuB,MAAM,eAAe,CAAC;AAEzE,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,OAAO,CAAC;IAClB,2EAA2E;IAC3E,QAAQ,EAAE,MAAM,CAAC;IACjB,yEAAyE;IACzE,aAAa,EAAE,uBAAuB,EAAE,CAAC;CAC1C;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,YAAY,GAAG,IAAI,CAAC;IAClC,eAAe,EAAE,kBAAkB,EAAE,CAAC;IACtC,2DAA2D;IAC3D,2BAA2B,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACzC,0FAA0F;IAC1F,2BAA2B,EAAE,OAAO,CAAC;IACrC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,mGAAmG;IACnG,0BAA0B,EAAE,OAAO,CAAC;IACpC,4FAA4F;IAC5F,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,qBAAqB,EAAE,OAAO,CAAC;IAC/B,uBAAuB,EAAE,OAAO,CAAC;CAClC;AA8bD,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,UAAU,GAAG,SAAS,GAAG,SAAS,CAAC;IAC/C,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAC3C;AAED,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,cAAc,GAAG,SAAS,EAClC,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,YAAY,GAAG,IAAI,EACjC,kBAAkB,CAAC,EAAE,sBAAsB,EAAE,GAAG,IAAI,GACnD,aAAa,CAmEf"}
@@ -0,0 +1,448 @@
1
+ /**
2
+ * useTradeItemSpec — schema-gedreven afleiding van welke velden + verplichte
3
+ * karakteristieken Floriday voor een trade-item verwacht.
4
+ *
5
+ * Bron van waarheid: `POST /api/public/gateway/schema` met
6
+ * `resource: "trade-item"` (zie docs/floriday-integratie.md §14). Deze hook
7
+ * destilleert daaruit:
8
+ * - de lijst characteristic-codes (S62, …) inclusief listName voor /code-list
9
+ * - of botanicalName / countryOfOrigin verplicht zijn
10
+ * - of paspoort-stap nodig is (planten i.p.v. snijbloem)
11
+ *
12
+ * Per-VBN classificatie (vroegere `listClassifications`) wordt niet meer
13
+ * gebruikt — die Floriday-resource bestaat niet meer in Suppliers 2026v1.
14
+ */
15
+ import { useMemo } from "react";
16
+ const CHAR_CODE_RE = /^[A-Z]\d{1,4}$/;
17
+ function pickCharacteristicCode(name) {
18
+ if (!name)
19
+ return null;
20
+ const m1 = name.match(/^characteristics?\.([A-Z]\d{1,4})$/i);
21
+ if (m1)
22
+ return m1[1].toUpperCase();
23
+ const m2 = name.match(/^characteristics?\[([A-Z]\d{1,4})\]$/i);
24
+ if (m2)
25
+ return m2[1].toUpperCase();
26
+ if (CHAR_CODE_RE.test(name))
27
+ return name.toUpperCase();
28
+ return null;
29
+ }
30
+ function labelMapFromSchema(schema) {
31
+ const labelMap = new Map();
32
+ const charList = schema?.codeLists?.characteristic ?? [];
33
+ for (const c of charList) {
34
+ if (!c?.code)
35
+ continue;
36
+ const label = c.label ?? c.description ?? c.payload?.label ?? c.payload?.description ?? c.code;
37
+ labelMap.set(String(c.code).toUpperCase(), String(label));
38
+ }
39
+ return labelMap;
40
+ }
41
+ function buildCharacteristicValueIndex(schema) {
42
+ const index = new Map();
43
+ const list = schema?.codeLists?.["vbn-characteristic-value"] ?? [];
44
+ if (!Array.isArray(list))
45
+ return index;
46
+ for (const raw of list) {
47
+ if (!raw || typeof raw !== "object")
48
+ continue;
49
+ const item = raw;
50
+ const payload = (item.payload && typeof item.payload === "object" && item.payload) ||
51
+ (item.data && typeof item.data === "object" && item.data) ||
52
+ item;
53
+ // Groepskey: expliciete verwijzing naar de karakteristiek, of prefix
54
+ // uit de eigen code (bijv. "A01.02" of "A01-02" → "A01").
55
+ const explicitParent = payload.characteristic_code ??
56
+ payload.characteristicCode ??
57
+ payload.vbn_characteristic_code ??
58
+ payload.vbnCharacteristicCode ??
59
+ payload.parent_code ??
60
+ payload.parentCode ??
61
+ payload.feature_type_code ??
62
+ payload.featureTypeCode;
63
+ const ownCode = String(item.code ?? payload.code ?? payload.vbn_code ?? "").trim();
64
+ let parent = explicitParent ? pickCharacteristicCode(String(explicitParent)) : null;
65
+ if (!parent && ownCode) {
66
+ const m = ownCode.match(/^([A-Z]\d{1,4})[\.\-_]/i);
67
+ if (m)
68
+ parent = m[1].toUpperCase();
69
+ }
70
+ if (!parent)
71
+ continue;
72
+ const valueCode = String(payload.value_code ??
73
+ payload.valueCode ??
74
+ payload.value ??
75
+ ownCode ??
76
+ "").trim();
77
+ if (!valueCode)
78
+ continue;
79
+ const label = String(payload.description ??
80
+ payload.short_name ??
81
+ payload.label ??
82
+ item.description ??
83
+ item.label ??
84
+ valueCode);
85
+ const arr = index.get(parent) ?? [];
86
+ if (!arr.some((v) => v.code === valueCode)) {
87
+ arr.push({ code: valueCode, label });
88
+ }
89
+ index.set(parent, arr);
90
+ }
91
+ for (const arr of index.values()) {
92
+ arr.sort((a, b) => a.label.localeCompare(b.label, "nl"));
93
+ }
94
+ return index;
95
+ }
96
+ function hydrateLabels(characteristics, schema) {
97
+ const labelMap = labelMapFromSchema(schema);
98
+ const valueIndex = buildCharacteristicValueIndex(schema);
99
+ for (const entry of characteristics) {
100
+ const lbl = labelMap.get(entry.code);
101
+ if (lbl)
102
+ entry.label = lbl;
103
+ const vals = valueIndex.get(entry.code);
104
+ if (vals && vals.length > 0)
105
+ entry.allowedValues = vals;
106
+ }
107
+ return characteristics.sort((a, b) => {
108
+ if (a.required !== b.required)
109
+ return Number(b.required) - Number(a.required);
110
+ return a.code.localeCompare(b.code);
111
+ });
112
+ }
113
+ function deriveSchemaCharacteristics(schema) {
114
+ if (!schema)
115
+ return [];
116
+ const map = new Map();
117
+ const upsert = (code, required, listName) => {
118
+ const existing = map.get(code);
119
+ if (existing) {
120
+ if (required)
121
+ existing.required = true;
122
+ if (listName && !existing.listName)
123
+ existing.listName = listName;
124
+ return;
125
+ }
126
+ map.set(code, { code, label: code, required, listName: listName ?? code, allowedValues: [] });
127
+ };
128
+ // 1) Scan fields voor characteristic-veld(en).
129
+ for (const f of (schema.fields ?? [])) {
130
+ const direct = pickCharacteristicCode(f.name);
131
+ if (direct) {
132
+ upsert(direct, Boolean(f.required), direct);
133
+ continue;
134
+ }
135
+ if (/^characteristics$/i.test(f.name)) {
136
+ const codes = (f.enum ?? f.items?.enum ?? []);
137
+ for (const c of codes) {
138
+ if (CHAR_CODE_RE.test(c))
139
+ upsert(c.toUpperCase(), false, c.toUpperCase());
140
+ }
141
+ }
142
+ }
143
+ // 2) schema.enums kan characteristic-codes als sleutel hebben.
144
+ if (schema.enums) {
145
+ for (const key of Object.keys(schema.enums)) {
146
+ const code = pickCharacteristicCode(key);
147
+ if (code)
148
+ upsert(code, false, code);
149
+ }
150
+ }
151
+ // 3) requiredFields → markeer required + voeg toe als nog niet gezien.
152
+ for (const r of schema.requiredFields ?? []) {
153
+ const code = pickCharacteristicCode(r);
154
+ if (code)
155
+ upsert(code, true, code);
156
+ }
157
+ // 4) Rauw JSON-Schema (schema.schema.properties.characteristics.properties.<CODE>).
158
+ const raw = schema.schema;
159
+ const charProps = raw?.properties?.characteristics?.properties ??
160
+ raw?.properties?.characteristics?.items?.properties ??
161
+ null;
162
+ if (charProps && typeof charProps === "object") {
163
+ const requiredSet = new Set(Array.isArray(raw?.properties?.characteristics?.required)
164
+ ? raw.properties.characteristics.required
165
+ : []);
166
+ for (const [key, val] of Object.entries(charProps)) {
167
+ const code = pickCharacteristicCode(key);
168
+ if (!code)
169
+ continue;
170
+ const listName = (val && (val["x-listName"] || val.listName)) || code;
171
+ upsert(code, requiredSet.has(key), String(listName));
172
+ }
173
+ }
174
+ // 5) Top-level projectie schema.characteristics / schema.characteristicCodes.
175
+ const proj = schema.characteristics ?? schema.characteristicCodes;
176
+ if (Array.isArray(proj)) {
177
+ for (const item of proj) {
178
+ if (typeof item === "string") {
179
+ const code = pickCharacteristicCode(item);
180
+ if (code)
181
+ upsert(code, false, code);
182
+ }
183
+ else if (item && typeof item === "object") {
184
+ const code = pickCharacteristicCode(String(item.code ?? item.name ?? item.id ?? ""));
185
+ if (!code)
186
+ continue;
187
+ upsert(code, Boolean(item.required ?? item.isRequired ?? item.mandatory), String(item.listName ?? item.list ?? code));
188
+ }
189
+ }
190
+ }
191
+ return hydrateLabels(Array.from(map.values()), schema);
192
+ }
193
+ function isRecord(value) {
194
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
195
+ }
196
+ function norm(value) {
197
+ return String(value ?? "").trim().toUpperCase();
198
+ }
199
+ function productCodeOf(value) {
200
+ if (!isRecord(value))
201
+ return String(value ?? "").trim();
202
+ const payload = isRecord(value.payload) ? value.payload : isRecord(value.data) ? value.data : undefined;
203
+ const raw = value.code ??
204
+ value.id ??
205
+ value.vbnProductCode ??
206
+ value.productCode ??
207
+ value.vbnCode ??
208
+ value.record_id ??
209
+ payload?.code ??
210
+ payload?.id ??
211
+ payload?.vbnProductCode ??
212
+ payload?.productCode ??
213
+ payload?.vbnCode ??
214
+ payload?.record_id;
215
+ return String(raw ?? "").trim();
216
+ }
217
+ function payloadOf(value) {
218
+ if (!isRecord(value))
219
+ return {};
220
+ if (isRecord(value.payload))
221
+ return value.payload;
222
+ if (isRecord(value.data))
223
+ return value.data;
224
+ return value;
225
+ }
226
+ function firstArray(...values) {
227
+ for (const v of values)
228
+ if (Array.isArray(v))
229
+ return v;
230
+ return [];
231
+ }
232
+ function characteristicCodeOf(value) {
233
+ if (typeof value === "string" || typeof value === "number") {
234
+ return pickCharacteristicCode(String(value));
235
+ }
236
+ if (!isRecord(value))
237
+ return null;
238
+ return pickCharacteristicCode(String(value.vbnCode ??
239
+ value.featureTypeCode ??
240
+ value.characteristicCode ??
241
+ value.code ??
242
+ value.id ??
243
+ value.name ??
244
+ ""));
245
+ }
246
+ function characteristicLabelOf(value, code) {
247
+ if (!isRecord(value))
248
+ return code;
249
+ return String(value.label ??
250
+ value.description ??
251
+ value.name ??
252
+ value.featureType ??
253
+ value.title ??
254
+ code);
255
+ }
256
+ function deriveVbnProductCharacteristics(schema, vbnCode) {
257
+ const productList = schema?.codeLists?.["vbn-product-code"] ?? [];
258
+ const hasVbnProductCodeList = Array.isArray(productList) && productList.length > 0;
259
+ if (!vbnCode) {
260
+ return {
261
+ characteristics: [],
262
+ hasVbnProductCodeList,
263
+ matchedVbn: false,
264
+ usedFallback: false,
265
+ productGroupId: null,
266
+ };
267
+ }
268
+ const target = norm(vbnCode);
269
+ // VBN-productcode lookup: primair op `code` (5- of 6-cijferig). id/record_id zijn
270
+ // interne Floricode-ID's en matchen NIET de VBN-code.
271
+ const entry = hasVbnProductCodeList
272
+ ? productList.find((item) => {
273
+ if (!isRecord(item))
274
+ return false;
275
+ const payload = payloadOf(item);
276
+ const candidates = [
277
+ item.code,
278
+ payload.code,
279
+ payload.vbn_code,
280
+ payload.vbnCode,
281
+ ];
282
+ return candidates.some((c) => norm(c) === target);
283
+ })
284
+ : null;
285
+ const map = new Map();
286
+ const upsert = (item, forcedRequired) => {
287
+ const code = characteristicCodeOf(item);
288
+ if (!code)
289
+ return;
290
+ const existing = map.get(code);
291
+ const required = forcedRequired ?? Boolean(isRecord(item) && (item.isRequired ?? item.required ?? item.mandatory ?? item.isMandatory));
292
+ const listName = isRecord(item)
293
+ ? String(item.listName ?? item.list ?? item.codeList ?? item.valueList ?? code)
294
+ : code;
295
+ if (existing) {
296
+ if (required)
297
+ existing.required = true;
298
+ if (listName && !existing.listName)
299
+ existing.listName = listName;
300
+ return;
301
+ }
302
+ map.set(code, {
303
+ code,
304
+ label: characteristicLabelOf(item, code),
305
+ required,
306
+ listName: listName || code,
307
+ allowedValues: [],
308
+ });
309
+ };
310
+ let productGroupId = null;
311
+ if (entry) {
312
+ const payload = payloadOf(entry);
313
+ productGroupId = payload.product_group_id != null
314
+ ? String(payload.product_group_id)
315
+ : null;
316
+ const required = firstArray(payload.requiredCharacteristics, payload.mandatoryCharacteristics, payload.requiredFeatures, payload.mandatoryFeatures);
317
+ const allowed = firstArray(payload.allowedCharacteristics, payload.characteristics, payload.allowedFeatures, payload.features);
318
+ allowed.forEach((item) => upsert(item));
319
+ required.forEach((item) => upsert(item, true));
320
+ }
321
+ // Stap 2: karakteristieken van de bijbehorende productgroep (8-cijferig).
322
+ // De VBN-productcode-entry bevat zelden zelf `requiredCharacteristics`;
323
+ // die eisen hangen aan de productgroep waaraan de code gekoppeld is.
324
+ let groupMatched = false;
325
+ if (map.size === 0 && productGroupId) {
326
+ const groupList = schema?.codeLists?.["vbn-product-group"] ?? [];
327
+ if (Array.isArray(groupList)) {
328
+ const targetGroup = norm(productGroupId);
329
+ const groupEntry = groupList.find((item) => {
330
+ if (!isRecord(item))
331
+ return false;
332
+ const payload = payloadOf(item);
333
+ const candidates = [
334
+ item.code,
335
+ item.id,
336
+ payload.code,
337
+ payload.id,
338
+ payload.product_group_id,
339
+ payload.productGroupId,
340
+ ];
341
+ return candidates.some((c) => norm(c) === targetGroup);
342
+ });
343
+ if (groupEntry) {
344
+ groupMatched = true;
345
+ const payload = payloadOf(groupEntry);
346
+ const required = firstArray(payload.requiredCharacteristics, payload.mandatoryCharacteristics, payload.requiredFeatures, payload.mandatoryFeatures, payload.mandatory_feature_types, payload.required_feature_type_codes, payload.mandatoryFeatureTypes, payload.requiredFeatureTypeCodes);
347
+ const allowed = firstArray(payload.allowedCharacteristics, payload.characteristics, payload.allowedFeatures, payload.features, payload.allowed_feature_type_codes, payload.feature_types, payload.allowedFeatureTypeCodes, payload.featureTypes);
348
+ allowed.forEach((item) => upsert(item));
349
+ required.forEach((item) => upsert(item, true));
350
+ }
351
+ }
352
+ }
353
+ // Stap 3 fallback: zonder Linnaeus-mapping bestaat er nog geen koppeling
354
+ // productgroep → verplichte FeatureTypes. Toon dan alle vbn-characteristics
355
+ // als optioneel zodat de gebruiker kan publiceren.
356
+ let usedFallback = false;
357
+ if (map.size === 0) {
358
+ const charList = schema?.codeLists?.["vbn-characteristic"] ?? [];
359
+ if (Array.isArray(charList) && charList.length > 0) {
360
+ usedFallback = true;
361
+ for (const item of charList) {
362
+ if (!isRecord(item))
363
+ continue;
364
+ const payload = payloadOf(item);
365
+ const code = pickCharacteristicCode(String(item.code ?? payload.code ?? payload.vbn_code ?? ""));
366
+ if (!code)
367
+ continue;
368
+ const label = payload.description ??
369
+ payload.short_name ??
370
+ item.description ??
371
+ item.label ??
372
+ code;
373
+ map.set(code, {
374
+ code,
375
+ label: String(label),
376
+ required: false,
377
+ listName: code,
378
+ allowedValues: [],
379
+ });
380
+ }
381
+ }
382
+ }
383
+ void groupMatched;
384
+ return {
385
+ characteristics: hydrateLabels(Array.from(map.values()), schema),
386
+ hasVbnProductCodeList,
387
+ matchedVbn: Boolean(entry),
388
+ usedFallback,
389
+ productGroupId,
390
+ };
391
+ }
392
+ export function useTradeItemSpec(schema, vbnCode, productGroup, regulatoryFeatures) {
393
+ return useMemo(() => {
394
+ const fromVbn = deriveVbnProductCharacteristics(schema, vbnCode);
395
+ const schemaCharacteristics = deriveSchemaCharacteristics(schema);
396
+ // Autoritatieve bron: RegulatoryFeatureType uit floricode_records.
397
+ // Als deze data beschikbaar is, gebruik hem — verplicht + advies tellen
398
+ // beide mee als "zichtbaar in de eigenschappen-stap"; alleen `required`
399
+ // markeert publish-validatie.
400
+ let fromRegulatory = [];
401
+ if (regulatoryFeatures && regulatoryFeatures.length > 0) {
402
+ fromRegulatory = regulatoryFeatures.map((f) => ({
403
+ code: f.code,
404
+ label: f.label,
405
+ required: f.regulation === "required",
406
+ listName: f.code,
407
+ allowedValues: f.values,
408
+ }));
409
+ }
410
+ const characteristics = fromRegulatory.length
411
+ ? fromRegulatory
412
+ : fromVbn.characteristics.length
413
+ ? fromVbn.characteristics
414
+ : schemaCharacteristics;
415
+ const requiredCharacteristicCodes = new Set(characteristics.filter((c) => c.required).map((c) => c.code));
416
+ const required = new Set((schema?.requiredFields ?? []).map((f) => f));
417
+ const canDetermineCharacteristics = !vbnCode
418
+ ? true
419
+ : characteristics.length > 0;
420
+ const characteristicIssue = !vbnCode || characteristics.length > 0
421
+ ? undefined
422
+ : !fromVbn.hasVbnProductCodeList
423
+ ? "codeLists['vbn-product-code'] ontbreekt in de schema-response"
424
+ : !fromVbn.matchedVbn
425
+ ? `VBN-code ${vbnCode} ontbreekt in codeLists['vbn-product-code']`
426
+ : fromVbn.productGroupId
427
+ ? `Productgroep ${fromVbn.productGroupId} (VBN-code ${vbnCode}) bevat geen karakteristiek-classificatie`
428
+ : `Voor VBN-code ${vbnCode} bevat de code-list geen karakteristiek-classificatie`;
429
+ const requiresBotanicalName = required.has("botanicalName") ||
430
+ required.has("botanicalNames") ||
431
+ (productGroup !== null && productGroup !== "CUT_FLOWER");
432
+ const requiresCountryOfOrigin = required.has("countryOfOrigin") ||
433
+ (productGroup !== null && productGroup !== "CUT_FLOWER");
434
+ return {
435
+ vbnCode,
436
+ productGroup,
437
+ characteristics,
438
+ requiredCharacteristicCodes,
439
+ canDetermineCharacteristics,
440
+ characteristicIssue,
441
+ // Alleen fallback als we noch regulatory, noch VBN-code data, noch schema-derived hadden.
442
+ characteristicsAreFallback: fromRegulatory.length === 0 && fromVbn.usedFallback,
443
+ productGroupId: fromVbn.productGroupId,
444
+ requiresBotanicalName,
445
+ requiresCountryOfOrigin,
446
+ };
447
+ }, [schema, vbnCode, productGroup, regulatoryFeatures]);
448
+ }
@@ -0,0 +1,19 @@
1
+ export interface TradeItemStockRow {
2
+ physical_stock: number;
3
+ minimum_stock: number;
4
+ reserved_stock: number;
5
+ incoming_stock: number;
6
+ notes: string | null;
7
+ updated_at?: string;
8
+ }
9
+ export declare function stockKeyFor(params: {
10
+ tradeItemId?: string | null;
11
+ draftId?: string | null;
12
+ }): string | null;
13
+ export declare function useTradeItemStock(connectionId: string | null, key: string | null): {
14
+ row: TradeItemStockRow;
15
+ loading: boolean;
16
+ save: (values: TradeItemStockRow) => Promise<void>;
17
+ };
18
+ export declare function fetchStockMap(connectionId: string): Promise<Map<string, TradeItemStockRow>>;
19
+ //# sourceMappingURL=useTradeItemStock.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useTradeItemStock.d.ts","sourceRoot":"","sources":["../../src/hooks/useTradeItemStock.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,iBAAiB;IAChC,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAUD,wBAAgB,WAAW,CAAC,MAAM,EAAE;IAClC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB,GAAG,MAAM,GAAG,IAAI,CAIhB;AAED,wBAAgB,iBAAiB,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI;;;mBA4B9D,iBAAiB;EA2BnC;AAED,wBAAsB,aAAa,CACjC,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAUzC"}
@@ -0,0 +1,78 @@
1
+ import { useCallback, useEffect, useState } from "react";
2
+ import { supabase } from "../integrations/supabase/client";
3
+ const EMPTY = {
4
+ physical_stock: 0,
5
+ minimum_stock: 0,
6
+ reserved_stock: 0,
7
+ incoming_stock: 0,
8
+ notes: null,
9
+ };
10
+ export function stockKeyFor(params) {
11
+ if (params.tradeItemId)
12
+ return params.tradeItemId;
13
+ if (params.draftId)
14
+ return `draft:${params.draftId}`;
15
+ return null;
16
+ }
17
+ export function useTradeItemStock(connectionId, key) {
18
+ const [row, setRow] = useState(null);
19
+ const [loading, setLoading] = useState(false);
20
+ useEffect(() => {
21
+ if (!connectionId || !key) {
22
+ setRow(null);
23
+ return;
24
+ }
25
+ let cancelled = false;
26
+ setLoading(true);
27
+ (async () => {
28
+ const { data } = await supabase
29
+ .from("trade_item_stock")
30
+ .select("physical_stock,minimum_stock,reserved_stock,incoming_stock,notes,updated_at")
31
+ .eq("connection_id", connectionId)
32
+ .eq("trade_item_key", key)
33
+ .maybeSingle();
34
+ if (cancelled)
35
+ return;
36
+ setRow(data ?? { ...EMPTY });
37
+ setLoading(false);
38
+ })();
39
+ return () => {
40
+ cancelled = true;
41
+ };
42
+ }, [connectionId, key]);
43
+ const save = useCallback(async (values) => {
44
+ if (!connectionId || !key)
45
+ throw new Error("Ontbrekende connectie of artikel-key");
46
+ const userRes = await supabase.auth.getUser();
47
+ const userId = userRes.data.user?.id;
48
+ if (!userId)
49
+ throw new Error("Niet ingelogd");
50
+ const { error } = await supabase
51
+ .from("trade_item_stock")
52
+ .upsert({
53
+ user_id: userId,
54
+ connection_id: connectionId,
55
+ trade_item_key: key,
56
+ physical_stock: values.physical_stock,
57
+ minimum_stock: values.minimum_stock,
58
+ reserved_stock: values.reserved_stock,
59
+ incoming_stock: values.incoming_stock,
60
+ notes: values.notes,
61
+ }, { onConflict: "connection_id,trade_item_key" });
62
+ if (error)
63
+ throw new Error(error.message);
64
+ setRow(values);
65
+ }, [connectionId, key]);
66
+ return { row: row ?? EMPTY, loading, save };
67
+ }
68
+ export async function fetchStockMap(connectionId) {
69
+ const { data } = await supabase
70
+ .from("trade_item_stock")
71
+ .select("trade_item_key,physical_stock,minimum_stock,reserved_stock,incoming_stock,notes,updated_at")
72
+ .eq("connection_id", connectionId);
73
+ const map = new Map();
74
+ for (const r of (data ?? [])) {
75
+ map.set(r.trade_item_key, r);
76
+ }
77
+ return map;
78
+ }
@@ -0,0 +1,3 @@
1
+ import { type GetRequiredFeaturesResult } from "../lib/floricode-required-features.functions";
2
+ export declare function useVbnRegulatoryFeatures(vbnCode: string | null | undefined): import("@tanstack/react-query").UseQueryResult<GetRequiredFeaturesResult, Error>;
3
+ //# sourceMappingURL=useVbnRegulatoryFeatures.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useVbnRegulatoryFeatures.d.ts","sourceRoot":"","sources":["../../src/hooks/useVbnRegulatoryFeatures.ts"],"names":[],"mappings":"AAQA,OAAO,EAEL,KAAK,yBAAyB,EAC/B,MAAM,8CAA8C,CAAC;AAEtD,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,oFAS1E"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Autoritatieve verplichte/geadviseerde karakteristieken per VBN-productcode,
3
+ * gelezen uit `floricode_records.RegulatoryFeatureType`.
4
+ *
5
+ * Vervangt de vroegere afleiding uit `codeLists['vbn-product-code']` /
6
+ * `vbn-product-group`, die deze koppeling niet bevat.
7
+ */
8
+ import { useQuery } from "@tanstack/react-query";
9
+ import { getVbnRequiredFeatures, } from "../lib/floricode-required-features.functions";
10
+ export function useVbnRegulatoryFeatures(vbnCode) {
11
+ const code = String(vbnCode ?? "").trim();
12
+ const enabled = /^\d{5,6}$/.test(code);
13
+ return useQuery({
14
+ queryKey: ["vbn-regulatory-features", code],
15
+ queryFn: () => getVbnRequiredFeatures({ data: { vbnCode: code } }),
16
+ enabled,
17
+ staleTime: 60 * 60 * 1000,
18
+ });
19
+ }
@@ -1,4 +1,10 @@
1
1
  import { type TradeItem } from "../lib/floriday-client";
2
+ /**
3
+ * Reads trade items from the shared `floriday_trade_items_cache` (gepubliceerd,
4
+ * gevuld door de Auth-module elke 5 min) en merget lokale, niet-gepubliceerde
5
+ * producten uit `trade_item_drafts`. Voorraadwaarden uit `trade_item_stock`
6
+ * overschrijven de cache-waarden (lokale bron van waarheid voor stock).
7
+ */
2
8
  export declare function useVoorraadData(connectionId: string | null): {
3
9
  items: TradeItem[];
4
10
  loading: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"useVoorraadData.d.ts","sourceRoot":"","sources":["../../src/hooks/useVoorraadData.ts"],"names":[],"mappings":"AACA,OAAO,EAAY,KAAK,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAElE,wBAAgB,eAAe,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI;;;;;EA4B1D"}
1
+ {"version":3,"file":"useVoorraadData.d.ts","sourceRoot":"","sources":["../../src/hooks/useVoorraadData.ts"],"names":[],"mappings":"AAEA,OAAO,EAAsB,KAAK,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAoB5E;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI;;;;;EAkE1D"}