@open-mercato/core 0.8.1-develop.7207.1.901ef7e47a → 0.8.1-develop.7218.1.9f22882799
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/.turbo/turbo-build.log +1 -1
- package/dist/modules/catalog/backend/catalog/products/[id]/page.js +6 -2
- package/dist/modules/catalog/backend/catalog/products/[id]/page.js.map +2 -2
- package/dist/modules/catalog/backend/catalog/products/create/page.js +7 -3
- package/dist/modules/catalog/backend/catalog/products/create/page.js.map +2 -2
- package/dist/modules/catalog/components/products/ProductUomSection.js +11 -22
- package/dist/modules/catalog/components/products/ProductUomSection.js.map +2 -2
- package/dist/modules/catalog/components/products/productForm.js +26 -1
- package/dist/modules/catalog/components/products/productForm.js.map +2 -2
- package/dist/modules/configs/lib/upgrade-actions.js +1 -0
- package/dist/modules/configs/lib/upgrade-actions.js.map +2 -2
- package/dist/modules/configs/services/upgradeActionsService.js +9 -2
- package/dist/modules/configs/services/upgradeActionsService.js.map +2 -2
- package/dist/modules/customers/components/calendar/CalendarEventEditor.js +1 -4
- package/dist/modules/customers/components/calendar/CalendarEventEditor.js.map +2 -2
- package/dist/modules/customers/components/calendar/CalendarToolbar.js +3 -0
- package/dist/modules/customers/components/calendar/CalendarToolbar.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/RepeatField.js +1 -3
- package/dist/modules/customers/components/calendar/editor/RepeatField.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/ScheduleSection.js +1 -5
- package/dist/modules/customers/components/calendar/editor/ScheduleSection.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/inputs.js.map +2 -2
- package/dist/modules/entities/lib/definition-scope-where.js +18 -0
- package/dist/modules/entities/lib/definition-scope-where.js.map +7 -0
- package/dist/modules/entities/lib/definition-scope.js +2 -8
- package/dist/modules/entities/lib/definition-scope.js.map +2 -2
- package/dist/modules/entities/lib/helpers.js +6 -32
- package/dist/modules/entities/lib/helpers.js.map +2 -2
- package/dist/modules/entities/lib/scoped-field-defs.js +5 -10
- package/dist/modules/entities/lib/scoped-field-defs.js.map +2 -2
- package/dist/modules/query_index/lib/batch.js +2 -2
- package/dist/modules/query_index/lib/batch.js.map +2 -2
- package/dist/modules/query_index/lib/document.js +7 -1
- package/dist/modules/query_index/lib/document.js.map +2 -2
- package/dist/modules/query_index/lib/engine.js.map +1 -1
- package/dist/modules/query_index/lib/indexer.js +9 -2
- package/dist/modules/query_index/lib/indexer.js.map +2 -2
- package/dist/modules/query_index/lib/search-tokens.js.map +2 -2
- package/dist/modules/wms/components/backend/AdjustInventoryDialog.js +15 -1
- package/dist/modules/wms/components/backend/AdjustInventoryDialog.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/catalog/backend/catalog/products/[id]/page.tsx +6 -2
- package/src/modules/catalog/backend/catalog/products/create/page.tsx +7 -6
- package/src/modules/catalog/components/products/ProductUomSection.tsx +11 -31
- package/src/modules/catalog/components/products/productForm.ts +53 -0
- package/src/modules/configs/lib/upgrade-actions.ts +7 -0
- package/src/modules/configs/services/upgradeActionsService.ts +17 -2
- package/src/modules/customers/components/calendar/CalendarEventEditor.tsx +1 -4
- package/src/modules/customers/components/calendar/CalendarToolbar.tsx +3 -0
- package/src/modules/customers/components/calendar/editor/RepeatField.tsx +0 -3
- package/src/modules/customers/components/calendar/editor/ScheduleSection.tsx +1 -7
- package/src/modules/customers/components/calendar/editor/inputs.tsx +0 -1
- package/src/modules/entities/lib/definition-scope-where.ts +27 -0
- package/src/modules/entities/lib/definition-scope.ts +2 -10
- package/src/modules/entities/lib/helpers.ts +6 -35
- package/src/modules/entities/lib/scoped-field-defs.ts +6 -15
- package/src/modules/query_index/lib/batch.ts +8 -2
- package/src/modules/query_index/lib/document.ts +28 -0
- package/src/modules/query_index/lib/engine.ts +4 -4
- package/src/modules/query_index/lib/indexer.ts +15 -2
- package/src/modules/query_index/lib/search-tokens.ts +31 -0
- package/src/modules/wms/components/backend/AdjustInventoryDialog.tsx +21 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/wms/components/backend/AdjustInventoryDialog.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { z } from 'zod'\nimport { useQueryClient } from '@tanstack/react-query'\nimport { Minus, Package, Plus } from 'lucide-react'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { flashMutationError } from '../../lib/flashMutationError'\nimport { ComboboxInput } from '@open-mercato/ui/backend/inputs/ComboboxInput'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { raiseCrudError } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from '@open-mercato/ui/primitives/dialog'\nimport { FormField } from '@open-mercato/ui/primitives/form-field'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { KbdShortcut } from '@open-mercato/ui/primitives/kbd'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@open-mercato/ui/primitives/select'\nimport { StatusBadge } from '@open-mercato/ui/primitives/status-badge'\nimport { Textarea } from '@open-mercato/ui/primitives/textarea'\nimport { useT, useLocale } from '@open-mercato/shared/lib/i18n/context'\nimport { parseLocaleNumber } from '@open-mercato/shared/lib/number'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { buildInventoryMutationReferenceId } from '../../lib/inventoryMutationUi'\nimport {\n BalanceLookupError,\n ensureLotIdForInventoryMutation,\n fetchBalanceOnHand,\n fetchVariantReorderPoint,\n findLotIdByNumber,\n InventoryLotMutationError,\n loadCatalogVariantOptions,\n loadLocationOptions,\n loadLotNumberOptions,\n loadWarehouseOptions,\n resolveCatalogVariantLabel,\n resolveLocationLabel,\n resolveLotNumberFromId,\n resolveWarehouseLabel,\n} from './inventoryMutationLoaders'\nimport type { useWmsInventoryMutationAccess } from './useWmsInventoryMutationAccess'\n\nconst logger = createLogger('wms')\n\nconst ADJUST_REASON_CODES = ['damaged', 'shrinkage', 'found', 'correction', 'other'] as const\n\ntype AdjustReasonCode = (typeof ADJUST_REASON_CODES)[number]\n\ntype AdjustFormValues = {\n catalogVariantId: string\n warehouseId: string\n locationId: string\n lotNumber: string\n delta: number\n reasonCode: AdjustReasonCode | ''\n notes: string\n serialNumber: string\n}\n\ntype AdjustInventoryDialogProps = {\n open: boolean\n onOpenChange: (open: boolean) => void\n access: ReturnType<typeof useWmsInventoryMutationAccess>\n initialCatalogVariantId?: string\n initialWarehouseId?: string\n initialLocationId?: string\n initialLotId?: string\n}\n\nconst EMPTY_FORM: AdjustFormValues = {\n catalogVariantId: '',\n warehouseId: '',\n locationId: '',\n lotNumber: '',\n delta: 1,\n reasonCode: '',\n notes: '',\n serialNumber: '',\n}\n\nexport function parseDeltaInput(value: string, locale?: string): number | null {\n const trimmed = value.trim()\n if (!trimmed || trimmed === '-' || trimmed === '+') return null\n return parseLocaleNumber(trimmed, locale)\n}\n\nexport function AdjustInventoryDialog({\n open,\n onOpenChange,\n access,\n initialCatalogVariantId,\n initialWarehouseId,\n initialLocationId,\n initialLotId,\n}: AdjustInventoryDialogProps) {\n const t = useT()\n const locale = useLocale()\n const queryClient = useQueryClient()\n const formRef = React.useRef<HTMLFormElement>(null)\n const { runMutation, retryLastMutation } = useGuardedMutation<{\n retryLastMutation: () => Promise<boolean>\n }>({\n contextId: 'wms-inventory-adjust',\n })\n const mutationContext = React.useMemo(\n () => ({ retryLastMutation }),\n [retryLastMutation],\n )\n const adjustFormSchema = React.useMemo(\n () =>\n z.object({\n catalogVariantId: z.string().uuid(),\n warehouseId: z.string().uuid(),\n locationId: z.string().uuid(),\n lotNumber: z.string().trim().max(120).optional(),\n delta: z.coerce.number().refine((value) => value !== 0, {\n message: t(\n 'wms.backend.inventory.adjust.errors.deltaZero',\n 'Inventory delta must be non-zero.',\n ),\n }),\n reasonCode: z.enum(ADJUST_REASON_CODES),\n notes: z.string().trim().max(500).optional(),\n serialNumber: z.string().trim().max(120).optional(),\n }),\n [t],\n )\n const [submitting, setSubmitting] = React.useState(false)\n const [form, setForm] = React.useState<AdjustFormValues>(EMPTY_FORM)\n const [fieldErrors, setFieldErrors] = React.useState<Record<string, string>>({})\n const [onHand, setOnHand] = React.useState<number | null>(null)\n const [previewError, setPreviewError] = React.useState<string | null>(null)\n const [reorderPoint, setReorderPoint] = React.useState(0)\n const [loadingPreview, setLoadingPreview] = React.useState(false)\n const [optionLabelByValue, setOptionLabelByValue] = React.useState<Record<string, string>>({})\n\n const registerOptionLabels = React.useCallback(\n (options: Array<{ value: string; label: string }>) => {\n setOptionLabelByValue((current) => {\n let changed = false\n const next = { ...current }\n for (const option of options) {\n const value = option.value.trim()\n const label = option.label.trim()\n if (!value || !label || next[value] === label) continue\n next[value] = label\n changed = true\n }\n return changed ? next : current\n })\n },\n [],\n )\n\n const resolveOptionLabel = React.useCallback(\n (value: string) => optionLabelByValue[value] ?? value,\n [optionLabelByValue],\n )\n const optionLabelByValueRef = React.useRef(optionLabelByValue)\n optionLabelByValueRef.current = optionLabelByValue\n\n const reasonLabel = React.useCallback(\n (code: AdjustReasonCode) => {\n const fallbacks: Record<AdjustReasonCode, string> = {\n damaged: 'Damaged',\n shrinkage: 'Shrinkage',\n found: 'Found stock',\n correction: 'Correction',\n other: 'Other',\n }\n return t(`wms.backend.inventory.adjust.reasons.${code}`, fallbacks[code])\n },\n [t],\n )\n\n const resetDialog = React.useCallback(() => {\n setForm(EMPTY_FORM)\n setFieldErrors({})\n setOnHand(null)\n setPreviewError(null)\n setReorderPoint(0)\n setLoadingPreview(false)\n setSubmitting(false)\n setOptionLabelByValue({})\n }, [])\n\n const closeDialog = React.useCallback(() => {\n onOpenChange(false)\n resetDialog()\n }, [onOpenChange, resetDialog])\n\n const patchForm = React.useCallback((patch: Partial<AdjustFormValues>) => {\n setForm((current) => ({ ...current, ...patch }))\n setFieldErrors({})\n }, [])\n\n React.useEffect(() => {\n if (!open) return\n const catalogVariantId = initialCatalogVariantId?.trim()\n const warehouseId = initialWarehouseId?.trim()\n const locationId = initialLocationId?.trim()\n const lotId = initialLotId?.trim()\n if (!catalogVariantId && !warehouseId && !locationId && !lotId) return\n setForm((current) => ({\n ...current,\n ...(catalogVariantId ? { catalogVariantId } : {}),\n ...(warehouseId ? { warehouseId, locationId: locationId ?? '' } : {}),\n ...(locationId ? { locationId } : {}),\n }))\n if (!lotId) return\n let cancelled = false\n void resolveLotNumberFromId(lotId).then((lotNumber) => {\n if (cancelled || !lotNumber) return\n setForm((current) => ({ ...current, lotNumber }))\n registerOptionLabels([{ value: lotNumber, label: lotNumber }])\n })\n return () => {\n cancelled = true\n }\n }, [\n initialCatalogVariantId,\n initialLocationId,\n initialLotId,\n initialWarehouseId,\n open,\n registerOptionLabels,\n ])\n\n React.useEffect(() => {\n if (!open) return\n let cancelled = false\n\n const ensureLabel = async (value: string, resolve: (id: string) => Promise<string | null>) => {\n const id = value.trim()\n if (!id || optionLabelByValueRef.current[id]) return\n const label = await resolve(id)\n if (cancelled || !label) return\n registerOptionLabels([{ value: id, label }])\n }\n\n void Promise.all([\n ensureLabel(form.catalogVariantId, resolveCatalogVariantLabel),\n ensureLabel(form.warehouseId, resolveWarehouseLabel),\n ensureLabel(form.locationId, resolveLocationLabel),\n ])\n\n return () => {\n cancelled = true\n }\n }, [\n form.catalogVariantId,\n form.locationId,\n form.warehouseId,\n open,\n registerOptionLabels,\n ])\n\n React.useEffect(() => {\n if (!open) return\n const variantId = form.catalogVariantId.trim()\n if (!variantId) {\n setReorderPoint(0)\n return\n }\n let cancelled = false\n void fetchVariantReorderPoint(variantId).then((value) => {\n if (!cancelled) setReorderPoint(value)\n })\n return () => {\n cancelled = true\n }\n }, [form.catalogVariantId, open])\n\n const previewContextReady = Boolean(\n form.catalogVariantId.trim() &&\n form.warehouseId.trim() &&\n form.locationId.trim(),\n )\n\n React.useEffect(() => {\n if (!open) return\n const warehouseId = form.warehouseId.trim()\n const locationId = form.locationId.trim()\n const catalogVariantId = form.catalogVariantId.trim()\n const lotNumber = form.lotNumber.trim()\n if (!warehouseId || !locationId || !catalogVariantId) {\n setOnHand(null)\n setPreviewError(null)\n setLoadingPreview(false)\n return\n }\n let cancelled = false\n setLoadingPreview(true)\n setPreviewError(null)\n void (async () => {\n try {\n const lotId = lotNumber\n ? await findLotIdByNumber(catalogVariantId, lotNumber)\n : null\n if (cancelled) return\n const value = await fetchBalanceOnHand({\n warehouseId,\n locationId,\n catalogVariantId,\n lotId,\n })\n if (!cancelled) {\n setOnHand(value)\n setPreviewError(null)\n }\n } catch (error: unknown) {\n if (cancelled) return\n setOnHand(null)\n if (error instanceof BalanceLookupError) {\n setPreviewError(\n t(\n 'wms.backend.inventory.adjust.errors.previewBalance',\n 'Failed to load balance preview.',\n ),\n )\n return\n }\n logger.error('fetchBalanceOnHand failed', { component: 'AdjustInventoryDialog', err: error })\n setPreviewError(\n t(\n 'wms.backend.inventory.adjust.errors.previewBalance',\n 'Failed to load balance preview.',\n ),\n )\n } finally {\n if (!cancelled) setLoadingPreview(false)\n }\n })()\n return () => {\n cancelled = true\n }\n }, [\n form.catalogVariantId,\n form.locationId,\n form.lotNumber,\n form.warehouseId,\n open,\n t,\n ])\n\n const projectedOnHand =\n onHand != null && form.delta !== 0 ? onHand + form.delta : null\n const showBelowReorder =\n projectedOnHand != null && reorderPoint > 0 && projectedOnHand <= reorderPoint\n\n const handleSubmit = React.useCallback(\n async (event?: React.FormEvent) => {\n event?.preventDefault()\n const parsed = adjustFormSchema.safeParse({\n ...form,\n lotNumber: form.lotNumber.trim() || undefined,\n notes: form.notes.trim() || undefined,\n serialNumber: form.serialNumber.trim() || undefined,\n })\n if (!parsed.success) {\n const nextErrors: Record<string, string> = {}\n for (const issue of parsed.error.issues) {\n const key = String(issue.path[0] ?? 'form')\n if (!nextErrors[key]) nextErrors[key] = issue.message\n }\n setFieldErrors(nextErrors)\n return\n }\n\n if (!access.scopeReady || !access.organizationId || !access.tenantId || !access.userId) {\n flash(\n t(\n 'wms.backend.inventory.mutations.errors.scope',\n 'Select an organization and sign in before posting inventory changes.',\n ),\n 'error',\n )\n return\n }\n\n setSubmitting(true)\n try {\n // Persist the stable reason code (not a locale-rendered label) so activity\n // feeds can re-translate for the viewer's language later.\n const reason = parsed.data.reasonCode\n const notes = parsed.data.notes?.trim()\n const lotNumber = parsed.data.lotNumber?.trim()\n let lotId: string | undefined\n if (lotNumber) {\n try {\n lotId = await ensureLotIdForInventoryMutation({\n catalogVariantId: parsed.data.catalogVariantId,\n lotNumber,\n organizationId: access.organizationId,\n tenantId: access.tenantId,\n })\n } catch (error: unknown) {\n const message =\n error instanceof InventoryLotMutationError\n ? error.message\n : t(\n 'wms.backend.inventory.adjust.errors.lot',\n 'Failed to resolve inventory lot.',\n )\n setFieldErrors({ lotNumber: message })\n return\n }\n }\n const payload: Record<string, unknown> = {\n organizationId: access.organizationId,\n tenantId: access.tenantId,\n warehouseId: parsed.data.warehouseId,\n locationId: parsed.data.locationId,\n catalogVariantId: parsed.data.catalogVariantId,\n delta: parsed.data.delta,\n reason,\n reasonCode: parsed.data.reasonCode,\n referenceType: 'manual',\n referenceId: buildInventoryMutationReferenceId(),\n performedBy: access.userId,\n }\n if (lotId) payload.lotId = lotId\n const serial = parsed.data.serialNumber?.trim()\n if (serial) payload.serialNumber = serial\n if (notes) payload.metadata = { notes }\n\n await runMutation({\n operation: async () => {\n const call = await apiCall<{ ok?: boolean; movementId?: string }>(\n '/api/wms/inventory/adjust',\n {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(payload),\n },\n )\n if (!call.ok) {\n await raiseCrudError(\n call.response,\n t('wms.backend.inventory.adjust.errors.submit', 'Failed to adjust inventory.'),\n )\n }\n return call.result ?? {}\n },\n context: mutationContext,\n mutationPayload: payload,\n })\n\n flash(t('wms.backend.inventory.adjust.flash.success', 'Inventory adjusted'), 'success')\n await queryClient.invalidateQueries({ queryKey: ['wms-inventory-console'] })\n await queryClient.invalidateQueries({ queryKey: ['wms-sku-detail'] })\n closeDialog()\n } catch (error) {\n flashMutationError(error, t('wms.backend.inventory.adjust.errors.submit', 'Failed to adjust inventory.'), t)\n } finally {\n setSubmitting(false)\n }\n },\n [\n access,\n adjustFormSchema,\n closeDialog,\n form,\n mutationContext,\n queryClient,\n runMutation,\n t,\n ],\n )\n\n const handleDialogKeyDown = React.useCallback(\n (event: React.KeyboardEvent<HTMLDivElement>) => {\n if (event.key === 'Escape') {\n event.preventDefault()\n closeDialog()\n return\n }\n if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {\n event.preventDefault()\n if (!submitting) void handleSubmit()\n }\n },\n [closeDialog, handleSubmit, submitting],\n )\n\n const adjustDelta = React.useCallback((step: -1 | 1) => {\n setForm((current) => {\n const next = current.delta + step\n return { ...current, delta: next === 0 ? step : next }\n })\n setFieldErrors({})\n }, [])\n\n return (\n <Dialog open={open} onOpenChange={(next) => (next ? onOpenChange(true) : closeDialog())}>\n <DialogContent\n className=\"max-w-lg gap-0 overflow-hidden p-0\"\n onKeyDown={handleDialogKeyDown}\n >\n <div className=\"border-b px-6 py-4 pr-12\">\n <DialogHeader className=\"space-y-1 text-left\">\n <DialogTitle>\n {t('wms.backend.inventory.adjust.dialog.title', 'Adjust inventory')}\n </DialogTitle>\n <DialogDescription>\n {t(\n 'wms.backend.inventory.adjust.dialog.description',\n 'Append a signed delta to the ledger',\n )}\n </DialogDescription>\n </DialogHeader>\n </div>\n\n <form ref={formRef} onSubmit={handleSubmit} className=\"flex min-h-0 flex-1 flex-col\">\n <div className=\"flex flex-col gap-5 overflow-y-auto px-6 py-6\">\n <FormField\n label={t('wms.backend.inventory.adjust.form.variant', 'Variant')}\n required\n error={fieldErrors.catalogVariantId}\n >\n <div className=\"relative [&_input]:pl-9\">\n <Package\n className=\"pointer-events-none absolute left-3 top-1/2 z-10 size-4 -translate-y-1/2 text-muted-foreground\"\n aria-hidden=\"true\"\n />\n <ComboboxInput\n value={form.catalogVariantId}\n onChange={(next) => {\n patchForm({\n catalogVariantId: next.trim(),\n lotNumber: '',\n })\n }}\n loadSuggestions={async (query) => {\n const options = await loadCatalogVariantOptions(query)\n registerOptionLabels(options)\n return options.map((option) => ({\n value: option.value,\n label: option.label,\n description: option.description,\n }))\n }}\n resolveLabel={resolveOptionLabel}\n placeholder={t(\n 'wms.backend.inventory.adjust.form.variantPlaceholder',\n 'Search variant or SKU',\n )}\n allowCustomValues={false}\n disabled={submitting}\n />\n </div>\n </FormField>\n\n <div className=\"grid gap-5 sm:grid-cols-2\">\n <FormField\n label={t('wms.backend.inventory.adjust.form.warehouse', 'Warehouse')}\n required\n error={fieldErrors.warehouseId}\n >\n <ComboboxInput\n value={form.warehouseId}\n onChange={(next) => {\n patchForm({\n warehouseId: next.trim(),\n locationId: '',\n })\n }}\n loadSuggestions={async (query) => {\n const options = await loadWarehouseOptions(query)\n registerOptionLabels(options)\n return options.map((option) => ({\n value: option.value,\n label: option.label,\n }))\n }}\n resolveLabel={resolveOptionLabel}\n placeholder={t(\n 'wms.backend.inventory.adjust.form.warehousePlaceholder',\n 'Select warehouse',\n )}\n allowCustomValues={false}\n disabled={submitting}\n />\n </FormField>\n\n <FormField\n label={t('wms.backend.inventory.adjust.form.location', 'Location')}\n required\n error={fieldErrors.locationId}\n >\n <ComboboxInput\n value={form.locationId}\n onChange={(next) => patchForm({ locationId: next.trim() })}\n loadSuggestions={async (query) => {\n const options = await loadLocationOptions(form.warehouseId, query)\n registerOptionLabels(options)\n return options.map((option) => ({\n value: option.value,\n label: option.label,\n }))\n }}\n resolveLabel={resolveOptionLabel}\n placeholder={t(\n 'wms.backend.inventory.adjust.form.locationPlaceholder',\n 'Select location',\n )}\n allowCustomValues={false}\n disabled={submitting || !form.warehouseId}\n />\n </FormField>\n </div>\n\n <FormField\n label={t('wms.backend.inventory.adjust.form.lot', 'Lot')}\n error={fieldErrors.lotNumber}\n >\n <ComboboxInput\n value={form.lotNumber}\n onChange={(next) => patchForm({ lotNumber: next.trim() })}\n loadSuggestions={async (query) => {\n const options = await loadLotNumberOptions(form.catalogVariantId, query)\n registerOptionLabels(options)\n return options.map((option) => ({\n value: option.value,\n label: option.label,\n }))\n }}\n resolveLabel={resolveOptionLabel}\n placeholder={t(\n 'wms.backend.inventory.adjust.form.lotPlaceholder',\n 'Select lot (optional)',\n )}\n allowCustomValues\n disabled={submitting || !form.catalogVariantId}\n />\n </FormField>\n\n <FormField\n label={t('wms.backend.inventory.adjust.form.serial', 'Serial number')}\n error={fieldErrors.serialNumber}\n >\n <Input\n value={form.serialNumber}\n onChange={(event) => patchForm({ serialNumber: event.target.value })}\n placeholder={t(\n 'wms.backend.inventory.adjust.form.serialPlaceholder',\n 'Optional \u2014 for serial-tracked variants',\n )}\n disabled={submitting}\n />\n <p className=\"mt-1 text-xs text-muted-foreground\">\n {t(\n 'wms.backend.inventory.adjust.form.serialHelp',\n 'Use when the variant profile tracks serials.',\n )}\n </p>\n </FormField>\n\n <FormField\n label={t('wms.backend.inventory.adjust.form.delta', 'Adjustment')}\n required\n error={fieldErrors.delta}\n >\n <div className=\"flex w-32 items-center gap-2 rounded-md border bg-background p-2 shadow-xs\">\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n aria-label={t('wms.backend.inventory.adjust.form.decrease', 'Decrease quantity')}\n onClick={() => adjustDelta(-1)}\n disabled={submitting}\n >\n <Minus className=\"size-4\" />\n </IconButton>\n <Input\n type=\"text\"\n inputMode=\"decimal\"\n value={String(form.delta)}\n onChange={(event) => {\n const parsed = parseDeltaInput(event.target.value, locale)\n if (parsed == null) return\n patchForm({ delta: parsed })\n }}\n className=\"h-8 w-auto min-w-0 flex-1 border-0 bg-transparent p-0 shadow-none focus-visible:ring-0\"\n inputClassName=\"text-center [appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none\"\n disabled={submitting}\n />\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n aria-label={t('wms.backend.inventory.adjust.form.increase', 'Increase quantity')}\n onClick={() => adjustDelta(1)}\n disabled={submitting}\n >\n <Plus className=\"size-4\" />\n </IconButton>\n </div>\n </FormField>\n\n <FormField\n label={t('wms.backend.inventory.adjust.form.reason', 'Reason')}\n required\n error={fieldErrors.reasonCode}\n >\n <Select\n value={form.reasonCode || undefined}\n onValueChange={(next) =>\n patchForm({ reasonCode: next as AdjustReasonCode })\n }\n disabled={submitting}\n >\n <SelectTrigger>\n <SelectValue\n placeholder={t(\n 'wms.backend.inventory.adjust.form.reasonPlaceholder',\n 'Select reason',\n )}\n />\n </SelectTrigger>\n <SelectContent>\n {ADJUST_REASON_CODES.map((code) => (\n <SelectItem key={code} value={code}>\n {reasonLabel(code)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </FormField>\n\n <FormField label={t('wms.backend.inventory.adjust.form.notes', 'Notes')}>\n <Textarea\n value={form.notes}\n onChange={(event) => patchForm({ notes: event.target.value })}\n placeholder={t(\n 'wms.backend.inventory.adjust.form.notesPlaceholder',\n 'Optional \u2014 context for auditors',\n )}\n rows={3}\n disabled={submitting}\n />\n </FormField>\n\n {previewContextReady ? (\n <div className=\"rounded-lg border bg-muted/40 px-4 py-3.5\">\n <p className=\"text-xs font-semibold uppercase tracking-wide text-muted-foreground\">\n {t('wms.backend.inventory.adjust.preview.title', 'Balance preview')}\n </p>\n <div className=\"mt-2 flex items-center justify-between gap-3\">\n {previewError ? (\n <p className=\"text-sm text-status-warning-fg\">{previewError}</p>\n ) : loadingPreview && onHand == null ? (\n <p className=\"text-sm text-muted-foreground\">\n {t('wms.backend.inventory.adjust.preview.loading', 'Refreshing balance\u2026')}\n </p>\n ) : onHand != null && projectedOnHand != null ? (\n <p className=\"text-sm font-semibold tabular-nums text-foreground\">\n {t('wms.backend.inventory.adjust.preview.onHandLabel', 'On hand')}{' '}\n {onHand}\n {' \u2192 '}\n {projectedOnHand}\n </p>\n ) : onHand != null ? (\n <p className=\"text-sm font-semibold tabular-nums text-foreground\">\n {t('wms.backend.inventory.adjust.preview.onHandLabel', 'On hand')}{' '}\n {onHand}\n </p>\n ) : null}\n {showBelowReorder ? (\n <StatusBadge variant=\"warning\">\n {t(\n 'wms.backend.inventory.adjust.preview.belowReorder',\n 'Below reorder',\n )}\n </StatusBadge>\n ) : null}\n </div>\n {loadingPreview && onHand != null ? (\n <p className=\"mt-1 text-xs text-muted-foreground\">\n {t('wms.backend.inventory.adjust.preview.loading', 'Refreshing balance\u2026')}\n </p>\n ) : null}\n </div>\n ) : null}\n </div>\n\n <DialogFooter bordered={false} className=\"border-t px-6 py-4 sm:justify-between\">\n <p className=\"hidden text-xs text-muted-foreground sm:inline-flex sm:items-center sm:gap-1.5\">\n <KbdShortcut keys={['\u2318', 'Enter']} />\n <span>/</span>\n <KbdShortcut keys={['Ctrl', 'Enter']} />\n <span>{t('wms.backend.inventory.adjust.dialog.shortcutSave', 'to save')}</span>\n </p>\n <div className=\"flex w-full flex-col-reverse gap-2 sm:w-auto sm:flex-row\">\n <Button type=\"button\" variant=\"outline\" onClick={closeDialog} disabled={submitting}>\n {t('common.cancel', 'Cancel')}\n </Button>\n <Button type=\"submit\" disabled={submitting}>\n {t('wms.backend.inventory.adjust.dialog.submit', 'Save adjustment')}\n </Button>\n </div>\n </DialogFooter>\n </form>\n </DialogContent>\n </Dialog>\n )\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { z } from 'zod'\nimport { useQueryClient } from '@tanstack/react-query'\nimport { Minus, Package, Plus } from 'lucide-react'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { flashMutationError } from '../../lib/flashMutationError'\nimport { ComboboxInput } from '@open-mercato/ui/backend/inputs/ComboboxInput'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { raiseCrudError } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from '@open-mercato/ui/primitives/dialog'\nimport { FormField } from '@open-mercato/ui/primitives/form-field'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { KbdShortcut } from '@open-mercato/ui/primitives/kbd'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@open-mercato/ui/primitives/select'\nimport { StatusBadge } from '@open-mercato/ui/primitives/status-badge'\nimport { Textarea } from '@open-mercato/ui/primitives/textarea'\nimport { useT, useLocale } from '@open-mercato/shared/lib/i18n/context'\nimport { parseLocaleNumber } from '@open-mercato/shared/lib/number'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { buildInventoryMutationReferenceId } from '../../lib/inventoryMutationUi'\nimport {\n BalanceLookupError,\n ensureLotIdForInventoryMutation,\n fetchBalanceOnHand,\n fetchVariantReorderPoint,\n findLotIdByNumber,\n InventoryLotMutationError,\n loadCatalogVariantOptions,\n loadLocationOptions,\n loadLotNumberOptions,\n loadWarehouseOptions,\n resolveCatalogVariantLabel,\n resolveLocationLabel,\n resolveLotNumberFromId,\n resolveWarehouseLabel,\n} from './inventoryMutationLoaders'\nimport type { useWmsInventoryMutationAccess } from './useWmsInventoryMutationAccess'\n\nconst logger = createLogger('wms')\n\nconst ADJUST_REASON_CODES = ['damaged', 'shrinkage', 'found', 'correction', 'other'] as const\n\ntype AdjustReasonCode = (typeof ADJUST_REASON_CODES)[number]\n\ntype AdjustFormValues = {\n catalogVariantId: string\n warehouseId: string\n locationId: string\n lotNumber: string\n delta: number\n reasonCode: AdjustReasonCode | ''\n notes: string\n serialNumber: string\n}\n\ntype AdjustInventoryDialogProps = {\n open: boolean\n onOpenChange: (open: boolean) => void\n access: ReturnType<typeof useWmsInventoryMutationAccess>\n initialCatalogVariantId?: string\n initialWarehouseId?: string\n initialLocationId?: string\n initialLotId?: string\n}\n\nconst EMPTY_FORM: AdjustFormValues = {\n catalogVariantId: '',\n warehouseId: '',\n locationId: '',\n lotNumber: '',\n delta: 1,\n reasonCode: '',\n notes: '',\n serialNumber: '',\n}\n\nexport function parseDeltaInput(value: string, locale?: string): number | null {\n const trimmed = value.trim()\n if (!trimmed || trimmed === '-' || trimmed === '+') return null\n return parseLocaleNumber(trimmed, locale)\n}\n\nexport function AdjustInventoryDialog({\n open,\n onOpenChange,\n access,\n initialCatalogVariantId,\n initialWarehouseId,\n initialLocationId,\n initialLotId,\n}: AdjustInventoryDialogProps) {\n const t = useT()\n const locale = useLocale()\n const queryClient = useQueryClient()\n const formRef = React.useRef<HTMLFormElement>(null)\n const { runMutation, retryLastMutation } = useGuardedMutation<{\n retryLastMutation: () => Promise<boolean>\n }>({\n contextId: 'wms-inventory-adjust',\n })\n const mutationContext = React.useMemo(\n () => ({ retryLastMutation }),\n [retryLastMutation],\n )\n const adjustFormSchema = React.useMemo(\n () =>\n z.object({\n catalogVariantId: z.string().uuid(),\n warehouseId: z.string().uuid(),\n locationId: z.string().uuid(),\n lotNumber: z.string().trim().max(120).optional(),\n delta: z.coerce.number().refine((value) => value !== 0, {\n message: t(\n 'wms.backend.inventory.adjust.errors.deltaZero',\n 'Inventory delta must be non-zero.',\n ),\n }),\n reasonCode: z.enum(ADJUST_REASON_CODES),\n notes: z.string().trim().max(500).optional(),\n serialNumber: z.string().trim().max(120).optional(),\n }),\n [t],\n )\n const [submitting, setSubmitting] = React.useState(false)\n const [form, setForm] = React.useState<AdjustFormValues>(EMPTY_FORM)\n // Raw text the user is typing into the delta field, decoupled from `form.delta` (issue\n // #5828): re-deriving the input's display from the committed number on every keystroke\n // drops an in-progress decimal separator the instant it's typed (`2,5` collapses to `2`\n // then the next digit appends, producing `25`). Synced from `form.delta` only while the\n // field is not focused, mirroring `CrudForm.NumberInput`'s buffer.\n const [deltaInputText, setDeltaInputText] = React.useState<string>(String(EMPTY_FORM.delta))\n const deltaInputFocusedRef = React.useRef(false)\n const [fieldErrors, setFieldErrors] = React.useState<Record<string, string>>({})\n const [onHand, setOnHand] = React.useState<number | null>(null)\n const [previewError, setPreviewError] = React.useState<string | null>(null)\n const [reorderPoint, setReorderPoint] = React.useState(0)\n const [loadingPreview, setLoadingPreview] = React.useState(false)\n const [optionLabelByValue, setOptionLabelByValue] = React.useState<Record<string, string>>({})\n\n const registerOptionLabels = React.useCallback(\n (options: Array<{ value: string; label: string }>) => {\n setOptionLabelByValue((current) => {\n let changed = false\n const next = { ...current }\n for (const option of options) {\n const value = option.value.trim()\n const label = option.label.trim()\n if (!value || !label || next[value] === label) continue\n next[value] = label\n changed = true\n }\n return changed ? next : current\n })\n },\n [],\n )\n\n const resolveOptionLabel = React.useCallback(\n (value: string) => optionLabelByValue[value] ?? value,\n [optionLabelByValue],\n )\n const optionLabelByValueRef = React.useRef(optionLabelByValue)\n optionLabelByValueRef.current = optionLabelByValue\n\n const reasonLabel = React.useCallback(\n (code: AdjustReasonCode) => {\n const fallbacks: Record<AdjustReasonCode, string> = {\n damaged: 'Damaged',\n shrinkage: 'Shrinkage',\n found: 'Found stock',\n correction: 'Correction',\n other: 'Other',\n }\n return t(`wms.backend.inventory.adjust.reasons.${code}`, fallbacks[code])\n },\n [t],\n )\n\n const resetDialog = React.useCallback(() => {\n setForm(EMPTY_FORM)\n setDeltaInputText(String(EMPTY_FORM.delta))\n setFieldErrors({})\n setOnHand(null)\n setPreviewError(null)\n setReorderPoint(0)\n setLoadingPreview(false)\n setSubmitting(false)\n setOptionLabelByValue({})\n }, [])\n\n const closeDialog = React.useCallback(() => {\n onOpenChange(false)\n resetDialog()\n }, [onOpenChange, resetDialog])\n\n const patchForm = React.useCallback((patch: Partial<AdjustFormValues>) => {\n setForm((current) => ({ ...current, ...patch }))\n setFieldErrors({})\n }, [])\n\n React.useEffect(() => {\n if (!deltaInputFocusedRef.current) setDeltaInputText(String(form.delta))\n }, [form.delta])\n\n React.useEffect(() => {\n if (!open) return\n const catalogVariantId = initialCatalogVariantId?.trim()\n const warehouseId = initialWarehouseId?.trim()\n const locationId = initialLocationId?.trim()\n const lotId = initialLotId?.trim()\n if (!catalogVariantId && !warehouseId && !locationId && !lotId) return\n setForm((current) => ({\n ...current,\n ...(catalogVariantId ? { catalogVariantId } : {}),\n ...(warehouseId ? { warehouseId, locationId: locationId ?? '' } : {}),\n ...(locationId ? { locationId } : {}),\n }))\n if (!lotId) return\n let cancelled = false\n void resolveLotNumberFromId(lotId).then((lotNumber) => {\n if (cancelled || !lotNumber) return\n setForm((current) => ({ ...current, lotNumber }))\n registerOptionLabels([{ value: lotNumber, label: lotNumber }])\n })\n return () => {\n cancelled = true\n }\n }, [\n initialCatalogVariantId,\n initialLocationId,\n initialLotId,\n initialWarehouseId,\n open,\n registerOptionLabels,\n ])\n\n React.useEffect(() => {\n if (!open) return\n let cancelled = false\n\n const ensureLabel = async (value: string, resolve: (id: string) => Promise<string | null>) => {\n const id = value.trim()\n if (!id || optionLabelByValueRef.current[id]) return\n const label = await resolve(id)\n if (cancelled || !label) return\n registerOptionLabels([{ value: id, label }])\n }\n\n void Promise.all([\n ensureLabel(form.catalogVariantId, resolveCatalogVariantLabel),\n ensureLabel(form.warehouseId, resolveWarehouseLabel),\n ensureLabel(form.locationId, resolveLocationLabel),\n ])\n\n return () => {\n cancelled = true\n }\n }, [\n form.catalogVariantId,\n form.locationId,\n form.warehouseId,\n open,\n registerOptionLabels,\n ])\n\n React.useEffect(() => {\n if (!open) return\n const variantId = form.catalogVariantId.trim()\n if (!variantId) {\n setReorderPoint(0)\n return\n }\n let cancelled = false\n void fetchVariantReorderPoint(variantId).then((value) => {\n if (!cancelled) setReorderPoint(value)\n })\n return () => {\n cancelled = true\n }\n }, [form.catalogVariantId, open])\n\n const previewContextReady = Boolean(\n form.catalogVariantId.trim() &&\n form.warehouseId.trim() &&\n form.locationId.trim(),\n )\n\n React.useEffect(() => {\n if (!open) return\n const warehouseId = form.warehouseId.trim()\n const locationId = form.locationId.trim()\n const catalogVariantId = form.catalogVariantId.trim()\n const lotNumber = form.lotNumber.trim()\n if (!warehouseId || !locationId || !catalogVariantId) {\n setOnHand(null)\n setPreviewError(null)\n setLoadingPreview(false)\n return\n }\n let cancelled = false\n setLoadingPreview(true)\n setPreviewError(null)\n void (async () => {\n try {\n const lotId = lotNumber\n ? await findLotIdByNumber(catalogVariantId, lotNumber)\n : null\n if (cancelled) return\n const value = await fetchBalanceOnHand({\n warehouseId,\n locationId,\n catalogVariantId,\n lotId,\n })\n if (!cancelled) {\n setOnHand(value)\n setPreviewError(null)\n }\n } catch (error: unknown) {\n if (cancelled) return\n setOnHand(null)\n if (error instanceof BalanceLookupError) {\n setPreviewError(\n t(\n 'wms.backend.inventory.adjust.errors.previewBalance',\n 'Failed to load balance preview.',\n ),\n )\n return\n }\n logger.error('fetchBalanceOnHand failed', { component: 'AdjustInventoryDialog', err: error })\n setPreviewError(\n t(\n 'wms.backend.inventory.adjust.errors.previewBalance',\n 'Failed to load balance preview.',\n ),\n )\n } finally {\n if (!cancelled) setLoadingPreview(false)\n }\n })()\n return () => {\n cancelled = true\n }\n }, [\n form.catalogVariantId,\n form.locationId,\n form.lotNumber,\n form.warehouseId,\n open,\n t,\n ])\n\n const projectedOnHand =\n onHand != null && form.delta !== 0 ? onHand + form.delta : null\n const showBelowReorder =\n projectedOnHand != null && reorderPoint > 0 && projectedOnHand <= reorderPoint\n\n const handleSubmit = React.useCallback(\n async (event?: React.FormEvent) => {\n event?.preventDefault()\n const parsed = adjustFormSchema.safeParse({\n ...form,\n lotNumber: form.lotNumber.trim() || undefined,\n notes: form.notes.trim() || undefined,\n serialNumber: form.serialNumber.trim() || undefined,\n })\n if (!parsed.success) {\n const nextErrors: Record<string, string> = {}\n for (const issue of parsed.error.issues) {\n const key = String(issue.path[0] ?? 'form')\n if (!nextErrors[key]) nextErrors[key] = issue.message\n }\n setFieldErrors(nextErrors)\n return\n }\n\n if (!access.scopeReady || !access.organizationId || !access.tenantId || !access.userId) {\n flash(\n t(\n 'wms.backend.inventory.mutations.errors.scope',\n 'Select an organization and sign in before posting inventory changes.',\n ),\n 'error',\n )\n return\n }\n\n setSubmitting(true)\n try {\n // Persist the stable reason code (not a locale-rendered label) so activity\n // feeds can re-translate for the viewer's language later.\n const reason = parsed.data.reasonCode\n const notes = parsed.data.notes?.trim()\n const lotNumber = parsed.data.lotNumber?.trim()\n let lotId: string | undefined\n if (lotNumber) {\n try {\n lotId = await ensureLotIdForInventoryMutation({\n catalogVariantId: parsed.data.catalogVariantId,\n lotNumber,\n organizationId: access.organizationId,\n tenantId: access.tenantId,\n })\n } catch (error: unknown) {\n const message =\n error instanceof InventoryLotMutationError\n ? error.message\n : t(\n 'wms.backend.inventory.adjust.errors.lot',\n 'Failed to resolve inventory lot.',\n )\n setFieldErrors({ lotNumber: message })\n return\n }\n }\n const payload: Record<string, unknown> = {\n organizationId: access.organizationId,\n tenantId: access.tenantId,\n warehouseId: parsed.data.warehouseId,\n locationId: parsed.data.locationId,\n catalogVariantId: parsed.data.catalogVariantId,\n delta: parsed.data.delta,\n reason,\n reasonCode: parsed.data.reasonCode,\n referenceType: 'manual',\n referenceId: buildInventoryMutationReferenceId(),\n performedBy: access.userId,\n }\n if (lotId) payload.lotId = lotId\n const serial = parsed.data.serialNumber?.trim()\n if (serial) payload.serialNumber = serial\n if (notes) payload.metadata = { notes }\n\n await runMutation({\n operation: async () => {\n const call = await apiCall<{ ok?: boolean; movementId?: string }>(\n '/api/wms/inventory/adjust',\n {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(payload),\n },\n )\n if (!call.ok) {\n await raiseCrudError(\n call.response,\n t('wms.backend.inventory.adjust.errors.submit', 'Failed to adjust inventory.'),\n )\n }\n return call.result ?? {}\n },\n context: mutationContext,\n mutationPayload: payload,\n })\n\n flash(t('wms.backend.inventory.adjust.flash.success', 'Inventory adjusted'), 'success')\n await queryClient.invalidateQueries({ queryKey: ['wms-inventory-console'] })\n await queryClient.invalidateQueries({ queryKey: ['wms-sku-detail'] })\n closeDialog()\n } catch (error) {\n flashMutationError(error, t('wms.backend.inventory.adjust.errors.submit', 'Failed to adjust inventory.'), t)\n } finally {\n setSubmitting(false)\n }\n },\n [\n access,\n adjustFormSchema,\n closeDialog,\n form,\n mutationContext,\n queryClient,\n runMutation,\n t,\n ],\n )\n\n const handleDialogKeyDown = React.useCallback(\n (event: React.KeyboardEvent<HTMLDivElement>) => {\n if (event.key === 'Escape') {\n event.preventDefault()\n closeDialog()\n return\n }\n if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {\n event.preventDefault()\n if (!submitting) void handleSubmit()\n }\n },\n [closeDialog, handleSubmit, submitting],\n )\n\n const adjustDelta = React.useCallback((step: -1 | 1) => {\n setForm((current) => {\n const next = current.delta + step\n return { ...current, delta: next === 0 ? step : next }\n })\n setFieldErrors({})\n }, [])\n\n return (\n <Dialog open={open} onOpenChange={(next) => (next ? onOpenChange(true) : closeDialog())}>\n <DialogContent\n className=\"max-w-lg gap-0 overflow-hidden p-0\"\n onKeyDown={handleDialogKeyDown}\n >\n <div className=\"border-b px-6 py-4 pr-12\">\n <DialogHeader className=\"space-y-1 text-left\">\n <DialogTitle>\n {t('wms.backend.inventory.adjust.dialog.title', 'Adjust inventory')}\n </DialogTitle>\n <DialogDescription>\n {t(\n 'wms.backend.inventory.adjust.dialog.description',\n 'Append a signed delta to the ledger',\n )}\n </DialogDescription>\n </DialogHeader>\n </div>\n\n <form ref={formRef} onSubmit={handleSubmit} className=\"flex min-h-0 flex-1 flex-col\">\n <div className=\"flex flex-col gap-5 overflow-y-auto px-6 py-6\">\n <FormField\n label={t('wms.backend.inventory.adjust.form.variant', 'Variant')}\n required\n error={fieldErrors.catalogVariantId}\n >\n <div className=\"relative [&_input]:pl-9\">\n <Package\n className=\"pointer-events-none absolute left-3 top-1/2 z-10 size-4 -translate-y-1/2 text-muted-foreground\"\n aria-hidden=\"true\"\n />\n <ComboboxInput\n value={form.catalogVariantId}\n onChange={(next) => {\n patchForm({\n catalogVariantId: next.trim(),\n lotNumber: '',\n })\n }}\n loadSuggestions={async (query) => {\n const options = await loadCatalogVariantOptions(query)\n registerOptionLabels(options)\n return options.map((option) => ({\n value: option.value,\n label: option.label,\n description: option.description,\n }))\n }}\n resolveLabel={resolveOptionLabel}\n placeholder={t(\n 'wms.backend.inventory.adjust.form.variantPlaceholder',\n 'Search variant or SKU',\n )}\n allowCustomValues={false}\n disabled={submitting}\n />\n </div>\n </FormField>\n\n <div className=\"grid gap-5 sm:grid-cols-2\">\n <FormField\n label={t('wms.backend.inventory.adjust.form.warehouse', 'Warehouse')}\n required\n error={fieldErrors.warehouseId}\n >\n <ComboboxInput\n value={form.warehouseId}\n onChange={(next) => {\n patchForm({\n warehouseId: next.trim(),\n locationId: '',\n })\n }}\n loadSuggestions={async (query) => {\n const options = await loadWarehouseOptions(query)\n registerOptionLabels(options)\n return options.map((option) => ({\n value: option.value,\n label: option.label,\n }))\n }}\n resolveLabel={resolveOptionLabel}\n placeholder={t(\n 'wms.backend.inventory.adjust.form.warehousePlaceholder',\n 'Select warehouse',\n )}\n allowCustomValues={false}\n disabled={submitting}\n />\n </FormField>\n\n <FormField\n label={t('wms.backend.inventory.adjust.form.location', 'Location')}\n required\n error={fieldErrors.locationId}\n >\n <ComboboxInput\n value={form.locationId}\n onChange={(next) => patchForm({ locationId: next.trim() })}\n loadSuggestions={async (query) => {\n const options = await loadLocationOptions(form.warehouseId, query)\n registerOptionLabels(options)\n return options.map((option) => ({\n value: option.value,\n label: option.label,\n }))\n }}\n resolveLabel={resolveOptionLabel}\n placeholder={t(\n 'wms.backend.inventory.adjust.form.locationPlaceholder',\n 'Select location',\n )}\n allowCustomValues={false}\n disabled={submitting || !form.warehouseId}\n />\n </FormField>\n </div>\n\n <FormField\n label={t('wms.backend.inventory.adjust.form.lot', 'Lot')}\n error={fieldErrors.lotNumber}\n >\n <ComboboxInput\n value={form.lotNumber}\n onChange={(next) => patchForm({ lotNumber: next.trim() })}\n loadSuggestions={async (query) => {\n const options = await loadLotNumberOptions(form.catalogVariantId, query)\n registerOptionLabels(options)\n return options.map((option) => ({\n value: option.value,\n label: option.label,\n }))\n }}\n resolveLabel={resolveOptionLabel}\n placeholder={t(\n 'wms.backend.inventory.adjust.form.lotPlaceholder',\n 'Select lot (optional)',\n )}\n allowCustomValues\n disabled={submitting || !form.catalogVariantId}\n />\n </FormField>\n\n <FormField\n label={t('wms.backend.inventory.adjust.form.serial', 'Serial number')}\n error={fieldErrors.serialNumber}\n >\n <Input\n value={form.serialNumber}\n onChange={(event) => patchForm({ serialNumber: event.target.value })}\n placeholder={t(\n 'wms.backend.inventory.adjust.form.serialPlaceholder',\n 'Optional \u2014 for serial-tracked variants',\n )}\n disabled={submitting}\n />\n <p className=\"mt-1 text-xs text-muted-foreground\">\n {t(\n 'wms.backend.inventory.adjust.form.serialHelp',\n 'Use when the variant profile tracks serials.',\n )}\n </p>\n </FormField>\n\n <FormField\n label={t('wms.backend.inventory.adjust.form.delta', 'Adjustment')}\n required\n error={fieldErrors.delta}\n >\n <div className=\"flex w-32 items-center gap-2 rounded-md border bg-background p-2 shadow-xs\">\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n aria-label={t('wms.backend.inventory.adjust.form.decrease', 'Decrease quantity')}\n onClick={() => adjustDelta(-1)}\n disabled={submitting}\n >\n <Minus className=\"size-4\" />\n </IconButton>\n <Input\n type=\"text\"\n inputMode=\"decimal\"\n value={deltaInputText}\n onChange={(event) => {\n setDeltaInputText(event.target.value)\n const parsed = parseDeltaInput(event.target.value, locale)\n if (parsed == null) return\n patchForm({ delta: parsed })\n }}\n onFocus={() => {\n deltaInputFocusedRef.current = true\n }}\n onBlur={() => {\n deltaInputFocusedRef.current = false\n setDeltaInputText(String(form.delta))\n }}\n className=\"h-8 w-auto min-w-0 flex-1 border-0 bg-transparent p-0 shadow-none focus-visible:ring-0\"\n inputClassName=\"text-center [appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none\"\n disabled={submitting}\n />\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n aria-label={t('wms.backend.inventory.adjust.form.increase', 'Increase quantity')}\n onClick={() => adjustDelta(1)}\n disabled={submitting}\n >\n <Plus className=\"size-4\" />\n </IconButton>\n </div>\n </FormField>\n\n <FormField\n label={t('wms.backend.inventory.adjust.form.reason', 'Reason')}\n required\n error={fieldErrors.reasonCode}\n >\n <Select\n value={form.reasonCode || undefined}\n onValueChange={(next) =>\n patchForm({ reasonCode: next as AdjustReasonCode })\n }\n disabled={submitting}\n >\n <SelectTrigger>\n <SelectValue\n placeholder={t(\n 'wms.backend.inventory.adjust.form.reasonPlaceholder',\n 'Select reason',\n )}\n />\n </SelectTrigger>\n <SelectContent>\n {ADJUST_REASON_CODES.map((code) => (\n <SelectItem key={code} value={code}>\n {reasonLabel(code)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </FormField>\n\n <FormField label={t('wms.backend.inventory.adjust.form.notes', 'Notes')}>\n <Textarea\n value={form.notes}\n onChange={(event) => patchForm({ notes: event.target.value })}\n placeholder={t(\n 'wms.backend.inventory.adjust.form.notesPlaceholder',\n 'Optional \u2014 context for auditors',\n )}\n rows={3}\n disabled={submitting}\n />\n </FormField>\n\n {previewContextReady ? (\n <div className=\"rounded-lg border bg-muted/40 px-4 py-3.5\">\n <p className=\"text-xs font-semibold uppercase tracking-wide text-muted-foreground\">\n {t('wms.backend.inventory.adjust.preview.title', 'Balance preview')}\n </p>\n <div className=\"mt-2 flex items-center justify-between gap-3\">\n {previewError ? (\n <p className=\"text-sm text-status-warning-fg\">{previewError}</p>\n ) : loadingPreview && onHand == null ? (\n <p className=\"text-sm text-muted-foreground\">\n {t('wms.backend.inventory.adjust.preview.loading', 'Refreshing balance\u2026')}\n </p>\n ) : onHand != null && projectedOnHand != null ? (\n <p className=\"text-sm font-semibold tabular-nums text-foreground\">\n {t('wms.backend.inventory.adjust.preview.onHandLabel', 'On hand')}{' '}\n {onHand}\n {' \u2192 '}\n {projectedOnHand}\n </p>\n ) : onHand != null ? (\n <p className=\"text-sm font-semibold tabular-nums text-foreground\">\n {t('wms.backend.inventory.adjust.preview.onHandLabel', 'On hand')}{' '}\n {onHand}\n </p>\n ) : null}\n {showBelowReorder ? (\n <StatusBadge variant=\"warning\">\n {t(\n 'wms.backend.inventory.adjust.preview.belowReorder',\n 'Below reorder',\n )}\n </StatusBadge>\n ) : null}\n </div>\n {loadingPreview && onHand != null ? (\n <p className=\"mt-1 text-xs text-muted-foreground\">\n {t('wms.backend.inventory.adjust.preview.loading', 'Refreshing balance\u2026')}\n </p>\n ) : null}\n </div>\n ) : null}\n </div>\n\n <DialogFooter bordered={false} className=\"border-t px-6 py-4 sm:justify-between\">\n <p className=\"hidden text-xs text-muted-foreground sm:inline-flex sm:items-center sm:gap-1.5\">\n <KbdShortcut keys={['\u2318', 'Enter']} />\n <span>/</span>\n <KbdShortcut keys={['Ctrl', 'Enter']} />\n <span>{t('wms.backend.inventory.adjust.dialog.shortcutSave', 'to save')}</span>\n </p>\n <div className=\"flex w-full flex-col-reverse gap-2 sm:w-auto sm:flex-row\">\n <Button type=\"button\" variant=\"outline\" onClick={closeDialog} disabled={submitting}>\n {t('common.cancel', 'Cancel')}\n </Button>\n <Button type=\"submit\" disabled={submitting}>\n {t('wms.backend.inventory.adjust.dialog.submit', 'Save adjustment')}\n </Button>\n </div>\n </DialogFooter>\n </form>\n </DialogContent>\n </Dialog>\n )\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.8.1-develop.
|
|
3
|
+
"version": "0.8.1-develop.7218.1.9f22882799",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -253,16 +253,16 @@
|
|
|
253
253
|
"zod": "^4.4.3"
|
|
254
254
|
},
|
|
255
255
|
"peerDependencies": {
|
|
256
|
-
"@open-mercato/ai-assistant": "0.8.1-develop.
|
|
257
|
-
"@open-mercato/shared": "0.8.1-develop.
|
|
258
|
-
"@open-mercato/ui": "0.8.1-develop.
|
|
256
|
+
"@open-mercato/ai-assistant": "0.8.1-develop.7218.1.9f22882799",
|
|
257
|
+
"@open-mercato/shared": "0.8.1-develop.7218.1.9f22882799",
|
|
258
|
+
"@open-mercato/ui": "0.8.1-develop.7218.1.9f22882799",
|
|
259
259
|
"react": "^19.0.0",
|
|
260
260
|
"react-dom": "^19.0.0"
|
|
261
261
|
},
|
|
262
262
|
"devDependencies": {
|
|
263
|
-
"@open-mercato/ai-assistant": "0.8.1-develop.
|
|
264
|
-
"@open-mercato/shared": "0.8.1-develop.
|
|
265
|
-
"@open-mercato/ui": "0.8.1-develop.
|
|
263
|
+
"@open-mercato/ai-assistant": "0.8.1-develop.7218.1.9f22882799",
|
|
264
|
+
"@open-mercato/shared": "0.8.1-develop.7218.1.9f22882799",
|
|
265
|
+
"@open-mercato/ui": "0.8.1-develop.7218.1.9f22882799",
|
|
266
266
|
"@testing-library/dom": "^10.4.1",
|
|
267
267
|
"@testing-library/jest-dom": "^7.0.1",
|
|
268
268
|
"@testing-library/react": "^16.3.3",
|
|
@@ -47,7 +47,7 @@ import {
|
|
|
47
47
|
buildRecordInjectionContext,
|
|
48
48
|
useSetCurrentRecordInjectionContext,
|
|
49
49
|
} from "@open-mercato/ui/backend/injection/recordContext";
|
|
50
|
-
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
50
|
+
import { useT, useLocale } from "@open-mercato/shared/lib/i18n/context";
|
|
51
51
|
import { useConfirmDialog } from "@open-mercato/ui/backend/confirm-dialog";
|
|
52
52
|
import { E } from "#generated/entities.ids.generated";
|
|
53
53
|
import {
|
|
@@ -73,6 +73,7 @@ import {
|
|
|
73
73
|
type ProductUnitPriceReferenceUnit,
|
|
74
74
|
type ProductUnitRoundingMode,
|
|
75
75
|
productFormSchema,
|
|
76
|
+
withCanonicalUomFields,
|
|
76
77
|
BASE_INITIAL_VALUES,
|
|
77
78
|
createLocalId,
|
|
78
79
|
slugify,
|
|
@@ -323,6 +324,7 @@ export default function EditCatalogProductPage({
|
|
|
323
324
|
}) {
|
|
324
325
|
const productId = params?.id ? String(params.id) : null;
|
|
325
326
|
const t = useT();
|
|
327
|
+
const locale = useLocale();
|
|
326
328
|
const pathname = usePathname();
|
|
327
329
|
const productSubpathPrefix = productId
|
|
328
330
|
? `/backend/catalog/products/${productId}/`
|
|
@@ -1049,7 +1051,9 @@ export default function EditCatalogProductPage({
|
|
|
1049
1051
|
),
|
|
1050
1052
|
);
|
|
1051
1053
|
}
|
|
1052
|
-
const parsed = productFormSchema.safeParse(
|
|
1054
|
+
const parsed = productFormSchema.safeParse(
|
|
1055
|
+
withCanonicalUomFields(formValues, locale),
|
|
1056
|
+
);
|
|
1053
1057
|
if (!parsed.success) {
|
|
1054
1058
|
const issues = parsed.error.issues;
|
|
1055
1059
|
const fieldErrors: Record<string, string> = {};
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import { extensionPoints } from "@open-mercato/core/modules/catalog/extension-points";
|
|
5
5
|
import { useRouter, useSearchParams } from "next/navigation";
|
|
6
|
-
import type { ZodType } from "zod";
|
|
7
6
|
import { Page, PageBody } from "@open-mercato/ui/backend/Page";
|
|
8
7
|
import {
|
|
9
8
|
CrudForm,
|
|
@@ -41,7 +40,7 @@ import {
|
|
|
41
40
|
apiCall,
|
|
42
41
|
readApiResultOrThrow,
|
|
43
42
|
} from "@open-mercato/ui/backend/utils/apiCall";
|
|
44
|
-
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
43
|
+
import { useT, useLocale } from "@open-mercato/shared/lib/i18n/context";
|
|
45
44
|
import { E } from "#generated/entities.ids.generated";
|
|
46
45
|
import {
|
|
47
46
|
ProductMediaManager,
|
|
@@ -60,7 +59,7 @@ import {
|
|
|
60
59
|
type ProductUnitConversionDraft,
|
|
61
60
|
type ProductUnitPriceReferenceUnit,
|
|
62
61
|
type ProductUnitRoundingMode,
|
|
63
|
-
|
|
62
|
+
buildLocaleAwareProductFormSchema,
|
|
64
63
|
createInitialProductFormValues,
|
|
65
64
|
createVariantDraft,
|
|
66
65
|
buildOptionValuesKey,
|
|
@@ -101,9 +100,6 @@ import { createLogger } from '@open-mercato/shared/lib/logger'
|
|
|
101
100
|
|
|
102
101
|
const logger = createLogger('catalog')
|
|
103
102
|
|
|
104
|
-
const productFormTypedSchema =
|
|
105
|
-
productFormSchema as unknown as ZodType<ProductFormValues>;
|
|
106
|
-
|
|
107
103
|
type VariantPriceRequest = {
|
|
108
104
|
variantDraftId: string;
|
|
109
105
|
priceKindId: string;
|
|
@@ -233,6 +229,11 @@ function readInboxProductDraft(): InboxProductDraft | null {
|
|
|
233
229
|
|
|
234
230
|
export default function CreateCatalogProductPage() {
|
|
235
231
|
const t = useT();
|
|
232
|
+
const locale = useLocale();
|
|
233
|
+
const productFormTypedSchema = React.useMemo(
|
|
234
|
+
() => buildLocaleAwareProductFormSchema(locale),
|
|
235
|
+
[locale],
|
|
236
|
+
);
|
|
236
237
|
const router = useRouter();
|
|
237
238
|
const searchParams = useSearchParams();
|
|
238
239
|
const fromInboxAction = searchParams.get("fromInboxAction");
|
|
@@ -57,15 +57,6 @@ const REFERENCE_UNIT_OPTIONS = REFERENCE_UNIT_CODES.map((code) => ({
|
|
|
57
57
|
fallback: REFERENCE_UNIT_DISPLAY[code] ?? code,
|
|
58
58
|
}));
|
|
59
59
|
|
|
60
|
-
// Keeps the field submit-ready (dot-decimal, no grouping characters) while the user is
|
|
61
|
-
// still typing. Falls back to the raw value when it isn't parseable yet (empty, a lone
|
|
62
|
-
// sign) so the field stays editable. Delegates to `parseLocaleNumber` instead of a
|
|
63
|
-
// hard-coded `,` -> `.` swap, which broke on grouped input such as `1 234,56` (#5828).
|
|
64
|
-
export function normalizeDecimalInput(value: string, locale?: string): string {
|
|
65
|
-
const parsed = parseLocaleNumber(value, locale);
|
|
66
|
-
return parsed === null ? value : String(parsed);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
60
|
export function toPositiveNumber(value: unknown, locale?: string): number | null {
|
|
70
61
|
if (typeof value === "number") {
|
|
71
62
|
return Number.isFinite(value) && value > 0 ? value : null;
|
|
@@ -88,10 +79,7 @@ function formatPreviewNumber(value: number): string {
|
|
|
88
79
|
return Number.isInteger(rounded) ? String(rounded) : rounded.toString();
|
|
89
80
|
}
|
|
90
81
|
|
|
91
|
-
function normalizeConversions(
|
|
92
|
-
value: unknown,
|
|
93
|
-
locale?: string,
|
|
94
|
-
): ProductUnitConversionDraft[] {
|
|
82
|
+
function normalizeConversions(value: unknown): ProductUnitConversionDraft[] {
|
|
95
83
|
if (!Array.isArray(value)) return [];
|
|
96
84
|
const normalized = value
|
|
97
85
|
.map((entry) => {
|
|
@@ -100,9 +88,9 @@ function normalizeConversions(
|
|
|
100
88
|
return {
|
|
101
89
|
id: toTrimmedOrNull(row.id) ?? null,
|
|
102
90
|
unitCode: toTrimmedOrNull(row.unitCode) ?? "",
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
91
|
+
// Stays raw while editing (issue #5828) — re-serializing on every render would drop
|
|
92
|
+
// an in-progress decimal separator the instant it's typed.
|
|
93
|
+
toBaseFactor: toTrimmedOrNull(row.toBaseFactor) ?? "",
|
|
106
94
|
sortOrder: toTrimmedOrNull(row.sortOrder) ?? "",
|
|
107
95
|
isActive: row.isActive !== false,
|
|
108
96
|
} satisfies ProductUnitConversionDraft;
|
|
@@ -150,8 +138,8 @@ export function ProductUomSection({
|
|
|
150
138
|
const [loadingUnits, setLoadingUnits] = React.useState(false);
|
|
151
139
|
const [errorLoadingUnits, setErrorLoadingUnits] = React.useState(false);
|
|
152
140
|
const conversions = React.useMemo(
|
|
153
|
-
() => normalizeConversions(values.unitConversions
|
|
154
|
-
[values.unitConversions
|
|
141
|
+
() => normalizeConversions(values.unitConversions),
|
|
142
|
+
[values.unitConversions],
|
|
155
143
|
);
|
|
156
144
|
|
|
157
145
|
React.useEffect(() => {
|
|
@@ -246,15 +234,13 @@ export function ProductUomSection({
|
|
|
246
234
|
|
|
247
235
|
const defaultUnit = toTrimmedOrNull(values.defaultUnit) ?? "";
|
|
248
236
|
const defaultSalesUnit = toTrimmedOrNull(values.defaultSalesUnit) ?? "";
|
|
249
|
-
const
|
|
237
|
+
const defaultSalesQuantity =
|
|
250
238
|
toTrimmedOrNull(values.defaultSalesUnitQuantity) ?? "1";
|
|
251
|
-
const defaultSalesQuantity = normalizeDecimalInput(defaultSalesQuantityRaw, locale);
|
|
252
239
|
const unitPriceEnabled = Boolean(values.unitPriceEnabled);
|
|
253
240
|
const unitPriceReferenceUnit =
|
|
254
241
|
toTrimmedOrNull(values.unitPriceReferenceUnit) ?? "";
|
|
255
|
-
const
|
|
242
|
+
const unitPriceBaseQuantity =
|
|
256
243
|
toTrimmedOrNull(values.unitPriceBaseQuantity) ?? "";
|
|
257
|
-
const unitPriceBaseQuantity = normalizeDecimalInput(unitPriceBaseQuantityRaw, locale);
|
|
258
244
|
|
|
259
245
|
const baseUnitLabel = findUnitLabel(defaultUnit) ?? defaultUnit;
|
|
260
246
|
const salesUnitLabel =
|
|
@@ -396,10 +382,7 @@ export function ProductUomSection({
|
|
|
396
382
|
inputMode="decimal"
|
|
397
383
|
value={defaultSalesQuantity}
|
|
398
384
|
onChange={(event) =>
|
|
399
|
-
setValue(
|
|
400
|
-
"defaultSalesUnitQuantity",
|
|
401
|
-
normalizeDecimalInput(event.target.value, locale),
|
|
402
|
-
)
|
|
385
|
+
setValue("defaultSalesUnitQuantity", event.target.value)
|
|
403
386
|
}
|
|
404
387
|
placeholder="1"
|
|
405
388
|
/>
|
|
@@ -549,10 +532,7 @@ export function ProductUomSection({
|
|
|
549
532
|
inputMode="decimal"
|
|
550
533
|
value={unitPriceBaseQuantity}
|
|
551
534
|
onChange={(event) =>
|
|
552
|
-
setValue(
|
|
553
|
-
"unitPriceBaseQuantity",
|
|
554
|
-
normalizeDecimalInput(event.target.value, locale),
|
|
555
|
-
)
|
|
535
|
+
setValue("unitPriceBaseQuantity", event.target.value)
|
|
556
536
|
}
|
|
557
537
|
placeholder="1"
|
|
558
538
|
/>
|
|
@@ -663,7 +643,7 @@ export function ProductUomSection({
|
|
|
663
643
|
value={entry.toBaseFactor}
|
|
664
644
|
onChange={(event) =>
|
|
665
645
|
updateConversion(index, {
|
|
666
|
-
toBaseFactor:
|
|
646
|
+
toBaseFactor: event.target.value,
|
|
667
647
|
})
|
|
668
648
|
}
|
|
669
649
|
placeholder="1"
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { slugify } from "@open-mercato/shared/lib/slugify";
|
|
3
3
|
import { parseObjectLike } from "@open-mercato/shared/lib/json/parseObjectLike";
|
|
4
|
+
import { parseLocaleNumber } from "@open-mercato/shared/lib/number";
|
|
4
5
|
import type { ReferenceUnitCode } from "@open-mercato/shared/lib/units/unitCodes";
|
|
5
6
|
import {
|
|
6
7
|
CATALOG_CONFIGURABLE_PRODUCT_TYPES,
|
|
@@ -348,6 +349,58 @@ export const productFormSchema = z
|
|
|
348
349
|
{ message: 'catalog.products.validation.availabilityDateRange', path: ['availableUntil'] }
|
|
349
350
|
);
|
|
350
351
|
|
|
352
|
+
// The UoM fields below stay raw locale-typed text while the user is editing them
|
|
353
|
+
// (`ProductUomSection`, issue #5828), so `z.coerce.number()` — which only accepts a dot
|
|
354
|
+
// decimal separator — cannot parse them directly. Convert to a canonical dot-decimal string
|
|
355
|
+
// right before validation; an unparseable value is left as-is so the schema's own "expected
|
|
356
|
+
// number" error still fires.
|
|
357
|
+
function toCanonicalDecimalString(raw: string, locale?: string): string {
|
|
358
|
+
if (!raw.trim()) return raw;
|
|
359
|
+
const parsed = parseLocaleNumber(raw, locale);
|
|
360
|
+
return parsed === null ? raw : String(parsed);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
export function withCanonicalUomFields(
|
|
364
|
+
values: ProductFormValues,
|
|
365
|
+
locale?: string,
|
|
366
|
+
): ProductFormValues {
|
|
367
|
+
return {
|
|
368
|
+
...values,
|
|
369
|
+
defaultSalesUnitQuantity:
|
|
370
|
+
typeof values.defaultSalesUnitQuantity === "string"
|
|
371
|
+
? toCanonicalDecimalString(values.defaultSalesUnitQuantity, locale)
|
|
372
|
+
: values.defaultSalesUnitQuantity,
|
|
373
|
+
unitPriceBaseQuantity:
|
|
374
|
+
typeof values.unitPriceBaseQuantity === "string"
|
|
375
|
+
? toCanonicalDecimalString(values.unitPriceBaseQuantity, locale)
|
|
376
|
+
: values.unitPriceBaseQuantity,
|
|
377
|
+
unitConversions: Array.isArray(values.unitConversions)
|
|
378
|
+
? values.unitConversions.map((conversion) => ({
|
|
379
|
+
...conversion,
|
|
380
|
+
toBaseFactor:
|
|
381
|
+
typeof conversion?.toBaseFactor === "string"
|
|
382
|
+
? toCanonicalDecimalString(conversion.toBaseFactor, locale)
|
|
383
|
+
: conversion?.toBaseFactor,
|
|
384
|
+
}))
|
|
385
|
+
: values.unitConversions,
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// `CrudForm`'s `schema` prop runs validation internally, before `onSubmit` — so a page that
|
|
390
|
+
// passes `productFormSchema` there directly (rather than calling `.safeParse` by hand inside
|
|
391
|
+
// `onSubmit`, as the edit page does) needs the UoM canonicalization baked into the schema
|
|
392
|
+
// itself. `locale` is only known at render time, so this builds the wrapped schema per-locale
|
|
393
|
+
// rather than exporting a single module-level constant.
|
|
394
|
+
export function buildLocaleAwareProductFormSchema(locale?: string): z.ZodType<ProductFormValues> {
|
|
395
|
+
return z.preprocess(
|
|
396
|
+
(raw) =>
|
|
397
|
+
raw && typeof raw === "object"
|
|
398
|
+
? withCanonicalUomFields(raw as ProductFormValues, locale)
|
|
399
|
+
: raw,
|
|
400
|
+
productFormSchema,
|
|
401
|
+
) as unknown as z.ZodType<ProductFormValues>;
|
|
402
|
+
}
|
|
403
|
+
|
|
351
404
|
export const PRODUCT_FORM_STEPS = [
|
|
352
405
|
"general",
|
|
353
406
|
"organize",
|
|
@@ -21,6 +21,12 @@ export type UpgradeActionDefinition = {
|
|
|
21
21
|
ctaKey: string
|
|
22
22
|
successKey: string
|
|
23
23
|
loadingKey?: string
|
|
24
|
+
/**
|
|
25
|
+
* Modules that must be enabled for this action to be offered/executed.
|
|
26
|
+
* Mirrors `InjectionWidgetMetadata.requiredModules` — omit for actions
|
|
27
|
+
* owned by `configs` itself.
|
|
28
|
+
*/
|
|
29
|
+
requiredModules?: string[]
|
|
24
30
|
run: (ctx: UpgradeActionContext) => Promise<void>
|
|
25
31
|
}
|
|
26
32
|
|
|
@@ -119,6 +125,7 @@ export const upgradeActions: UpgradeActionDefinition[] = [
|
|
|
119
125
|
ctaKey: 'payment_gateways.upgradeActions.sessionInitializationPrune.cta',
|
|
120
126
|
successKey: 'payment_gateways.upgradeActions.sessionInitializationPrune.success',
|
|
121
127
|
loadingKey: 'payment_gateways.upgradeActions.sessionInitializationPrune.loading',
|
|
128
|
+
requiredModules: ['payment_gateways'],
|
|
122
129
|
run: async ({ container, tenantId, organizationId }) => {
|
|
123
130
|
const { registerSessionInitializationPruneSchedule } = await import(
|
|
124
131
|
'@open-mercato/core/modules/payment_gateways/setup'
|
|
@@ -3,6 +3,7 @@ import type { EntityManager } from '@mikro-orm/postgresql'
|
|
|
3
3
|
import type { AwilixContainer } from 'awilix'
|
|
4
4
|
import { appVersion } from '@open-mercato/shared/lib/version'
|
|
5
5
|
import { parseBooleanToken } from '@open-mercato/shared/lib/boolean'
|
|
6
|
+
import { getEnabledModuleIds } from '@open-mercato/shared/security/enabledModulesRegistry'
|
|
6
7
|
import { UpgradeActionRun } from '../data/entities'
|
|
7
8
|
import { actionsUpToVersion, findUpgradeAction, type UpgradeActionDefinition } from '../lib/upgrade-actions'
|
|
8
9
|
import { createLogger } from '@open-mercato/shared/lib/logger'
|
|
@@ -19,6 +20,18 @@ export function getCurrentVersion(): string {
|
|
|
19
20
|
return appVersion
|
|
20
21
|
}
|
|
21
22
|
|
|
23
|
+
/**
|
|
24
|
+
* An action with no `requiredModules` is owned by `configs` itself and is
|
|
25
|
+
* always available. Fails closed when the module registry is empty/unavailable,
|
|
26
|
+
* mirroring `isDocumentEntityRegistryModuleEnabled`.
|
|
27
|
+
*/
|
|
28
|
+
function isActionModuleEnabled(definition: UpgradeActionDefinition): boolean {
|
|
29
|
+
const required = definition.requiredModules
|
|
30
|
+
if (!Array.isArray(required) || required.length === 0) return true
|
|
31
|
+
const enabledModuleIds = new Set(getEnabledModuleIds())
|
|
32
|
+
return required.every((moduleId) => enabledModuleIds.has(moduleId))
|
|
33
|
+
}
|
|
34
|
+
|
|
22
35
|
export async function listPendingUpgradeActions(
|
|
23
36
|
em: EntityManager,
|
|
24
37
|
{
|
|
@@ -37,7 +50,9 @@ export async function listPendingUpgradeActions(
|
|
|
37
50
|
version: { $in: versions },
|
|
38
51
|
})
|
|
39
52
|
const completed = new Set(runs.map((run) => `${run.version}::${run.actionId}`))
|
|
40
|
-
return definitions
|
|
53
|
+
return definitions
|
|
54
|
+
.filter((definition) => !completed.has(`${definition.version}::${definition.id}`))
|
|
55
|
+
.filter(isActionModuleEnabled)
|
|
41
56
|
}
|
|
42
57
|
|
|
43
58
|
export async function executeUpgradeAction(
|
|
@@ -53,7 +68,7 @@ export async function executeUpgradeAction(
|
|
|
53
68
|
throw new Error('UPGRADE_ACTIONS_DISABLED')
|
|
54
69
|
}
|
|
55
70
|
const definition = findUpgradeAction(actionId, version)
|
|
56
|
-
if (!definition) {
|
|
71
|
+
if (!definition || !isActionModuleEnabled(definition)) {
|
|
57
72
|
throw new Error('UPGRADE_ACTION_NOT_AVAILABLE')
|
|
58
73
|
}
|
|
59
74
|
const em = container.resolve<EntityManager>('em')
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import * as React from 'react'
|
|
4
4
|
import { Calendar, X } from 'lucide-react'
|
|
5
5
|
import { VisuallyHidden } from '@radix-ui/react-visually-hidden'
|
|
6
|
-
import {
|
|
6
|
+
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
7
7
|
import { apiCallOrThrow } from '@open-mercato/ui/backend/utils/apiCall'
|
|
8
8
|
import { extractOptimisticLockConflict } from '@open-mercato/ui/backend/utils/optimisticLock'
|
|
9
9
|
import { surfaceRecordConflict } from '@open-mercato/ui/backend/conflicts'
|
|
@@ -113,7 +113,6 @@ function EditorBody({
|
|
|
113
113
|
staffEnabled,
|
|
114
114
|
}: EditorBodyProps) {
|
|
115
115
|
const t = useT()
|
|
116
|
-
const locale = useLocale()
|
|
117
116
|
const { setValue, errors } = ctx
|
|
118
117
|
const form = formStateOfValues(ctx.values)
|
|
119
118
|
const config = KIND_CONFIG[form.kind]
|
|
@@ -223,7 +222,6 @@ function EditorBody({
|
|
|
223
222
|
startTime={form.startTime}
|
|
224
223
|
endDate={form.endDate}
|
|
225
224
|
endTime={form.endTime}
|
|
226
|
-
locale={locale}
|
|
227
225
|
endsError={errors.ends}
|
|
228
226
|
onAllDayChange={(allDay) => update({ allDay })}
|
|
229
227
|
onDateChange={(date) => {
|
|
@@ -246,7 +244,6 @@ function EditorBody({
|
|
|
246
244
|
endType={form.repeatEndType}
|
|
247
245
|
count={form.repeatCount}
|
|
248
246
|
untilDate={form.repeatUntilDate}
|
|
249
|
-
locale={locale}
|
|
250
247
|
onFreqChange={(repeatFreq) =>
|
|
251
248
|
update(
|
|
252
249
|
repeatFreq === 'weekly'
|