@open-mercato/core 0.4.5-develop-f4858e0ef3 → 0.4.5-develop-4849712ccb
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/generated/entities/catalog_product/index.js +16 -0
- package/dist/generated/entities/catalog_product/index.js.map +2 -2
- package/dist/generated/entities/catalog_product_unit_conversion/index.js +27 -0
- package/dist/generated/entities/catalog_product_unit_conversion/index.js.map +7 -0
- package/dist/generated/entities/sales_credit_memo_line/index.js +7 -1
- package/dist/generated/entities/sales_credit_memo_line/index.js.map +2 -2
- package/dist/generated/entities/sales_invoice_line/index.js +7 -1
- package/dist/generated/entities/sales_invoice_line/index.js.map +2 -2
- package/dist/generated/entities/sales_order_line/index.js +6 -0
- package/dist/generated/entities/sales_order_line/index.js.map +2 -2
- package/dist/generated/entities/sales_quote_line/index.js +6 -0
- package/dist/generated/entities/sales_quote_line/index.js.map +2 -2
- package/dist/generated/entities.ids.generated.js +1 -0
- package/dist/generated/entities.ids.generated.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +2 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/modules/catalog/api/prices/route.js +123 -8
- package/dist/modules/catalog/api/prices/route.js.map +2 -2
- package/dist/modules/catalog/api/product-unit-conversions/route.js +194 -0
- package/dist/modules/catalog/api/product-unit-conversions/route.js.map +7 -0
- package/dist/modules/catalog/api/products/route.js +351 -201
- package/dist/modules/catalog/api/products/route.js.map +2 -2
- package/dist/modules/catalog/backend/catalog/products/[id]/page.js +1267 -497
- package/dist/modules/catalog/backend/catalog/products/[id]/page.js.map +2 -2
- package/dist/modules/catalog/backend/catalog/products/create/page.js +733 -210
- package/dist/modules/catalog/backend/catalog/products/create/page.js.map +2 -2
- package/dist/modules/catalog/commands/index.js +1 -0
- package/dist/modules/catalog/commands/index.js.map +2 -2
- package/dist/modules/catalog/commands/productUnitConversions.js +503 -0
- package/dist/modules/catalog/commands/productUnitConversions.js.map +7 -0
- package/dist/modules/catalog/commands/products.js +355 -73
- package/dist/modules/catalog/commands/products.js.map +2 -2
- package/dist/modules/catalog/commands/shared.js +18 -4
- package/dist/modules/catalog/commands/shared.js.map +2 -2
- package/dist/modules/catalog/components/products/ProductUomSection.js +591 -0
- package/dist/modules/catalog/components/products/ProductUomSection.js.map +7 -0
- package/dist/modules/catalog/components/products/productForm.js +66 -5
- package/dist/modules/catalog/components/products/productForm.js.map +2 -2
- package/dist/modules/catalog/components/products/productFormUtils.js +68 -0
- package/dist/modules/catalog/components/products/productFormUtils.js.map +7 -0
- package/dist/modules/catalog/data/entities.js +86 -0
- package/dist/modules/catalog/data/entities.js.map +2 -2
- package/dist/modules/catalog/data/validators.js +65 -3
- package/dist/modules/catalog/data/validators.js.map +2 -2
- package/dist/modules/catalog/events.js +3 -0
- package/dist/modules/catalog/events.js.map +2 -2
- package/dist/modules/catalog/lib/unitCodes.js +7 -0
- package/dist/modules/catalog/lib/unitCodes.js.map +7 -0
- package/dist/modules/catalog/lib/unitResolution.js +53 -0
- package/dist/modules/catalog/lib/unitResolution.js.map +7 -0
- package/dist/modules/catalog/migrations/Migration20260218225422.js +19 -0
- package/dist/modules/catalog/migrations/Migration20260218225422.js.map +7 -0
- package/dist/modules/catalog/migrations/Migration20260219084500.js +27 -0
- package/dist/modules/catalog/migrations/Migration20260219084500.js.map +7 -0
- package/dist/modules/catalog/search.js +69 -1
- package/dist/modules/catalog/search.js.map +2 -2
- package/dist/modules/catalog/seed/examples.js +91 -42
- package/dist/modules/catalog/seed/examples.js.map +2 -2
- package/dist/modules/dashboards/seed/analytics.js +3 -0
- package/dist/modules/dashboards/seed/analytics.js.map +2 -2
- package/dist/modules/sales/api/order-lines/route.js +98 -15
- package/dist/modules/sales/api/order-lines/route.js.map +2 -2
- package/dist/modules/sales/api/quote-lines/route.js +101 -14
- package/dist/modules/sales/api/quote-lines/route.js.map +2 -2
- package/dist/modules/sales/api/quotes/public/[token]/route.js +87 -12
- package/dist/modules/sales/api/quotes/public/[token]/route.js.map +2 -2
- package/dist/modules/sales/commands/documents.js +1424 -260
- package/dist/modules/sales/commands/documents.js.map +3 -3
- package/dist/modules/sales/commands/shared.js +6 -2
- package/dist/modules/sales/commands/shared.js.map +2 -2
- package/dist/modules/sales/components/documents/ItemsSection.js +216 -86
- package/dist/modules/sales/components/documents/ItemsSection.js.map +2 -2
- package/dist/modules/sales/components/documents/LineItemDialog.js +913 -241
- package/dist/modules/sales/components/documents/LineItemDialog.js.map +3 -3
- package/dist/modules/sales/components/documents/ShipmentsSection.js +15 -3
- package/dist/modules/sales/components/documents/ShipmentsSection.js.map +2 -2
- package/dist/modules/sales/data/entities.js +59 -3
- package/dist/modules/sales/data/entities.js.map +2 -2
- package/dist/modules/sales/data/validators.js +35 -0
- package/dist/modules/sales/data/validators.js.map +2 -2
- package/dist/modules/sales/frontend/quote/[token]/page.js +15 -1
- package/dist/modules/sales/frontend/quote/[token]/page.js.map +2 -2
- package/dist/modules/sales/migrations/Migration20260218225423.js +31 -0
- package/dist/modules/sales/migrations/Migration20260218225423.js.map +7 -0
- package/dist/modules/sales/migrations/Migration20260219084501.js +71 -0
- package/dist/modules/sales/migrations/Migration20260219084501.js.map +7 -0
- package/dist/modules/sales/search.js +28 -0
- package/dist/modules/sales/search.js.map +2 -2
- package/dist/modules/sales/seed/examples.js +14 -1
- package/dist/modules/sales/seed/examples.js.map +2 -2
- package/dist/modules/sales/widgets/injection/document-history/widget.client.js +1 -1
- package/dist/modules/sales/widgets/injection/document-history/widget.client.js.map +2 -2
- package/generated/entities/catalog_product/index.ts +8 -0
- package/generated/entities/catalog_product_unit_conversion/index.ts +12 -0
- package/generated/entities/sales_credit_memo_line/index.ts +3 -0
- package/generated/entities/sales_invoice_line/index.ts +3 -0
- package/generated/entities/sales_order_line/index.ts +3 -0
- package/generated/entities/sales_quote_line/index.ts +3 -0
- package/generated/entities.ids.generated.ts +1 -0
- package/generated/entity-fields-registry.ts +2 -0
- package/package.json +2 -2
- package/src/modules/auth/i18n/de.json +1 -1
- package/src/modules/auth/i18n/en.json +1 -1
- package/src/modules/auth/i18n/es.json +1 -1
- package/src/modules/auth/i18n/pl.json +1 -1
- package/src/modules/catalog/api/prices/route.ts +213 -81
- package/src/modules/catalog/api/product-unit-conversions/route.ts +195 -0
- package/src/modules/catalog/api/products/route.ts +638 -402
- package/src/modules/catalog/backend/catalog/products/[id]/page.tsx +2085 -1072
- package/src/modules/catalog/backend/catalog/products/create/page.tsx +1288 -593
- package/src/modules/catalog/commands/index.ts +1 -0
- package/src/modules/catalog/commands/productUnitConversions.ts +626 -0
- package/src/modules/catalog/commands/products.ts +1151 -693
- package/src/modules/catalog/commands/shared.ts +19 -5
- package/src/modules/catalog/components/products/ProductUomSection.tsx +745 -0
- package/src/modules/catalog/components/products/productForm.ts +369 -256
- package/src/modules/catalog/components/products/productFormUtils.ts +82 -0
- package/src/modules/catalog/data/entities.ts +82 -1
- package/src/modules/catalog/data/validators.ts +118 -34
- package/src/modules/catalog/events.ts +3 -0
- package/src/modules/catalog/i18n/de.json +56 -0
- package/src/modules/catalog/i18n/en.json +56 -0
- package/src/modules/catalog/i18n/es.json +56 -0
- package/src/modules/catalog/i18n/pl.json +56 -0
- package/src/modules/catalog/lib/unitCodes.ts +1 -0
- package/src/modules/catalog/lib/unitResolution.ts +62 -0
- package/src/modules/catalog/migrations/.snapshot-open-mercato.json +245 -0
- package/src/modules/catalog/migrations/Migration20260218225422.ts +21 -0
- package/src/modules/catalog/migrations/Migration20260219084500.ts +26 -0
- package/src/modules/catalog/search.ts +73 -1
- package/src/modules/catalog/seed/examples.ts +552 -479
- package/src/modules/dashboards/i18n/de.json +1 -1
- package/src/modules/dashboards/i18n/en.json +1 -1
- package/src/modules/dashboards/i18n/es.json +1 -1
- package/src/modules/dashboards/i18n/pl.json +1 -1
- package/src/modules/dashboards/seed/analytics.ts +3 -0
- package/src/modules/sales/api/order-lines/route.ts +158 -68
- package/src/modules/sales/api/quote-lines/route.ts +161 -67
- package/src/modules/sales/api/quotes/public/[token]/route.ts +122 -36
- package/src/modules/sales/commands/documents.ts +4250 -2424
- package/src/modules/sales/commands/shared.ts +7 -2
- package/src/modules/sales/components/documents/ItemsSection.tsx +580 -310
- package/src/modules/sales/components/documents/LineItemDialog.tsx +1988 -833
- package/src/modules/sales/components/documents/ShipmentsSection.tsx +17 -3
- package/src/modules/sales/components/documents/lineItemTypes.ts +6 -0
- package/src/modules/sales/data/entities.ts +53 -0
- package/src/modules/sales/data/validators.ts +36 -0
- package/src/modules/sales/frontend/quote/[token]/page.tsx +25 -1
- package/src/modules/sales/i18n/de.json +23 -3
- package/src/modules/sales/i18n/en.json +23 -3
- package/src/modules/sales/i18n/es.json +23 -3
- package/src/modules/sales/i18n/pl.json +23 -3
- package/src/modules/sales/lib/types.ts +30 -0
- package/src/modules/sales/migrations/.snapshot-open-mercato.json +172 -0
- package/src/modules/sales/migrations/Migration20260218225423.ts +37 -0
- package/src/modules/sales/migrations/Migration20260219084501.ts +73 -0
- package/src/modules/sales/search.ts +28 -0
- package/src/modules/sales/seed/examples.ts +20 -1
- package/src/modules/sales/widgets/injection/document-history/widget.client.tsx +1 -1
- package/src/modules/workflows/i18n/de.json +4 -4
- package/src/modules/workflows/i18n/en.json +4 -4
- package/src/modules/workflows/i18n/es.json +4 -4
- package/src/modules/workflows/i18n/pl.json +4 -4
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/modules/catalog/components/products/ProductUomSection.tsx"],
|
|
4
|
+
"sourcesContent": ["\"use client\";\n\nimport * as React from \"react\";\nimport { useT } from \"@open-mercato/shared/lib/i18n/context\";\nimport { apiCall } from \"@open-mercato/ui/backend/utils/apiCall\";\nimport { Button } from \"@open-mercato/ui/primitives/button\";\nimport { Checkbox } from \"@open-mercato/ui/primitives/checkbox\";\nimport { Input } from \"@open-mercato/ui/primitives/input\";\nimport { Label } from \"@open-mercato/ui/primitives/label\";\nimport { Spinner } from \"@open-mercato/ui/primitives/spinner\";\nimport { ArrowDown, ArrowUp, Plus, Trash2 } from \"lucide-react\";\nimport type {\n ProductFormValues,\n ProductUnitConversionDraft,\n} from \"./productForm\";\nimport { createProductUnitConversionDraft } from \"./productForm\";\nimport { REFERENCE_UNIT_CODES } from \"@open-mercato/shared/lib/units/unitCodes\";\nimport { toTrimmedOrNull } from \"./productFormUtils\";\n\ntype UnitDictionaryEntry = {\n id?: string;\n value?: string;\n label?: string;\n};\n\ntype UnitDictionaryResponse = {\n entries?: UnitDictionaryEntry[];\n};\n\ntype UnitOption = {\n value: string;\n label: string;\n};\n\ntype ProductUomSectionProps = {\n values: ProductFormValues;\n errors: Record<string, string>;\n setValue: (id: string, value: unknown) => void;\n embedded?: boolean;\n};\n\nconst REFERENCE_UNIT_DISPLAY: Record<string, string> = {\n kg: \"1 kg\", l: \"1 l\", m2: \"1 m\\u00B2\", m3: \"1 m\\u00B3\", pc: \"1 pc\",\n};\nconst REFERENCE_UNIT_OPTIONS = REFERENCE_UNIT_CODES.map((code) => ({\n value: code,\n i18nKey: `catalog.products.unitPrice.options.${code}` as const,\n fallback: REFERENCE_UNIT_DISPLAY[code] ?? code,\n}));\n\nfunction normalizeDecimalInput(value: string): string {\n return value.replace(/,/g, \".\");\n}\n\nfunction toPositiveNumber(value: unknown): number | null {\n if (typeof value === \"number\") {\n return Number.isFinite(value) && value > 0 ? value : null;\n }\n if (typeof value !== \"string\") return null;\n const normalized = toTrimmedOrNull(value);\n if (!normalized) return null;\n const numeric = Number(normalized.replace(\",\", \".\"));\n return Number.isFinite(numeric) && numeric > 0 ? numeric : null;\n}\n\nfunction toSortValue(value: string): number {\n const numeric = Number(value);\n return Number.isFinite(numeric) ? numeric : Number.MAX_SAFE_INTEGER;\n}\n\nfunction formatPreviewNumber(value: number): string {\n if (!Number.isFinite(value)) return \"0\";\n const rounded = Math.round(value * 1_000_000) / 1_000_000;\n return Number.isInteger(rounded) ? String(rounded) : rounded.toString();\n}\n\nfunction normalizeConversions(value: unknown): ProductUnitConversionDraft[] {\n if (!Array.isArray(value)) return [];\n const normalized = value\n .map((entry) => {\n if (!entry || typeof entry !== \"object\") return null;\n const row = entry as ProductUnitConversionDraft;\n return {\n id: toTrimmedOrNull(row.id) ?? null,\n unitCode: toTrimmedOrNull(row.unitCode) ?? \"\",\n toBaseFactor: toTrimmedOrNull(row.toBaseFactor)\n ? normalizeDecimalInput(toTrimmedOrNull(row.toBaseFactor) as string)\n : \"\",\n sortOrder: toTrimmedOrNull(row.sortOrder) ?? \"\",\n isActive: row.isActive !== false,\n } satisfies ProductUnitConversionDraft;\n })\n .filter((entry): entry is ProductUnitConversionDraft => Boolean(entry));\n normalized.sort((left, right) => {\n const leftOrder = toSortValue(left.sortOrder);\n const rightOrder = toSortValue(right.sortOrder);\n if (leftOrder === rightOrder) return left.unitCode.localeCompare(right.unitCode);\n return leftOrder - rightOrder;\n });\n return normalized.map((entry, index) => ({\n ...entry,\n sortOrder: String((index + 1) * 10),\n }));\n}\n\nfunction buildUnitOptions(\n entries: UnitDictionaryEntry[] | undefined,\n): UnitOption[] {\n const list = Array.isArray(entries) ? entries : [];\n const options = list\n .map((entry) => {\n const value = toTrimmedOrNull(entry.value);\n if (!value) return null;\n return {\n value,\n label: toTrimmedOrNull(entry.label) ?? value,\n } satisfies UnitOption;\n })\n .filter((entry): entry is UnitOption => Boolean(entry));\n return options.sort((left, right) => left.label.localeCompare(right.label));\n}\n\nexport function ProductUomSection({\n values,\n errors,\n setValue,\n embedded = false,\n}: ProductUomSectionProps) {\n const t = useT();\n const [unitOptions, setUnitOptions] = React.useState<UnitOption[]>([]);\n const [loadingUnits, setLoadingUnits] = React.useState(false);\n const [errorLoadingUnits, setErrorLoadingUnits] = React.useState(false);\n const conversions = React.useMemo(\n () => normalizeConversions(values.unitConversions),\n [values.unitConversions],\n );\n\n React.useEffect(() => {\n let cancelled = false;\n async function loadUnits() {\n setLoadingUnits(true);\n setErrorLoadingUnits(false);\n try {\n const response = await apiCall<UnitDictionaryResponse>(\n \"/api/catalog/dictionaries/unit\",\n undefined,\n { fallback: { entries: [] } },\n );\n if (cancelled) return;\n setUnitOptions(buildUnitOptions(response.result?.entries));\n } catch {\n if (!cancelled) {\n setUnitOptions([]);\n setErrorLoadingUnits(true);\n }\n } finally {\n if (!cancelled) setLoadingUnits(false);\n }\n }\n void loadUnits();\n return () => {\n cancelled = true;\n };\n }, []);\n\n const findUnitLabel = React.useCallback(\n (value: string | null | undefined) => {\n const code = toTrimmedOrNull(value);\n if (!code) return null;\n const option = unitOptions.find((entry) => entry.value === code);\n return option?.label ?? code;\n },\n [unitOptions],\n );\n\n const setConversions = React.useCallback(\n (next: ProductUnitConversionDraft[]) => {\n const normalized = next.map((entry, index) => ({\n ...entry,\n sortOrder: String((index + 1) * 10),\n }));\n setValue(\"unitConversions\", normalized);\n },\n [setValue],\n );\n\n const addConversion = React.useCallback(() => {\n const next = [\n ...conversions,\n createProductUnitConversionDraft({\n sortOrder: String((conversions.length + 1) * 10),\n }),\n ];\n setConversions(next);\n }, [conversions, setConversions]);\n\n const updateConversion = React.useCallback(\n (index: number, patch: Partial<ProductUnitConversionDraft>) => {\n const next = conversions.map((entry, rowIndex) =>\n rowIndex === index ? { ...entry, ...patch } : entry,\n );\n setConversions(next);\n },\n [conversions, setConversions],\n );\n\n const removeConversion = React.useCallback(\n (index: number) => {\n const next = conversions.filter((_entry, rowIndex) => rowIndex !== index);\n setConversions(next);\n },\n [conversions, setConversions],\n );\n\n const moveConversion = React.useCallback(\n (index: number, direction: \"up\" | \"down\") => {\n const targetIndex = direction === \"up\" ? index - 1 : index + 1;\n if (targetIndex < 0 || targetIndex >= conversions.length) return;\n const next = [...conversions];\n const source = next[index];\n next[index] = next[targetIndex];\n next[targetIndex] = source;\n setConversions(next);\n },\n [conversions, setConversions],\n );\n\n const defaultUnit = toTrimmedOrNull(values.defaultUnit) ?? \"\";\n const defaultSalesUnit = toTrimmedOrNull(values.defaultSalesUnit) ?? \"\";\n const defaultSalesQuantityRaw =\n toTrimmedOrNull(values.defaultSalesUnitQuantity) ?? \"1\";\n const defaultSalesQuantity = normalizeDecimalInput(defaultSalesQuantityRaw);\n const unitPriceEnabled = Boolean(values.unitPriceEnabled);\n const unitPriceReferenceUnit =\n toTrimmedOrNull(values.unitPriceReferenceUnit) ?? \"\";\n const unitPriceBaseQuantityRaw =\n toTrimmedOrNull(values.unitPriceBaseQuantity) ?? \"\";\n const unitPriceBaseQuantity = normalizeDecimalInput(unitPriceBaseQuantityRaw);\n\n const baseUnitLabel = findUnitLabel(defaultUnit) ?? defaultUnit;\n const salesUnitLabel =\n findUnitLabel(defaultSalesUnit || defaultUnit) ??\n defaultSalesUnit ??\n defaultUnit;\n\n const defaultSalesFactor = React.useMemo(() => {\n const defaultUnitKey = defaultUnit.toLowerCase();\n const defaultSalesKey = (defaultSalesUnit || defaultUnit).toLowerCase();\n if (!defaultUnitKey || !defaultSalesKey) return null;\n if (defaultSalesKey === defaultUnitKey) return 1;\n const row = conversions.find(\n (entry) =>\n entry.isActive &&\n entry.unitCode.toLowerCase() === defaultSalesKey &&\n toPositiveNumber(entry.toBaseFactor) !== null,\n );\n return row ? toPositiveNumber(row.toBaseFactor) : null;\n }, [conversions, defaultSalesUnit, defaultUnit]);\n\n const defaultSalesQuantityNumber = toPositiveNumber(defaultSalesQuantity);\n const defaultSalesQuantityNormalized =\n defaultSalesQuantityNumber && defaultSalesFactor\n ? defaultSalesQuantityNumber * defaultSalesFactor\n : null;\n const unitPriceBaseQuantityNumber = toPositiveNumber(unitPriceBaseQuantity);\n\n const validConversions = conversions.filter(\n (entry) =>\n toTrimmedOrNull(entry.unitCode) && toTrimmedOrNull(entry.toBaseFactor),\n );\n const conversionPreviewItems = validConversions\n .slice(0, 3)\n .map((entry) => {\n const label = findUnitLabel(entry.unitCode) ?? entry.unitCode;\n const baseLabel = findUnitLabel(defaultUnit) ?? defaultUnit;\n const factor = toTrimmedOrNull(entry.toBaseFactor) ?? \"1\";\n return `1 ${label} = ${factor} ${baseLabel || t(\"catalog.products.uom.baseUnit\", \"base unit\")}`;\n });\n const conversionPreview =\n validConversions.length > 3\n ? `${conversionPreviewItems.join(\" \u2022 \")} (+${validConversions.length - 3})`\n : conversionPreviewItems.join(\" \u2022 \");\n\n return (\n <div\n className={\n embedded ? \"space-y-5\" : \"space-y-5 rounded-lg border bg-card p-4\"\n }\n >\n <div className=\"space-y-1\">\n <h3 className=\"text-sm font-semibold\">\n {t(\"catalog.products.uom.title\", \"Units of measure\")}\n </h3>\n <p className=\"text-xs text-muted-foreground\">\n {t(\n \"catalog.products.uom.description\",\n \"Set base unit, sales unit, and packaging conversions.\",\n )}\n </p>\n </div>\n\n {loadingUnits ? (\n <div className=\"flex items-center gap-2 text-xs text-muted-foreground\">\n <Spinner className=\"h-3 w-3\" />\n {t(\"catalog.products.uom.loadingUnits\", \"Loading units...\")}\n </div>\n ) : null}\n\n {errorLoadingUnits ? (\n <p className=\"text-xs text-destructive\">\n {t(\n \"catalog.products.uom.errors.loadUnits\",\n \"Failed to load units. Please try refreshing the page.\",\n )}\n </p>\n ) : null}\n\n <div className=\"grid gap-4 md:grid-cols-2\">\n <div className=\"space-y-2\">\n <Label htmlFor=\"catalog-product-uom-base-unit\">{t(\"catalog.products.uom.baseUnit\", \"Base unit\")}</Label>\n <select\n id=\"catalog-product-uom-base-unit\"\n className=\"w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2\"\n value={defaultUnit}\n onChange={(event) =>\n setValue(\"defaultUnit\", event.target.value || null)\n }\n disabled={loadingUnits}\n >\n <option value=\"\">\n {t(\"catalog.products.uom.selectUnit\", \"Select unit\")}\n </option>\n {unitOptions.map((option) => (\n <option key={option.value} value={option.value}>\n {option.label}\n </option>\n ))}\n </select>\n {errors.defaultUnit ? (\n <p className=\"text-xs text-destructive\">{errors.defaultUnit}</p>\n ) : null}\n </div>\n\n <div className=\"space-y-2\">\n <Label htmlFor=\"catalog-product-uom-sales-unit\">\n {t(\"catalog.products.uom.defaultSalesUnit\", \"Default sales unit\")}\n </Label>\n <select\n id=\"catalog-product-uom-sales-unit\"\n className=\"w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2\"\n value={defaultSalesUnit}\n onChange={(event) =>\n setValue(\"defaultSalesUnit\", event.target.value || null)\n }\n disabled={loadingUnits}\n >\n <option value=\"\">\n {t(\"catalog.products.uom.selectUnit\", \"Select unit\")}\n </option>\n {unitOptions.map((option) => (\n <option key={option.value} value={option.value}>\n {option.label}\n </option>\n ))}\n </select>\n {errors.defaultSalesUnit ? (\n <p className=\"text-xs text-destructive\">{errors.defaultSalesUnit}</p>\n ) : null}\n </div>\n\n <div className=\"space-y-2 md:col-span-2\">\n <Label htmlFor=\"catalog-product-uom-default-sales-quantity\">\n {t(\n \"catalog.products.uom.defaultSalesQuantityLabel\",\n \"Default line quantity (in sales unit)\",\n )}\n </Label>\n <Input\n id=\"catalog-product-uom-default-sales-quantity\"\n type=\"text\"\n inputMode=\"decimal\"\n value={defaultSalesQuantity}\n onChange={(event) =>\n setValue(\n \"defaultSalesUnitQuantity\",\n normalizeDecimalInput(event.target.value),\n )\n }\n placeholder=\"1\"\n />\n <p className=\"text-xs text-muted-foreground\">\n {t(\n \"catalog.products.uom.defaultSalesQuantityHint\",\n \"Used to prefill quantity in quote/order lines. Value is interpreted in Default sales unit.\",\n )}\n </p>\n {defaultSalesQuantityNumber && salesUnitLabel ? (\n <p className=\"text-xs text-muted-foreground\">\n {defaultSalesQuantityNormalized && baseUnitLabel\n ? t(\n \"catalog.products.uom.defaultSalesQuantityPreviewWithNormalization\",\n \"Default line: {{quantity}} {{salesUnit}} (= {{normalized}} {{baseUnit}}).\",\n {\n quantity: formatPreviewNumber(defaultSalesQuantityNumber),\n salesUnit: salesUnitLabel,\n normalized: formatPreviewNumber(\n defaultSalesQuantityNormalized,\n ),\n baseUnit: baseUnitLabel,\n },\n )\n : t(\n \"catalog.products.uom.defaultSalesQuantityPreview\",\n \"Default line: {{quantity}} {{salesUnit}}.\",\n {\n quantity: formatPreviewNumber(defaultSalesQuantityNumber),\n salesUnit: salesUnitLabel,\n },\n )}\n </p>\n ) : null}\n {errors.defaultSalesUnitQuantity ? (\n <p className=\"text-xs text-destructive\">\n {errors.defaultSalesUnitQuantity}\n </p>\n ) : null}\n </div>\n </div>\n\n <div className=\"grid gap-4 md:grid-cols-2\">\n <div className=\"space-y-2\">\n <Label htmlFor=\"catalog-product-uom-rounding-mode\">\n {t(\"catalog.products.uom.roundingMode\", \"Rounding mode\")}\n </Label>\n <select\n id=\"catalog-product-uom-rounding-mode\"\n className=\"w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2\"\n value={values.uomRoundingMode ?? \"half_up\"}\n onChange={(event) =>\n setValue(\"uomRoundingMode\", event.target.value)\n }\n >\n <option value=\"half_up\">\n {t(\"catalog.products.uom.roundingModeHalfUp\", \"Half up (default)\")}\n </option>\n <option value=\"down\">\n {t(\"catalog.products.uom.roundingModeDown\", \"Round down\")}\n </option>\n <option value=\"up\">\n {t(\"catalog.products.uom.roundingModeUp\", \"Round up\")}\n </option>\n </select>\n </div>\n <div className=\"space-y-2\">\n <Label htmlFor=\"catalog-product-uom-rounding-scale\">\n {t(\"catalog.products.uom.roundingScale\", \"Rounding scale (decimal places)\")}\n </Label>\n <Input\n id=\"catalog-product-uom-rounding-scale\"\n type=\"number\"\n min={0}\n max={6}\n value={values.uomRoundingScale ?? 4}\n onChange={(event) => {\n const parsed = Number.parseInt(event.target.value, 10);\n setValue(\"uomRoundingScale\", Number.isFinite(parsed) ? Math.max(0, Math.min(6, parsed)) : 4);\n }}\n />\n </div>\n </div>\n\n <div className=\"space-y-3\">\n <div className=\"flex items-center gap-2\">\n <Checkbox\n id=\"catalog-product-unit-price-enabled\"\n checked={unitPriceEnabled}\n onCheckedChange={(checked) =>\n setValue(\"unitPriceEnabled\", checked === true)\n }\n />\n <Label\n htmlFor=\"catalog-product-unit-price-enabled\"\n className=\"text-sm\"\n >\n {t(\n \"catalog.products.unitPrice.enable\",\n \"Enable EU unit price display\",\n )}\n </Label>\n </div>\n\n {unitPriceEnabled ? (\n <div className=\"grid gap-3 md:grid-cols-2\">\n <div className=\"space-y-2\">\n <Label htmlFor=\"catalog-product-uom-reference-unit\">\n {t(\n \"catalog.products.unitPrice.referenceUnit\",\n \"Reference unit\",\n )}\n </Label>\n <select\n id=\"catalog-product-uom-reference-unit\"\n className=\"w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2\"\n value={unitPriceReferenceUnit}\n onChange={(event) =>\n setValue(\"unitPriceReferenceUnit\", event.target.value || null)\n }\n >\n <option value=\"\">\n {t(\n \"catalog.products.unitPrice.selectReferenceUnit\",\n \"Select reference unit\",\n )}\n </option>\n {REFERENCE_UNIT_OPTIONS.map((option) => (\n <option key={option.value} value={option.value}>\n {t(option.i18nKey, option.fallback)}\n </option>\n ))}\n </select>\n </div>\n <div className=\"space-y-2\">\n <Label htmlFor=\"catalog-product-uom-unit-price-base-quantity\">\n {t(\n \"catalog.products.unitPrice.baseQuantity\",\n \"Reference quantity (in base unit)\",\n )}\n </Label>\n <Input\n id=\"catalog-product-uom-unit-price-base-quantity\"\n type=\"text\"\n inputMode=\"decimal\"\n value={unitPriceBaseQuantity}\n onChange={(event) =>\n setValue(\n \"unitPriceBaseQuantity\",\n normalizeDecimalInput(event.target.value),\n )\n }\n placeholder=\"1\"\n />\n </div>\n </div>\n ) : null}\n {unitPriceEnabled ? (\n <p className=\"text-xs text-muted-foreground\">\n {unitPriceReferenceUnit && unitPriceBaseQuantityNumber\n ? t(\n \"catalog.products.unitPrice.hintWithPreview\",\n \"Show calculated price per {{quantity}} {{unit}}. For most products use 1 (for example: 1 kg, 1 l, 1 m\u00B2).\",\n {\n quantity: formatPreviewNumber(unitPriceBaseQuantityNumber),\n unit: unitPriceReferenceUnit,\n },\n )\n : t(\n \"catalog.products.unitPrice.hint\",\n \"Show calculated price per selected reference unit. In most cases set quantity to 1.\",\n )}\n </p>\n ) : null}\n </div>\n\n <div className=\"space-y-3\">\n <div className=\"flex items-center justify-between gap-2\">\n <Label className=\"text-sm\">\n {t(\"catalog.products.uom.conversions\", \"Product conversions\")}\n </Label>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={addConversion}\n >\n <Plus className=\"mr-1.5 h-4 w-4\" />\n {t(\"catalog.products.uom.addConversion\", \"Add conversion\")}\n </Button>\n </div>\n\n {conversions.length === 0 ? (\n <p className=\"text-xs text-muted-foreground\">\n {t(\n \"catalog.products.uom.emptyConversions\",\n \"No conversions configured yet.\",\n )}\n </p>\n ) : (\n <div className=\"space-y-2\">\n {conversions.map((entry, index) => {\n const conversionFactor = toPositiveNumber(entry.toBaseFactor);\n const conversionPreviewText =\n entry.unitCode && conversionFactor !== null\n ? t(\n \"catalog.products.uom.conversionPreview\",\n \"1 {{fromUnit}} = {{factor}} {{baseUnit}}\",\n {\n fromUnit: findUnitLabel(entry.unitCode) ?? entry.unitCode,\n factor: formatPreviewNumber(conversionFactor),\n baseUnit:\n findUnitLabel(defaultUnit) ??\n defaultUnit ??\n t(\"catalog.products.uom.baseUnit\", \"base unit\"),\n },\n )\n : null;\n\n return (\n <div\n key={entry.id ?? `uom-conversion-${index}`}\n className=\"grid gap-3 rounded-md border p-3 md:grid-cols-12\"\n >\n <div className=\"space-y-1 md:col-span-4\">\n <Label htmlFor={`catalog-product-uom-conversion-unit-${index}`} className=\"text-xs text-muted-foreground\">\n {t(\"catalog.products.uom.conversionUnit\", \"Sales unit\")}\n </Label>\n <select\n id={`catalog-product-uom-conversion-unit-${index}`}\n className=\"w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2\"\n value={entry.unitCode}\n onChange={(event) =>\n updateConversion(index, { unitCode: event.target.value })\n }\n disabled={loadingUnits}\n >\n <option value=\"\">\n {t(\"catalog.products.uom.selectUnit\", \"Select unit\")}\n </option>\n {unitOptions.map((option) => (\n <option key={option.value} value={option.value}>\n {option.label}\n </option>\n ))}\n </select>\n </div>\n\n <div className=\"space-y-1 md:col-span-3\">\n <Label htmlFor={`catalog-product-uom-conversion-factor-${index}`} className=\"text-xs text-muted-foreground\">\n {t(\n \"catalog.products.uom.toBaseFactor\",\n \"Base units per 1 sales unit\",\n )}\n </Label>\n <Input\n id={`catalog-product-uom-conversion-factor-${index}`}\n type=\"text\"\n inputMode=\"decimal\"\n value={entry.toBaseFactor}\n onChange={(event) =>\n updateConversion(index, {\n toBaseFactor: normalizeDecimalInput(event.target.value),\n })\n }\n placeholder=\"1\"\n />\n </div>\n\n <div className=\"flex items-end gap-2 md:col-span-3\">\n <div className=\"inline-flex h-9 rounded-md border\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n className=\"h-9 w-9 rounded-none border-r\"\n onClick={() => moveConversion(index, \"up\")}\n disabled={index === 0}\n aria-label={t(\n \"catalog.products.uom.moveUp\",\n \"Move conversion up\",\n )}\n >\n <ArrowUp className=\"h-4 w-4\" />\n </Button>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n className=\"h-9 w-9 rounded-none\"\n onClick={() => moveConversion(index, \"down\")}\n disabled={index === conversions.length - 1}\n aria-label={t(\n \"catalog.products.uom.moveDown\",\n \"Move conversion down\",\n )}\n >\n <ArrowDown className=\"h-4 w-4\" />\n </Button>\n </div>\n <label\n htmlFor={`catalog-product-uom-conversion-active-${index}`}\n className=\"flex items-center gap-2 px-1 text-xs text-muted-foreground\"\n >\n <Checkbox\n id={`catalog-product-uom-conversion-active-${index}`}\n checked={entry.isActive}\n onCheckedChange={(checked) =>\n updateConversion(index, { isActive: checked === true })\n }\n />\n {t(\"catalog.products.uom.active\", \"Active\")}\n </label>\n </div>\n\n <div className=\"flex items-end justify-end md:col-span-2\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n className=\"text-destructive\"\n onClick={() => removeConversion(index)}\n aria-label={t(\n \"catalog.products.uom.removeConversion\",\n \"Remove conversion\",\n )}\n >\n <Trash2 className=\"h-4 w-4\" />\n </Button>\n </div>\n\n {conversionPreviewText && (\n <p className=\"text-xs text-muted-foreground md:col-span-12\">\n {conversionPreviewText}\n </p>\n )}\n </div>\n );\n })}\n </div>\n )}\n\n {conversions.length > 1 ? (\n <p className=\"text-xs text-muted-foreground\">\n {t(\n \"catalog.products.uom.conversionOrderHint\",\n \"Use arrows to reorder conversion priority.\",\n )}\n </p>\n ) : null}\n\n {conversionPreview ? (\n <p className=\"text-xs text-muted-foreground\">{conversionPreview}</p>\n ) : null}\n </div>\n </div>\n );\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -1,10 +1,24 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { slugify } from "@open-mercato/shared/lib/slugify";
|
|
3
3
|
import { parseObjectLike } from "@open-mercato/shared/lib/json/parseObjectLike";
|
|
4
|
+
const optionalPositiveNumberInput = z.preprocess((value) => {
|
|
5
|
+
if (value === null || value === void 0) return void 0;
|
|
6
|
+
if (typeof value === "string" && value.trim().length === 0) return void 0;
|
|
7
|
+
return value;
|
|
8
|
+
}, z.coerce.number().positive().optional());
|
|
9
|
+
const optionalBoundedIntegerInput = (min, max) => z.preprocess((value) => {
|
|
10
|
+
if (value === null || value === void 0) return void 0;
|
|
11
|
+
if (typeof value === "string" && value.trim().length === 0)
|
|
12
|
+
return void 0;
|
|
13
|
+
return value;
|
|
14
|
+
}, z.coerce.number().int().min(min).max(max).optional());
|
|
4
15
|
const productFormSchema = z.object({
|
|
5
|
-
title: z.string().trim().min(1, "
|
|
16
|
+
title: z.string().trim().min(1, "catalog.products.validation.titleRequired"),
|
|
6
17
|
subtitle: z.string().optional(),
|
|
7
|
-
handle: z.string().trim().regex(
|
|
18
|
+
handle: z.string().trim().regex(
|
|
19
|
+
/^[a-z0-9\-_]*$/,
|
|
20
|
+
"catalog.products.validation.handleFormat"
|
|
21
|
+
).max(150).optional(),
|
|
8
22
|
description: z.string().optional(),
|
|
9
23
|
useMarkdown: z.boolean().optional(),
|
|
10
24
|
taxRateId: z.string().uuid().nullable().optional(),
|
|
@@ -27,13 +41,41 @@ const productFormSchema = z.object({
|
|
|
27
41
|
value: z.coerce.number().min(0).optional(),
|
|
28
42
|
unit: z.string().trim().max(25).optional()
|
|
29
43
|
}).nullable().optional(),
|
|
44
|
+
defaultUnit: z.string().trim().max(50).nullable().optional(),
|
|
45
|
+
defaultSalesUnit: z.string().trim().max(50).nullable().optional(),
|
|
46
|
+
defaultSalesUnitQuantity: optionalPositiveNumberInput,
|
|
47
|
+
uomRoundingScale: optionalBoundedIntegerInput(0, 6),
|
|
48
|
+
uomRoundingMode: z.enum(["half_up", "down", "up"]).optional(),
|
|
49
|
+
unitPriceEnabled: z.boolean().optional(),
|
|
50
|
+
unitPriceReferenceUnit: z.string().trim().max(50).nullable().optional(),
|
|
51
|
+
unitPriceBaseQuantity: optionalPositiveNumberInput,
|
|
52
|
+
unitConversions: z.array(
|
|
53
|
+
z.object({
|
|
54
|
+
id: z.string().nullable().optional(),
|
|
55
|
+
unitCode: z.string().trim().max(50),
|
|
56
|
+
toBaseFactor: z.coerce.number().positive(),
|
|
57
|
+
sortOrder: z.coerce.number().int().min(0).max(1e5).optional(),
|
|
58
|
+
isActive: z.boolean().optional()
|
|
59
|
+
})
|
|
60
|
+
).optional(),
|
|
30
61
|
customFieldsetCode: z.string().optional().nullable(),
|
|
31
62
|
categoryIds: z.array(z.string().uuid()).optional(),
|
|
32
63
|
channelIds: z.array(z.string().uuid()).optional(),
|
|
33
64
|
tags: z.array(z.string().trim().min(1).max(100)).optional(),
|
|
34
65
|
optionSchemaId: z.string().uuid().nullable().optional()
|
|
35
|
-
}).passthrough()
|
|
36
|
-
|
|
66
|
+
}).passthrough().refine(
|
|
67
|
+
(data) => !data.unitPriceEnabled || data.unitPriceReferenceUnit != null && data.unitPriceReferenceUnit.length > 0,
|
|
68
|
+
{ message: "catalog.products.validation.referenceUnitRequired", path: ["unitPriceReferenceUnit"] }
|
|
69
|
+
).refine(
|
|
70
|
+
(data) => !data.defaultSalesUnit || data.defaultUnit != null && data.defaultUnit.length > 0,
|
|
71
|
+
{ message: "catalog.products.validation.baseUnitRequired", path: ["defaultUnit"] }
|
|
72
|
+
);
|
|
73
|
+
const PRODUCT_FORM_STEPS = [
|
|
74
|
+
"general",
|
|
75
|
+
"organize",
|
|
76
|
+
"uom",
|
|
77
|
+
"variants"
|
|
78
|
+
];
|
|
37
79
|
const BASE_INITIAL_VALUES = {
|
|
38
80
|
title: "",
|
|
39
81
|
subtitle: "",
|
|
@@ -51,6 +93,15 @@ const BASE_INITIAL_VALUES = {
|
|
|
51
93
|
metadata: {},
|
|
52
94
|
dimensions: null,
|
|
53
95
|
weight: null,
|
|
96
|
+
defaultUnit: null,
|
|
97
|
+
defaultSalesUnit: null,
|
|
98
|
+
defaultSalesUnitQuantity: "1",
|
|
99
|
+
uomRoundingScale: "4",
|
|
100
|
+
uomRoundingMode: "half_up",
|
|
101
|
+
unitPriceEnabled: false,
|
|
102
|
+
unitPriceReferenceUnit: null,
|
|
103
|
+
unitPriceBaseQuantity: "",
|
|
104
|
+
unitConversions: [],
|
|
54
105
|
customFieldsetCode: null,
|
|
55
106
|
categoryIds: [],
|
|
56
107
|
channelIds: [],
|
|
@@ -75,6 +126,14 @@ const createVariantDraft = (productTaxRateId, overrides = {}) => ({
|
|
|
75
126
|
prices: {},
|
|
76
127
|
...overrides
|
|
77
128
|
});
|
|
129
|
+
const createProductUnitConversionDraft = (overrides = {}) => ({
|
|
130
|
+
id: null,
|
|
131
|
+
unitCode: "",
|
|
132
|
+
toBaseFactor: "",
|
|
133
|
+
sortOrder: "",
|
|
134
|
+
isActive: true,
|
|
135
|
+
...overrides
|
|
136
|
+
});
|
|
78
137
|
const buildOptionValuesKey = (optionValues) => {
|
|
79
138
|
if (!optionValues) return "";
|
|
80
139
|
return Object.keys(optionValues).sort().map((key) => `${key}:${optionValues[key] ?? ""}`).join("|");
|
|
@@ -155,7 +214,8 @@ const normalizePriceKindSummary = (input) => {
|
|
|
155
214
|
if (!input) return null;
|
|
156
215
|
const getString = (value) => {
|
|
157
216
|
if (typeof value === "string" && value.trim().length) return value.trim();
|
|
158
|
-
if (typeof value === "number" || typeof value === "bigint")
|
|
217
|
+
if (typeof value === "number" || typeof value === "bigint")
|
|
218
|
+
return String(value);
|
|
159
219
|
return null;
|
|
160
220
|
};
|
|
161
221
|
const id = getString(input.id);
|
|
@@ -262,6 +322,7 @@ export {
|
|
|
262
322
|
convertSchemaToProductOptions,
|
|
263
323
|
createInitialProductFormValues,
|
|
264
324
|
createLocalId,
|
|
325
|
+
createProductUnitConversionDraft,
|
|
265
326
|
createVariantDraft,
|
|
266
327
|
formatTaxRateLabel,
|
|
267
328
|
haveSameOptionValues,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/catalog/components/products/productForm.ts"],
|
|
4
|
-
"sourcesContent": ["import { z } from 'zod'\nimport { slugify } from '@open-mercato/shared/lib/slugify'\nimport { parseObjectLike } from '@open-mercato/shared/lib/json/parseObjectLike'\nimport type { CatalogProductOptionSchema } from '../../data/types'\nimport type { ProductMediaItem } from './ProductMediaManager'\n\nexport { slugify }\n\nexport type PriceKindSummary = {\n id: string\n code: string\n title: string\n currencyCode: string | null\n displayMode: 'including-tax' | 'excluding-tax'\n}\n\nexport type PriceKindApiPayload = {\n id?: string | number\n code?: string\n title?: string\n currencyCode?: string | null\n currency_code?: string | null\n displayMode?: string | null\n display_mode?: string | null\n}\n\nexport type TaxRateSummary = {\n id: string\n name: string\n code: string | null\n rate: number | null\n isDefault: boolean\n}\n\nexport type ProductOptionInput = {\n id: string\n title: string\n values: Array<{ id: string; label: string }>\n}\n\nexport type ProductDimensions = {\n width?: number\n height?: number\n depth?: number\n unit?: string | null\n} | null\n\nexport type ProductWeight = {\n value?: number\n unit?: string | null\n} | null\n\nexport type VariantPriceValue = {\n amount: string\n}\n\nexport type VariantDraft = {\n id: string\n title: string\n sku: string\n isDefault: boolean\n taxRateId: string | null\n manageInventory: boolean\n allowBackorder: boolean\n hasInventoryKit: boolean\n optionValues: Record<string, string>\n prices: Record<string, VariantPriceValue>\n}\n\nexport type ProductFormValues = {\n title: string\n subtitle: string\n handle: string\n description: string\n useMarkdown: boolean\n taxRateId: string | null\n mediaDraftId: string\n mediaItems: ProductMediaItem[]\n defaultMediaId: string | null\n defaultMediaUrl: string\n hasVariants: boolean\n options: ProductOptionInput[]\n variants: VariantDraft[]\n metadata?: Record<string, unknown> | null\n dimensions?: ProductDimensions\n weight?: ProductWeight\n customFieldsetCode?: string | null\n categoryIds: string[]\n channelIds: string[]\n tags: string[]\n optionSchemaId?: string | null\n}\n\nexport const productFormSchema = z.object({\n title: z.string().trim().min(1, 'Title is required'),\n subtitle: z.string().optional(),\n handle: z\n .string()\n .trim()\n .regex(/^[a-z0-9\\-_]*$/, 'Handle must include lowercase letters, digits, hyphen, or underscore')\n .max(150)\n .optional(),\n description: z.string().optional(),\n useMarkdown: z.boolean().optional(),\n taxRateId: z.string().uuid().nullable().optional(),\n hasVariants: z.boolean().optional(),\n mediaDraftId: z.string().optional(),\n mediaItems: z.any().optional(),\n defaultMediaId: z.string().uuid().nullable().optional(),\n defaultMediaUrl: z.string().trim().max(500).nullable().optional(),\n options: z.any().optional(),\n variants: z.any().optional(),\n // Use a permissive schema to avoid zod classic `_zod` runtime crashes on records in edge builds.\n metadata: z.custom<Record<string, unknown>>(() => true).nullable().optional(),\n dimensions: z\n .object({\n width: z.coerce.number().min(0).optional(),\n height: z.coerce.number().min(0).optional(),\n depth: z.coerce.number().min(0).optional(),\n unit: z.string().trim().max(25).optional(),\n })\n .nullable()\n .optional(),\n weight: z\n .object({\n value: z.coerce.number().min(0).optional(),\n unit: z.string().trim().max(25).optional(),\n })\n .nullable()\n .optional(),\n customFieldsetCode: z.string().optional().nullable(),\n categoryIds: z.array(z.string().uuid()).optional(),\n channelIds: z.array(z.string().uuid()).optional(),\n tags: z.array(z.string().trim().min(1).max(100)).optional(),\n optionSchemaId: z.string().uuid().nullable().optional(),\n}).passthrough()\n\nexport const PRODUCT_FORM_STEPS = ['general', 'organize', 'variants'] as const\n\nexport const BASE_INITIAL_VALUES: ProductFormValues = {\n title: '',\n subtitle: '',\n handle: '',\n description: '',\n useMarkdown: false,\n mediaDraftId: '',\n mediaItems: [],\n defaultMediaId: null,\n defaultMediaUrl: '',\n taxRateId: null,\n hasVariants: false,\n options: [],\n variants: [],\n metadata: {},\n dimensions: null,\n weight: null,\n customFieldsetCode: null,\n categoryIds: [],\n channelIds: [],\n tags: [],\n optionSchemaId: null,\n}\n\nexport const createInitialProductFormValues = (): ProductFormValues => ({\n ...BASE_INITIAL_VALUES,\n mediaDraftId: createLocalId(),\n variants: [createVariantDraft(null, { isDefault: true })],\n})\n\nexport const createVariantDraft = (\n productTaxRateId: string | null,\n overrides: Partial<VariantDraft> = {},\n): VariantDraft => ({\n id: createLocalId(),\n title: 'Default variant',\n sku: '',\n isDefault: false,\n taxRateId: productTaxRateId ?? null,\n manageInventory: false,\n allowBackorder: false,\n hasInventoryKit: false,\n optionValues: {},\n prices: {},\n ...overrides,\n})\n\nexport const buildOptionValuesKey = (optionValues?: Record<string, string>): string => {\n if (!optionValues) return ''\n return Object.keys(optionValues)\n .sort()\n .map((key) => `${key}:${optionValues[key] ?? ''}`)\n .join('|')\n}\n\nexport const haveSameOptionValues = (\n current: Record<string, string> | undefined,\n next: Record<string, string>,\n): boolean => {\n const a = current ?? {}\n const keys = new Set([...Object.keys(a), ...Object.keys(next)])\n for (const key of keys) {\n if ((a[key] ?? '') !== (next[key] ?? '')) return false\n }\n return true\n}\n\nconst parseNumeric = (input: unknown): number | null => {\n const numeric = typeof input === 'number' ? input : Number(input)\n if (!Number.isFinite(numeric) || numeric < 0) return null\n return numeric\n}\n\nexport const normalizeProductDimensions = (raw: unknown): ProductDimensions => {\n const source = parseObjectLike(raw)\n if (!source) return null\n const width = parseNumeric(source.width)\n const height = parseNumeric(source.height)\n const depth = parseNumeric(source.depth)\n const unit = typeof source.unit === 'string' && source.unit.trim().length ? source.unit.trim() : null\n const clean: Record<string, unknown> = {}\n if (width !== null) clean.width = width\n if (height !== null) clean.height = height\n if (depth !== null) clean.depth = depth\n if (unit) clean.unit = unit\n return Object.keys(clean).length ? (clean as ProductDimensions) : null\n}\n\nexport const normalizeProductWeight = (raw: unknown): ProductWeight => {\n const source = parseObjectLike(raw)\n if (!source) return null\n const value = parseNumeric(source.value)\n const unit = typeof source.unit === 'string' && source.unit.trim().length ? source.unit.trim() : null\n if (value === null && !unit) return null\n const clean: Record<string, unknown> = {}\n if (value !== null) clean.value = value\n if (unit) clean.unit = unit\n return clean as ProductWeight\n}\n\nexport const sanitizeProductDimensions = (raw: ProductDimensions): ProductDimensions => {\n return normalizeProductDimensions(raw ?? null)\n}\n\nexport const sanitizeProductWeight = (raw: ProductWeight): ProductWeight => {\n return normalizeProductWeight(raw ?? null)\n}\n\nexport const updateDimensionValue = (\n current: ProductDimensions,\n field: 'width' | 'height' | 'depth' | 'unit',\n raw: string\n): ProductDimensions => {\n const base = normalizeProductDimensions(current) ?? {}\n if (field === 'unit') {\n base.unit = raw\n } else {\n const numeric = parseNumeric(raw)\n if (numeric === null) {\n delete base[field]\n } else {\n base[field] = numeric\n }\n }\n return sanitizeProductDimensions(base)\n}\n\nexport const updateWeightValue = (\n current: ProductWeight,\n field: 'value' | 'unit',\n raw: string\n): ProductWeight => {\n const base = normalizeProductWeight(current) ?? {}\n if (field === 'unit') {\n base.unit = raw\n } else {\n const numeric = parseNumeric(raw)\n if (numeric === null) {\n delete (base as Record<string, unknown>).value\n } else {\n base.value = numeric\n }\n }\n return sanitizeProductWeight(base)\n}\n\nexport const normalizePriceKindSummary = (input: PriceKindApiPayload | undefined | null): PriceKindSummary | null => {\n if (!input) return null\n const getString = (value: unknown): string | null => {\n if (typeof value === 'string' && value.trim().length) return value.trim()\n if (typeof value === 'number' || typeof value === 'bigint') return String(value)\n return null\n }\n const id = getString(input.id)\n const code = getString(input.code)\n const title = getString(input.title)\n if (!id || !code || !title) return null\n const currency = getString(input.currencyCode) ?? getString(input.currency_code)\n const displayRaw = getString(input.displayMode) ?? getString(input.display_mode)\n const displayMode: PriceKindSummary['displayMode'] =\n displayRaw === 'including-tax' ? 'including-tax' : 'excluding-tax'\n return {\n id,\n code,\n title,\n currencyCode: currency,\n displayMode,\n }\n}\n\nexport const formatTaxRateLabel = (rate: TaxRateSummary): string => {\n const extras: string[] = []\n if (typeof rate.rate === 'number' && Number.isFinite(rate.rate)) {\n extras.push(`${rate.rate}%`)\n }\n if (rate.code) {\n extras.push(rate.code.toUpperCase())\n }\n if (!extras.length) return rate.name\n return `${rate.name} \u2022 ${extras.join(' \u00B7 ')}`\n}\n\nexport function createLocalId(): string {\n return Math.random().toString(36).slice(2, 10)\n}\n\nexport function buildOptionSchemaDefinition(\n options: ProductOptionInput[] | undefined,\n name: string\n): CatalogProductOptionSchema | null {\n const list = Array.isArray(options) ? options : []\n if (!list.length) return null\n const normalizedName = name && name.trim().length ? name.trim() : 'Product options'\n const schemaOptions = list\n .map((option) => {\n const title = option.title?.trim() || ''\n const code = resolveOptionCode(option)\n const values = Array.isArray(option.values) ? option.values : []\n return {\n code: code || slugify(createLocalId()),\n label: title || code || 'Option',\n inputType: 'select' as const,\n choices: values\n .map((value) => {\n const label = value.label?.trim() || ''\n const valueCode = slugify(label || value.id || createLocalId())\n if (!label && !valueCode) return null\n return {\n code: valueCode || slugify(createLocalId()),\n label: label || valueCode || 'Choice',\n }\n })\n .filter((entry): entry is { code: string; label: string } => !!entry),\n }\n })\n .filter((entry) => entry.label.trim().length)\n if (!schemaOptions.length) return null\n return {\n version: 1,\n name: normalizedName,\n options: schemaOptions,\n }\n}\n\nexport function convertSchemaToProductOptions(\n schema: CatalogProductOptionSchema | null | undefined\n): ProductOptionInput[] {\n if (!schema || !Array.isArray(schema.options)) return []\n return schema.options.map((option) => ({\n id: createLocalId(),\n title: option.label ?? option.code ?? 'Option',\n values: Array.isArray(option.choices)\n ? option.choices.map((choice) => ({\n id: createLocalId(),\n label: choice.label ?? choice.code ?? '',\n }))\n : [],\n }))\n}\n\nfunction resolveOptionCode(option: ProductOptionInput): string {\n const base = option.title?.trim() || option.id?.trim() || ''\n const slugged = slugify(base)\n if (slugged.length) return slugged\n if (base.length) return base\n return createLocalId()\n}\n\nexport function buildVariantCombinations(options: ProductOptionInput[]): Record<string, string>[] {\n if (!options.length) return []\n const [first, ...rest] = options\n if (!first || !Array.isArray(first.values) || !first.values.length) return []\n const firstKey = resolveOptionCode(first)\n const initial = first.values.map((value) => ({ [firstKey]: value.label }))\n return rest.reduce<Record<string, string>[]>((acc, option) => {\n if (!Array.isArray(option.values) || !option.values.length) return []\n const optionKey = resolveOptionCode(option)\n const combos: Record<string, string>[] = []\n acc.forEach((partial) => {\n option.values.forEach((value) => {\n combos.push({ ...partial, [optionKey]: value.label })\n })\n })\n return combos\n }, initial)\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,SAAS;AAClB,SAAS,eAAe;AACxB,SAAS,uBAAuB;
|
|
4
|
+
"sourcesContent": ["import { z } from \"zod\";\nimport { slugify } from \"@open-mercato/shared/lib/slugify\";\nimport { parseObjectLike } from \"@open-mercato/shared/lib/json/parseObjectLike\";\nimport type { ReferenceUnitCode } from \"@open-mercato/shared/lib/units/unitCodes\";\nimport type { CatalogProductOptionSchema } from \"../../data/types\";\nimport type { ProductMediaItem } from \"./ProductMediaManager\";\n\nexport { slugify };\n\nexport type PriceKindSummary = {\n id: string;\n code: string;\n title: string;\n currencyCode: string | null;\n displayMode: \"including-tax\" | \"excluding-tax\";\n};\n\nexport type PriceKindApiPayload = {\n id?: string | number;\n code?: string;\n title?: string;\n currencyCode?: string | null;\n currency_code?: string | null;\n displayMode?: string | null;\n display_mode?: string | null;\n};\n\nexport type TaxRateSummary = {\n id: string;\n name: string;\n code: string | null;\n rate: number | null;\n isDefault: boolean;\n};\n\nexport type ProductOptionInput = {\n id: string;\n title: string;\n values: Array<{ id: string; label: string }>;\n};\n\nexport type ProductDimensions = {\n width?: number;\n height?: number;\n depth?: number;\n unit?: string | null;\n} | null;\n\nexport type ProductWeight = {\n value?: number;\n unit?: string | null;\n} | null;\n\nexport type VariantPriceValue = {\n amount: string;\n};\n\nexport type ProductUnitRoundingMode = \"half_up\" | \"down\" | \"up\";\nexport type ProductUnitPriceReferenceUnit = ReferenceUnitCode;\n\nexport type ProductUnitConversionDraft = {\n id: string | null;\n unitCode: string;\n toBaseFactor: string;\n sortOrder: string;\n isActive: boolean;\n};\n\nexport type VariantDraft = {\n id: string;\n title: string;\n sku: string;\n isDefault: boolean;\n taxRateId: string | null;\n manageInventory: boolean;\n allowBackorder: boolean;\n hasInventoryKit: boolean;\n optionValues: Record<string, string>;\n prices: Record<string, VariantPriceValue>;\n};\n\nexport type ProductFormValues = {\n title: string;\n subtitle: string;\n handle: string;\n description: string;\n useMarkdown: boolean;\n taxRateId: string | null;\n mediaDraftId: string;\n mediaItems: ProductMediaItem[];\n defaultMediaId: string | null;\n defaultMediaUrl: string;\n hasVariants: boolean;\n options: ProductOptionInput[];\n variants: VariantDraft[];\n metadata?: Record<string, unknown> | null;\n dimensions?: ProductDimensions;\n weight?: ProductWeight;\n defaultUnit: string | null;\n defaultSalesUnit: string | null;\n defaultSalesUnitQuantity: string;\n uomRoundingScale: string;\n uomRoundingMode: ProductUnitRoundingMode;\n unitPriceEnabled: boolean;\n unitPriceReferenceUnit: string | null;\n unitPriceBaseQuantity: string;\n unitConversions: ProductUnitConversionDraft[];\n customFieldsetCode?: string | null;\n categoryIds: string[];\n channelIds: string[];\n tags: string[];\n optionSchemaId?: string | null;\n};\n\nconst optionalPositiveNumberInput = z.preprocess((value) => {\n if (value === null || value === undefined) return undefined;\n if (typeof value === \"string\" && value.trim().length === 0) return undefined;\n return value;\n}, z.coerce.number().positive().optional());\n\nconst optionalBoundedIntegerInput = (min: number, max: number) =>\n z.preprocess((value) => {\n if (value === null || value === undefined) return undefined;\n if (typeof value === \"string\" && value.trim().length === 0)\n return undefined;\n return value;\n }, z.coerce.number().int().min(min).max(max).optional());\n\nexport const productFormSchema = z\n .object({\n title: z.string().trim().min(1, \"catalog.products.validation.titleRequired\"),\n subtitle: z.string().optional(),\n handle: z\n .string()\n .trim()\n .regex(\n /^[a-z0-9\\-_]*$/,\n \"catalog.products.validation.handleFormat\",\n )\n .max(150)\n .optional(),\n description: z.string().optional(),\n useMarkdown: z.boolean().optional(),\n taxRateId: z.string().uuid().nullable().optional(),\n hasVariants: z.boolean().optional(),\n mediaDraftId: z.string().optional(),\n mediaItems: z.any().optional(),\n defaultMediaId: z.string().uuid().nullable().optional(),\n defaultMediaUrl: z.string().trim().max(500).nullable().optional(),\n options: z.any().optional(),\n variants: z.any().optional(),\n // Use a permissive schema to avoid zod classic `_zod` runtime crashes on records in edge builds.\n metadata: z\n .custom<Record<string, unknown>>(() => true)\n .nullable()\n .optional(),\n dimensions: z\n .object({\n width: z.coerce.number().min(0).optional(),\n height: z.coerce.number().min(0).optional(),\n depth: z.coerce.number().min(0).optional(),\n unit: z.string().trim().max(25).optional(),\n })\n .nullable()\n .optional(),\n weight: z\n .object({\n value: z.coerce.number().min(0).optional(),\n unit: z.string().trim().max(25).optional(),\n })\n .nullable()\n .optional(),\n defaultUnit: z.string().trim().max(50).nullable().optional(),\n defaultSalesUnit: z.string().trim().max(50).nullable().optional(),\n defaultSalesUnitQuantity: optionalPositiveNumberInput,\n uomRoundingScale: optionalBoundedIntegerInput(0, 6),\n uomRoundingMode: z.enum([\"half_up\", \"down\", \"up\"]).optional(),\n unitPriceEnabled: z.boolean().optional(),\n unitPriceReferenceUnit: z.string().trim().max(50).nullable().optional(),\n unitPriceBaseQuantity: optionalPositiveNumberInput,\n unitConversions: z\n .array(\n z.object({\n id: z.string().nullable().optional(),\n unitCode: z.string().trim().max(50),\n toBaseFactor: z.coerce.number().positive(),\n sortOrder: z.coerce.number().int().min(0).max(100000).optional(),\n isActive: z.boolean().optional(),\n }),\n )\n .optional(),\n customFieldsetCode: z.string().optional().nullable(),\n categoryIds: z.array(z.string().uuid()).optional(),\n channelIds: z.array(z.string().uuid()).optional(),\n tags: z.array(z.string().trim().min(1).max(100)).optional(),\n optionSchemaId: z.string().uuid().nullable().optional(),\n })\n .passthrough()\n .refine(\n (data) => !data.unitPriceEnabled || (data.unitPriceReferenceUnit != null && data.unitPriceReferenceUnit.length > 0),\n { message: 'catalog.products.validation.referenceUnitRequired', path: ['unitPriceReferenceUnit'] }\n )\n .refine(\n (data) => !data.defaultSalesUnit || (data.defaultUnit != null && data.defaultUnit.length > 0),\n { message: 'catalog.products.validation.baseUnitRequired', path: ['defaultUnit'] }\n );\n\nexport const PRODUCT_FORM_STEPS = [\n \"general\",\n \"organize\",\n \"uom\",\n \"variants\",\n] as const;\n\nexport const BASE_INITIAL_VALUES: ProductFormValues = {\n title: \"\",\n subtitle: \"\",\n handle: \"\",\n description: \"\",\n useMarkdown: false,\n mediaDraftId: \"\",\n mediaItems: [],\n defaultMediaId: null,\n defaultMediaUrl: \"\",\n taxRateId: null,\n hasVariants: false,\n options: [],\n variants: [],\n metadata: {},\n dimensions: null,\n weight: null,\n defaultUnit: null,\n defaultSalesUnit: null,\n defaultSalesUnitQuantity: \"1\",\n uomRoundingScale: \"4\",\n uomRoundingMode: \"half_up\",\n unitPriceEnabled: false,\n unitPriceReferenceUnit: null,\n unitPriceBaseQuantity: \"\",\n unitConversions: [],\n customFieldsetCode: null,\n categoryIds: [],\n channelIds: [],\n tags: [],\n optionSchemaId: null,\n};\n\nexport const createInitialProductFormValues = (): ProductFormValues => ({\n ...BASE_INITIAL_VALUES,\n mediaDraftId: createLocalId(),\n variants: [createVariantDraft(null, { isDefault: true })],\n});\n\nexport const createVariantDraft = (\n productTaxRateId: string | null,\n overrides: Partial<VariantDraft> = {},\n): VariantDraft => ({\n id: createLocalId(),\n title: \"Default variant\",\n sku: \"\",\n isDefault: false,\n taxRateId: productTaxRateId ?? null,\n manageInventory: false,\n allowBackorder: false,\n hasInventoryKit: false,\n optionValues: {},\n prices: {},\n ...overrides,\n});\n\nexport const createProductUnitConversionDraft = (\n overrides: Partial<ProductUnitConversionDraft> = {},\n): ProductUnitConversionDraft => ({\n id: null,\n unitCode: \"\",\n toBaseFactor: \"\",\n sortOrder: \"\",\n isActive: true,\n ...overrides,\n});\n\nexport const buildOptionValuesKey = (\n optionValues?: Record<string, string>,\n): string => {\n if (!optionValues) return \"\";\n return Object.keys(optionValues)\n .sort()\n .map((key) => `${key}:${optionValues[key] ?? \"\"}`)\n .join(\"|\");\n};\n\nexport const haveSameOptionValues = (\n current: Record<string, string> | undefined,\n next: Record<string, string>,\n): boolean => {\n const a = current ?? {};\n const keys = new Set([...Object.keys(a), ...Object.keys(next)]);\n for (const key of keys) {\n if ((a[key] ?? \"\") !== (next[key] ?? \"\")) return false;\n }\n return true;\n};\n\nconst parseNumeric = (input: unknown): number | null => {\n const numeric = typeof input === \"number\" ? input : Number(input);\n if (!Number.isFinite(numeric) || numeric < 0) return null;\n return numeric;\n};\n\nexport const normalizeProductDimensions = (raw: unknown): ProductDimensions => {\n const source = parseObjectLike(raw);\n if (!source) return null;\n const width = parseNumeric(source.width);\n const height = parseNumeric(source.height);\n const depth = parseNumeric(source.depth);\n const unit =\n typeof source.unit === \"string\" && source.unit.trim().length\n ? source.unit.trim()\n : null;\n const clean: Record<string, unknown> = {};\n if (width !== null) clean.width = width;\n if (height !== null) clean.height = height;\n if (depth !== null) clean.depth = depth;\n if (unit) clean.unit = unit;\n return Object.keys(clean).length ? (clean as ProductDimensions) : null;\n};\n\nexport const normalizeProductWeight = (raw: unknown): ProductWeight => {\n const source = parseObjectLike(raw);\n if (!source) return null;\n const value = parseNumeric(source.value);\n const unit =\n typeof source.unit === \"string\" && source.unit.trim().length\n ? source.unit.trim()\n : null;\n if (value === null && !unit) return null;\n const clean: Record<string, unknown> = {};\n if (value !== null) clean.value = value;\n if (unit) clean.unit = unit;\n return clean as ProductWeight;\n};\n\nexport const sanitizeProductDimensions = (\n raw: ProductDimensions,\n): ProductDimensions => {\n return normalizeProductDimensions(raw ?? null);\n};\n\nexport const sanitizeProductWeight = (raw: ProductWeight): ProductWeight => {\n return normalizeProductWeight(raw ?? null);\n};\n\nexport const updateDimensionValue = (\n current: ProductDimensions,\n field: \"width\" | \"height\" | \"depth\" | \"unit\",\n raw: string,\n): ProductDimensions => {\n const base = normalizeProductDimensions(current) ?? {};\n if (field === \"unit\") {\n base.unit = raw;\n } else {\n const numeric = parseNumeric(raw);\n if (numeric === null) {\n delete base[field];\n } else {\n base[field] = numeric;\n }\n }\n return sanitizeProductDimensions(base);\n};\n\nexport const updateWeightValue = (\n current: ProductWeight,\n field: \"value\" | \"unit\",\n raw: string,\n): ProductWeight => {\n const base = normalizeProductWeight(current) ?? {};\n if (field === \"unit\") {\n base.unit = raw;\n } else {\n const numeric = parseNumeric(raw);\n if (numeric === null) {\n delete (base as Record<string, unknown>).value;\n } else {\n base.value = numeric;\n }\n }\n return sanitizeProductWeight(base);\n};\n\nexport const normalizePriceKindSummary = (\n input: PriceKindApiPayload | undefined | null,\n): PriceKindSummary | null => {\n if (!input) return null;\n const getString = (value: unknown): string | null => {\n if (typeof value === \"string\" && value.trim().length) return value.trim();\n if (typeof value === \"number\" || typeof value === \"bigint\")\n return String(value);\n return null;\n };\n const id = getString(input.id);\n const code = getString(input.code);\n const title = getString(input.title);\n if (!id || !code || !title) return null;\n const currency =\n getString(input.currencyCode) ?? getString(input.currency_code);\n const displayRaw =\n getString(input.displayMode) ?? getString(input.display_mode);\n const displayMode: PriceKindSummary[\"displayMode\"] =\n displayRaw === \"including-tax\" ? \"including-tax\" : \"excluding-tax\";\n return {\n id,\n code,\n title,\n currencyCode: currency,\n displayMode,\n };\n};\n\nexport const formatTaxRateLabel = (rate: TaxRateSummary): string => {\n const extras: string[] = [];\n if (typeof rate.rate === \"number\" && Number.isFinite(rate.rate)) {\n extras.push(`${rate.rate}%`);\n }\n if (rate.code) {\n extras.push(rate.code.toUpperCase());\n }\n if (!extras.length) return rate.name;\n return `${rate.name} \u2022 ${extras.join(\" \u00B7 \")}`;\n};\n\nexport function createLocalId(): string {\n return Math.random().toString(36).slice(2, 10);\n}\n\nexport function buildOptionSchemaDefinition(\n options: ProductOptionInput[] | undefined,\n name: string,\n): CatalogProductOptionSchema | null {\n const list = Array.isArray(options) ? options : [];\n if (!list.length) return null;\n const normalizedName =\n name && name.trim().length ? name.trim() : \"Product options\";\n const schemaOptions = list\n .map((option) => {\n const title = option.title?.trim() || \"\";\n const code = resolveOptionCode(option);\n const values = Array.isArray(option.values) ? option.values : [];\n return {\n code: code || slugify(createLocalId()),\n label: title || code || \"Option\",\n inputType: \"select\" as const,\n choices: values\n .map((value) => {\n const label = value.label?.trim() || \"\";\n const valueCode = slugify(label || value.id || createLocalId());\n if (!label && !valueCode) return null;\n return {\n code: valueCode || slugify(createLocalId()),\n label: label || valueCode || \"Choice\",\n };\n })\n .filter((entry): entry is { code: string; label: string } => !!entry),\n };\n })\n .filter((entry) => entry.label.trim().length);\n if (!schemaOptions.length) return null;\n return {\n version: 1,\n name: normalizedName,\n options: schemaOptions,\n };\n}\n\nexport function convertSchemaToProductOptions(\n schema: CatalogProductOptionSchema | null | undefined,\n): ProductOptionInput[] {\n if (!schema || !Array.isArray(schema.options)) return [];\n return schema.options.map((option) => ({\n id: createLocalId(),\n title: option.label ?? option.code ?? \"Option\",\n values: Array.isArray(option.choices)\n ? option.choices.map((choice) => ({\n id: createLocalId(),\n label: choice.label ?? choice.code ?? \"\",\n }))\n : [],\n }));\n}\n\nfunction resolveOptionCode(option: ProductOptionInput): string {\n const base = option.title?.trim() || option.id?.trim() || \"\";\n const slugged = slugify(base);\n if (slugged.length) return slugged;\n if (base.length) return base;\n return createLocalId();\n}\n\nexport function buildVariantCombinations(\n options: ProductOptionInput[],\n): Record<string, string>[] {\n if (!options.length) return [];\n const [first, ...rest] = options;\n if (!first || !Array.isArray(first.values) || !first.values.length) return [];\n const firstKey = resolveOptionCode(first);\n const initial = first.values.map((value) => ({ [firstKey]: value.label }));\n return rest.reduce<Record<string, string>[]>((acc, option) => {\n if (!Array.isArray(option.values) || !option.values.length) return [];\n const optionKey = resolveOptionCode(option);\n const combos: Record<string, string>[] = [];\n acc.forEach((partial) => {\n option.values.forEach((value) => {\n combos.push({ ...partial, [optionKey]: value.label });\n });\n });\n return combos;\n }, initial);\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { REFERENCE_UNIT_CODES } from "@open-mercato/shared/lib/units/unitCodes";
|
|
2
|
+
import { canonicalizeUnitCode } from "../../lib/unitCodes.js";
|
|
3
|
+
import { createCrudFormError } from "@open-mercato/ui/backend/utils/serverErrors";
|
|
4
|
+
const UNIT_PRICE_REFERENCE_UNITS = new Set(REFERENCE_UNIT_CODES);
|
|
5
|
+
function toTrimmedOrNull(value) {
|
|
6
|
+
if (typeof value !== "string") return null;
|
|
7
|
+
const trimmed = value.trim();
|
|
8
|
+
return trimmed.length ? trimmed : null;
|
|
9
|
+
}
|
|
10
|
+
function parseNumericInput(value) {
|
|
11
|
+
if (typeof value === "number") return value;
|
|
12
|
+
if (typeof value === "string") {
|
|
13
|
+
const normalized = value.trim().replace(/\s+/g, "").replace(/,/g, ".");
|
|
14
|
+
if (!normalized.length) return Number.NaN;
|
|
15
|
+
return Number(normalized);
|
|
16
|
+
}
|
|
17
|
+
return Number(value);
|
|
18
|
+
}
|
|
19
|
+
function toPositiveNumberOrNull(value) {
|
|
20
|
+
const numeric = parseNumericInput(value);
|
|
21
|
+
if (!Number.isFinite(numeric) || numeric <= 0) return null;
|
|
22
|
+
return numeric;
|
|
23
|
+
}
|
|
24
|
+
function toIntegerInRangeOrDefault(value, min, max, fallback) {
|
|
25
|
+
const numeric = parseNumericInput(value);
|
|
26
|
+
if (!Number.isInteger(numeric) || numeric < min || numeric > max)
|
|
27
|
+
return fallback;
|
|
28
|
+
return numeric;
|
|
29
|
+
}
|
|
30
|
+
function normalizeProductConversionInputs(rows, duplicateMessage) {
|
|
31
|
+
const list = Array.isArray(rows) ? rows : [];
|
|
32
|
+
const normalized = [];
|
|
33
|
+
const seen = /* @__PURE__ */ new Set();
|
|
34
|
+
for (const row of list) {
|
|
35
|
+
const unitCode = canonicalizeUnitCode(row?.unitCode);
|
|
36
|
+
const toBaseFactor = toPositiveNumberOrNull(row?.toBaseFactor);
|
|
37
|
+
if (!unitCode || toBaseFactor === null) continue;
|
|
38
|
+
const unitKey = unitCode.toLowerCase();
|
|
39
|
+
if (seen.has(unitKey)) {
|
|
40
|
+
throw createCrudFormError(duplicateMessage, {
|
|
41
|
+
unitConversions: duplicateMessage
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
seen.add(unitKey);
|
|
45
|
+
normalized.push({
|
|
46
|
+
id: toTrimmedOrNull(row?.id),
|
|
47
|
+
unitCode,
|
|
48
|
+
toBaseFactor,
|
|
49
|
+
sortOrder: toIntegerInRangeOrDefault(
|
|
50
|
+
row?.sortOrder,
|
|
51
|
+
0,
|
|
52
|
+
1e5,
|
|
53
|
+
normalized.length * 10
|
|
54
|
+
),
|
|
55
|
+
isActive: row?.isActive !== false
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
return normalized;
|
|
59
|
+
}
|
|
60
|
+
export {
|
|
61
|
+
UNIT_PRICE_REFERENCE_UNITS,
|
|
62
|
+
normalizeProductConversionInputs,
|
|
63
|
+
parseNumericInput,
|
|
64
|
+
toIntegerInRangeOrDefault,
|
|
65
|
+
toPositiveNumberOrNull,
|
|
66
|
+
toTrimmedOrNull
|
|
67
|
+
};
|
|
68
|
+
//# sourceMappingURL=productFormUtils.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/modules/catalog/components/products/productFormUtils.ts"],
|
|
4
|
+
"sourcesContent": ["import { REFERENCE_UNIT_CODES, type ReferenceUnitCode } from \"@open-mercato/shared/lib/units/unitCodes\";\nimport { canonicalizeUnitCode } from \"../../lib/unitCodes\";\nimport { createCrudFormError } from \"@open-mercato/ui/backend/utils/serverErrors\";\nimport type { ProductUnitConversionDraft } from \"./productForm\";\n\nexport const UNIT_PRICE_REFERENCE_UNITS = new Set<ReferenceUnitCode>(REFERENCE_UNIT_CODES);\n\nexport function toTrimmedOrNull(value: unknown): string | null {\n if (typeof value !== \"string\") return null;\n const trimmed = value.trim();\n return trimmed.length ? trimmed : null;\n}\n\nexport function parseNumericInput(value: unknown): number {\n if (typeof value === \"number\") return value;\n if (typeof value === \"string\") {\n const normalized = value.trim().replace(/\\s+/g, \"\").replace(/,/g, \".\");\n if (!normalized.length) return Number.NaN;\n return Number(normalized);\n }\n return Number(value);\n}\n\nexport function toPositiveNumberOrNull(value: unknown): number | null {\n const numeric = parseNumericInput(value);\n if (!Number.isFinite(numeric) || numeric <= 0) return null;\n return numeric;\n}\n\nexport function toIntegerInRangeOrDefault(\n value: unknown,\n min: number,\n max: number,\n fallback: number,\n): number {\n const numeric = parseNumericInput(value);\n if (!Number.isInteger(numeric) || numeric < min || numeric > max)\n return fallback;\n return numeric;\n}\n\nexport type ProductUnitConversionInput = {\n id?: string | null;\n unitCode: string;\n toBaseFactor: number;\n sortOrder: number;\n isActive: boolean;\n};\n\nexport function normalizeProductConversionInputs(\n rows: ProductUnitConversionDraft[] | undefined,\n duplicateMessage: string,\n): ProductUnitConversionInput[] {\n const list = Array.isArray(rows) ? rows : [];\n const normalized: ProductUnitConversionInput[] = [];\n const seen = new Set<string>();\n for (const row of list) {\n const unitCode = canonicalizeUnitCode(row?.unitCode);\n const toBaseFactor = toPositiveNumberOrNull(row?.toBaseFactor);\n if (!unitCode || toBaseFactor === null) continue;\n const unitKey = unitCode.toLowerCase();\n if (seen.has(unitKey)) {\n throw createCrudFormError(duplicateMessage, {\n unitConversions: duplicateMessage,\n });\n }\n seen.add(unitKey);\n normalized.push({\n id: toTrimmedOrNull(row?.id),\n unitCode,\n toBaseFactor,\n sortOrder: toIntegerInRangeOrDefault(\n row?.sortOrder,\n 0,\n 100000,\n normalized.length * 10,\n ),\n isActive: row?.isActive !== false,\n });\n }\n return normalized;\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,4BAAoD;AAC7D,SAAS,4BAA4B;AACrC,SAAS,2BAA2B;AAG7B,MAAM,6BAA6B,IAAI,IAAuB,oBAAoB;AAElF,SAAS,gBAAgB,OAA+B;AAC7D,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,QAAM,UAAU,MAAM,KAAK;AAC3B,SAAO,QAAQ,SAAS,UAAU;AACpC;AAEO,SAAS,kBAAkB,OAAwB;AACxD,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,MAAI,OAAO,UAAU,UAAU;AAC7B,UAAM,aAAa,MAAM,KAAK,EAAE,QAAQ,QAAQ,EAAE,EAAE,QAAQ,MAAM,GAAG;AACrE,QAAI,CAAC,WAAW,OAAQ,QAAO,OAAO;AACtC,WAAO,OAAO,UAAU;AAAA,EAC1B;AACA,SAAO,OAAO,KAAK;AACrB;AAEO,SAAS,uBAAuB,OAA+B;AACpE,QAAM,UAAU,kBAAkB,KAAK;AACvC,MAAI,CAAC,OAAO,SAAS,OAAO,KAAK,WAAW,EAAG,QAAO;AACtD,SAAO;AACT;AAEO,SAAS,0BACd,OACA,KACA,KACA,UACQ;AACR,QAAM,UAAU,kBAAkB,KAAK;AACvC,MAAI,CAAC,OAAO,UAAU,OAAO,KAAK,UAAU,OAAO,UAAU;AAC3D,WAAO;AACT,SAAO;AACT;AAUO,SAAS,iCACd,MACA,kBAC8B;AAC9B,QAAM,OAAO,MAAM,QAAQ,IAAI,IAAI,OAAO,CAAC;AAC3C,QAAM,aAA2C,CAAC;AAClD,QAAM,OAAO,oBAAI,IAAY;AAC7B,aAAW,OAAO,MAAM;AACtB,UAAM,WAAW,qBAAqB,KAAK,QAAQ;AACnD,UAAM,eAAe,uBAAuB,KAAK,YAAY;AAC7D,QAAI,CAAC,YAAY,iBAAiB,KAAM;AACxC,UAAM,UAAU,SAAS,YAAY;AACrC,QAAI,KAAK,IAAI,OAAO,GAAG;AACrB,YAAM,oBAAoB,kBAAkB;AAAA,QAC1C,iBAAiB;AAAA,MACnB,CAAC;AAAA,IACH;AACA,SAAK,IAAI,OAAO;AAChB,eAAW,KAAK;AAAA,MACd,IAAI,gBAAgB,KAAK,EAAE;AAAA,MAC3B;AAAA,MACA;AAAA,MACA,WAAW;AAAA,QACT,KAAK;AAAA,QACL;AAAA,QACA;AAAA,QACA,WAAW,SAAS;AAAA,MACtB;AAAA,MACA,UAAU,KAAK,aAAa;AAAA,IAC9B,CAAC;AAAA,EACH;AACA,SAAO;AACT;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -78,6 +78,10 @@ OptionalProps;
|
|
|
78
78
|
let CatalogProduct = class {
|
|
79
79
|
constructor() {
|
|
80
80
|
this.productType = "simple";
|
|
81
|
+
this.defaultSalesUnitQuantity = "1";
|
|
82
|
+
this.uomRoundingScale = 4;
|
|
83
|
+
this.uomRoundingMode = "half_up";
|
|
84
|
+
this.unitPriceEnabled = false;
|
|
81
85
|
this.isConfigurable = false;
|
|
82
86
|
this.isActive = true;
|
|
83
87
|
this.createdAt = /* @__PURE__ */ new Date();
|
|
@@ -86,6 +90,7 @@ let CatalogProduct = class {
|
|
|
86
90
|
this.offers = new Collection(this);
|
|
87
91
|
this.categoryAssignments = new Collection(this);
|
|
88
92
|
this.tagAssignments = new Collection(this);
|
|
93
|
+
this.unitConversions = new Collection(this);
|
|
89
94
|
}
|
|
90
95
|
};
|
|
91
96
|
__decorateClass([
|
|
@@ -130,6 +135,27 @@ __decorateClass([
|
|
|
130
135
|
__decorateClass([
|
|
131
136
|
Property({ name: "default_unit", type: "text", nullable: true })
|
|
132
137
|
], CatalogProduct.prototype, "defaultUnit", 2);
|
|
138
|
+
__decorateClass([
|
|
139
|
+
Property({ name: "default_sales_unit", type: "text", nullable: true })
|
|
140
|
+
], CatalogProduct.prototype, "defaultSalesUnit", 2);
|
|
141
|
+
__decorateClass([
|
|
142
|
+
Property({ name: "default_sales_unit_quantity", type: "numeric", precision: 18, scale: 6, default: "1" })
|
|
143
|
+
], CatalogProduct.prototype, "defaultSalesUnitQuantity", 2);
|
|
144
|
+
__decorateClass([
|
|
145
|
+
Property({ name: "uom_rounding_scale", type: "smallint", default: 4 })
|
|
146
|
+
], CatalogProduct.prototype, "uomRoundingScale", 2);
|
|
147
|
+
__decorateClass([
|
|
148
|
+
Property({ name: "uom_rounding_mode", type: "text", default: "half_up" })
|
|
149
|
+
], CatalogProduct.prototype, "uomRoundingMode", 2);
|
|
150
|
+
__decorateClass([
|
|
151
|
+
Property({ name: "unit_price_enabled", type: "boolean", default: false })
|
|
152
|
+
], CatalogProduct.prototype, "unitPriceEnabled", 2);
|
|
153
|
+
__decorateClass([
|
|
154
|
+
Property({ name: "unit_price_reference_unit", type: "text", nullable: true })
|
|
155
|
+
], CatalogProduct.prototype, "unitPriceReferenceUnit", 2);
|
|
156
|
+
__decorateClass([
|
|
157
|
+
Property({ name: "unit_price_base_quantity", type: "numeric", precision: 18, scale: 6, nullable: true })
|
|
158
|
+
], CatalogProduct.prototype, "unitPriceBaseQuantity", 2);
|
|
133
159
|
__decorateClass([
|
|
134
160
|
Property({ name: "default_media_id", type: "uuid", nullable: true })
|
|
135
161
|
], CatalogProduct.prototype, "defaultMediaId", 2);
|
|
@@ -185,6 +211,9 @@ __decorateClass([
|
|
|
185
211
|
__decorateClass([
|
|
186
212
|
OneToMany(() => CatalogProductTagAssignment, (assignment) => assignment.product)
|
|
187
213
|
], CatalogProduct.prototype, "tagAssignments", 2);
|
|
214
|
+
__decorateClass([
|
|
215
|
+
OneToMany(() => CatalogProductUnitConversion, (conversion) => conversion.product)
|
|
216
|
+
], CatalogProduct.prototype, "unitConversions", 2);
|
|
188
217
|
CatalogProduct = __decorateClass([
|
|
189
218
|
Entity({ tableName: "catalog_products" }),
|
|
190
219
|
Index({ name: "catalog_products_org_tenant_idx", properties: ["organizationId", "tenantId"] }),
|
|
@@ -192,6 +221,62 @@ CatalogProduct = __decorateClass([
|
|
|
192
221
|
Unique({ name: "catalog_products_handle_scope_unique", properties: ["organizationId", "tenantId", "handle"] })
|
|
193
222
|
], CatalogProduct);
|
|
194
223
|
OptionalProps;
|
|
224
|
+
let CatalogProductUnitConversion = class {
|
|
225
|
+
constructor() {
|
|
226
|
+
this.sortOrder = 0;
|
|
227
|
+
this.isActive = true;
|
|
228
|
+
this.createdAt = /* @__PURE__ */ new Date();
|
|
229
|
+
this.updatedAt = /* @__PURE__ */ new Date();
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
__decorateClass([
|
|
233
|
+
PrimaryKey({ type: "uuid", defaultRaw: "gen_random_uuid()" })
|
|
234
|
+
], CatalogProductUnitConversion.prototype, "id", 2);
|
|
235
|
+
__decorateClass([
|
|
236
|
+
ManyToOne(() => CatalogProduct, { fieldName: "product_id", deleteRule: "cascade" })
|
|
237
|
+
], CatalogProductUnitConversion.prototype, "product", 2);
|
|
238
|
+
__decorateClass([
|
|
239
|
+
Property({ name: "organization_id", type: "uuid" })
|
|
240
|
+
], CatalogProductUnitConversion.prototype, "organizationId", 2);
|
|
241
|
+
__decorateClass([
|
|
242
|
+
Property({ name: "tenant_id", type: "uuid" })
|
|
243
|
+
], CatalogProductUnitConversion.prototype, "tenantId", 2);
|
|
244
|
+
__decorateClass([
|
|
245
|
+
Property({ name: "unit_code", type: "text" })
|
|
246
|
+
], CatalogProductUnitConversion.prototype, "unitCode", 2);
|
|
247
|
+
__decorateClass([
|
|
248
|
+
Property({ name: "to_base_factor", type: "numeric", precision: 24, scale: 12 })
|
|
249
|
+
], CatalogProductUnitConversion.prototype, "toBaseFactor", 2);
|
|
250
|
+
__decorateClass([
|
|
251
|
+
Property({ name: "sort_order", type: "integer", default: 0 })
|
|
252
|
+
], CatalogProductUnitConversion.prototype, "sortOrder", 2);
|
|
253
|
+
__decorateClass([
|
|
254
|
+
Property({ name: "is_active", type: "boolean", default: true })
|
|
255
|
+
], CatalogProductUnitConversion.prototype, "isActive", 2);
|
|
256
|
+
__decorateClass([
|
|
257
|
+
Property({ name: "metadata", type: "jsonb", nullable: true })
|
|
258
|
+
], CatalogProductUnitConversion.prototype, "metadata", 2);
|
|
259
|
+
__decorateClass([
|
|
260
|
+
Property({ name: "created_at", type: Date, onCreate: () => /* @__PURE__ */ new Date() })
|
|
261
|
+
], CatalogProductUnitConversion.prototype, "createdAt", 2);
|
|
262
|
+
__decorateClass([
|
|
263
|
+
Property({ name: "updated_at", type: Date, onUpdate: () => /* @__PURE__ */ new Date() })
|
|
264
|
+
], CatalogProductUnitConversion.prototype, "updatedAt", 2);
|
|
265
|
+
__decorateClass([
|
|
266
|
+
Property({ name: "deleted_at", type: Date, nullable: true })
|
|
267
|
+
], CatalogProductUnitConversion.prototype, "deletedAt", 2);
|
|
268
|
+
CatalogProductUnitConversion = __decorateClass([
|
|
269
|
+
Entity({ tableName: "catalog_product_unit_conversions" }),
|
|
270
|
+
Index({
|
|
271
|
+
name: "catalog_product_unit_conversions_scope_idx",
|
|
272
|
+
properties: ["organizationId", "tenantId", "product"]
|
|
273
|
+
}),
|
|
274
|
+
Unique({
|
|
275
|
+
name: "catalog_product_unit_conversions_unique",
|
|
276
|
+
properties: ["product", "unitCode"]
|
|
277
|
+
})
|
|
278
|
+
], CatalogProductUnitConversion);
|
|
279
|
+
OptionalProps;
|
|
195
280
|
let CatalogProductCategory = class {
|
|
196
281
|
constructor() {
|
|
197
282
|
this.depth = 0;
|
|
@@ -777,6 +862,7 @@ export {
|
|
|
777
862
|
CatalogProductPrice,
|
|
778
863
|
CatalogProductTag,
|
|
779
864
|
CatalogProductTagAssignment,
|
|
865
|
+
CatalogProductUnitConversion,
|
|
780
866
|
CatalogProductVariant,
|
|
781
867
|
CatalogProductVariantRelation
|
|
782
868
|
};
|