@open-mercato/core 0.4.7-develop-80fa80ebe3 → 0.4.7-develop-4381462230
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/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.js +10 -3
- package/dist/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.js.map +2 -2
- package/dist/modules/catalog/backend/catalog/products/[productId]/variants/create/page.js +10 -3
- package/dist/modules/catalog/backend/catalog/products/[productId]/variants/create/page.js.map +2 -2
- package/dist/modules/catalog/components/products/variantForm.js +13 -0
- package/dist/modules/catalog/components/products/variantForm.js.map +2 -2
- package/dist/modules/directory/backend/directory/organizations/create/page.js +2 -1
- package/dist/modules/directory/backend/directory/organizations/create/page.js.map +2 -2
- package/package.json +2 -2
- package/src/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.tsx +9 -2
- package/src/modules/catalog/backend/catalog/products/[productId]/variants/create/page.tsx +9 -2
- package/src/modules/catalog/components/products/variantForm.ts +17 -1
- package/src/modules/catalog/i18n/de.json +1 -0
- package/src/modules/catalog/i18n/en.json +1 -0
- package/src/modules/catalog/i18n/es.json +1 -0
- package/src/modules/catalog/i18n/pl.json +1 -0
- package/src/modules/directory/backend/directory/organizations/create/page.tsx +2 -1
- package/src/modules/directory/i18n/de.json +1 -0
- package/src/modules/directory/i18n/en.json +1 -0
- package/src/modules/directory/i18n/es.json +1 -0
- package/src/modules/directory/i18n/pl.json +1 -0
package/dist/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.js
CHANGED
|
@@ -15,11 +15,13 @@ import { SendObjectMessageDialog } from "@open-mercato/ui/backend/messages";
|
|
|
15
15
|
import {
|
|
16
16
|
createVariantInitialValues,
|
|
17
17
|
normalizeOptionSchema,
|
|
18
|
-
mapPriceItemToDraft
|
|
18
|
+
mapPriceItemToDraft,
|
|
19
|
+
findInvalidVariantPriceKinds
|
|
19
20
|
} from "@open-mercato/core/modules/catalog/components/products/variantForm";
|
|
20
21
|
import {
|
|
21
22
|
normalizePriceKindSummary
|
|
22
23
|
} from "@open-mercato/core/modules/catalog/components/products/productForm";
|
|
24
|
+
import { parseNumericInput } from "@open-mercato/core/modules/catalog/components/products/productFormUtils";
|
|
23
25
|
import {
|
|
24
26
|
VariantBasicsSection,
|
|
25
27
|
VariantOptionValuesSection,
|
|
@@ -328,6 +330,11 @@ function EditVariantPage({ params }) {
|
|
|
328
330
|
const message = t("catalog.variants.form.errors.nameRequired", "Provide the variant name.");
|
|
329
331
|
throw createCrudFormError(message, { name: message });
|
|
330
332
|
}
|
|
333
|
+
const invalidPriceKinds = findInvalidVariantPriceKinds(priceKinds, values.prices);
|
|
334
|
+
if (invalidPriceKinds.length) {
|
|
335
|
+
const message = t("catalog.variants.form.errors.invalidPrice", "Provide a valid non-negative price.");
|
|
336
|
+
throw createCrudFormError(message, { prices: message });
|
|
337
|
+
}
|
|
331
338
|
const resolveTaxRateValue = (taxRateId) => {
|
|
332
339
|
if (!taxRateId) return null;
|
|
333
340
|
const match = taxRates.find((rate) => rate.id === taxRateId);
|
|
@@ -501,8 +508,8 @@ async function syncVariantPricesUpdate({
|
|
|
501
508
|
}
|
|
502
509
|
continue;
|
|
503
510
|
}
|
|
504
|
-
const numeric =
|
|
505
|
-
if (Number.
|
|
511
|
+
const numeric = parseNumericInput(amount);
|
|
512
|
+
if (!Number.isFinite(numeric) || numeric < 0) continue;
|
|
506
513
|
const payload = {
|
|
507
514
|
productId,
|
|
508
515
|
variantId,
|
package/dist/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../../../../src/modules/catalog/backend/catalog/products/%5BproductId%5D/variants/%5BvariantId%5D/page.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { useRouter } from 'next/navigation'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { CrudForm, type CrudFormGroup } from '@open-mercato/ui/backend/CrudForm'\nimport { createCrud, updateCrud, deleteCrud } from '@open-mercato/ui/backend/utils/crud'\nimport { createCrudFormError } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { collectCustomFieldValues } from '@open-mercato/ui/backend/utils/customFieldValues'\nimport { apiCall, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { E } from '#generated/entities.ids.generated'\nimport { SendObjectMessageDialog } from '@open-mercato/ui/backend/messages'\nimport {\n type VariantFormValues,\n type VariantPriceDraft,\n type OptionDefinition,\n createVariantInitialValues,\n normalizeOptionSchema,\n mapPriceItemToDraft,\n} from '@open-mercato/core/modules/catalog/components/products/variantForm'\nimport {\n type PriceKindSummary,\n type PriceKindApiPayload,\n type TaxRateSummary,\n normalizePriceKindSummary,\n} from '@open-mercato/core/modules/catalog/components/products/productForm'\nimport {\n VariantBasicsSection,\n VariantOptionValuesSection,\n VariantDimensionsSection,\n VariantMetadataSection,\n VariantPricesSection,\n VariantMediaSection,\n} from '@open-mercato/core/modules/catalog/components/products/VariantBuilder'\nimport type { ProductMediaItem } from '@open-mercato/core/modules/catalog/components/products/ProductMediaManager'\nimport { buildAttachmentImageUrl, slugifyAttachmentFileName } from '@open-mercato/core/modules/attachments/lib/imageUrls'\nimport { fetchOptionSchemaTemplate } from '../../../optionSchemaClient'\nimport CreateVariantPage from '../create/page'\n\ntype VariantResponse = {\n items?: Array<Record<string, unknown>>\n}\n\ntype ProductResponse = {\n items?: Array<{\n id?: string\n title?: string | null\n metadata?: Record<string, unknown> | null\n tax_rate_id?: string | null\n taxRateId?: string | null\n tax_rate?: number | string | null\n taxRate?: number | string | null\n }>\n}\n\ntype PriceListResponse = {\n items?: Array<Record<string, unknown>>\n}\n\ntype AttachmentListResponse = {\n items?: ProductMediaItem[]\n}\n\nfunction resolveVariantPriceLabel(prices: Record<string, VariantPriceDraft> | undefined): string | null {\n if (!prices || typeof prices !== 'object') return null\n const entries = Object.values(prices)\n for (const entry of entries) {\n const amount = typeof entry?.amount === 'string' ? entry.amount.trim() : ''\n if (!amount) continue\n const currencyCode =\n typeof entry.currencyCode === 'string' && entry.currencyCode.trim().length\n ? entry.currencyCode.trim().toUpperCase()\n : null\n return currencyCode ? `${currencyCode} ${amount}` : amount\n }\n return null\n}\n\nexport default function EditVariantPage({ params }: { params?: { productId?: string; variantId?: string } }) {\n const router = useRouter()\n const t = useT()\n const productId = params?.productId ? String(params.productId) : null\n const variantId = params?.variantId ? String(params.variantId) : null\n const isCreateSentinel = variantId === 'create'\n const [priceKinds, setPriceKinds] = React.useState<PriceKindSummary[]>([])\n const [taxRates, setTaxRates] = React.useState<TaxRateSummary[]>([])\n const [optionDefinitions, setOptionDefinitions] = React.useState<OptionDefinition[]>([])\n const [initialValues, setInitialValues] = React.useState<VariantFormValues | null>(null)\n const [existingPriceIds, setExistingPriceIds] = React.useState<Record<string, string>>({})\n const [loading, setLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [currentProductId, setCurrentProductId] = React.useState<string | null>(productId)\n const [productTitle, setProductTitle] = React.useState<string>('')\n const [productTaxRateId, setProductTaxRateId] = React.useState<string | null>(null)\n const [productTaxRate, setProductTaxRate] = React.useState<number | null>(null)\n\n React.useEffect(() => {\n const loadPriceKinds = async () => {\n try {\n const payload = await readApiResultOrThrow<{ items?: PriceKindApiPayload[] }>(\n '/api/catalog/price-kinds?pageSize=100',\n undefined,\n { errorMessage: t('catalog.priceKinds.errors.load', 'Failed to load price kinds.') },\n )\n const items = Array.isArray(payload.items) ? payload.items : []\n setPriceKinds(items.map((item) => normalizePriceKindSummary(item)).filter((item): item is PriceKindSummary => !!item))\n } catch (err) {\n console.error('catalog.price-kinds.fetch failed', err)\n setPriceKinds([])\n }\n }\n loadPriceKinds().catch(() => {})\n }, [t])\n\n React.useEffect(() => {\n const loadTaxRates = async () => {\n try {\n const payload = await readApiResultOrThrow<{ items?: Array<Record<string, unknown>> }>(\n '/api/sales/tax-rates?pageSize=200',\n undefined,\n { errorMessage: t('catalog.products.create.taxRates.error', 'Failed to load tax rates.'), fallback: { items: [] } },\n )\n const items = Array.isArray(payload.items) ? payload.items : []\n setTaxRates(\n items.map((item) => {\n const rawRate = typeof item.rate === 'number' ? item.rate : Number(item.rate ?? Number.NaN)\n return {\n id: String(item.id),\n name:\n typeof item.name === 'string' && item.name.trim().length\n ? item.name\n : t('catalog.products.create.taxRates.unnamed', 'Untitled tax rate'),\n code: typeof item.code === 'string' && item.code.trim().length ? item.code : null,\n rate: Number.isFinite(rawRate) ? rawRate : null,\n isDefault: Boolean(\n typeof item.isDefault === 'boolean'\n ? item.isDefault\n : typeof item.is_default === 'boolean'\n ? item.is_default\n : false,\n ),\n }\n }),\n )\n } catch (err) {\n console.error('sales.tax-rates.fetch failed', err)\n setTaxRates([])\n }\n }\n loadTaxRates().catch(() => {})\n }, [t])\n\n React.useEffect(() => {\n if (!variantId || isCreateSentinel || priceKinds.length === 0) return\n let cancelled = false\n async function load() {\n setLoading(true)\n setError(null)\n try {\n const variantRes = await apiCall<VariantResponse>(\n `/api/catalog/variants?id=${encodeURIComponent(variantId!)}&page=1&pageSize=1`,\n )\n if (!variantRes.ok) throw new Error('load_variant_failed')\n const record = Array.isArray(variantRes.result?.items) ? variantRes.result?.items?.[0] : undefined\n if (!record) throw new Error(t('catalog.variants.form.errors.notFound', 'Variant not found.'))\n const resolvedProductId =\n typeof record.product_id === 'string'\n ? record.product_id\n : typeof record.productId === 'string'\n ? record.productId\n : currentProductId\n if (resolvedProductId) setCurrentProductId(resolvedProductId)\n const metadata = typeof record.metadata === 'object' && record.metadata ? { ...(record.metadata as Record<string, unknown>) } : {}\n const attachments = await fetchVariantAttachments(variantId!)\n const priceDrafts = await loadVariantPrices(variantId!, priceKinds)\n const priceIdMap: Record<string, string> = {}\n Object.entries(priceDrafts).forEach(([kindId, draft]) => {\n if (draft.priceId) priceIdMap[kindId] = draft.priceId\n })\n setExistingPriceIds(priceIdMap)\n const customDefaults = extractCustomFieldValues(record)\n let loadedOptionDefinitions: OptionDefinition[] = []\n if (resolvedProductId) {\n const productRes = await apiCall<ProductResponse>(\n `/api/catalog/products?id=${encodeURIComponent(resolvedProductId)}&page=1&pageSize=1`,\n )\n if (productRes.ok) {\n const product = Array.isArray(productRes.result?.items) ? productRes.result?.items?.[0] : undefined\n if (product) {\n setProductTitle(typeof product.title === 'string' ? product.title : '')\n const taxRateId =\n typeof (product as any).tax_rate_id === 'string'\n ? (product as any).tax_rate_id\n : typeof (product as any).taxRateId === 'string'\n ? (product as any).taxRateId\n : null\n const taxRateValueRaw =\n typeof (product as any).tax_rate === 'number'\n ? (product as any).tax_rate\n : typeof (product as any).tax_rate === 'string'\n ? Number((product as any).tax_rate)\n : typeof (product as any).taxRate === 'number'\n ? (product as any).taxRate\n : typeof (product as any).taxRate === 'string'\n ? Number((product as any).taxRate)\n : null\n const taxRateValue = Number.isFinite(taxRateValueRaw) ? Number(taxRateValueRaw) : null\n setProductTaxRateId(taxRateId)\n setProductTaxRate(taxRateValue)\n const productMetadata = (product.metadata ?? {}) as Record<string, unknown>\n const optionSchemaId =\n typeof (product as any).option_schema_id === 'string'\n ? (product as any).option_schema_id\n : typeof (product as any).optionSchemaId === 'string'\n ? (product as any).optionSchemaId\n : null\n let schemaSource: unknown =\n productMetadata.optionSchema ?? (productMetadata.option_schema as unknown)\n if (optionSchemaId) {\n const template = await fetchOptionSchemaTemplate(optionSchemaId)\n if (template?.schema?.options) {\n schemaSource = template.schema.options.map((option) => ({\n code: option.code,\n label: option.label,\n values: Array.isArray(option.choices)\n ? option.choices.map((choice) => ({\n id: choice.code ?? undefined,\n label: choice.label ?? choice.code ?? '',\n }))\n : [],\n }))\n }\n }\n loadedOptionDefinitions = normalizeOptionSchema(schemaSource)\n setOptionDefinitions(loadedOptionDefinitions)\n }\n }\n }\n if (!cancelled) {\n const optionValues =\n typeof record.option_values === 'object' && record.option_values\n ? { ...(record.option_values as Record<string, string>) }\n : typeof record.optionValues === 'object' && record.optionValues\n ? { ...(record.optionValues as Record<string, string>) }\n : {}\n const normalizedOptionValues = reconcileOptionValues(optionValues, loadedOptionDefinitions)\n const defaultMediaId =\n typeof record.default_media_id === 'string'\n ? record.default_media_id\n : typeof record.defaultMediaId === 'string'\n ? record.defaultMediaId\n : attachments[0]?.id ?? null\n const defaultMediaUrl =\n typeof record.default_media_url === 'string'\n ? record.default_media_url\n : typeof record.defaultMediaUrl === 'string'\n ? record.defaultMediaUrl\n : ''\n const base = createVariantInitialValues()\n setInitialValues({\n ...base,\n mediaDraftId: variantId!,\n name: typeof record.name === 'string' ? record.name : '',\n sku: typeof record.sku === 'string' ? record.sku : '',\n barcode: typeof record.barcode === 'string' ? record.barcode : '',\n isDefault: record.is_default === true || record.isDefault === true,\n isActive: record.is_active !== false && record.isActive !== false,\n optionValues: normalizedOptionValues,\n metadata,\n mediaItems: attachments,\n defaultMediaId,\n defaultMediaUrl,\n prices: priceDrafts,\n taxRateId:\n typeof (record as any).tax_rate_id === 'string'\n ? (record as any).tax_rate_id\n : typeof (record as any).taxRateId === 'string'\n ? (record as any).taxRateId\n : null,\n customFieldsetCode:\n typeof record.custom_fieldset_code === 'string'\n ? record.custom_fieldset_code\n : typeof record.customFieldsetCode === 'string'\n ? record.customFieldsetCode\n : null,\n ...customDefaults,\n })\n }\n } catch (err) {\n console.error('catalog.variants.load.failed', err)\n if (!cancelled) {\n const message = err instanceof Error && err.message ? err.message : t('catalog.variants.form.errors.load', 'Failed to load variant.')\n setError(message)\n }\n } finally {\n if (!cancelled) setLoading(false)\n }\n }\n load()\n return () => { cancelled = true }\n }, [variantId, t, currentProductId, priceKinds])\n\n const groups = React.useMemo<CrudFormGroup[]>(() => {\n const list: CrudFormGroup[] = [\n {\n id: 'general',\n column: 1,\n title: t('catalog.variants.form.nameLabel', 'Name'),\n component: ({ values, setValue, errors }) => (\n <VariantBasicsSection values={values as VariantFormValues} setValue={setValue} errors={errors} />\n ),\n },\n {\n id: 'metadata',\n column: 1,\n title: t('catalog.products.edit.metadata.title', 'Metadata'),\n description: t('catalog.products.edit.metadata.hint', 'Attach structured key/value pairs for integrations.'),\n component: ({ values, setValue }) => (\n <VariantMetadataSection values={values as VariantFormValues} setValue={setValue} showIntro={false} embedded />\n ),\n },\n {\n id: 'prices',\n column: 1,\n title: t('catalog.variants.form.pricesLabel', 'Prices'),\n description: t('catalog.variants.form.pricesHint', 'Populate list prices per price kind.'),\n component: ({ values, setValue }) => (\n <VariantPricesSection\n values={values as VariantFormValues}\n setValue={setValue}\n priceKinds={priceKinds}\n taxRates={taxRates}\n showHeader={false}\n embedded\n />\n ),\n },\n {\n id: 'media',\n column: 1,\n title: t('catalog.variants.form.media', 'Media'),\n component: ({ values, setValue }) => (\n <VariantMediaSection values={values as VariantFormValues} setValue={setValue} showLabel={false} />\n ),\n },\n ]\n\n if (optionDefinitions.length) {\n list.push({\n id: 'options',\n column: 2,\n title: t('catalog.variants.form.options', 'Option values'),\n component: ({ values, setValue }) => (\n <VariantOptionValuesSection\n values={values as VariantFormValues}\n setValue={setValue}\n optionDefinitions={optionDefinitions}\n showHeading={false}\n />\n ),\n })\n }\n\n list.push({\n id: 'dimensions',\n column: 2,\n title: t('catalog.variants.form.dimensions', 'Dimensions & weight'),\n component: ({ values, setValue }) => (\n <VariantDimensionsSection values={values as VariantFormValues} setValue={setValue} showHeading={false} />\n ),\n })\n\n list.push({\n id: 'custom',\n column: 2,\n title: t('catalog.variants.form.customFields', 'Custom attributes'),\n kind: 'customFields',\n })\n\n return list\n }, [optionDefinitions, priceKinds, t, taxRates])\n\n if (isCreateSentinel) {\n if (!productId) {\n return (\n <Page>\n <PageBody>\n <div className=\"rounded border border-destructive/40 bg-destructive/10 px-4 py-3 text-sm text-destructive\">\n {t('catalog.variants.form.errors.productMissing', 'Product identifier is missing.')}\n </div>\n </PageBody>\n </Page>\n )\n }\n return <CreateVariantPage params={{ productId }} />\n }\n\n if (!variantId || !currentProductId) {\n return (\n <Page>\n <PageBody>\n <div className=\"rounded border border-destructive/40 bg-destructive/10 px-4 py-3 text-sm text-destructive\">\n {t('catalog.variants.form.errors.variantMissing', 'Variant identifier is missing.')}\n </div>\n </PageBody>\n </Page>\n )\n }\n\n const formTitle = productTitle\n ? t('catalog.variants.form.editTitleFor', 'Edit variant \u2022 {{title}}').replace('{{title}}', productTitle)\n : t('catalog.variants.form.editTitle', 'Edit variant')\n const productVariantsHref = `/backend/catalog/products/${currentProductId}#variants`\n\n return (\n <Page>\n <PageBody>\n {error ? (\n <div className=\"mb-4 rounded border border-destructive/40 bg-destructive/10 px-4 py-3 text-sm text-destructive\">{error}</div>\n ) : null}\n <CrudForm<VariantFormValues>\n title={formTitle}\n backHref={productVariantsHref}\n versionHistory={{ resourceKind: 'catalog.variant', resourceId: variantId ? String(variantId) : '' }}\n extraActions={(\n <SendObjectMessageDialog\n object={{\n entityModule: 'catalog',\n entityType: 'variant',\n entityId: variantId,\n previewData: {\n title:\n (typeof initialValues?.name === 'string' && initialValues.name.trim().length\n ? initialValues.name\n : variantId),\n metadata: {\n [t('catalog.variants.form.skuLabel')]:\n (typeof initialValues?.sku === 'string' && initialValues.sku.trim().length\n ? initialValues.sku\n : '-'),\n [t('catalog.variants.form.pricesLabel')]:\n resolveVariantPriceLabel(initialValues?.prices) ?? '-',\n },\n },\n }}\n viewHref={`/backend/catalog/products/${currentProductId}/variants/${variantId}`}\n />\n )}\n fields={[]}\n groups={groups}\n entityId={E.catalog.catalog_product_variant}\n customFieldsetBindings={{ [E.catalog.catalog_product_variant]: { valueKey: 'customFieldsetCode' } }}\n initialValues={initialValues ?? undefined}\n isLoading={loading}\n loadingMessage={t('catalog.variants.form.loading', 'Loading variant...')}\n submitLabel={t('catalog.variants.form.save', 'Save changes')}\n cancelHref={productVariantsHref}\n onSubmit={async (values) => {\n const name = values.name?.trim()\n if (!name) {\n const message = t('catalog.variants.form.errors.nameRequired', 'Provide the variant name.')\n throw createCrudFormError(message, { name: message })\n }\n const resolveTaxRateValue = (taxRateId?: string | null) => {\n if (!taxRateId) return null\n const match = taxRates.find((rate) => rate.id === taxRateId)\n return typeof match?.rate === 'number' && Number.isFinite(match.rate) ? match.rate : null\n }\n const resolvedTaxRateId = values.taxRateId ?? productTaxRateId ?? null\n const resolvedTaxRateValue =\n values.taxRateId && resolvedTaxRateId\n ? resolveTaxRateValue(resolvedTaxRateId)\n : productTaxRateId\n ? resolveTaxRateValue(productTaxRateId) ?? productTaxRate\n : productTaxRate ?? null\n const metadata = typeof values.metadata === 'object' && values.metadata ? { ...values.metadata } : {}\n const defaultMediaEntry = values.defaultMediaId\n ? (Array.isArray(values.mediaItems) ? values.mediaItems : []).find((item) => item.id === values.defaultMediaId)\n : null\n const defaultMediaUrl = defaultMediaEntry\n ? buildAttachmentImageUrl(defaultMediaEntry.id, {\n slug: slugifyAttachmentFileName(defaultMediaEntry.fileName),\n })\n : null\n const payload: Record<string, unknown> = {\n id: variantId,\n productId: currentProductId,\n name,\n sku: values.sku?.trim() || undefined,\n barcode: values.barcode?.trim() || undefined,\n isDefault: Boolean(values.isDefault),\n isActive: values.isActive !== false,\n optionValues: Object.keys(values.optionValues ?? {}).length ? values.optionValues : undefined,\n metadata,\n defaultMediaId: values.defaultMediaId ?? undefined,\n defaultMediaUrl: defaultMediaUrl ?? undefined,\n customFieldsetCode: values.customFieldsetCode?.trim().length ? values.customFieldsetCode : undefined,\n taxRateId: resolvedTaxRateId,\n taxRate: resolvedTaxRateValue,\n }\n const customFields = collectCustomFieldValues(values)\n if (Object.keys(customFields).length) payload.customFields = customFields\n\n await updateCrud('catalog/variants', payload)\n await syncVariantPricesUpdate({\n priceKinds,\n priceDrafts: values.prices ?? {},\n existingPriceIds,\n productId: currentProductId,\n variantId,\n taxRates,\n taxRateId: values.taxRateId,\n productTaxRateId,\n productTaxRate,\n })\n flash(t('catalog.variants.form.updated', 'Variant updated.'), 'success')\n router.push(productVariantsHref)\n }}\n onDelete={async () => {\n await deleteCrud('catalog/variants', variantId!, {\n errorMessage: t('catalog.variants.form.deleteError', 'Failed to delete variant.'),\n })\n flash(t('catalog.variants.form.deleted', 'Variant deleted.'), 'success')\n router.push(productVariantsHref)\n }}\n deleteRedirect={productVariantsHref}\n />\n </PageBody>\n </Page>\n )\n}\n\nfunction reconcileOptionValues(\n optionValues: Record<string, string>,\n optionDefinitions: OptionDefinition[],\n): Record<string, string> {\n if (!optionValues || !optionDefinitions.length) {\n return optionValues ?? {}\n }\n const remaining = new Map(Object.entries(optionValues))\n const normalized: Record<string, string> = {}\n\n for (const option of optionDefinitions) {\n const code = option.code?.trim()\n if (!code) continue\n if (remaining.has(code)) {\n const value = remaining.get(code)\n if (value !== undefined) {\n normalized[code] = value\n }\n remaining.delete(code)\n continue\n }\n const matchKey = findOptionKeyByValue(remaining, option.values)\n if (matchKey) {\n const value = remaining.get(matchKey)\n if (value !== undefined) {\n normalized[code] = value\n }\n remaining.delete(matchKey)\n }\n }\n\n remaining.forEach((value, key) => {\n if (normalized[key] === undefined) {\n normalized[key] = value\n }\n })\n\n return normalized\n}\n\nfunction findOptionKeyByValue(\n candidates: Map<string, string>,\n optionValues: { id: string; label: string }[],\n): string | null {\n if (!optionValues.length) return null\n const matches: string[] = []\n candidates.forEach((value, key) => {\n if (optionValues.some((entry) => entry.label === value)) {\n matches.push(key)\n }\n })\n return matches.length === 1 ? matches[0] : null\n}\n\nasync function fetchVariantAttachments(variantId: string): Promise<ProductMediaItem[]> {\n try {\n const res = await apiCall<AttachmentListResponse>(\n `/api/attachments?entityId=${encodeURIComponent(E.catalog.catalog_product_variant)}&recordId=${encodeURIComponent(variantId)}`,\n )\n if (!res.ok) return []\n return Array.isArray(res.result?.items) ? res.result?.items ?? [] : []\n } catch (err) {\n console.error('catalog.variants.attachments.load', err)\n return []\n }\n}\n\nasync function loadVariantPrices(variantId: string, priceKinds: PriceKindSummary[]): Promise<Record<string, VariantPriceDraft>> {\n const kindDisplayModes = new Map(priceKinds.map((k) => [k.id, k.displayMode]))\n const drafts: Record<string, VariantPriceDraft> = {}\n const pageSize = 100\n let page = 1\n try {\n while (true) {\n const res = await apiCall<PriceListResponse>(\n `/api/catalog/prices?variantId=${encodeURIComponent(variantId)}&page=${page}&pageSize=${pageSize}`,\n )\n if (!res.ok) break\n const items = Array.isArray(res.result?.items) ? res.result?.items : []\n for (const item of items) {\n const draft = mapPriceItemToDraft(item as Record<string, unknown>, kindDisplayModes)\n if (draft) drafts[draft.priceKindId] = draft\n }\n if (items.length < pageSize) break\n page += 1\n }\n } catch (err) {\n console.error('catalog.variants.prices.load', err)\n }\n return drafts\n}\n\nfunction extractCustomFieldValues(record: Record<string, unknown>): Record<string, unknown> {\n const customValues: Record<string, unknown> = {}\n Object.entries(record).forEach(([key, value]) => {\n if (key.startsWith('cf_')) customValues[key] = value\n else if (key.startsWith('cf:')) customValues[`cf_${key.slice(3)}`] = value\n })\n return customValues\n}\n\nasync function syncVariantPricesUpdate({\n priceKinds,\n priceDrafts,\n existingPriceIds,\n productId,\n variantId,\n taxRates,\n taxRateId,\n productTaxRateId,\n productTaxRate,\n}: {\n priceKinds: PriceKindSummary[]\n priceDrafts: Record<string, VariantPriceDraft>\n existingPriceIds: Record<string, string>\n productId: string\n variantId: string\n taxRates: TaxRateSummary[]\n taxRateId: string | null\n productTaxRateId?: string | null\n productTaxRate?: number | null\n}): Promise<void> {\n const selectedTaxRate = taxRates.find((rate) => rate.id === taxRateId) ?? null\n const fallbackProductTaxRate =\n !selectedTaxRate && productTaxRateId\n ? taxRates.find((rate) => rate.id === productTaxRateId) ?? null\n : null\n const resolvedTaxRateValue =\n selectedTaxRate?.rate ??\n fallbackProductTaxRate?.rate ??\n (Number.isFinite(productTaxRate ?? null) ? productTaxRate ?? null : null)\n const resolvedTaxRateId = (selectedTaxRate ?? fallbackProductTaxRate)?.id ?? null\n for (const kind of priceKinds) {\n const draft = priceDrafts?.[kind.id]\n const amount = typeof draft?.amount === 'string' ? draft.amount.trim() : ''\n const existingId = draft?.priceId ?? existingPriceIds[kind.id]\n if (!amount) {\n if (existingId) {\n try {\n await deleteCrud('catalog/prices', existingId)\n } catch (err) {\n console.error('catalog.prices.delete', err)\n }\n }\n continue\n }\n const numeric = Number(amount)\n if (Number.isNaN(numeric) || numeric < 0) continue\n const payload: Record<string, unknown> = {\n productId,\n variantId,\n priceKindId: kind.id,\n currencyCode: kind.currencyCode ?? undefined,\n }\n if (resolvedTaxRateId) payload.taxRateId = resolvedTaxRateId\n else if (typeof resolvedTaxRateValue === 'number' && Number.isFinite(resolvedTaxRateValue)) payload.taxRate = resolvedTaxRateValue\n if (kind.displayMode === 'including-tax') payload.unitPriceGross = numeric\n else payload.unitPriceNet = numeric\n if (existingId) {\n await updateCrud('catalog/prices', { id: existingId, ...payload })\n } else {\n await createCrud('catalog/prices', payload)\n }\n }\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { useRouter } from 'next/navigation'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { CrudForm, type CrudFormGroup } from '@open-mercato/ui/backend/CrudForm'\nimport { createCrud, updateCrud, deleteCrud } from '@open-mercato/ui/backend/utils/crud'\nimport { createCrudFormError } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { collectCustomFieldValues } from '@open-mercato/ui/backend/utils/customFieldValues'\nimport { apiCall, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { E } from '#generated/entities.ids.generated'\nimport { SendObjectMessageDialog } from '@open-mercato/ui/backend/messages'\nimport {\n type VariantFormValues,\n type VariantPriceDraft,\n type OptionDefinition,\n createVariantInitialValues,\n normalizeOptionSchema,\n mapPriceItemToDraft,\n findInvalidVariantPriceKinds,\n} from '@open-mercato/core/modules/catalog/components/products/variantForm'\nimport {\n type PriceKindSummary,\n type PriceKindApiPayload,\n type TaxRateSummary,\n normalizePriceKindSummary,\n} from '@open-mercato/core/modules/catalog/components/products/productForm'\nimport { parseNumericInput } from '@open-mercato/core/modules/catalog/components/products/productFormUtils'\nimport {\n VariantBasicsSection,\n VariantOptionValuesSection,\n VariantDimensionsSection,\n VariantMetadataSection,\n VariantPricesSection,\n VariantMediaSection,\n} from '@open-mercato/core/modules/catalog/components/products/VariantBuilder'\nimport type { ProductMediaItem } from '@open-mercato/core/modules/catalog/components/products/ProductMediaManager'\nimport { buildAttachmentImageUrl, slugifyAttachmentFileName } from '@open-mercato/core/modules/attachments/lib/imageUrls'\nimport { fetchOptionSchemaTemplate } from '../../../optionSchemaClient'\nimport CreateVariantPage from '../create/page'\n\ntype VariantResponse = {\n items?: Array<Record<string, unknown>>\n}\n\ntype ProductResponse = {\n items?: Array<{\n id?: string\n title?: string | null\n metadata?: Record<string, unknown> | null\n tax_rate_id?: string | null\n taxRateId?: string | null\n tax_rate?: number | string | null\n taxRate?: number | string | null\n }>\n}\n\ntype PriceListResponse = {\n items?: Array<Record<string, unknown>>\n}\n\ntype AttachmentListResponse = {\n items?: ProductMediaItem[]\n}\n\nfunction resolveVariantPriceLabel(prices: Record<string, VariantPriceDraft> | undefined): string | null {\n if (!prices || typeof prices !== 'object') return null\n const entries = Object.values(prices)\n for (const entry of entries) {\n const amount = typeof entry?.amount === 'string' ? entry.amount.trim() : ''\n if (!amount) continue\n const currencyCode =\n typeof entry.currencyCode === 'string' && entry.currencyCode.trim().length\n ? entry.currencyCode.trim().toUpperCase()\n : null\n return currencyCode ? `${currencyCode} ${amount}` : amount\n }\n return null\n}\n\nexport default function EditVariantPage({ params }: { params?: { productId?: string; variantId?: string } }) {\n const router = useRouter()\n const t = useT()\n const productId = params?.productId ? String(params.productId) : null\n const variantId = params?.variantId ? String(params.variantId) : null\n const isCreateSentinel = variantId === 'create'\n const [priceKinds, setPriceKinds] = React.useState<PriceKindSummary[]>([])\n const [taxRates, setTaxRates] = React.useState<TaxRateSummary[]>([])\n const [optionDefinitions, setOptionDefinitions] = React.useState<OptionDefinition[]>([])\n const [initialValues, setInitialValues] = React.useState<VariantFormValues | null>(null)\n const [existingPriceIds, setExistingPriceIds] = React.useState<Record<string, string>>({})\n const [loading, setLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [currentProductId, setCurrentProductId] = React.useState<string | null>(productId)\n const [productTitle, setProductTitle] = React.useState<string>('')\n const [productTaxRateId, setProductTaxRateId] = React.useState<string | null>(null)\n const [productTaxRate, setProductTaxRate] = React.useState<number | null>(null)\n\n React.useEffect(() => {\n const loadPriceKinds = async () => {\n try {\n const payload = await readApiResultOrThrow<{ items?: PriceKindApiPayload[] }>(\n '/api/catalog/price-kinds?pageSize=100',\n undefined,\n { errorMessage: t('catalog.priceKinds.errors.load', 'Failed to load price kinds.') },\n )\n const items = Array.isArray(payload.items) ? payload.items : []\n setPriceKinds(items.map((item) => normalizePriceKindSummary(item)).filter((item): item is PriceKindSummary => !!item))\n } catch (err) {\n console.error('catalog.price-kinds.fetch failed', err)\n setPriceKinds([])\n }\n }\n loadPriceKinds().catch(() => {})\n }, [t])\n\n React.useEffect(() => {\n const loadTaxRates = async () => {\n try {\n const payload = await readApiResultOrThrow<{ items?: Array<Record<string, unknown>> }>(\n '/api/sales/tax-rates?pageSize=200',\n undefined,\n { errorMessage: t('catalog.products.create.taxRates.error', 'Failed to load tax rates.'), fallback: { items: [] } },\n )\n const items = Array.isArray(payload.items) ? payload.items : []\n setTaxRates(\n items.map((item) => {\n const rawRate = typeof item.rate === 'number' ? item.rate : Number(item.rate ?? Number.NaN)\n return {\n id: String(item.id),\n name:\n typeof item.name === 'string' && item.name.trim().length\n ? item.name\n : t('catalog.products.create.taxRates.unnamed', 'Untitled tax rate'),\n code: typeof item.code === 'string' && item.code.trim().length ? item.code : null,\n rate: Number.isFinite(rawRate) ? rawRate : null,\n isDefault: Boolean(\n typeof item.isDefault === 'boolean'\n ? item.isDefault\n : typeof item.is_default === 'boolean'\n ? item.is_default\n : false,\n ),\n }\n }),\n )\n } catch (err) {\n console.error('sales.tax-rates.fetch failed', err)\n setTaxRates([])\n }\n }\n loadTaxRates().catch(() => {})\n }, [t])\n\n React.useEffect(() => {\n if (!variantId || isCreateSentinel || priceKinds.length === 0) return\n let cancelled = false\n async function load() {\n setLoading(true)\n setError(null)\n try {\n const variantRes = await apiCall<VariantResponse>(\n `/api/catalog/variants?id=${encodeURIComponent(variantId!)}&page=1&pageSize=1`,\n )\n if (!variantRes.ok) throw new Error('load_variant_failed')\n const record = Array.isArray(variantRes.result?.items) ? variantRes.result?.items?.[0] : undefined\n if (!record) throw new Error(t('catalog.variants.form.errors.notFound', 'Variant not found.'))\n const resolvedProductId =\n typeof record.product_id === 'string'\n ? record.product_id\n : typeof record.productId === 'string'\n ? record.productId\n : currentProductId\n if (resolvedProductId) setCurrentProductId(resolvedProductId)\n const metadata = typeof record.metadata === 'object' && record.metadata ? { ...(record.metadata as Record<string, unknown>) } : {}\n const attachments = await fetchVariantAttachments(variantId!)\n const priceDrafts = await loadVariantPrices(variantId!, priceKinds)\n const priceIdMap: Record<string, string> = {}\n Object.entries(priceDrafts).forEach(([kindId, draft]) => {\n if (draft.priceId) priceIdMap[kindId] = draft.priceId\n })\n setExistingPriceIds(priceIdMap)\n const customDefaults = extractCustomFieldValues(record)\n let loadedOptionDefinitions: OptionDefinition[] = []\n if (resolvedProductId) {\n const productRes = await apiCall<ProductResponse>(\n `/api/catalog/products?id=${encodeURIComponent(resolvedProductId)}&page=1&pageSize=1`,\n )\n if (productRes.ok) {\n const product = Array.isArray(productRes.result?.items) ? productRes.result?.items?.[0] : undefined\n if (product) {\n setProductTitle(typeof product.title === 'string' ? product.title : '')\n const taxRateId =\n typeof (product as any).tax_rate_id === 'string'\n ? (product as any).tax_rate_id\n : typeof (product as any).taxRateId === 'string'\n ? (product as any).taxRateId\n : null\n const taxRateValueRaw =\n typeof (product as any).tax_rate === 'number'\n ? (product as any).tax_rate\n : typeof (product as any).tax_rate === 'string'\n ? Number((product as any).tax_rate)\n : typeof (product as any).taxRate === 'number'\n ? (product as any).taxRate\n : typeof (product as any).taxRate === 'string'\n ? Number((product as any).taxRate)\n : null\n const taxRateValue = Number.isFinite(taxRateValueRaw) ? Number(taxRateValueRaw) : null\n setProductTaxRateId(taxRateId)\n setProductTaxRate(taxRateValue)\n const productMetadata = (product.metadata ?? {}) as Record<string, unknown>\n const optionSchemaId =\n typeof (product as any).option_schema_id === 'string'\n ? (product as any).option_schema_id\n : typeof (product as any).optionSchemaId === 'string'\n ? (product as any).optionSchemaId\n : null\n let schemaSource: unknown =\n productMetadata.optionSchema ?? (productMetadata.option_schema as unknown)\n if (optionSchemaId) {\n const template = await fetchOptionSchemaTemplate(optionSchemaId)\n if (template?.schema?.options) {\n schemaSource = template.schema.options.map((option) => ({\n code: option.code,\n label: option.label,\n values: Array.isArray(option.choices)\n ? option.choices.map((choice) => ({\n id: choice.code ?? undefined,\n label: choice.label ?? choice.code ?? '',\n }))\n : [],\n }))\n }\n }\n loadedOptionDefinitions = normalizeOptionSchema(schemaSource)\n setOptionDefinitions(loadedOptionDefinitions)\n }\n }\n }\n if (!cancelled) {\n const optionValues =\n typeof record.option_values === 'object' && record.option_values\n ? { ...(record.option_values as Record<string, string>) }\n : typeof record.optionValues === 'object' && record.optionValues\n ? { ...(record.optionValues as Record<string, string>) }\n : {}\n const normalizedOptionValues = reconcileOptionValues(optionValues, loadedOptionDefinitions)\n const defaultMediaId =\n typeof record.default_media_id === 'string'\n ? record.default_media_id\n : typeof record.defaultMediaId === 'string'\n ? record.defaultMediaId\n : attachments[0]?.id ?? null\n const defaultMediaUrl =\n typeof record.default_media_url === 'string'\n ? record.default_media_url\n : typeof record.defaultMediaUrl === 'string'\n ? record.defaultMediaUrl\n : ''\n const base = createVariantInitialValues()\n setInitialValues({\n ...base,\n mediaDraftId: variantId!,\n name: typeof record.name === 'string' ? record.name : '',\n sku: typeof record.sku === 'string' ? record.sku : '',\n barcode: typeof record.barcode === 'string' ? record.barcode : '',\n isDefault: record.is_default === true || record.isDefault === true,\n isActive: record.is_active !== false && record.isActive !== false,\n optionValues: normalizedOptionValues,\n metadata,\n mediaItems: attachments,\n defaultMediaId,\n defaultMediaUrl,\n prices: priceDrafts,\n taxRateId:\n typeof (record as any).tax_rate_id === 'string'\n ? (record as any).tax_rate_id\n : typeof (record as any).taxRateId === 'string'\n ? (record as any).taxRateId\n : null,\n customFieldsetCode:\n typeof record.custom_fieldset_code === 'string'\n ? record.custom_fieldset_code\n : typeof record.customFieldsetCode === 'string'\n ? record.customFieldsetCode\n : null,\n ...customDefaults,\n })\n }\n } catch (err) {\n console.error('catalog.variants.load.failed', err)\n if (!cancelled) {\n const message = err instanceof Error && err.message ? err.message : t('catalog.variants.form.errors.load', 'Failed to load variant.')\n setError(message)\n }\n } finally {\n if (!cancelled) setLoading(false)\n }\n }\n load()\n return () => { cancelled = true }\n }, [variantId, t, currentProductId, priceKinds])\n\n const groups = React.useMemo<CrudFormGroup[]>(() => {\n const list: CrudFormGroup[] = [\n {\n id: 'general',\n column: 1,\n title: t('catalog.variants.form.nameLabel', 'Name'),\n component: ({ values, setValue, errors }) => (\n <VariantBasicsSection values={values as VariantFormValues} setValue={setValue} errors={errors} />\n ),\n },\n {\n id: 'metadata',\n column: 1,\n title: t('catalog.products.edit.metadata.title', 'Metadata'),\n description: t('catalog.products.edit.metadata.hint', 'Attach structured key/value pairs for integrations.'),\n component: ({ values, setValue }) => (\n <VariantMetadataSection values={values as VariantFormValues} setValue={setValue} showIntro={false} embedded />\n ),\n },\n {\n id: 'prices',\n column: 1,\n title: t('catalog.variants.form.pricesLabel', 'Prices'),\n description: t('catalog.variants.form.pricesHint', 'Populate list prices per price kind.'),\n component: ({ values, setValue }) => (\n <VariantPricesSection\n values={values as VariantFormValues}\n setValue={setValue}\n priceKinds={priceKinds}\n taxRates={taxRates}\n showHeader={false}\n embedded\n />\n ),\n },\n {\n id: 'media',\n column: 1,\n title: t('catalog.variants.form.media', 'Media'),\n component: ({ values, setValue }) => (\n <VariantMediaSection values={values as VariantFormValues} setValue={setValue} showLabel={false} />\n ),\n },\n ]\n\n if (optionDefinitions.length) {\n list.push({\n id: 'options',\n column: 2,\n title: t('catalog.variants.form.options', 'Option values'),\n component: ({ values, setValue }) => (\n <VariantOptionValuesSection\n values={values as VariantFormValues}\n setValue={setValue}\n optionDefinitions={optionDefinitions}\n showHeading={false}\n />\n ),\n })\n }\n\n list.push({\n id: 'dimensions',\n column: 2,\n title: t('catalog.variants.form.dimensions', 'Dimensions & weight'),\n component: ({ values, setValue }) => (\n <VariantDimensionsSection values={values as VariantFormValues} setValue={setValue} showHeading={false} />\n ),\n })\n\n list.push({\n id: 'custom',\n column: 2,\n title: t('catalog.variants.form.customFields', 'Custom attributes'),\n kind: 'customFields',\n })\n\n return list\n }, [optionDefinitions, priceKinds, t, taxRates])\n\n if (isCreateSentinel) {\n if (!productId) {\n return (\n <Page>\n <PageBody>\n <div className=\"rounded border border-destructive/40 bg-destructive/10 px-4 py-3 text-sm text-destructive\">\n {t('catalog.variants.form.errors.productMissing', 'Product identifier is missing.')}\n </div>\n </PageBody>\n </Page>\n )\n }\n return <CreateVariantPage params={{ productId }} />\n }\n\n if (!variantId || !currentProductId) {\n return (\n <Page>\n <PageBody>\n <div className=\"rounded border border-destructive/40 bg-destructive/10 px-4 py-3 text-sm text-destructive\">\n {t('catalog.variants.form.errors.variantMissing', 'Variant identifier is missing.')}\n </div>\n </PageBody>\n </Page>\n )\n }\n\n const formTitle = productTitle\n ? t('catalog.variants.form.editTitleFor', 'Edit variant \u2022 {{title}}').replace('{{title}}', productTitle)\n : t('catalog.variants.form.editTitle', 'Edit variant')\n const productVariantsHref = `/backend/catalog/products/${currentProductId}#variants`\n\n return (\n <Page>\n <PageBody>\n {error ? (\n <div className=\"mb-4 rounded border border-destructive/40 bg-destructive/10 px-4 py-3 text-sm text-destructive\">{error}</div>\n ) : null}\n <CrudForm<VariantFormValues>\n title={formTitle}\n backHref={productVariantsHref}\n versionHistory={{ resourceKind: 'catalog.variant', resourceId: variantId ? String(variantId) : '' }}\n extraActions={(\n <SendObjectMessageDialog\n object={{\n entityModule: 'catalog',\n entityType: 'variant',\n entityId: variantId,\n previewData: {\n title:\n (typeof initialValues?.name === 'string' && initialValues.name.trim().length\n ? initialValues.name\n : variantId),\n metadata: {\n [t('catalog.variants.form.skuLabel')]:\n (typeof initialValues?.sku === 'string' && initialValues.sku.trim().length\n ? initialValues.sku\n : '-'),\n [t('catalog.variants.form.pricesLabel')]:\n resolveVariantPriceLabel(initialValues?.prices) ?? '-',\n },\n },\n }}\n viewHref={`/backend/catalog/products/${currentProductId}/variants/${variantId}`}\n />\n )}\n fields={[]}\n groups={groups}\n entityId={E.catalog.catalog_product_variant}\n customFieldsetBindings={{ [E.catalog.catalog_product_variant]: { valueKey: 'customFieldsetCode' } }}\n initialValues={initialValues ?? undefined}\n isLoading={loading}\n loadingMessage={t('catalog.variants.form.loading', 'Loading variant...')}\n submitLabel={t('catalog.variants.form.save', 'Save changes')}\n cancelHref={productVariantsHref}\n onSubmit={async (values) => {\n const name = values.name?.trim()\n if (!name) {\n const message = t('catalog.variants.form.errors.nameRequired', 'Provide the variant name.')\n throw createCrudFormError(message, { name: message })\n }\n const invalidPriceKinds = findInvalidVariantPriceKinds(priceKinds, values.prices)\n if (invalidPriceKinds.length) {\n const message = t('catalog.variants.form.errors.invalidPrice', 'Provide a valid non-negative price.')\n throw createCrudFormError(message, { prices: message })\n }\n const resolveTaxRateValue = (taxRateId?: string | null) => {\n if (!taxRateId) return null\n const match = taxRates.find((rate) => rate.id === taxRateId)\n return typeof match?.rate === 'number' && Number.isFinite(match.rate) ? match.rate : null\n }\n const resolvedTaxRateId = values.taxRateId ?? productTaxRateId ?? null\n const resolvedTaxRateValue =\n values.taxRateId && resolvedTaxRateId\n ? resolveTaxRateValue(resolvedTaxRateId)\n : productTaxRateId\n ? resolveTaxRateValue(productTaxRateId) ?? productTaxRate\n : productTaxRate ?? null\n const metadata = typeof values.metadata === 'object' && values.metadata ? { ...values.metadata } : {}\n const defaultMediaEntry = values.defaultMediaId\n ? (Array.isArray(values.mediaItems) ? values.mediaItems : []).find((item) => item.id === values.defaultMediaId)\n : null\n const defaultMediaUrl = defaultMediaEntry\n ? buildAttachmentImageUrl(defaultMediaEntry.id, {\n slug: slugifyAttachmentFileName(defaultMediaEntry.fileName),\n })\n : null\n const payload: Record<string, unknown> = {\n id: variantId,\n productId: currentProductId,\n name,\n sku: values.sku?.trim() || undefined,\n barcode: values.barcode?.trim() || undefined,\n isDefault: Boolean(values.isDefault),\n isActive: values.isActive !== false,\n optionValues: Object.keys(values.optionValues ?? {}).length ? values.optionValues : undefined,\n metadata,\n defaultMediaId: values.defaultMediaId ?? undefined,\n defaultMediaUrl: defaultMediaUrl ?? undefined,\n customFieldsetCode: values.customFieldsetCode?.trim().length ? values.customFieldsetCode : undefined,\n taxRateId: resolvedTaxRateId,\n taxRate: resolvedTaxRateValue,\n }\n const customFields = collectCustomFieldValues(values)\n if (Object.keys(customFields).length) payload.customFields = customFields\n\n await updateCrud('catalog/variants', payload)\n await syncVariantPricesUpdate({\n priceKinds,\n priceDrafts: values.prices ?? {},\n existingPriceIds,\n productId: currentProductId,\n variantId,\n taxRates,\n taxRateId: values.taxRateId,\n productTaxRateId,\n productTaxRate,\n })\n flash(t('catalog.variants.form.updated', 'Variant updated.'), 'success')\n router.push(productVariantsHref)\n }}\n onDelete={async () => {\n await deleteCrud('catalog/variants', variantId!, {\n errorMessage: t('catalog.variants.form.deleteError', 'Failed to delete variant.'),\n })\n flash(t('catalog.variants.form.deleted', 'Variant deleted.'), 'success')\n router.push(productVariantsHref)\n }}\n deleteRedirect={productVariantsHref}\n />\n </PageBody>\n </Page>\n )\n}\n\nfunction reconcileOptionValues(\n optionValues: Record<string, string>,\n optionDefinitions: OptionDefinition[],\n): Record<string, string> {\n if (!optionValues || !optionDefinitions.length) {\n return optionValues ?? {}\n }\n const remaining = new Map(Object.entries(optionValues))\n const normalized: Record<string, string> = {}\n\n for (const option of optionDefinitions) {\n const code = option.code?.trim()\n if (!code) continue\n if (remaining.has(code)) {\n const value = remaining.get(code)\n if (value !== undefined) {\n normalized[code] = value\n }\n remaining.delete(code)\n continue\n }\n const matchKey = findOptionKeyByValue(remaining, option.values)\n if (matchKey) {\n const value = remaining.get(matchKey)\n if (value !== undefined) {\n normalized[code] = value\n }\n remaining.delete(matchKey)\n }\n }\n\n remaining.forEach((value, key) => {\n if (normalized[key] === undefined) {\n normalized[key] = value\n }\n })\n\n return normalized\n}\n\nfunction findOptionKeyByValue(\n candidates: Map<string, string>,\n optionValues: { id: string; label: string }[],\n): string | null {\n if (!optionValues.length) return null\n const matches: string[] = []\n candidates.forEach((value, key) => {\n if (optionValues.some((entry) => entry.label === value)) {\n matches.push(key)\n }\n })\n return matches.length === 1 ? matches[0] : null\n}\n\nasync function fetchVariantAttachments(variantId: string): Promise<ProductMediaItem[]> {\n try {\n const res = await apiCall<AttachmentListResponse>(\n `/api/attachments?entityId=${encodeURIComponent(E.catalog.catalog_product_variant)}&recordId=${encodeURIComponent(variantId)}`,\n )\n if (!res.ok) return []\n return Array.isArray(res.result?.items) ? res.result?.items ?? [] : []\n } catch (err) {\n console.error('catalog.variants.attachments.load', err)\n return []\n }\n}\n\nasync function loadVariantPrices(variantId: string, priceKinds: PriceKindSummary[]): Promise<Record<string, VariantPriceDraft>> {\n const kindDisplayModes = new Map(priceKinds.map((k) => [k.id, k.displayMode]))\n const drafts: Record<string, VariantPriceDraft> = {}\n const pageSize = 100\n let page = 1\n try {\n while (true) {\n const res = await apiCall<PriceListResponse>(\n `/api/catalog/prices?variantId=${encodeURIComponent(variantId)}&page=${page}&pageSize=${pageSize}`,\n )\n if (!res.ok) break\n const items = Array.isArray(res.result?.items) ? res.result?.items : []\n for (const item of items) {\n const draft = mapPriceItemToDraft(item as Record<string, unknown>, kindDisplayModes)\n if (draft) drafts[draft.priceKindId] = draft\n }\n if (items.length < pageSize) break\n page += 1\n }\n } catch (err) {\n console.error('catalog.variants.prices.load', err)\n }\n return drafts\n}\n\nfunction extractCustomFieldValues(record: Record<string, unknown>): Record<string, unknown> {\n const customValues: Record<string, unknown> = {}\n Object.entries(record).forEach(([key, value]) => {\n if (key.startsWith('cf_')) customValues[key] = value\n else if (key.startsWith('cf:')) customValues[`cf_${key.slice(3)}`] = value\n })\n return customValues\n}\n\nasync function syncVariantPricesUpdate({\n priceKinds,\n priceDrafts,\n existingPriceIds,\n productId,\n variantId,\n taxRates,\n taxRateId,\n productTaxRateId,\n productTaxRate,\n}: {\n priceKinds: PriceKindSummary[]\n priceDrafts: Record<string, VariantPriceDraft>\n existingPriceIds: Record<string, string>\n productId: string\n variantId: string\n taxRates: TaxRateSummary[]\n taxRateId: string | null\n productTaxRateId?: string | null\n productTaxRate?: number | null\n}): Promise<void> {\n const selectedTaxRate = taxRates.find((rate) => rate.id === taxRateId) ?? null\n const fallbackProductTaxRate =\n !selectedTaxRate && productTaxRateId\n ? taxRates.find((rate) => rate.id === productTaxRateId) ?? null\n : null\n const resolvedTaxRateValue =\n selectedTaxRate?.rate ??\n fallbackProductTaxRate?.rate ??\n (Number.isFinite(productTaxRate ?? null) ? productTaxRate ?? null : null)\n const resolvedTaxRateId = (selectedTaxRate ?? fallbackProductTaxRate)?.id ?? null\n for (const kind of priceKinds) {\n const draft = priceDrafts?.[kind.id]\n const amount = typeof draft?.amount === 'string' ? draft.amount.trim() : ''\n const existingId = draft?.priceId ?? existingPriceIds[kind.id]\n if (!amount) {\n if (existingId) {\n try {\n await deleteCrud('catalog/prices', existingId)\n } catch (err) {\n console.error('catalog.prices.delete', err)\n }\n }\n continue\n }\n const numeric = parseNumericInput(amount)\n if (!Number.isFinite(numeric) || numeric < 0) continue\n const payload: Record<string, unknown> = {\n productId,\n variantId,\n priceKindId: kind.id,\n currencyCode: kind.currencyCode ?? undefined,\n }\n if (resolvedTaxRateId) payload.taxRateId = resolvedTaxRateId\n else if (typeof resolvedTaxRateValue === 'number' && Number.isFinite(resolvedTaxRateValue)) payload.taxRate = resolvedTaxRateValue\n if (kind.displayMode === 'including-tax') payload.unitPriceGross = numeric\n else payload.unitPriceNet = numeric\n if (existingId) {\n await updateCrud('catalog/prices', { id: existingId, ...payload })\n } else {\n await createCrud('catalog/prices', payload)\n }\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -13,11 +13,13 @@ import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
|
13
13
|
import { E } from "../../../../../../../../generated/entities.ids.generated.js";
|
|
14
14
|
import {
|
|
15
15
|
createVariantInitialValues,
|
|
16
|
-
normalizeOptionSchema
|
|
16
|
+
normalizeOptionSchema,
|
|
17
|
+
findInvalidVariantPriceKinds
|
|
17
18
|
} from "@open-mercato/core/modules/catalog/components/products/variantForm";
|
|
18
19
|
import {
|
|
19
20
|
normalizePriceKindSummary
|
|
20
21
|
} from "@open-mercato/core/modules/catalog/components/products/productForm";
|
|
22
|
+
import { parseNumericInput } from "@open-mercato/core/modules/catalog/components/products/productFormUtils";
|
|
21
23
|
import {
|
|
22
24
|
VariantBasicsSection,
|
|
23
25
|
VariantOptionValuesSection,
|
|
@@ -237,6 +239,11 @@ function CreateVariantPage({ params }) {
|
|
|
237
239
|
const message = t("catalog.variants.form.errors.nameRequired", "Provide the variant name.");
|
|
238
240
|
throw createCrudFormError(message, { name: message });
|
|
239
241
|
}
|
|
242
|
+
const invalidPriceKinds = findInvalidVariantPriceKinds(priceKinds, values.prices);
|
|
243
|
+
if (invalidPriceKinds.length) {
|
|
244
|
+
const message = t("catalog.variants.form.errors.invalidPrice", "Provide a valid non-negative price.");
|
|
245
|
+
throw createCrudFormError(message, { prices: message });
|
|
246
|
+
}
|
|
240
247
|
const resolveTaxRateValue = (taxRateId) => {
|
|
241
248
|
if (!taxRateId) return null;
|
|
242
249
|
const match = taxRates.find((rate) => rate.id === taxRateId);
|
|
@@ -335,8 +342,8 @@ async function syncVariantPrices({
|
|
|
335
342
|
const draft = priceDrafts?.[kind.id];
|
|
336
343
|
const amount = typeof draft?.amount === "string" ? draft.amount.trim() : "";
|
|
337
344
|
if (!amount) continue;
|
|
338
|
-
const numeric =
|
|
339
|
-
if (Number.
|
|
345
|
+
const numeric = parseNumericInput(amount);
|
|
346
|
+
if (!Number.isFinite(numeric) || numeric < 0) continue;
|
|
340
347
|
const payload = {
|
|
341
348
|
productId,
|
|
342
349
|
variantId,
|
package/dist/modules/catalog/backend/catalog/products/[productId]/variants/create/page.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../../../../src/modules/catalog/backend/catalog/products/%5BproductId%5D/variants/create/page.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { useRouter } from 'next/navigation'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { CrudForm, type CrudFormGroup } from '@open-mercato/ui/backend/CrudForm'\nimport { createCrud } from '@open-mercato/ui/backend/utils/crud'\nimport { createCrudFormError } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { collectCustomFieldValues } from '@open-mercato/ui/backend/utils/customFieldValues'\nimport { apiCall, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { E } from '#generated/entities.ids.generated'\nimport {\n type VariantFormValues,\n type VariantPriceDraft,\n type OptionDefinition,\n createVariantInitialValues,\n normalizeOptionSchema,\n} from '@open-mercato/core/modules/catalog/components/products/variantForm'\nimport {\n type PriceKindSummary,\n type PriceKindApiPayload,\n type TaxRateSummary,\n normalizePriceKindSummary,\n} from '@open-mercato/core/modules/catalog/components/products/productForm'\nimport {\n VariantBasicsSection,\n VariantOptionValuesSection,\n VariantDimensionsSection,\n VariantMetadataSection,\n VariantPricesSection,\n VariantMediaSection,\n} from '@open-mercato/core/modules/catalog/components/products/VariantBuilder'\nimport type { ProductMediaItem } from '@open-mercato/core/modules/catalog/components/products/ProductMediaManager'\nimport { buildAttachmentImageUrl, slugifyAttachmentFileName } from '@open-mercato/core/modules/attachments/lib/imageUrls'\nimport { fetchOptionSchemaTemplate } from '../../../optionSchemaClient'\n\ntype ProductResponse = {\n items?: Array<{\n id?: string\n title?: string | null\n metadata?: Record<string, unknown> | null\n custom_fieldset_code?: string | null\n customFieldsetCode?: string | null\n tax_rate_id?: string | null\n taxRateId?: string | null\n tax_rate?: number | string | null\n }>\n}\n\ntype VariantCreateResult = {\n id?: string\n variantId?: string\n}\n\nexport default function CreateVariantPage({ params }: { params?: { productId?: string } }) {\n const productId = params?.productId ? String(params.productId) : null\n const t = useT()\n const router = useRouter()\n const [optionDefinitions, setOptionDefinitions] = React.useState<OptionDefinition[]>([])\n const [priceKinds, setPriceKinds] = React.useState<PriceKindSummary[]>([])\n const [taxRates, setTaxRates] = React.useState<TaxRateSummary[]>([])\n const [initialValues, setInitialValues] = React.useState<VariantFormValues | null>(null)\n const [productTitle, setProductTitle] = React.useState<string>('')\n const [productTaxRateId, setProductTaxRateId] = React.useState<string | null>(null)\n const [productTaxRate, setProductTaxRate] = React.useState<number | null>(null)\n const [loading, setLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n\n React.useEffect(() => {\n const loadPriceKinds = async () => {\n try {\n const payload = await readApiResultOrThrow<{ items?: PriceKindApiPayload[] }>(\n '/api/catalog/price-kinds?pageSize=100',\n undefined,\n { errorMessage: t('catalog.priceKinds.errors.load', 'Failed to load price kinds.') },\n )\n const items = Array.isArray(payload.items) ? payload.items : []\n setPriceKinds(items.map((item) => normalizePriceKindSummary(item)).filter((item): item is PriceKindSummary => !!item))\n } catch (err) {\n console.error('catalog.price-kinds.fetch failed', err)\n setPriceKinds([])\n }\n }\n loadPriceKinds().catch(() => {})\n }, [t])\n\n React.useEffect(() => {\n const loadTaxRates = async () => {\n try {\n const payload = await readApiResultOrThrow<{ items?: Array<Record<string, unknown>> }>(\n '/api/sales/tax-rates?pageSize=200',\n undefined,\n { errorMessage: t('catalog.products.create.taxRates.error', 'Failed to load tax rates.'), fallback: { items: [] } },\n )\n const items = Array.isArray(payload.items) ? payload.items : []\n setTaxRates(\n items.map((item) => {\n const rawRate = typeof item.rate === 'number' ? item.rate : Number(item.rate ?? Number.NaN)\n return {\n id: String(item.id),\n name:\n typeof item.name === 'string' && item.name.trim().length\n ? item.name\n : t('catalog.products.create.taxRates.unnamed', 'Untitled tax rate'),\n code: typeof item.code === 'string' && item.code.trim().length ? item.code : null,\n rate: Number.isFinite(rawRate) ? rawRate : null,\n isDefault: Boolean(\n typeof item.isDefault === 'boolean'\n ? item.isDefault\n : typeof item.is_default === 'boolean'\n ? item.is_default\n : false,\n ),\n }\n }),\n )\n } catch (err) {\n console.error('sales.tax-rates.fetch failed', err)\n setTaxRates([])\n }\n }\n loadTaxRates().catch(() => {})\n }, [t])\n\n React.useEffect(() => {\n if (!productId) return\n let cancelled = false\n async function load() {\n setLoading(true)\n setError(null)\n try {\n const res = await apiCall<ProductResponse>(\n `/api/catalog/products?id=${encodeURIComponent(productId!)}&page=1&pageSize=1`,\n )\n if (!res.ok) throw new Error('load_failed')\n const record = Array.isArray(res.result?.items) ? res.result?.items?.[0] : undefined\n if (!record) throw new Error(t('catalog.products.edit.errors.notFound', 'Product not found.'))\n const metadata = (record.metadata ?? {}) as Record<string, unknown>\n const taxRateId =\n typeof (record as any).tax_rate_id === 'string'\n ? (record as any).tax_rate_id\n : typeof (record as any).taxRateId === 'string'\n ? (record as any).taxRateId\n : null\n const taxRateValueRaw =\n typeof (record as any).tax_rate === 'number'\n ? (record as any).tax_rate\n : typeof (record as any).tax_rate === 'string'\n ? Number((record as any).tax_rate)\n : typeof (record as any).taxRate === 'number'\n ? (record as any).taxRate\n : typeof (record as any).taxRate === 'string'\n ? Number((record as any).taxRate)\n : null\n const taxRateValue = Number.isFinite(taxRateValueRaw) ? Number(taxRateValueRaw) : null\n const optionSchemaId =\n typeof (record as any).option_schema_id === 'string'\n ? (record as any).option_schema_id\n : typeof (record as any).optionSchemaId === 'string'\n ? (record as any).optionSchemaId\n : null\n let schemaSource: unknown = metadata.optionSchema ?? (metadata.option_schema as unknown)\n if (optionSchemaId) {\n const template = await fetchOptionSchemaTemplate(optionSchemaId)\n if (template?.schema?.options) {\n schemaSource = template.schema.options.map((option) => ({\n code: option.code,\n label: option.label,\n values: Array.isArray(option.choices)\n ? option.choices.map((choice) => ({ id: choice.code ?? undefined, label: choice.label ?? choice.code ?? '' }))\n : [],\n }))\n }\n }\n if (!cancelled) {\n setOptionDefinitions(normalizeOptionSchema(schemaSource))\n setProductTitle(typeof record.title === 'string' ? record.title : '')\n setProductTaxRateId(taxRateId)\n setProductTaxRate(taxRateValue)\n const base = createVariantInitialValues()\n setInitialValues(base)\n }\n } catch (err) {\n console.error('catalog.variants.loadProduct failed', err)\n if (!cancelled) {\n const message = err instanceof Error && err.message ? err.message : t('catalog.variants.form.errors.load', 'Failed to load product context.')\n setError(message)\n }\n } finally {\n if (!cancelled) setLoading(false)\n }\n }\n load()\n return () => { cancelled = true }\n }, [productId, t])\n\n const groups = React.useMemo<CrudFormGroup[]>(() => {\n const list: CrudFormGroup[] = [\n {\n id: 'general',\n column: 1,\n title: t('catalog.variants.form.nameLabel', 'Name'),\n component: ({ values, setValue, errors }) => (\n <VariantBasicsSection values={values as VariantFormValues} setValue={setValue} errors={errors} />\n ),\n },\n {\n id: 'metadata',\n column: 1,\n title: t('catalog.products.edit.metadata.title', 'Metadata'),\n description: t('catalog.products.edit.metadata.hint', 'Attach structured key/value pairs for integrations.'),\n component: ({ values, setValue }) => (\n <VariantMetadataSection values={values as VariantFormValues} setValue={setValue} showIntro={false} embedded />\n ),\n },\n {\n id: 'prices',\n column: 1,\n title: t('catalog.variants.form.pricesLabel', 'Prices'),\n description: t('catalog.variants.form.pricesHint', 'Populate list prices per price kind.'),\n component: ({ values, setValue }) => (\n <VariantPricesSection\n values={values as VariantFormValues}\n setValue={setValue}\n priceKinds={priceKinds}\n taxRates={taxRates}\n showHeader={false}\n embedded\n />\n ),\n },\n {\n id: 'media',\n column: 1,\n title: t('catalog.variants.form.media', 'Media'),\n component: ({ values, setValue }) => (\n <VariantMediaSection values={values as VariantFormValues} setValue={setValue} showLabel={false} />\n ),\n },\n ]\n\n if (optionDefinitions.length) {\n list.push({\n id: 'options',\n column: 2,\n title: t('catalog.variants.form.options', 'Option values'),\n component: ({ values, setValue }) => (\n <VariantOptionValuesSection\n values={values as VariantFormValues}\n setValue={setValue}\n optionDefinitions={optionDefinitions}\n showHeading={false}\n />\n ),\n })\n }\n\n list.push({\n id: 'dimensions',\n column: 2,\n title: t('catalog.variants.form.dimensions', 'Dimensions & weight'),\n component: ({ values, setValue }) => (\n <VariantDimensionsSection values={values as VariantFormValues} setValue={setValue} showHeading={false} />\n ),\n })\n\n list.push({\n id: 'custom',\n column: 2,\n title: t('catalog.variants.form.customFields', 'Custom attributes'),\n kind: 'customFields',\n })\n\n return list\n }, [optionDefinitions, priceKinds, t, taxRates])\n\n if (!productId) {\n return (\n <Page>\n <PageBody>\n <div className=\"rounded border border-destructive/40 bg-destructive/10 px-4 py-3 text-sm text-destructive\">\n {t('catalog.variants.form.errors.productMissing', 'Product identifier is missing.')}\n </div>\n </PageBody>\n </Page>\n )\n }\n\n const formTitle = productTitle\n ? t('catalog.variants.form.createTitleFor', 'Create variant for {{title}}').replace('{{title}}', productTitle)\n : t('catalog.variants.form.createTitle', 'Create variant')\n\n return (\n <Page>\n <PageBody>\n {error ? (\n <div className=\"mb-4 rounded border border-destructive/40 bg-destructive/10 px-4 py-3 text-sm text-destructive\">{error}</div>\n ) : null}\n <CrudForm<VariantFormValues>\n title={formTitle}\n backHref={`/backend/catalog/products/${productId}`}\n fields={[]}\n groups={groups}\n entityId={E.catalog.catalog_product_variant}\n customFieldsetBindings={{ [E.catalog.catalog_product_variant]: { valueKey: 'customFieldsetCode' } }}\n initialValues={initialValues ?? undefined}\n isLoading={loading}\n loadingMessage={t('catalog.variants.form.loading', 'Loading form...')}\n submitLabel={t('catalog.variants.form.createAction', 'Create variant')}\n cancelHref={`/backend/catalog/products/${productId}`}\n onSubmit={async (values) => {\n const name = values.name?.trim()\n if (!name) {\n const message = t('catalog.variants.form.errors.nameRequired', 'Provide the variant name.')\n throw createCrudFormError(message, { name: message })\n }\n const resolveTaxRateValue = (taxRateId?: string | null) => {\n if (!taxRateId) return null\n const match = taxRates.find((rate) => rate.id === taxRateId)\n return typeof match?.rate === 'number' && Number.isFinite(match.rate) ? match.rate : null\n }\n const resolvedTaxRateId = values.taxRateId ?? productTaxRateId ?? null\n const resolvedTaxRateValue =\n values.taxRateId && resolvedTaxRateId\n ? resolveTaxRateValue(resolvedTaxRateId)\n : productTaxRateId\n ? resolveTaxRateValue(productTaxRateId) ?? productTaxRate\n : productTaxRate ?? null\n const metadata = typeof values.metadata === 'object' && values.metadata ? { ...values.metadata } : {}\n const defaultMediaEntry = values.defaultMediaId\n ? (Array.isArray(values.mediaItems) ? values.mediaItems : []).find((item) => item.id === values.defaultMediaId)\n : null\n const defaultMediaUrl = defaultMediaEntry\n ? buildAttachmentImageUrl(defaultMediaEntry.id, {\n slug: slugifyAttachmentFileName(defaultMediaEntry.fileName),\n })\n : null\n const payload: Record<string, unknown> = {\n productId,\n name,\n sku: values.sku?.trim() || undefined,\n barcode: values.barcode?.trim() || undefined,\n isDefault: Boolean(values.isDefault),\n isActive: values.isActive !== false,\n optionValues: Object.keys(values.optionValues ?? {}).length ? values.optionValues : undefined,\n metadata,\n defaultMediaId: values.defaultMediaId ?? undefined,\n defaultMediaUrl: defaultMediaUrl ?? undefined,\n customFieldsetCode: values.customFieldsetCode?.trim().length ? values.customFieldsetCode : undefined,\n taxRateId: resolvedTaxRateId,\n taxRate: resolvedTaxRateValue,\n }\n // CrudForm injects a sentinel `id` (\"create\") while the record is new; never send it to the API.\n Reflect.deleteProperty(payload, 'id')\n const customFields = collectCustomFieldValues(values)\n if (Object.keys(customFields).length) payload.customFields = customFields\n\n const { result } = await createCrud<VariantCreateResult>('catalog/variants', payload)\n const variantId = result?.variantId ?? result?.id\n if (!variantId) {\n throw createCrudFormError(t('catalog.variants.form.errors.idMissing', 'Variant id missing after create.'))\n }\n await transferVariantMedia({\n draftId: values.mediaDraftId,\n variantId,\n mediaItems: Array.isArray(values.mediaItems) ? values.mediaItems : [],\n })\n await syncVariantPrices({\n priceKinds,\n priceDrafts: values.prices ?? {},\n productId,\n variantId,\n taxRates,\n taxRateId: values.taxRateId,\n productTaxRateId,\n productTaxRate,\n })\n flash(t('catalog.variants.form.createSuccess', 'Variant created.'), 'success')\n router.push(`/backend/catalog/products/${productId}/variants/${variantId}`)\n }}\n />\n </PageBody>\n </Page>\n )\n}\n\nasync function transferVariantMedia({\n draftId,\n variantId,\n mediaItems,\n}: {\n draftId?: string | null\n variantId: string\n mediaItems: ProductMediaItem[]\n}): Promise<void> {\n if (!draftId || !variantId) return\n const attachmentIds = mediaItems.map((item) => item.id).filter((id): id is string => typeof id === 'string' && id.length > 0)\n if (!attachmentIds.length) return\n await apiCall(\n '/api/attachments/transfer',\n {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n entityId: E.catalog.catalog_product_variant,\n attachmentIds,\n fromRecordId: draftId,\n toRecordId: variantId,\n }),\n },\n { fallback: null },\n )\n}\n\nasync function syncVariantPrices({\n priceKinds,\n priceDrafts,\n productId,\n variantId,\n taxRates,\n taxRateId,\n productTaxRateId,\n productTaxRate,\n}: {\n priceKinds: PriceKindSummary[]\n priceDrafts: Record<string, VariantPriceDraft>\n productId: string\n variantId: string\n taxRates: TaxRateSummary[]\n taxRateId: string | null\n productTaxRateId?: string | null\n productTaxRate?: number | null\n}): Promise<void> {\n const selectedTaxRate = taxRates.find((rate) => rate.id === taxRateId) ?? null\n const fallbackProductTaxRate =\n !selectedTaxRate && productTaxRateId\n ? taxRates.find((rate) => rate.id === productTaxRateId) ?? null\n : null\n const resolvedTaxRateValue =\n selectedTaxRate?.rate ??\n fallbackProductTaxRate?.rate ??\n (Number.isFinite(productTaxRate ?? null) ? productTaxRate ?? null : null)\n const resolvedTaxRateId = (selectedTaxRate ?? fallbackProductTaxRate)?.id ?? null\n for (const kind of priceKinds) {\n const draft = priceDrafts?.[kind.id]\n const amount = typeof draft?.amount === 'string' ? draft.amount.trim() : ''\n if (!amount) continue\n const numeric = Number(amount)\n if (Number.isNaN(numeric) || numeric < 0) continue\n const payload: Record<string, unknown> = {\n productId,\n variantId,\n priceKindId: kind.id,\n currencyCode: kind.currencyCode ?? undefined,\n }\n if (resolvedTaxRateId) {\n payload.taxRateId = resolvedTaxRateId\n } else if (typeof resolvedTaxRateValue === 'number' && Number.isFinite(resolvedTaxRateValue)) {\n payload.taxRate = resolvedTaxRateValue\n }\n if (kind.displayMode === 'including-tax') payload.unitPriceGross = numeric\n else payload.unitPriceNet = numeric\n await createCrud('catalog/prices', payload)\n }\n}\n"],
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { useRouter } from 'next/navigation'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { CrudForm, type CrudFormGroup } from '@open-mercato/ui/backend/CrudForm'\nimport { createCrud } from '@open-mercato/ui/backend/utils/crud'\nimport { createCrudFormError } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { collectCustomFieldValues } from '@open-mercato/ui/backend/utils/customFieldValues'\nimport { apiCall, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { E } from '#generated/entities.ids.generated'\nimport {\n type VariantFormValues,\n type VariantPriceDraft,\n type OptionDefinition,\n createVariantInitialValues,\n normalizeOptionSchema,\n findInvalidVariantPriceKinds,\n} from '@open-mercato/core/modules/catalog/components/products/variantForm'\nimport {\n type PriceKindSummary,\n type PriceKindApiPayload,\n type TaxRateSummary,\n normalizePriceKindSummary,\n} from '@open-mercato/core/modules/catalog/components/products/productForm'\nimport { parseNumericInput } from '@open-mercato/core/modules/catalog/components/products/productFormUtils'\nimport {\n VariantBasicsSection,\n VariantOptionValuesSection,\n VariantDimensionsSection,\n VariantMetadataSection,\n VariantPricesSection,\n VariantMediaSection,\n} from '@open-mercato/core/modules/catalog/components/products/VariantBuilder'\nimport type { ProductMediaItem } from '@open-mercato/core/modules/catalog/components/products/ProductMediaManager'\nimport { buildAttachmentImageUrl, slugifyAttachmentFileName } from '@open-mercato/core/modules/attachments/lib/imageUrls'\nimport { fetchOptionSchemaTemplate } from '../../../optionSchemaClient'\n\ntype ProductResponse = {\n items?: Array<{\n id?: string\n title?: string | null\n metadata?: Record<string, unknown> | null\n custom_fieldset_code?: string | null\n customFieldsetCode?: string | null\n tax_rate_id?: string | null\n taxRateId?: string | null\n tax_rate?: number | string | null\n }>\n}\n\ntype VariantCreateResult = {\n id?: string\n variantId?: string\n}\n\nexport default function CreateVariantPage({ params }: { params?: { productId?: string } }) {\n const productId = params?.productId ? String(params.productId) : null\n const t = useT()\n const router = useRouter()\n const [optionDefinitions, setOptionDefinitions] = React.useState<OptionDefinition[]>([])\n const [priceKinds, setPriceKinds] = React.useState<PriceKindSummary[]>([])\n const [taxRates, setTaxRates] = React.useState<TaxRateSummary[]>([])\n const [initialValues, setInitialValues] = React.useState<VariantFormValues | null>(null)\n const [productTitle, setProductTitle] = React.useState<string>('')\n const [productTaxRateId, setProductTaxRateId] = React.useState<string | null>(null)\n const [productTaxRate, setProductTaxRate] = React.useState<number | null>(null)\n const [loading, setLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n\n React.useEffect(() => {\n const loadPriceKinds = async () => {\n try {\n const payload = await readApiResultOrThrow<{ items?: PriceKindApiPayload[] }>(\n '/api/catalog/price-kinds?pageSize=100',\n undefined,\n { errorMessage: t('catalog.priceKinds.errors.load', 'Failed to load price kinds.') },\n )\n const items = Array.isArray(payload.items) ? payload.items : []\n setPriceKinds(items.map((item) => normalizePriceKindSummary(item)).filter((item): item is PriceKindSummary => !!item))\n } catch (err) {\n console.error('catalog.price-kinds.fetch failed', err)\n setPriceKinds([])\n }\n }\n loadPriceKinds().catch(() => {})\n }, [t])\n\n React.useEffect(() => {\n const loadTaxRates = async () => {\n try {\n const payload = await readApiResultOrThrow<{ items?: Array<Record<string, unknown>> }>(\n '/api/sales/tax-rates?pageSize=200',\n undefined,\n { errorMessage: t('catalog.products.create.taxRates.error', 'Failed to load tax rates.'), fallback: { items: [] } },\n )\n const items = Array.isArray(payload.items) ? payload.items : []\n setTaxRates(\n items.map((item) => {\n const rawRate = typeof item.rate === 'number' ? item.rate : Number(item.rate ?? Number.NaN)\n return {\n id: String(item.id),\n name:\n typeof item.name === 'string' && item.name.trim().length\n ? item.name\n : t('catalog.products.create.taxRates.unnamed', 'Untitled tax rate'),\n code: typeof item.code === 'string' && item.code.trim().length ? item.code : null,\n rate: Number.isFinite(rawRate) ? rawRate : null,\n isDefault: Boolean(\n typeof item.isDefault === 'boolean'\n ? item.isDefault\n : typeof item.is_default === 'boolean'\n ? item.is_default\n : false,\n ),\n }\n }),\n )\n } catch (err) {\n console.error('sales.tax-rates.fetch failed', err)\n setTaxRates([])\n }\n }\n loadTaxRates().catch(() => {})\n }, [t])\n\n React.useEffect(() => {\n if (!productId) return\n let cancelled = false\n async function load() {\n setLoading(true)\n setError(null)\n try {\n const res = await apiCall<ProductResponse>(\n `/api/catalog/products?id=${encodeURIComponent(productId!)}&page=1&pageSize=1`,\n )\n if (!res.ok) throw new Error('load_failed')\n const record = Array.isArray(res.result?.items) ? res.result?.items?.[0] : undefined\n if (!record) throw new Error(t('catalog.products.edit.errors.notFound', 'Product not found.'))\n const metadata = (record.metadata ?? {}) as Record<string, unknown>\n const taxRateId =\n typeof (record as any).tax_rate_id === 'string'\n ? (record as any).tax_rate_id\n : typeof (record as any).taxRateId === 'string'\n ? (record as any).taxRateId\n : null\n const taxRateValueRaw =\n typeof (record as any).tax_rate === 'number'\n ? (record as any).tax_rate\n : typeof (record as any).tax_rate === 'string'\n ? Number((record as any).tax_rate)\n : typeof (record as any).taxRate === 'number'\n ? (record as any).taxRate\n : typeof (record as any).taxRate === 'string'\n ? Number((record as any).taxRate)\n : null\n const taxRateValue = Number.isFinite(taxRateValueRaw) ? Number(taxRateValueRaw) : null\n const optionSchemaId =\n typeof (record as any).option_schema_id === 'string'\n ? (record as any).option_schema_id\n : typeof (record as any).optionSchemaId === 'string'\n ? (record as any).optionSchemaId\n : null\n let schemaSource: unknown = metadata.optionSchema ?? (metadata.option_schema as unknown)\n if (optionSchemaId) {\n const template = await fetchOptionSchemaTemplate(optionSchemaId)\n if (template?.schema?.options) {\n schemaSource = template.schema.options.map((option) => ({\n code: option.code,\n label: option.label,\n values: Array.isArray(option.choices)\n ? option.choices.map((choice) => ({ id: choice.code ?? undefined, label: choice.label ?? choice.code ?? '' }))\n : [],\n }))\n }\n }\n if (!cancelled) {\n setOptionDefinitions(normalizeOptionSchema(schemaSource))\n setProductTitle(typeof record.title === 'string' ? record.title : '')\n setProductTaxRateId(taxRateId)\n setProductTaxRate(taxRateValue)\n const base = createVariantInitialValues()\n setInitialValues(base)\n }\n } catch (err) {\n console.error('catalog.variants.loadProduct failed', err)\n if (!cancelled) {\n const message = err instanceof Error && err.message ? err.message : t('catalog.variants.form.errors.load', 'Failed to load product context.')\n setError(message)\n }\n } finally {\n if (!cancelled) setLoading(false)\n }\n }\n load()\n return () => { cancelled = true }\n }, [productId, t])\n\n const groups = React.useMemo<CrudFormGroup[]>(() => {\n const list: CrudFormGroup[] = [\n {\n id: 'general',\n column: 1,\n title: t('catalog.variants.form.nameLabel', 'Name'),\n component: ({ values, setValue, errors }) => (\n <VariantBasicsSection values={values as VariantFormValues} setValue={setValue} errors={errors} />\n ),\n },\n {\n id: 'metadata',\n column: 1,\n title: t('catalog.products.edit.metadata.title', 'Metadata'),\n description: t('catalog.products.edit.metadata.hint', 'Attach structured key/value pairs for integrations.'),\n component: ({ values, setValue }) => (\n <VariantMetadataSection values={values as VariantFormValues} setValue={setValue} showIntro={false} embedded />\n ),\n },\n {\n id: 'prices',\n column: 1,\n title: t('catalog.variants.form.pricesLabel', 'Prices'),\n description: t('catalog.variants.form.pricesHint', 'Populate list prices per price kind.'),\n component: ({ values, setValue }) => (\n <VariantPricesSection\n values={values as VariantFormValues}\n setValue={setValue}\n priceKinds={priceKinds}\n taxRates={taxRates}\n showHeader={false}\n embedded\n />\n ),\n },\n {\n id: 'media',\n column: 1,\n title: t('catalog.variants.form.media', 'Media'),\n component: ({ values, setValue }) => (\n <VariantMediaSection values={values as VariantFormValues} setValue={setValue} showLabel={false} />\n ),\n },\n ]\n\n if (optionDefinitions.length) {\n list.push({\n id: 'options',\n column: 2,\n title: t('catalog.variants.form.options', 'Option values'),\n component: ({ values, setValue }) => (\n <VariantOptionValuesSection\n values={values as VariantFormValues}\n setValue={setValue}\n optionDefinitions={optionDefinitions}\n showHeading={false}\n />\n ),\n })\n }\n\n list.push({\n id: 'dimensions',\n column: 2,\n title: t('catalog.variants.form.dimensions', 'Dimensions & weight'),\n component: ({ values, setValue }) => (\n <VariantDimensionsSection values={values as VariantFormValues} setValue={setValue} showHeading={false} />\n ),\n })\n\n list.push({\n id: 'custom',\n column: 2,\n title: t('catalog.variants.form.customFields', 'Custom attributes'),\n kind: 'customFields',\n })\n\n return list\n }, [optionDefinitions, priceKinds, t, taxRates])\n\n if (!productId) {\n return (\n <Page>\n <PageBody>\n <div className=\"rounded border border-destructive/40 bg-destructive/10 px-4 py-3 text-sm text-destructive\">\n {t('catalog.variants.form.errors.productMissing', 'Product identifier is missing.')}\n </div>\n </PageBody>\n </Page>\n )\n }\n\n const formTitle = productTitle\n ? t('catalog.variants.form.createTitleFor', 'Create variant for {{title}}').replace('{{title}}', productTitle)\n : t('catalog.variants.form.createTitle', 'Create variant')\n\n return (\n <Page>\n <PageBody>\n {error ? (\n <div className=\"mb-4 rounded border border-destructive/40 bg-destructive/10 px-4 py-3 text-sm text-destructive\">{error}</div>\n ) : null}\n <CrudForm<VariantFormValues>\n title={formTitle}\n backHref={`/backend/catalog/products/${productId}`}\n fields={[]}\n groups={groups}\n entityId={E.catalog.catalog_product_variant}\n customFieldsetBindings={{ [E.catalog.catalog_product_variant]: { valueKey: 'customFieldsetCode' } }}\n initialValues={initialValues ?? undefined}\n isLoading={loading}\n loadingMessage={t('catalog.variants.form.loading', 'Loading form...')}\n submitLabel={t('catalog.variants.form.createAction', 'Create variant')}\n cancelHref={`/backend/catalog/products/${productId}`}\n onSubmit={async (values) => {\n const name = values.name?.trim()\n if (!name) {\n const message = t('catalog.variants.form.errors.nameRequired', 'Provide the variant name.')\n throw createCrudFormError(message, { name: message })\n }\n const invalidPriceKinds = findInvalidVariantPriceKinds(priceKinds, values.prices)\n if (invalidPriceKinds.length) {\n const message = t('catalog.variants.form.errors.invalidPrice', 'Provide a valid non-negative price.')\n throw createCrudFormError(message, { prices: message })\n }\n const resolveTaxRateValue = (taxRateId?: string | null) => {\n if (!taxRateId) return null\n const match = taxRates.find((rate) => rate.id === taxRateId)\n return typeof match?.rate === 'number' && Number.isFinite(match.rate) ? match.rate : null\n }\n const resolvedTaxRateId = values.taxRateId ?? productTaxRateId ?? null\n const resolvedTaxRateValue =\n values.taxRateId && resolvedTaxRateId\n ? resolveTaxRateValue(resolvedTaxRateId)\n : productTaxRateId\n ? resolveTaxRateValue(productTaxRateId) ?? productTaxRate\n : productTaxRate ?? null\n const metadata = typeof values.metadata === 'object' && values.metadata ? { ...values.metadata } : {}\n const defaultMediaEntry = values.defaultMediaId\n ? (Array.isArray(values.mediaItems) ? values.mediaItems : []).find((item) => item.id === values.defaultMediaId)\n : null\n const defaultMediaUrl = defaultMediaEntry\n ? buildAttachmentImageUrl(defaultMediaEntry.id, {\n slug: slugifyAttachmentFileName(defaultMediaEntry.fileName),\n })\n : null\n const payload: Record<string, unknown> = {\n productId,\n name,\n sku: values.sku?.trim() || undefined,\n barcode: values.barcode?.trim() || undefined,\n isDefault: Boolean(values.isDefault),\n isActive: values.isActive !== false,\n optionValues: Object.keys(values.optionValues ?? {}).length ? values.optionValues : undefined,\n metadata,\n defaultMediaId: values.defaultMediaId ?? undefined,\n defaultMediaUrl: defaultMediaUrl ?? undefined,\n customFieldsetCode: values.customFieldsetCode?.trim().length ? values.customFieldsetCode : undefined,\n taxRateId: resolvedTaxRateId,\n taxRate: resolvedTaxRateValue,\n }\n // CrudForm injects a sentinel `id` (\"create\") while the record is new; never send it to the API.\n Reflect.deleteProperty(payload, 'id')\n const customFields = collectCustomFieldValues(values)\n if (Object.keys(customFields).length) payload.customFields = customFields\n\n const { result } = await createCrud<VariantCreateResult>('catalog/variants', payload)\n const variantId = result?.variantId ?? result?.id\n if (!variantId) {\n throw createCrudFormError(t('catalog.variants.form.errors.idMissing', 'Variant id missing after create.'))\n }\n await transferVariantMedia({\n draftId: values.mediaDraftId,\n variantId,\n mediaItems: Array.isArray(values.mediaItems) ? values.mediaItems : [],\n })\n await syncVariantPrices({\n priceKinds,\n priceDrafts: values.prices ?? {},\n productId,\n variantId,\n taxRates,\n taxRateId: values.taxRateId,\n productTaxRateId,\n productTaxRate,\n })\n flash(t('catalog.variants.form.createSuccess', 'Variant created.'), 'success')\n router.push(`/backend/catalog/products/${productId}/variants/${variantId}`)\n }}\n />\n </PageBody>\n </Page>\n )\n}\n\nasync function transferVariantMedia({\n draftId,\n variantId,\n mediaItems,\n}: {\n draftId?: string | null\n variantId: string\n mediaItems: ProductMediaItem[]\n}): Promise<void> {\n if (!draftId || !variantId) return\n const attachmentIds = mediaItems.map((item) => item.id).filter((id): id is string => typeof id === 'string' && id.length > 0)\n if (!attachmentIds.length) return\n await apiCall(\n '/api/attachments/transfer',\n {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n entityId: E.catalog.catalog_product_variant,\n attachmentIds,\n fromRecordId: draftId,\n toRecordId: variantId,\n }),\n },\n { fallback: null },\n )\n}\n\nasync function syncVariantPrices({\n priceKinds,\n priceDrafts,\n productId,\n variantId,\n taxRates,\n taxRateId,\n productTaxRateId,\n productTaxRate,\n}: {\n priceKinds: PriceKindSummary[]\n priceDrafts: Record<string, VariantPriceDraft>\n productId: string\n variantId: string\n taxRates: TaxRateSummary[]\n taxRateId: string | null\n productTaxRateId?: string | null\n productTaxRate?: number | null\n}): Promise<void> {\n const selectedTaxRate = taxRates.find((rate) => rate.id === taxRateId) ?? null\n const fallbackProductTaxRate =\n !selectedTaxRate && productTaxRateId\n ? taxRates.find((rate) => rate.id === productTaxRateId) ?? null\n : null\n const resolvedTaxRateValue =\n selectedTaxRate?.rate ??\n fallbackProductTaxRate?.rate ??\n (Number.isFinite(productTaxRate ?? null) ? productTaxRate ?? null : null)\n const resolvedTaxRateId = (selectedTaxRate ?? fallbackProductTaxRate)?.id ?? null\n for (const kind of priceKinds) {\n const draft = priceDrafts?.[kind.id]\n const amount = typeof draft?.amount === 'string' ? draft.amount.trim() : ''\n if (!amount) continue\n const numeric = parseNumericInput(amount)\n if (!Number.isFinite(numeric) || numeric < 0) continue\n const payload: Record<string, unknown> = {\n productId,\n variantId,\n priceKindId: kind.id,\n currencyCode: kind.currencyCode ?? undefined,\n }\n if (resolvedTaxRateId) {\n payload.taxRateId = resolvedTaxRateId\n } else if (typeof resolvedTaxRateValue === 'number' && Number.isFinite(resolvedTaxRateValue)) {\n payload.taxRate = resolvedTaxRateValue\n }\n if (kind.displayMode === 'including-tax') payload.unitPriceGross = numeric\n else payload.unitPriceNet = numeric\n await createCrud('catalog/prices', payload)\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { createLocalId } from "./productForm.js";
|
|
3
|
+
import { parseNumericInput } from "./productFormUtils.js";
|
|
3
4
|
const VARIANT_BASE_VALUES = {
|
|
4
5
|
name: "",
|
|
5
6
|
sku: "",
|
|
@@ -49,6 +50,17 @@ function buildVariantMetadata(values) {
|
|
|
49
50
|
const metadata = typeof values.metadata === "object" && values.metadata ? { ...values.metadata } : {};
|
|
50
51
|
return metadata;
|
|
51
52
|
}
|
|
53
|
+
function findInvalidVariantPriceKinds(priceKinds, priceDrafts) {
|
|
54
|
+
const invalid = [];
|
|
55
|
+
for (const kind of priceKinds) {
|
|
56
|
+
const draft = priceDrafts?.[kind.id];
|
|
57
|
+
const amount = typeof draft?.amount === "string" ? draft.amount.trim() : "";
|
|
58
|
+
if (!amount) continue;
|
|
59
|
+
const numeric = parseNumericInput(amount);
|
|
60
|
+
if (!Number.isFinite(numeric) || numeric < 0) invalid.push(kind.id);
|
|
61
|
+
}
|
|
62
|
+
return invalid;
|
|
63
|
+
}
|
|
52
64
|
function mapPriceItemToDraft(item, kindDisplayModes) {
|
|
53
65
|
const kindId = typeof item.price_kind_id === "string" ? item.price_kind_id : typeof item.priceKindId === "string" ? item.priceKindId : null;
|
|
54
66
|
if (!kindId) return null;
|
|
@@ -67,6 +79,7 @@ export {
|
|
|
67
79
|
VARIANT_BASE_VALUES,
|
|
68
80
|
buildVariantMetadata,
|
|
69
81
|
createVariantInitialValues,
|
|
82
|
+
findInvalidVariantPriceKinds,
|
|
70
83
|
mapPriceItemToDraft,
|
|
71
84
|
normalizeOptionSchema
|
|
72
85
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/catalog/components/products/variantForm.ts"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport type { ProductMediaItem } from './ProductMediaManager'\nimport { createLocalId } from './productForm'\n\nexport type OptionDefinition = {\n id: string\n code: string\n label: string\n values: Array<{ id: string; label: string }>\n}\n\nexport type VariantPriceDraft = {\n priceKindId: string\n priceId?: string\n amount: string\n currencyCode?: string | null\n displayMode: 'including-tax' | 'excluding-tax'\n}\n\nexport type VariantFormValues = {\n name: string\n sku: string\n barcode: string\n isDefault: boolean\n isActive: boolean\n optionValues: Record<string, string>\n metadata?: Record<string, unknown> | null\n mediaDraftId: string\n mediaItems: ProductMediaItem[]\n defaultMediaId: string | null\n defaultMediaUrl: string\n prices: Record<string, VariantPriceDraft>\n taxRateId: string | null\n customFieldsetCode?: string | null\n}\n\nexport const VARIANT_BASE_VALUES: VariantFormValues = {\n name: '',\n sku: '',\n barcode: '',\n isDefault: false,\n isActive: true,\n optionValues: {},\n metadata: {},\n mediaDraftId: '',\n mediaItems: [],\n defaultMediaId: null,\n defaultMediaUrl: '',\n prices: {},\n taxRateId: null,\n customFieldsetCode: null,\n}\n\nexport const createVariantInitialValues = (): VariantFormValues => ({\n ...VARIANT_BASE_VALUES,\n mediaDraftId: createLocalId(),\n})\n\nexport function normalizeOptionSchema(raw: unknown): OptionDefinition[] {\n if (!Array.isArray(raw)) return []\n return raw\n .map((entry) => normalizeOptionDefinition(entry))\n .filter((entry): entry is OptionDefinition => !!entry)\n}\n\nfunction normalizeOptionDefinition(entry: unknown): OptionDefinition | null {\n if (!entry || typeof entry !== 'object') return null\n const code = extractString((entry as any).code) || createLocalId()\n const label = extractString((entry as any).label) || code\n const values = Array.isArray((entry as any).values)\n ? (entry as any).values\n .map((value: any) => {\n const id = extractString(value?.id) || createLocalId()\n const valueLabel = extractString(value?.label) || id\n return { id, label: valueLabel }\n })\n .filter(\n (value: { id: string; label: string }): value is { id: string; label: string } =>\n value.label.length > 0,\n )\n : []\n return {\n id: extractString((entry as any).id) || createLocalId(),\n code,\n label,\n values,\n }\n}\n\nfunction extractString(value: unknown): string {\n return typeof value === 'string' ? value.trim() : ''\n}\n\nexport function buildVariantMetadata(values: VariantFormValues): Record<string, unknown> {\n const metadata = typeof values.metadata === 'object' && values.metadata ? { ...values.metadata } : {}\n return metadata\n}\n\nexport function mapPriceItemToDraft(\n item: Record<string, unknown>,\n kindDisplayModes: Map<string, 'including-tax' | 'excluding-tax'>,\n): VariantPriceDraft | null {\n const kindId =\n typeof item.price_kind_id === 'string'\n ? item.price_kind_id\n : typeof item.priceKindId === 'string'\n ? item.priceKindId\n : null\n if (!kindId) return null\n const unitNet =\n typeof item.unit_price_net === 'string'\n ? item.unit_price_net\n : typeof item.unitPriceNet === 'string'\n ? item.unitPriceNet\n : null\n const unitGross =\n typeof item.unit_price_gross === 'string'\n ? item.unit_price_gross\n : typeof item.unitPriceGross === 'string'\n ? item.unitPriceGross\n : null\n const kindMode = kindDisplayModes.get(kindId) ?? (unitGross ? 'including-tax' : 'excluding-tax')\n return {\n priceKindId: kindId,\n priceId: typeof item.id === 'string' ? item.id : undefined,\n amount: kindMode === 'including-tax' ? (unitGross ?? unitNet ?? '') : (unitNet ?? unitGross ?? ''),\n currencyCode:\n typeof item.currency_code === 'string'\n ? item.currency_code\n : typeof item.currencyCode === 'string'\n ? item.currencyCode\n : null,\n displayMode: kindMode,\n }\n}\n"],
|
|
5
|
-
"mappings": ";AAGA,SAAS,
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport type { ProductMediaItem } from './ProductMediaManager'\nimport { createLocalId, type PriceKindSummary } from './productForm'\nimport { parseNumericInput } from './productFormUtils'\n\nexport type OptionDefinition = {\n id: string\n code: string\n label: string\n values: Array<{ id: string; label: string }>\n}\n\nexport type VariantPriceDraft = {\n priceKindId: string\n priceId?: string\n amount: string\n currencyCode?: string | null\n displayMode: 'including-tax' | 'excluding-tax'\n}\n\nexport type VariantFormValues = {\n name: string\n sku: string\n barcode: string\n isDefault: boolean\n isActive: boolean\n optionValues: Record<string, string>\n metadata?: Record<string, unknown> | null\n mediaDraftId: string\n mediaItems: ProductMediaItem[]\n defaultMediaId: string | null\n defaultMediaUrl: string\n prices: Record<string, VariantPriceDraft>\n taxRateId: string | null\n customFieldsetCode?: string | null\n}\n\nexport const VARIANT_BASE_VALUES: VariantFormValues = {\n name: '',\n sku: '',\n barcode: '',\n isDefault: false,\n isActive: true,\n optionValues: {},\n metadata: {},\n mediaDraftId: '',\n mediaItems: [],\n defaultMediaId: null,\n defaultMediaUrl: '',\n prices: {},\n taxRateId: null,\n customFieldsetCode: null,\n}\n\nexport const createVariantInitialValues = (): VariantFormValues => ({\n ...VARIANT_BASE_VALUES,\n mediaDraftId: createLocalId(),\n})\n\nexport function normalizeOptionSchema(raw: unknown): OptionDefinition[] {\n if (!Array.isArray(raw)) return []\n return raw\n .map((entry) => normalizeOptionDefinition(entry))\n .filter((entry): entry is OptionDefinition => !!entry)\n}\n\nfunction normalizeOptionDefinition(entry: unknown): OptionDefinition | null {\n if (!entry || typeof entry !== 'object') return null\n const code = extractString((entry as any).code) || createLocalId()\n const label = extractString((entry as any).label) || code\n const values = Array.isArray((entry as any).values)\n ? (entry as any).values\n .map((value: any) => {\n const id = extractString(value?.id) || createLocalId()\n const valueLabel = extractString(value?.label) || id\n return { id, label: valueLabel }\n })\n .filter(\n (value: { id: string; label: string }): value is { id: string; label: string } =>\n value.label.length > 0,\n )\n : []\n return {\n id: extractString((entry as any).id) || createLocalId(),\n code,\n label,\n values,\n }\n}\n\nfunction extractString(value: unknown): string {\n return typeof value === 'string' ? value.trim() : ''\n}\n\nexport function buildVariantMetadata(values: VariantFormValues): Record<string, unknown> {\n const metadata = typeof values.metadata === 'object' && values.metadata ? { ...values.metadata } : {}\n return metadata\n}\n\nexport function findInvalidVariantPriceKinds(\n priceKinds: PriceKindSummary[],\n priceDrafts: Record<string, VariantPriceDraft> | undefined,\n): string[] {\n const invalid: string[] = []\n for (const kind of priceKinds) {\n const draft = priceDrafts?.[kind.id]\n const amount = typeof draft?.amount === 'string' ? draft.amount.trim() : ''\n if (!amount) continue\n const numeric = parseNumericInput(amount)\n if (!Number.isFinite(numeric) || numeric < 0) invalid.push(kind.id)\n }\n return invalid\n}\n\nexport function mapPriceItemToDraft(\n item: Record<string, unknown>,\n kindDisplayModes: Map<string, 'including-tax' | 'excluding-tax'>,\n): VariantPriceDraft | null {\n const kindId =\n typeof item.price_kind_id === 'string'\n ? item.price_kind_id\n : typeof item.priceKindId === 'string'\n ? item.priceKindId\n : null\n if (!kindId) return null\n const unitNet =\n typeof item.unit_price_net === 'string'\n ? item.unit_price_net\n : typeof item.unitPriceNet === 'string'\n ? item.unitPriceNet\n : null\n const unitGross =\n typeof item.unit_price_gross === 'string'\n ? item.unit_price_gross\n : typeof item.unitPriceGross === 'string'\n ? item.unitPriceGross\n : null\n const kindMode = kindDisplayModes.get(kindId) ?? (unitGross ? 'including-tax' : 'excluding-tax')\n return {\n priceKindId: kindId,\n priceId: typeof item.id === 'string' ? item.id : undefined,\n amount: kindMode === 'including-tax' ? (unitGross ?? unitNet ?? '') : (unitNet ?? unitGross ?? ''),\n currencyCode:\n typeof item.currency_code === 'string'\n ? item.currency_code\n : typeof item.currencyCode === 'string'\n ? item.currencyCode\n : null,\n displayMode: kindMode,\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -95,7 +95,8 @@ function CreateOrganizationPage() {
|
|
|
95
95
|
setSelectedTenantId(normalized);
|
|
96
96
|
setValue(normalized);
|
|
97
97
|
},
|
|
98
|
-
includeEmptyOption:
|
|
98
|
+
includeEmptyOption: true,
|
|
99
|
+
emptyOptionLabel: t("directory.organizations.form.tenant.select", "Select tenant"),
|
|
99
100
|
className: "w-full h-9 rounded border px-2 text-sm"
|
|
100
101
|
}
|
|
101
102
|
)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../../src/modules/directory/backend/directory/organizations/create/page.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { E } from '#generated/entities.ids.generated'\nimport { OrganizationSelect } from '@open-mercato/core/modules/directory/components/OrganizationSelect'\nimport { TenantSelect } from '@open-mercato/core/modules/directory/components/TenantSelect'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { CrudForm, type CrudField, type CrudFormGroup } from '@open-mercato/ui/backend/CrudForm'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { collectCustomFieldValues } from '@open-mercato/ui/backend/utils/customFieldValues'\nimport { createCrud } from '@open-mercato/ui/backend/utils/crud'\nimport { createCrudFormError } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { type OrganizationTreeNode } from '@open-mercato/core/modules/directory/lib/tree'\n\ntype TreeResponse = {\n items: OrganizationTreeNode[]\n}\n\ntype ChildTreeSelectProps = {\n nodes: OrganizationTreeNode[]\n value: string[]\n onChange: (vals: string[]) => void\n}\n\nfunction ChildTreeSelect({ nodes, value, onChange }: ChildTreeSelectProps) {\n const t = useT()\n const selected = React.useMemo(() => new Set(value), [value])\n const handleToggle = React.useCallback((id: string) => {\n const next = new Set(value)\n if (next.has(id)) next.delete(id)\n else next.add(id)\n onChange(Array.from(next))\n }, [value, onChange])\n\n if (!nodes.length) {\n return (\n <div className=\"text-sm text-muted-foreground\">\n {t('directory.organizations.form.children.empty', 'No organizations available to assign.')}\n </div>\n )\n }\n\n return (\n <div className=\"rounded border px-3 py-2 max-h-64 overflow-auto space-y-2\">\n <TreeCheckboxGroup nodes={nodes} selected={selected} onToggle={handleToggle} level={0} />\n </div>\n )\n}\n\nfunction TreeCheckboxGroup({ nodes, selected, onToggle, level }: { nodes: OrganizationTreeNode[]; selected: Set<string>; onToggle: (id: string) => void; level: number }) {\n return (\n <div className={level === 0 ? 'space-y-1' : 'space-y-1 pl-5'}>\n {nodes.map((node) => (\n <div key={node.id} className=\"space-y-1\">\n <label className=\"inline-flex items-start gap-2 text-sm\">\n <input\n type=\"checkbox\"\n className=\"size-4 mt-0.5\"\n checked={selected.has(node.id)}\n onChange={() => onToggle(node.id)}\n />\n <span>{node.name}</span>\n </label>\n {node.children?.length ? (\n <TreeCheckboxGroup nodes={node.children} selected={selected} onToggle={onToggle} level={level + 1} />\n ) : null}\n </div>\n ))}\n </div>\n )\n}\n\nexport default function CreateOrganizationPage() {\n const [tree, setTree] = React.useState<OrganizationTreeNode[]>([])\n const [actorIsSuperAdmin, setActorIsSuperAdmin] = React.useState(false)\n const [selectedTenantId, setSelectedTenantId] = React.useState<string | null>(null)\n const t = useT()\n\n const loadTree = React.useCallback(async (tenantId: string | null) => {\n const params = new URLSearchParams({ view: 'tree', includeInactive: 'true' })\n if (tenantId) params.set('tenantId', tenantId)\n try {\n const call = await apiCall<TreeResponse>(`/api/directory/organizations?${params.toString()}`)\n const items = Array.isArray(call.result?.items) ? call.result.items : []\n setTree(items)\n } catch {\n setTree([])\n }\n }, [])\n\n React.useEffect(() => {\n let cancelled = false\n async function bootstrap() {\n try {\n const call = await apiCall<{ isSuperAdmin?: boolean }>('/api/auth/roles?page=1&pageSize=1')\n if (!cancelled) setActorIsSuperAdmin(Boolean(call.result?.isSuperAdmin))\n } catch {\n if (!cancelled) setActorIsSuperAdmin(false)\n }\n if (!cancelled) await loadTree(null)\n }\n bootstrap()\n return () => { cancelled = true }\n }, [loadTree])\n\n React.useEffect(() => {\n if (!actorIsSuperAdmin) return\n void loadTree(selectedTenantId)\n }, [actorIsSuperAdmin, loadTree, selectedTenantId])\n\n const fields = React.useMemo<CrudField[]>(() => [\n ...(actorIsSuperAdmin ? [\n {\n id: 'tenantId',\n label: t('directory.organizations.form.field.tenant', 'Tenant'),\n type: 'custom',\n required: true,\n component: ({ value, setValue }) => (\n <TenantSelect\n id=\"tenantId\"\n value={typeof value === 'string' ? value : selectedTenantId}\n onChange={(next) => {\n const normalized = next ?? null\n setSelectedTenantId(normalized)\n setValue(normalized)\n }}\n includeEmptyOption={false}\n className=\"w-full h-9 rounded border px-2 text-sm\"\n />\n ),\n } as CrudField,\n ] : []),\n { id: 'name', label: t('directory.organizations.form.field.name', 'Name'), type: 'text', required: true },\n {\n id: 'parentId',\n label: t('directory.organizations.form.field.parent', 'Parent'),\n type: 'custom',\n component: ({ id, value, setValue }) => (\n <OrganizationSelect\n id={id}\n value={value ? String(value) : null}\n onChange={(next) => setValue(next ?? '')}\n tenantId={selectedTenantId}\n fetchOnMount={true}\n includeEmptyOption\n emptyOptionLabel={t('directory.organizations.form.rootOption', '\u2014 Root level \u2014')}\n className=\"w-full h-9 rounded border px-2 text-sm\"\n />\n ),\n },\n {\n id: 'childIds',\n label: t('directory.organizations.form.field.children', 'Children'),\n type: 'custom',\n component: ({ value, setValue }) => (\n <ChildTreeSelect\n nodes={tree}\n value={Array.isArray(value) ? value : []}\n onChange={(vals) => setValue(vals)}\n />\n ),\n },\n { id: 'isActive', label: t('directory.organizations.form.field.isActive', 'Active'), type: 'checkbox' },\n ], [actorIsSuperAdmin, selectedTenantId, t, tree])\n\n const detailFields = React.useMemo(() => (\n actorIsSuperAdmin\n ? ['tenantId', 'name', 'parentId', 'childIds', 'isActive']\n : ['name', 'parentId', 'childIds', 'isActive']\n ), [actorIsSuperAdmin])\n\n const groups: CrudFormGroup[] = React.useMemo(() => ([\n { id: 'details', title: t('directory.organizations.form.group.details', 'Details'), column: 1, fields: detailFields },\n { id: 'custom', title: t('directory.organizations.form.group.customFields', 'Custom Data'), column: 2, kind: 'customFields' },\n ]), [detailFields, t])\n const formTitle = t('directory.nav.organizations.create', 'Create Organization')\n const successMessage = encodeURIComponent(t('directory.organizations.flash.created', 'Organization created'))\n\n return (\n <Page>\n <PageBody>\n <CrudForm\n title={formTitle}\n backHref=\"/backend/directory/organizations\"\n fields={fields}\n groups={groups}\n entityId={E.directory.organization}\n initialValues={{ tenantId: selectedTenantId ?? null, name: '', parentId: '', childIds: [], isActive: true }}\n submitLabel={t('directory.organizations.form.action.create', 'Create')}\n cancelHref=\"/backend/directory/organizations\"\n successRedirect={`/backend/directory/organizations?flash=${successMessage}&type=success`}\n onSubmit={async (values) => {\n await submitCreateOrganization({\n values: values as Record<string, unknown>,\n actorIsSuperAdmin,\n selectedTenantId,\n messages: {\n tenantRequired: t('directory.organizations.errors.tenantRequired', 'Tenant selection is required for super administrators'),\n },\n })\n }}\n />\n </PageBody>\n </Page>\n )\n}\n\ntype CreateOrganizationPayload = {\n name: string\n isActive: boolean\n parentId: string | null\n childIds: string[]\n tenantId?: string\n customFields?: Record<string, unknown>\n}\n\ntype CreateOrganizationRequest = (payload: CreateOrganizationPayload) => Promise<void>\n\nasync function defaultCreateOrganizationRequest(payload: CreateOrganizationPayload) {\n await createCrud('directory/organizations', payload)\n}\n\nexport async function submitCreateOrganization(options: {\n values: Record<string, unknown>\n actorIsSuperAdmin: boolean\n selectedTenantId: string | null\n createOrganization?: CreateOrganizationRequest\n messages?: {\n tenantRequired?: string\n }\n}): Promise<void> {\n const {\n values,\n actorIsSuperAdmin,\n selectedTenantId,\n createOrganization = defaultCreateOrganizationRequest,\n messages,\n } = options\n\n const customFields = collectCustomFieldValues(values)\n\n const tenantValue =\n typeof values.tenantId === 'string' && values.tenantId.trim().length\n ? values.tenantId.trim()\n : selectedTenantId\n\n if (actorIsSuperAdmin && !tenantValue) {\n const message = messages?.tenantRequired ?? 'Tenant selection is required for super administrators'\n throw createCrudFormError(message, {\n tenantId: message,\n })\n }\n\n const payload: CreateOrganizationPayload = {\n name: typeof values.name === 'string' ? values.name : '',\n isActive: values.isActive !== false,\n parentId: typeof values.parentId === 'string' && values.parentId.length\n ? values.parentId\n : null,\n childIds: Array.isArray(values.childIds) ? values.childIds.filter((id): id is string => typeof id === 'string') : [],\n }\n\n if (tenantValue) payload.tenantId = tenantValue\n if (Object.keys(customFields).length > 0) payload.customFields = customFields\n\n await createOrganization(payload)\n}\n"],
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { E } from '#generated/entities.ids.generated'\nimport { OrganizationSelect } from '@open-mercato/core/modules/directory/components/OrganizationSelect'\nimport { TenantSelect } from '@open-mercato/core/modules/directory/components/TenantSelect'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { CrudForm, type CrudField, type CrudFormGroup } from '@open-mercato/ui/backend/CrudForm'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { collectCustomFieldValues } from '@open-mercato/ui/backend/utils/customFieldValues'\nimport { createCrud } from '@open-mercato/ui/backend/utils/crud'\nimport { createCrudFormError } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { type OrganizationTreeNode } from '@open-mercato/core/modules/directory/lib/tree'\n\ntype TreeResponse = {\n items: OrganizationTreeNode[]\n}\n\ntype ChildTreeSelectProps = {\n nodes: OrganizationTreeNode[]\n value: string[]\n onChange: (vals: string[]) => void\n}\n\nfunction ChildTreeSelect({ nodes, value, onChange }: ChildTreeSelectProps) {\n const t = useT()\n const selected = React.useMemo(() => new Set(value), [value])\n const handleToggle = React.useCallback((id: string) => {\n const next = new Set(value)\n if (next.has(id)) next.delete(id)\n else next.add(id)\n onChange(Array.from(next))\n }, [value, onChange])\n\n if (!nodes.length) {\n return (\n <div className=\"text-sm text-muted-foreground\">\n {t('directory.organizations.form.children.empty', 'No organizations available to assign.')}\n </div>\n )\n }\n\n return (\n <div className=\"rounded border px-3 py-2 max-h-64 overflow-auto space-y-2\">\n <TreeCheckboxGroup nodes={nodes} selected={selected} onToggle={handleToggle} level={0} />\n </div>\n )\n}\n\nfunction TreeCheckboxGroup({ nodes, selected, onToggle, level }: { nodes: OrganizationTreeNode[]; selected: Set<string>; onToggle: (id: string) => void; level: number }) {\n return (\n <div className={level === 0 ? 'space-y-1' : 'space-y-1 pl-5'}>\n {nodes.map((node) => (\n <div key={node.id} className=\"space-y-1\">\n <label className=\"inline-flex items-start gap-2 text-sm\">\n <input\n type=\"checkbox\"\n className=\"size-4 mt-0.5\"\n checked={selected.has(node.id)}\n onChange={() => onToggle(node.id)}\n />\n <span>{node.name}</span>\n </label>\n {node.children?.length ? (\n <TreeCheckboxGroup nodes={node.children} selected={selected} onToggle={onToggle} level={level + 1} />\n ) : null}\n </div>\n ))}\n </div>\n )\n}\n\nexport default function CreateOrganizationPage() {\n const [tree, setTree] = React.useState<OrganizationTreeNode[]>([])\n const [actorIsSuperAdmin, setActorIsSuperAdmin] = React.useState(false)\n const [selectedTenantId, setSelectedTenantId] = React.useState<string | null>(null)\n const t = useT()\n\n const loadTree = React.useCallback(async (tenantId: string | null) => {\n const params = new URLSearchParams({ view: 'tree', includeInactive: 'true' })\n if (tenantId) params.set('tenantId', tenantId)\n try {\n const call = await apiCall<TreeResponse>(`/api/directory/organizations?${params.toString()}`)\n const items = Array.isArray(call.result?.items) ? call.result.items : []\n setTree(items)\n } catch {\n setTree([])\n }\n }, [])\n\n React.useEffect(() => {\n let cancelled = false\n async function bootstrap() {\n try {\n const call = await apiCall<{ isSuperAdmin?: boolean }>('/api/auth/roles?page=1&pageSize=1')\n if (!cancelled) setActorIsSuperAdmin(Boolean(call.result?.isSuperAdmin))\n } catch {\n if (!cancelled) setActorIsSuperAdmin(false)\n }\n if (!cancelled) await loadTree(null)\n }\n bootstrap()\n return () => { cancelled = true }\n }, [loadTree])\n\n React.useEffect(() => {\n if (!actorIsSuperAdmin) return\n void loadTree(selectedTenantId)\n }, [actorIsSuperAdmin, loadTree, selectedTenantId])\n\n const fields = React.useMemo<CrudField[]>(() => [\n ...(actorIsSuperAdmin ? [\n {\n id: 'tenantId',\n label: t('directory.organizations.form.field.tenant', 'Tenant'),\n type: 'custom',\n required: true,\n component: ({ value, setValue }) => (\n <TenantSelect\n id=\"tenantId\"\n value={typeof value === 'string' ? value : selectedTenantId}\n onChange={(next) => {\n const normalized = next ?? null\n setSelectedTenantId(normalized)\n setValue(normalized)\n }}\n includeEmptyOption\n emptyOptionLabel={t('directory.organizations.form.tenant.select', 'Select tenant')}\n className=\"w-full h-9 rounded border px-2 text-sm\"\n />\n ),\n } as CrudField,\n ] : []),\n { id: 'name', label: t('directory.organizations.form.field.name', 'Name'), type: 'text', required: true },\n {\n id: 'parentId',\n label: t('directory.organizations.form.field.parent', 'Parent'),\n type: 'custom',\n component: ({ id, value, setValue }) => (\n <OrganizationSelect\n id={id}\n value={value ? String(value) : null}\n onChange={(next) => setValue(next ?? '')}\n tenantId={selectedTenantId}\n fetchOnMount={true}\n includeEmptyOption\n emptyOptionLabel={t('directory.organizations.form.rootOption', '\u2014 Root level \u2014')}\n className=\"w-full h-9 rounded border px-2 text-sm\"\n />\n ),\n },\n {\n id: 'childIds',\n label: t('directory.organizations.form.field.children', 'Children'),\n type: 'custom',\n component: ({ value, setValue }) => (\n <ChildTreeSelect\n nodes={tree}\n value={Array.isArray(value) ? value : []}\n onChange={(vals) => setValue(vals)}\n />\n ),\n },\n { id: 'isActive', label: t('directory.organizations.form.field.isActive', 'Active'), type: 'checkbox' },\n ], [actorIsSuperAdmin, selectedTenantId, t, tree])\n\n const detailFields = React.useMemo(() => (\n actorIsSuperAdmin\n ? ['tenantId', 'name', 'parentId', 'childIds', 'isActive']\n : ['name', 'parentId', 'childIds', 'isActive']\n ), [actorIsSuperAdmin])\n\n const groups: CrudFormGroup[] = React.useMemo(() => ([\n { id: 'details', title: t('directory.organizations.form.group.details', 'Details'), column: 1, fields: detailFields },\n { id: 'custom', title: t('directory.organizations.form.group.customFields', 'Custom Data'), column: 2, kind: 'customFields' },\n ]), [detailFields, t])\n const formTitle = t('directory.nav.organizations.create', 'Create Organization')\n const successMessage = encodeURIComponent(t('directory.organizations.flash.created', 'Organization created'))\n\n return (\n <Page>\n <PageBody>\n <CrudForm\n title={formTitle}\n backHref=\"/backend/directory/organizations\"\n fields={fields}\n groups={groups}\n entityId={E.directory.organization}\n initialValues={{ tenantId: selectedTenantId ?? null, name: '', parentId: '', childIds: [], isActive: true }}\n submitLabel={t('directory.organizations.form.action.create', 'Create')}\n cancelHref=\"/backend/directory/organizations\"\n successRedirect={`/backend/directory/organizations?flash=${successMessage}&type=success`}\n onSubmit={async (values) => {\n await submitCreateOrganization({\n values: values as Record<string, unknown>,\n actorIsSuperAdmin,\n selectedTenantId,\n messages: {\n tenantRequired: t('directory.organizations.errors.tenantRequired', 'Tenant selection is required for super administrators'),\n },\n })\n }}\n />\n </PageBody>\n </Page>\n )\n}\n\ntype CreateOrganizationPayload = {\n name: string\n isActive: boolean\n parentId: string | null\n childIds: string[]\n tenantId?: string\n customFields?: Record<string, unknown>\n}\n\ntype CreateOrganizationRequest = (payload: CreateOrganizationPayload) => Promise<void>\n\nasync function defaultCreateOrganizationRequest(payload: CreateOrganizationPayload) {\n await createCrud('directory/organizations', payload)\n}\n\nexport async function submitCreateOrganization(options: {\n values: Record<string, unknown>\n actorIsSuperAdmin: boolean\n selectedTenantId: string | null\n createOrganization?: CreateOrganizationRequest\n messages?: {\n tenantRequired?: string\n }\n}): Promise<void> {\n const {\n values,\n actorIsSuperAdmin,\n selectedTenantId,\n createOrganization = defaultCreateOrganizationRequest,\n messages,\n } = options\n\n const customFields = collectCustomFieldValues(values)\n\n const tenantValue =\n typeof values.tenantId === 'string' && values.tenantId.trim().length\n ? values.tenantId.trim()\n : selectedTenantId\n\n if (actorIsSuperAdmin && !tenantValue) {\n const message = messages?.tenantRequired ?? 'Tenant selection is required for super administrators'\n throw createCrudFormError(message, {\n tenantId: message,\n })\n }\n\n const payload: CreateOrganizationPayload = {\n name: typeof values.name === 'string' ? values.name : '',\n isActive: values.isActive !== false,\n parentId: typeof values.parentId === 'string' && values.parentId.length\n ? values.parentId\n : null,\n childIds: Array.isArray(values.childIds) ? values.childIds.filter((id): id is string => typeof id === 'string') : [],\n }\n\n if (tenantValue) payload.tenantId = tenantValue\n if (Object.keys(customFields).length > 0) payload.customFields = customFields\n\n await createOrganization(payload)\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/core",
|
|
3
|
-
"version": "0.4.7-develop-
|
|
3
|
+
"version": "0.4.7-develop-4381462230",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -207,7 +207,7 @@
|
|
|
207
207
|
}
|
|
208
208
|
},
|
|
209
209
|
"dependencies": {
|
|
210
|
-
"@open-mercato/shared": "0.4.7-develop-
|
|
210
|
+
"@open-mercato/shared": "0.4.7-develop-4381462230",
|
|
211
211
|
"@types/html-to-text": "^9.0.4",
|
|
212
212
|
"@types/semver": "^7.5.8",
|
|
213
213
|
"@xyflow/react": "^12.6.0",
|
package/src/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.tsx
CHANGED
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
createVariantInitialValues,
|
|
20
20
|
normalizeOptionSchema,
|
|
21
21
|
mapPriceItemToDraft,
|
|
22
|
+
findInvalidVariantPriceKinds,
|
|
22
23
|
} from '@open-mercato/core/modules/catalog/components/products/variantForm'
|
|
23
24
|
import {
|
|
24
25
|
type PriceKindSummary,
|
|
@@ -26,6 +27,7 @@ import {
|
|
|
26
27
|
type TaxRateSummary,
|
|
27
28
|
normalizePriceKindSummary,
|
|
28
29
|
} from '@open-mercato/core/modules/catalog/components/products/productForm'
|
|
30
|
+
import { parseNumericInput } from '@open-mercato/core/modules/catalog/components/products/productFormUtils'
|
|
29
31
|
import {
|
|
30
32
|
VariantBasicsSection,
|
|
31
33
|
VariantOptionValuesSection,
|
|
@@ -463,6 +465,11 @@ export default function EditVariantPage({ params }: { params?: { productId?: str
|
|
|
463
465
|
const message = t('catalog.variants.form.errors.nameRequired', 'Provide the variant name.')
|
|
464
466
|
throw createCrudFormError(message, { name: message })
|
|
465
467
|
}
|
|
468
|
+
const invalidPriceKinds = findInvalidVariantPriceKinds(priceKinds, values.prices)
|
|
469
|
+
if (invalidPriceKinds.length) {
|
|
470
|
+
const message = t('catalog.variants.form.errors.invalidPrice', 'Provide a valid non-negative price.')
|
|
471
|
+
throw createCrudFormError(message, { prices: message })
|
|
472
|
+
}
|
|
466
473
|
const resolveTaxRateValue = (taxRateId?: string | null) => {
|
|
467
474
|
if (!taxRateId) return null
|
|
468
475
|
const match = taxRates.find((rate) => rate.id === taxRateId)
|
|
@@ -678,8 +685,8 @@ async function syncVariantPricesUpdate({
|
|
|
678
685
|
}
|
|
679
686
|
continue
|
|
680
687
|
}
|
|
681
|
-
const numeric =
|
|
682
|
-
if (Number.
|
|
688
|
+
const numeric = parseNumericInput(amount)
|
|
689
|
+
if (!Number.isFinite(numeric) || numeric < 0) continue
|
|
683
690
|
const payload: Record<string, unknown> = {
|
|
684
691
|
productId,
|
|
685
692
|
variantId,
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
type OptionDefinition,
|
|
18
18
|
createVariantInitialValues,
|
|
19
19
|
normalizeOptionSchema,
|
|
20
|
+
findInvalidVariantPriceKinds,
|
|
20
21
|
} from '@open-mercato/core/modules/catalog/components/products/variantForm'
|
|
21
22
|
import {
|
|
22
23
|
type PriceKindSummary,
|
|
@@ -24,6 +25,7 @@ import {
|
|
|
24
25
|
type TaxRateSummary,
|
|
25
26
|
normalizePriceKindSummary,
|
|
26
27
|
} from '@open-mercato/core/modules/catalog/components/products/productForm'
|
|
28
|
+
import { parseNumericInput } from '@open-mercato/core/modules/catalog/components/products/productFormUtils'
|
|
27
29
|
import {
|
|
28
30
|
VariantBasicsSection,
|
|
29
31
|
VariantOptionValuesSection,
|
|
@@ -316,6 +318,11 @@ export default function CreateVariantPage({ params }: { params?: { productId?: s
|
|
|
316
318
|
const message = t('catalog.variants.form.errors.nameRequired', 'Provide the variant name.')
|
|
317
319
|
throw createCrudFormError(message, { name: message })
|
|
318
320
|
}
|
|
321
|
+
const invalidPriceKinds = findInvalidVariantPriceKinds(priceKinds, values.prices)
|
|
322
|
+
if (invalidPriceKinds.length) {
|
|
323
|
+
const message = t('catalog.variants.form.errors.invalidPrice', 'Provide a valid non-negative price.')
|
|
324
|
+
throw createCrudFormError(message, { prices: message })
|
|
325
|
+
}
|
|
319
326
|
const resolveTaxRateValue = (taxRateId?: string | null) => {
|
|
320
327
|
if (!taxRateId) return null
|
|
321
328
|
const match = taxRates.find((rate) => rate.id === taxRateId)
|
|
@@ -447,8 +454,8 @@ async function syncVariantPrices({
|
|
|
447
454
|
const draft = priceDrafts?.[kind.id]
|
|
448
455
|
const amount = typeof draft?.amount === 'string' ? draft.amount.trim() : ''
|
|
449
456
|
if (!amount) continue
|
|
450
|
-
const numeric =
|
|
451
|
-
if (Number.
|
|
457
|
+
const numeric = parseNumericInput(amount)
|
|
458
|
+
if (!Number.isFinite(numeric) || numeric < 0) continue
|
|
452
459
|
const payload: Record<string, unknown> = {
|
|
453
460
|
productId,
|
|
454
461
|
variantId,
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
3
|
import type { ProductMediaItem } from './ProductMediaManager'
|
|
4
|
-
import { createLocalId } from './productForm'
|
|
4
|
+
import { createLocalId, type PriceKindSummary } from './productForm'
|
|
5
|
+
import { parseNumericInput } from './productFormUtils'
|
|
5
6
|
|
|
6
7
|
export type OptionDefinition = {
|
|
7
8
|
id: string
|
|
@@ -97,6 +98,21 @@ export function buildVariantMetadata(values: VariantFormValues): Record<string,
|
|
|
97
98
|
return metadata
|
|
98
99
|
}
|
|
99
100
|
|
|
101
|
+
export function findInvalidVariantPriceKinds(
|
|
102
|
+
priceKinds: PriceKindSummary[],
|
|
103
|
+
priceDrafts: Record<string, VariantPriceDraft> | undefined,
|
|
104
|
+
): string[] {
|
|
105
|
+
const invalid: string[] = []
|
|
106
|
+
for (const kind of priceKinds) {
|
|
107
|
+
const draft = priceDrafts?.[kind.id]
|
|
108
|
+
const amount = typeof draft?.amount === 'string' ? draft.amount.trim() : ''
|
|
109
|
+
if (!amount) continue
|
|
110
|
+
const numeric = parseNumericInput(amount)
|
|
111
|
+
if (!Number.isFinite(numeric) || numeric < 0) invalid.push(kind.id)
|
|
112
|
+
}
|
|
113
|
+
return invalid
|
|
114
|
+
}
|
|
115
|
+
|
|
100
116
|
export function mapPriceItemToDraft(
|
|
101
117
|
item: Record<string, unknown>,
|
|
102
118
|
kindDisplayModes: Map<string, 'including-tax' | 'excluding-tax'>,
|
|
@@ -540,6 +540,7 @@
|
|
|
540
540
|
"catalog.variants.form.editTitleFor": "Variante bearbeiten · {{title}}",
|
|
541
541
|
"catalog.variants.form.errors.deleteError": "Variante konnte nicht gelöscht werden.",
|
|
542
542
|
"catalog.variants.form.errors.idMissing": "Varianten-ID fehlt nach dem Erstellen.",
|
|
543
|
+
"catalog.variants.form.errors.invalidPrice": "Geben Sie einen gultigen, nicht negativen Preis an.",
|
|
543
544
|
"catalog.variants.form.errors.load": "Variante konnte nicht geladen werden.",
|
|
544
545
|
"catalog.variants.form.errors.nameRequired": "Geben Sie den Variantennamen an.",
|
|
545
546
|
"catalog.variants.form.errors.notFound": "Variante nicht gefunden.",
|
|
@@ -540,6 +540,7 @@
|
|
|
540
540
|
"catalog.variants.form.editTitleFor": "Edit variant • {{title}}",
|
|
541
541
|
"catalog.variants.form.errors.deleteError": "Failed to delete variant.",
|
|
542
542
|
"catalog.variants.form.errors.idMissing": "Variant id missing after create.",
|
|
543
|
+
"catalog.variants.form.errors.invalidPrice": "Provide a valid non-negative price.",
|
|
543
544
|
"catalog.variants.form.errors.load": "Failed to load variant.",
|
|
544
545
|
"catalog.variants.form.errors.nameRequired": "Provide the variant name.",
|
|
545
546
|
"catalog.variants.form.errors.notFound": "Variant not found.",
|
|
@@ -540,6 +540,7 @@
|
|
|
540
540
|
"catalog.variants.form.editTitleFor": "Editar variante · {{title}}",
|
|
541
541
|
"catalog.variants.form.errors.deleteError": "No se pudo eliminar la variante.",
|
|
542
542
|
"catalog.variants.form.errors.idMissing": "Falta el ID de la variante después de la creación.",
|
|
543
|
+
"catalog.variants.form.errors.invalidPrice": "Proporcione un precio valido y no negativo.",
|
|
543
544
|
"catalog.variants.form.errors.load": "No se pudo cargar la variante.",
|
|
544
545
|
"catalog.variants.form.errors.nameRequired": "Ingrese el nombre de la variante.",
|
|
545
546
|
"catalog.variants.form.errors.notFound": "Variante no encontrada.",
|
|
@@ -540,6 +540,7 @@
|
|
|
540
540
|
"catalog.variants.form.editTitleFor": "Edytuj wariant • {{title}}",
|
|
541
541
|
"catalog.variants.form.errors.deleteError": "Nie udało się usunąć wariantu.",
|
|
542
542
|
"catalog.variants.form.errors.idMissing": "Brak ID wariantu po utworzeniu.",
|
|
543
|
+
"catalog.variants.form.errors.invalidPrice": "Podaj poprawną, nienegatywną cene.",
|
|
543
544
|
"catalog.variants.form.errors.load": "Nie udało się załadować wariantu.",
|
|
544
545
|
"catalog.variants.form.errors.nameRequired": "Podaj nazwę wariantu.",
|
|
545
546
|
"catalog.variants.form.errors.notFound": "Nie znaleziono wariantu.",
|
|
@@ -124,7 +124,8 @@ export default function CreateOrganizationPage() {
|
|
|
124
124
|
setSelectedTenantId(normalized)
|
|
125
125
|
setValue(normalized)
|
|
126
126
|
}}
|
|
127
|
-
includeEmptyOption
|
|
127
|
+
includeEmptyOption
|
|
128
|
+
emptyOptionLabel={t('directory.organizations.form.tenant.select', 'Select tenant')}
|
|
128
129
|
className="w-full h-9 rounded border px-2 text-sm"
|
|
129
130
|
/>
|
|
130
131
|
),
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
"directory.organizations.form.loading": "Organisation wird geladen...",
|
|
34
34
|
"directory.organizations.form.pathLabel": "Pfad: {path}",
|
|
35
35
|
"directory.organizations.form.rootOption": "— Wurzelebene —",
|
|
36
|
+
"directory.organizations.form.tenant.select": "Mandanten auswählen",
|
|
36
37
|
"directory.organizations.form.title.edit": "Organisation bearbeiten",
|
|
37
38
|
"directory.organizations.list.actions.create": "Erstellen",
|
|
38
39
|
"directory.organizations.list.actions.delete": "Löschen",
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
"directory.organizations.form.loading": "Loading organization...",
|
|
34
34
|
"directory.organizations.form.pathLabel": "Path: {path}",
|
|
35
35
|
"directory.organizations.form.rootOption": "— Root level —",
|
|
36
|
+
"directory.organizations.form.tenant.select": "Select tenant",
|
|
36
37
|
"directory.organizations.form.title.edit": "Edit Organization",
|
|
37
38
|
"directory.organizations.list.actions.create": "Create",
|
|
38
39
|
"directory.organizations.list.actions.delete": "Delete",
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
"directory.organizations.form.loading": "Cargando organización...",
|
|
34
34
|
"directory.organizations.form.pathLabel": "Ruta: {path}",
|
|
35
35
|
"directory.organizations.form.rootOption": "— Nivel raíz —",
|
|
36
|
+
"directory.organizations.form.tenant.select": "Seleccionar inquilino",
|
|
36
37
|
"directory.organizations.form.title.edit": "Editar organización",
|
|
37
38
|
"directory.organizations.list.actions.create": "Crear",
|
|
38
39
|
"directory.organizations.list.actions.delete": "Eliminar",
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
"directory.organizations.form.loading": "Ładowanie organizacji...",
|
|
34
34
|
"directory.organizations.form.pathLabel": "Ścieżka: {path}",
|
|
35
35
|
"directory.organizations.form.rootOption": "— Poziom główny —",
|
|
36
|
+
"directory.organizations.form.tenant.select": "Wybierz najemcę",
|
|
36
37
|
"directory.organizations.form.title.edit": "Edytuj organizację",
|
|
37
38
|
"directory.organizations.list.actions.create": "Utwórz",
|
|
38
39
|
"directory.organizations.list.actions.delete": "Usuń",
|