@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.
- package/dist-lib/assets/floriday-logo.png.asset.json +11 -0
- package/dist-lib/components/voorraad/ArtikelWizard.d.ts +5 -0
- package/dist-lib/components/voorraad/ArtikelWizard.d.ts.map +1 -0
- package/dist-lib/components/voorraad/ArtikelWizard.js +1229 -0
- package/dist-lib/components/voorraad/EditArtikelPage.d.ts +4 -0
- package/dist-lib/components/voorraad/EditArtikelPage.d.ts.map +1 -0
- package/dist-lib/components/voorraad/EditArtikelPage.js +51 -0
- package/dist-lib/components/voorraad/NieuwArtikelPage.d.ts +2 -0
- package/dist-lib/components/voorraad/NieuwArtikelPage.d.ts.map +1 -0
- package/dist-lib/components/voorraad/NieuwArtikelPage.js +5 -0
- package/dist-lib/components/voorraad/StockBulkButtons.d.ts +7 -0
- package/dist-lib/components/voorraad/StockBulkButtons.d.ts.map +1 -0
- package/dist-lib/components/voorraad/StockBulkButtons.js +356 -0
- package/dist-lib/components/voorraad/ValidationOverviewTable.d.ts +7 -0
- package/dist-lib/components/voorraad/ValidationOverviewTable.d.ts.map +1 -0
- package/dist-lib/components/voorraad/ValidationOverviewTable.js +87 -0
- package/dist-lib/components/voorraad/ValidationPage.d.ts +2 -0
- package/dist-lib/components/voorraad/ValidationPage.d.ts.map +1 -0
- package/dist-lib/components/voorraad/ValidationPage.js +12 -0
- package/dist-lib/components/voorraad/VoorraadDashboard.d.ts +6 -0
- package/dist-lib/components/voorraad/VoorraadDashboard.d.ts.map +1 -0
- package/dist-lib/components/voorraad/VoorraadDashboard.js +42 -0
- package/dist-lib/components/voorraad/VoorraadPage.d.ts.map +1 -1
- package/dist-lib/components/voorraad/VoorraadPage.js +70 -11
- package/dist-lib/components/voorraad/VoorraadPagination.d.ts +10 -0
- package/dist-lib/components/voorraad/VoorraadPagination.d.ts.map +1 -0
- package/dist-lib/components/voorraad/VoorraadPagination.js +12 -0
- package/dist-lib/components/voorraad/VoorraadStockCard.d.ts +8 -0
- package/dist-lib/components/voorraad/VoorraadStockCard.d.ts.map +1 -0
- package/dist-lib/components/voorraad/VoorraadStockCard.js +33 -0
- package/dist-lib/components/voorraad/VoorraadTable.d.ts.map +1 -1
- package/dist-lib/components/voorraad/VoorraadTable.js +16 -5
- package/dist-lib/hooks/useFloriday.d.ts +55 -0
- package/dist-lib/hooks/useFloriday.d.ts.map +1 -0
- package/dist-lib/hooks/useFloriday.js +298 -0
- package/dist-lib/hooks/useLocalTradeItems.d.ts +17 -1
- package/dist-lib/hooks/useLocalTradeItems.d.ts.map +1 -1
- package/dist-lib/hooks/useLocalTradeItems.js +54 -10
- package/dist-lib/hooks/useTradeItemSpec.d.ts +42 -0
- package/dist-lib/hooks/useTradeItemSpec.d.ts.map +1 -0
- package/dist-lib/hooks/useTradeItemSpec.js +448 -0
- package/dist-lib/hooks/useTradeItemStock.d.ts +19 -0
- package/dist-lib/hooks/useTradeItemStock.d.ts.map +1 -0
- package/dist-lib/hooks/useTradeItemStock.js +78 -0
- package/dist-lib/hooks/useVbnRegulatoryFeatures.d.ts +3 -0
- package/dist-lib/hooks/useVbnRegulatoryFeatures.d.ts.map +1 -0
- package/dist-lib/hooks/useVbnRegulatoryFeatures.js +19 -0
- package/dist-lib/hooks/useVoorraadData.d.ts +2 -1
- package/dist-lib/hooks/useVoorraadData.d.ts.map +1 -1
- package/dist-lib/hooks/useVoorraadData.js +39 -11
- package/dist-lib/index.d.ts +4 -0
- package/dist-lib/index.d.ts.map +1 -1
- package/dist-lib/index.js +6 -5
- package/dist-lib/integrations/supabase/auth-middleware.d.ts +8346 -3367
- package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.d.ts +8346 -3367
- package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.server.d.ts +8346 -3367
- package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.d.ts +8460 -3341
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.js +10 -1
- package/dist-lib/lib/__tests__/floricode-required-features.test.d.ts +2 -0
- package/dist-lib/lib/__tests__/floricode-required-features.test.d.ts.map +1 -0
- package/dist-lib/lib/__tests__/floricode-required-features.test.js +186 -0
- package/dist-lib/lib/floricode-client.d.ts +15 -1
- package/dist-lib/lib/floricode-client.d.ts.map +1 -1
- package/dist-lib/lib/floricode-client.js +42 -4
- package/dist-lib/lib/floricode-required-features.functions.d.ts +10026 -0
- package/dist-lib/lib/floricode-required-features.functions.d.ts.map +1 -0
- package/dist-lib/lib/floricode-required-features.functions.js +170 -0
- package/dist-lib/lib/floriday-client.d.ts +36 -8
- package/dist-lib/lib/floriday-client.d.ts.map +1 -1
- package/dist-lib/lib/floriday-client.js +206 -25
- package/dist-lib/lib/floriday-env.d.ts +29 -0
- package/dist-lib/lib/floriday-env.d.ts.map +1 -0
- package/dist-lib/lib/floriday-env.js +42 -0
- package/dist-lib/lib/floriday-env.server.d.ts +15 -0
- package/dist-lib/lib/floriday-env.server.d.ts.map +1 -0
- package/dist-lib/lib/floriday-env.server.js +13 -0
- package/dist-lib/lib/floriday-field-sources.d.ts +18 -0
- package/dist-lib/lib/floriday-field-sources.d.ts.map +1 -0
- package/dist-lib/lib/floriday-field-sources.js +45 -0
- package/dist-lib/lib/floriday-gateway-entities.d.ts +44 -0
- package/dist-lib/lib/floriday-gateway-entities.d.ts.map +1 -0
- package/dist-lib/lib/floriday-gateway-entities.js +51 -0
- package/dist-lib/lib/floriday-gateway.functions.d.ts +39999 -0
- package/dist-lib/lib/floriday-gateway.functions.d.ts.map +1 -0
- package/dist-lib/lib/floriday-gateway.functions.js +68 -0
- package/dist-lib/lib/floriday-gateway.server.d.ts +122 -0
- package/dist-lib/lib/floriday-gateway.server.d.ts.map +1 -0
- package/dist-lib/lib/floriday-gateway.server.js +230 -0
- package/dist-lib/lib/floriday-import-validator.d.ts +26 -0
- package/dist-lib/lib/floriday-import-validator.d.ts.map +1 -0
- package/dist-lib/lib/floriday-import-validator.js +41 -0
- package/dist-lib/lib/floriday-payload.d.ts +47 -3
- package/dist-lib/lib/floriday-payload.d.ts.map +1 -1
- package/dist-lib/lib/floriday-payload.js +179 -7
- package/dist-lib/lib/floriday-schema-to-zod.d.ts +9 -0
- package/dist-lib/lib/floriday-schema-to-zod.d.ts.map +1 -0
- package/dist-lib/lib/floriday-schema-to-zod.js +53 -0
- package/dist-lib/lib/floriday-writes.functions.d.ts +19998 -0
- package/dist-lib/lib/floriday-writes.functions.d.ts.map +1 -0
- package/dist-lib/lib/floriday-writes.functions.js +74 -0
- package/dist-lib/lib/image-resize.d.ts +43 -0
- package/dist-lib/lib/image-resize.d.ts.map +1 -0
- package/dist-lib/lib/image-resize.js +212 -0
- package/dist-lib/lib/normalize-errors-middleware.d.ts +2 -0
- package/dist-lib/lib/normalize-errors-middleware.d.ts.map +1 -0
- package/dist-lib/lib/normalize-errors-middleware.js +24 -0
- package/dist-lib/lib/wizard-step-fields.d.ts +15 -0
- package/dist-lib/lib/wizard-step-fields.d.ts.map +1 -0
- package/dist-lib/lib/wizard-step-fields.js +51 -0
- 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(
|
|
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(
|
|
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
|
|
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
|
|
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"}
|