@flowselections/floriday-voorraad 1.0.13 → 1.0.15

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 (114) 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/EditArtikelPage.d.ts +4 -0
  6. package/dist-lib/components/voorraad/EditArtikelPage.d.ts.map +1 -0
  7. package/dist-lib/components/voorraad/EditArtikelPage.js +51 -0
  8. package/dist-lib/components/voorraad/NieuwArtikelPage.d.ts +2 -0
  9. package/dist-lib/components/voorraad/NieuwArtikelPage.d.ts.map +1 -0
  10. package/dist-lib/components/voorraad/NieuwArtikelPage.js +5 -0
  11. package/dist-lib/components/voorraad/StockBulkButtons.d.ts +7 -0
  12. package/dist-lib/components/voorraad/StockBulkButtons.d.ts.map +1 -0
  13. package/dist-lib/components/voorraad/StockBulkButtons.js +356 -0
  14. package/dist-lib/components/voorraad/ValidationOverviewTable.d.ts +7 -0
  15. package/dist-lib/components/voorraad/ValidationOverviewTable.d.ts.map +1 -0
  16. package/dist-lib/components/voorraad/ValidationOverviewTable.js +87 -0
  17. package/dist-lib/components/voorraad/ValidationPage.d.ts +2 -0
  18. package/dist-lib/components/voorraad/ValidationPage.d.ts.map +1 -0
  19. package/dist-lib/components/voorraad/ValidationPage.js +12 -0
  20. package/dist-lib/components/voorraad/VoorraadDashboard.d.ts +6 -0
  21. package/dist-lib/components/voorraad/VoorraadDashboard.d.ts.map +1 -0
  22. package/dist-lib/components/voorraad/VoorraadDashboard.js +42 -0
  23. package/dist-lib/components/voorraad/VoorraadPage.d.ts.map +1 -1
  24. package/dist-lib/components/voorraad/VoorraadPage.js +70 -11
  25. package/dist-lib/components/voorraad/VoorraadPagination.d.ts +10 -0
  26. package/dist-lib/components/voorraad/VoorraadPagination.d.ts.map +1 -0
  27. package/dist-lib/components/voorraad/VoorraadPagination.js +12 -0
  28. package/dist-lib/components/voorraad/VoorraadStockCard.d.ts +8 -0
  29. package/dist-lib/components/voorraad/VoorraadStockCard.d.ts.map +1 -0
  30. package/dist-lib/components/voorraad/VoorraadStockCard.js +33 -0
  31. package/dist-lib/components/voorraad/VoorraadTable.d.ts.map +1 -1
  32. package/dist-lib/components/voorraad/VoorraadTable.js +16 -5
  33. package/dist-lib/hooks/useFloriday.d.ts +55 -0
  34. package/dist-lib/hooks/useFloriday.d.ts.map +1 -0
  35. package/dist-lib/hooks/useFloriday.js +298 -0
  36. package/dist-lib/hooks/useLocalTradeItems.d.ts +17 -1
  37. package/dist-lib/hooks/useLocalTradeItems.d.ts.map +1 -1
  38. package/dist-lib/hooks/useLocalTradeItems.js +54 -10
  39. package/dist-lib/hooks/useTradeItemSpec.d.ts +42 -0
  40. package/dist-lib/hooks/useTradeItemSpec.d.ts.map +1 -0
  41. package/dist-lib/hooks/useTradeItemSpec.js +448 -0
  42. package/dist-lib/hooks/useTradeItemStock.d.ts +19 -0
  43. package/dist-lib/hooks/useTradeItemStock.d.ts.map +1 -0
  44. package/dist-lib/hooks/useTradeItemStock.js +78 -0
  45. package/dist-lib/hooks/useVbnRegulatoryFeatures.d.ts +3 -0
  46. package/dist-lib/hooks/useVbnRegulatoryFeatures.d.ts.map +1 -0
  47. package/dist-lib/hooks/useVbnRegulatoryFeatures.js +19 -0
  48. package/dist-lib/hooks/useVoorraadData.d.ts +2 -1
  49. package/dist-lib/hooks/useVoorraadData.d.ts.map +1 -1
  50. package/dist-lib/hooks/useVoorraadData.js +39 -11
  51. package/dist-lib/index.d.ts +4 -0
  52. package/dist-lib/index.d.ts.map +1 -1
  53. package/dist-lib/index.js +6 -5
  54. package/dist-lib/integrations/supabase/auth-middleware.d.ts +8346 -3367
  55. package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
  56. package/dist-lib/integrations/supabase/client.d.ts +8346 -3367
  57. package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
  58. package/dist-lib/integrations/supabase/client.server.d.ts +8346 -3367
  59. package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
  60. package/dist-lib/integrations/supabase/types.d.ts +8460 -3341
  61. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  62. package/dist-lib/integrations/supabase/types.js +10 -1
  63. package/dist-lib/lib/__tests__/floricode-required-features.test.d.ts +2 -0
  64. package/dist-lib/lib/__tests__/floricode-required-features.test.d.ts.map +1 -0
  65. package/dist-lib/lib/__tests__/floricode-required-features.test.js +186 -0
  66. package/dist-lib/lib/floricode-client.d.ts +15 -1
  67. package/dist-lib/lib/floricode-client.d.ts.map +1 -1
  68. package/dist-lib/lib/floricode-client.js +42 -4
  69. package/dist-lib/lib/floricode-required-features.functions.d.ts +10026 -0
  70. package/dist-lib/lib/floricode-required-features.functions.d.ts.map +1 -0
  71. package/dist-lib/lib/floricode-required-features.functions.js +170 -0
  72. package/dist-lib/lib/floriday-client.d.ts +36 -8
  73. package/dist-lib/lib/floriday-client.d.ts.map +1 -1
  74. package/dist-lib/lib/floriday-client.js +206 -25
  75. package/dist-lib/lib/floriday-env.d.ts +29 -0
  76. package/dist-lib/lib/floriday-env.d.ts.map +1 -0
  77. package/dist-lib/lib/floriday-env.js +42 -0
  78. package/dist-lib/lib/floriday-env.server.d.ts +15 -0
  79. package/dist-lib/lib/floriday-env.server.d.ts.map +1 -0
  80. package/dist-lib/lib/floriday-env.server.js +13 -0
  81. package/dist-lib/lib/floriday-field-sources.d.ts +18 -0
  82. package/dist-lib/lib/floriday-field-sources.d.ts.map +1 -0
  83. package/dist-lib/lib/floriday-field-sources.js +45 -0
  84. package/dist-lib/lib/floriday-gateway-entities.d.ts +44 -0
  85. package/dist-lib/lib/floriday-gateway-entities.d.ts.map +1 -0
  86. package/dist-lib/lib/floriday-gateway-entities.js +51 -0
  87. package/dist-lib/lib/floriday-gateway.functions.d.ts +39999 -0
  88. package/dist-lib/lib/floriday-gateway.functions.d.ts.map +1 -0
  89. package/dist-lib/lib/floriday-gateway.functions.js +68 -0
  90. package/dist-lib/lib/floriday-gateway.server.d.ts +122 -0
  91. package/dist-lib/lib/floriday-gateway.server.d.ts.map +1 -0
  92. package/dist-lib/lib/floriday-gateway.server.js +230 -0
  93. package/dist-lib/lib/floriday-import-validator.d.ts +26 -0
  94. package/dist-lib/lib/floriday-import-validator.d.ts.map +1 -0
  95. package/dist-lib/lib/floriday-import-validator.js +41 -0
  96. package/dist-lib/lib/floriday-payload.d.ts +47 -3
  97. package/dist-lib/lib/floriday-payload.d.ts.map +1 -1
  98. package/dist-lib/lib/floriday-payload.js +179 -7
  99. package/dist-lib/lib/floriday-schema-to-zod.d.ts +9 -0
  100. package/dist-lib/lib/floriday-schema-to-zod.d.ts.map +1 -0
  101. package/dist-lib/lib/floriday-schema-to-zod.js +53 -0
  102. package/dist-lib/lib/floriday-writes.functions.d.ts +19998 -0
  103. package/dist-lib/lib/floriday-writes.functions.d.ts.map +1 -0
  104. package/dist-lib/lib/floriday-writes.functions.js +74 -0
  105. package/dist-lib/lib/image-resize.d.ts +43 -0
  106. package/dist-lib/lib/image-resize.d.ts.map +1 -0
  107. package/dist-lib/lib/image-resize.js +212 -0
  108. package/dist-lib/lib/normalize-errors-middleware.d.ts +2 -0
  109. package/dist-lib/lib/normalize-errors-middleware.d.ts.map +1 -0
  110. package/dist-lib/lib/normalize-errors-middleware.js +24 -0
  111. package/dist-lib/lib/wizard-step-fields.d.ts +15 -0
  112. package/dist-lib/lib/wizard-step-fields.d.ts.map +1 -0
  113. package/dist-lib/lib/wizard-step-fields.js +51 -0
  114. package/package.json +35 -14
@@ -18,8 +18,14 @@ function draftToTradeItem(row) {
18
18
  economicQuantity: 0,
19
19
  raw: row,
20
20
  published: false,
21
+ linkStatus: row.floriday_link_status ?? "unlinked",
22
+ missingFields: row.missing_required_fields ?? [],
23
+ linkError: row.floriday_link_error ?? null,
24
+ lastSyncAt: row.last_floriday_sync_at ?? null,
25
+ floridayId: row.floriday_id ?? null,
21
26
  };
22
27
  }
28
+ const SELECT_COLS = "id,connection_id,data,updated_at,created_at,floriday_link_status,floriday_id,floriday_link_error,missing_required_fields,last_floriday_sync_at,source";
23
29
  export function useLocalTradeItems(connectionId) {
24
30
  const [items, setItems] = useState([]);
25
31
  const [loading, setLoading] = useState(false);
@@ -32,7 +38,7 @@ export function useLocalTradeItems(connectionId) {
32
38
  try {
33
39
  const { data, error } = await supabase
34
40
  .from("trade_item_drafts")
35
- .select("id,connection_id,data,updated_at,created_at")
41
+ .select(SELECT_COLS)
36
42
  .eq("connection_id", connectionId)
37
43
  .order("updated_at", { ascending: false })
38
44
  .limit(1000);
@@ -55,7 +61,7 @@ export function useLocalTradeItems(connectionId) {
55
61
  export async function getLocalTradeItem(id) {
56
62
  const { data, error } = await supabase
57
63
  .from("trade_item_drafts")
58
- .select("id,connection_id,data,updated_at,created_at")
64
+ .select(SELECT_COLS)
59
65
  .eq("id", id)
60
66
  .maybeSingle();
61
67
  if (error)
@@ -67,32 +73,70 @@ export async function upsertLocalTradeItem(params) {
67
73
  const userId = userRes.data.user?.id;
68
74
  if (!userId)
69
75
  throw new Error("Niet ingelogd");
76
+ const metaRow = {};
77
+ const m = params.meta;
78
+ if (m) {
79
+ if (m.floridayLinkStatus !== undefined)
80
+ metaRow.floriday_link_status = m.floridayLinkStatus;
81
+ if (m.floridayId !== undefined)
82
+ metaRow.floriday_id = m.floridayId;
83
+ if (m.floridayLinkError !== undefined)
84
+ metaRow.floriday_link_error = m.floridayLinkError;
85
+ if (m.missingRequiredFields !== undefined)
86
+ metaRow.missing_required_fields = m.missingRequiredFields;
87
+ if (m.lastFloridaySyncAt !== undefined)
88
+ metaRow.last_floriday_sync_at = m.lastFloridaySyncAt;
89
+ if (m.source !== undefined)
90
+ metaRow.source = m.source;
91
+ }
70
92
  if (params.id) {
71
- const { error } = await supabase
72
- .from("trade_item_drafts")
73
- .update({
93
+ const patch = {
74
94
  data: params.data,
75
95
  connection_id: params.connectionId,
76
96
  updated_at: new Date().toISOString(),
77
- })
97
+ ...metaRow,
98
+ };
99
+ const { error } = await supabase
100
+ .from("trade_item_drafts")
101
+ .update(patch)
78
102
  .eq("id", params.id);
79
103
  if (error)
80
104
  throw new Error(error.message);
81
105
  return params.id;
82
106
  }
83
- const { data, error } = await supabase
84
- .from("trade_item_drafts")
85
- .insert({
107
+ const insertRow = {
86
108
  user_id: userId,
87
109
  connection_id: params.connectionId,
88
110
  data: params.data,
89
- })
111
+ ...metaRow,
112
+ };
113
+ const { data, error } = await supabase
114
+ .from("trade_item_drafts")
115
+ .insert(insertRow)
90
116
  .select("id")
91
117
  .single();
92
118
  if (error)
93
119
  throw new Error(error.message);
94
120
  return data.id;
95
121
  }
122
+ export async function updateDraftMeta(id, meta) {
123
+ const row = {};
124
+ if (meta.floridayLinkStatus !== undefined)
125
+ row.floriday_link_status = meta.floridayLinkStatus;
126
+ if (meta.floridayId !== undefined)
127
+ row.floriday_id = meta.floridayId;
128
+ if (meta.floridayLinkError !== undefined)
129
+ row.floriday_link_error = meta.floridayLinkError;
130
+ if (meta.missingRequiredFields !== undefined)
131
+ row.missing_required_fields = meta.missingRequiredFields;
132
+ if (meta.lastFloridaySyncAt !== undefined)
133
+ row.last_floriday_sync_at = meta.lastFloridaySyncAt;
134
+ if (Object.keys(row).length === 0)
135
+ return;
136
+ const { error } = await supabase.from("trade_item_drafts").update(row).eq("id", id);
137
+ if (error)
138
+ throw new Error(error.message);
139
+ }
96
140
  export async function deleteLocalTradeItem(id) {
97
141
  const { error } = await supabase.from("trade_item_drafts").delete().eq("id", id);
98
142
  if (error)
@@ -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"}