@open-mercato/core 0.7.1-develop.7200.1.8ba2d93d3f → 0.7.1-develop.7201.1.9bd6c88e3d
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/components/products/ProductUomSection.js +27 -22
- package/dist/modules/catalog/components/products/ProductUomSection.js.map +2 -2
- package/dist/modules/sales/components/documents/PaymentDialog.js +14 -11
- package/dist/modules/sales/components/documents/PaymentDialog.js.map +2 -2
- package/dist/modules/wms/components/backend/AdjustInventoryDialog.js +8 -6
- package/dist/modules/wms/components/backend/AdjustInventoryDialog.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/catalog/components/products/ProductUomSection.tsx +31 -21
- package/src/modules/sales/components/documents/PaymentDialog.tsx +18 -11
- package/src/modules/sales/i18n/de.json +1 -0
- package/src/modules/sales/i18n/en.json +1 -0
- package/src/modules/sales/i18n/es.json +1 -0
- package/src/modules/sales/i18n/ko.json +1 -0
- package/src/modules/sales/i18n/pl.json +1 -0
- package/src/modules/wms/components/backend/AdjustInventoryDialog.tsx +6 -5
|
@@ -32,7 +32,8 @@ import {
|
|
|
32
32
|
} from "@open-mercato/ui/primitives/select";
|
|
33
33
|
import { StatusBadge } from "@open-mercato/ui/primitives/status-badge";
|
|
34
34
|
import { Textarea } from "@open-mercato/ui/primitives/textarea";
|
|
35
|
-
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
35
|
+
import { useT, useLocale } from "@open-mercato/shared/lib/i18n/context";
|
|
36
|
+
import { parseLocaleNumber } from "@open-mercato/shared/lib/number";
|
|
36
37
|
import { createLogger } from "@open-mercato/shared/lib/logger";
|
|
37
38
|
import { buildInventoryMutationReferenceId } from "../../lib/inventoryMutationUi.js";
|
|
38
39
|
import {
|
|
@@ -63,11 +64,10 @@ const EMPTY_FORM = {
|
|
|
63
64
|
notes: "",
|
|
64
65
|
serialNumber: ""
|
|
65
66
|
};
|
|
66
|
-
function parseDeltaInput(value) {
|
|
67
|
+
function parseDeltaInput(value, locale) {
|
|
67
68
|
const trimmed = value.trim();
|
|
68
69
|
if (!trimmed || trimmed === "-" || trimmed === "+") return null;
|
|
69
|
-
|
|
70
|
-
return Number.isFinite(parsed) ? parsed : null;
|
|
70
|
+
return parseLocaleNumber(trimmed, locale);
|
|
71
71
|
}
|
|
72
72
|
function AdjustInventoryDialog({
|
|
73
73
|
open,
|
|
@@ -79,6 +79,7 @@ function AdjustInventoryDialog({
|
|
|
79
79
|
initialLotId
|
|
80
80
|
}) {
|
|
81
81
|
const t = useT();
|
|
82
|
+
const locale = useLocale();
|
|
82
83
|
const queryClient = useQueryClient();
|
|
83
84
|
const formRef = React.useRef(null);
|
|
84
85
|
const { runMutation, retryLastMutation } = useGuardedMutation({
|
|
@@ -645,7 +646,7 @@ function AdjustInventoryDialog({
|
|
|
645
646
|
inputMode: "decimal",
|
|
646
647
|
value: String(form.delta),
|
|
647
648
|
onChange: (event) => {
|
|
648
|
-
const parsed = parseDeltaInput(event.target.value);
|
|
649
|
+
const parsed = parseDeltaInput(event.target.value, locale);
|
|
649
650
|
if (parsed == null) return;
|
|
650
651
|
patchForm({ delta: parsed });
|
|
651
652
|
},
|
|
@@ -750,6 +751,7 @@ function AdjustInventoryDialog({
|
|
|
750
751
|
) });
|
|
751
752
|
}
|
|
752
753
|
export {
|
|
753
|
-
AdjustInventoryDialog
|
|
754
|
+
AdjustInventoryDialog,
|
|
755
|
+
parseDeltaInput
|
|
754
756
|
};
|
|
755
757
|
//# sourceMappingURL=AdjustInventoryDialog.js.map
|
|
@@ -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 } from '@open-mercato/shared/lib/i18n/context'\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\nfunction parseDeltaInput(value: string): number | null {\n const trimmed = value.trim()\n if (!trimmed || trimmed === '-' || trimmed === '+') return null\n const parsed = Number(trimmed)\n return Number.isFinite(parsed) ? parsed : null\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 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)\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 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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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/core",
|
|
3
|
-
"version": "0.7.1-develop.
|
|
3
|
+
"version": "0.7.1-develop.7201.1.9bd6c88e3d",
|
|
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.7.1-develop.
|
|
257
|
-
"@open-mercato/shared": "0.7.1-develop.
|
|
258
|
-
"@open-mercato/ui": "0.7.1-develop.
|
|
256
|
+
"@open-mercato/ai-assistant": "0.7.1-develop.7201.1.9bd6c88e3d",
|
|
257
|
+
"@open-mercato/shared": "0.7.1-develop.7201.1.9bd6c88e3d",
|
|
258
|
+
"@open-mercato/ui": "0.7.1-develop.7201.1.9bd6c88e3d",
|
|
259
259
|
"react": "^19.0.0",
|
|
260
260
|
"react-dom": "^19.0.0"
|
|
261
261
|
},
|
|
262
262
|
"devDependencies": {
|
|
263
|
-
"@open-mercato/ai-assistant": "0.7.1-develop.
|
|
264
|
-
"@open-mercato/shared": "0.7.1-develop.
|
|
265
|
-
"@open-mercato/ui": "0.7.1-develop.
|
|
263
|
+
"@open-mercato/ai-assistant": "0.7.1-develop.7201.1.9bd6c88e3d",
|
|
264
|
+
"@open-mercato/shared": "0.7.1-develop.7201.1.9bd6c88e3d",
|
|
265
|
+
"@open-mercato/ui": "0.7.1-develop.7201.1.9bd6c88e3d",
|
|
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",
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import * as React from "react";
|
|
4
|
-
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
4
|
+
import { useT, useLocale } from "@open-mercato/shared/lib/i18n/context";
|
|
5
|
+
import { parseLocaleNumber } from "@open-mercato/shared/lib/number";
|
|
5
6
|
import { apiCall } from "@open-mercato/ui/backend/utils/apiCall";
|
|
6
7
|
import { Button } from "@open-mercato/ui/primitives/button";
|
|
7
8
|
import { Checkbox } from "@open-mercato/ui/primitives/checkbox";
|
|
@@ -56,19 +57,24 @@ const REFERENCE_UNIT_OPTIONS = REFERENCE_UNIT_CODES.map((code) => ({
|
|
|
56
57
|
fallback: REFERENCE_UNIT_DISPLAY[code] ?? code,
|
|
57
58
|
}));
|
|
58
59
|
|
|
59
|
-
|
|
60
|
-
|
|
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);
|
|
61
67
|
}
|
|
62
68
|
|
|
63
|
-
function toPositiveNumber(value: unknown): number | null {
|
|
69
|
+
export function toPositiveNumber(value: unknown, locale?: string): number | null {
|
|
64
70
|
if (typeof value === "number") {
|
|
65
71
|
return Number.isFinite(value) && value > 0 ? value : null;
|
|
66
72
|
}
|
|
67
73
|
if (typeof value !== "string") return null;
|
|
68
74
|
const normalized = toTrimmedOrNull(value);
|
|
69
75
|
if (!normalized) return null;
|
|
70
|
-
const numeric =
|
|
71
|
-
return
|
|
76
|
+
const numeric = parseLocaleNumber(normalized, locale);
|
|
77
|
+
return numeric !== null && numeric > 0 ? numeric : null;
|
|
72
78
|
}
|
|
73
79
|
|
|
74
80
|
function toSortValue(value: string): number {
|
|
@@ -82,7 +88,10 @@ function formatPreviewNumber(value: number): string {
|
|
|
82
88
|
return Number.isInteger(rounded) ? String(rounded) : rounded.toString();
|
|
83
89
|
}
|
|
84
90
|
|
|
85
|
-
function normalizeConversions(
|
|
91
|
+
function normalizeConversions(
|
|
92
|
+
value: unknown,
|
|
93
|
+
locale?: string,
|
|
94
|
+
): ProductUnitConversionDraft[] {
|
|
86
95
|
if (!Array.isArray(value)) return [];
|
|
87
96
|
const normalized = value
|
|
88
97
|
.map((entry) => {
|
|
@@ -92,7 +101,7 @@ function normalizeConversions(value: unknown): ProductUnitConversionDraft[] {
|
|
|
92
101
|
id: toTrimmedOrNull(row.id) ?? null,
|
|
93
102
|
unitCode: toTrimmedOrNull(row.unitCode) ?? "",
|
|
94
103
|
toBaseFactor: toTrimmedOrNull(row.toBaseFactor)
|
|
95
|
-
? normalizeDecimalInput(toTrimmedOrNull(row.toBaseFactor) as string)
|
|
104
|
+
? normalizeDecimalInput(toTrimmedOrNull(row.toBaseFactor) as string, locale)
|
|
96
105
|
: "",
|
|
97
106
|
sortOrder: toTrimmedOrNull(row.sortOrder) ?? "",
|
|
98
107
|
isActive: row.isActive !== false,
|
|
@@ -135,13 +144,14 @@ export function ProductUomSection({
|
|
|
135
144
|
embedded = false,
|
|
136
145
|
}: ProductUomSectionProps) {
|
|
137
146
|
const t = useT();
|
|
147
|
+
const locale = useLocale();
|
|
138
148
|
const { enabled: unitPriceDisplayEnabled } = useUnitPriceDisplayEnabled();
|
|
139
149
|
const [unitOptions, setUnitOptions] = React.useState<UnitOption[]>([]);
|
|
140
150
|
const [loadingUnits, setLoadingUnits] = React.useState(false);
|
|
141
151
|
const [errorLoadingUnits, setErrorLoadingUnits] = React.useState(false);
|
|
142
152
|
const conversions = React.useMemo(
|
|
143
|
-
() => normalizeConversions(values.unitConversions),
|
|
144
|
-
[values.unitConversions],
|
|
153
|
+
() => normalizeConversions(values.unitConversions, locale),
|
|
154
|
+
[values.unitConversions, locale],
|
|
145
155
|
);
|
|
146
156
|
|
|
147
157
|
React.useEffect(() => {
|
|
@@ -238,13 +248,13 @@ export function ProductUomSection({
|
|
|
238
248
|
const defaultSalesUnit = toTrimmedOrNull(values.defaultSalesUnit) ?? "";
|
|
239
249
|
const defaultSalesQuantityRaw =
|
|
240
250
|
toTrimmedOrNull(values.defaultSalesUnitQuantity) ?? "1";
|
|
241
|
-
const defaultSalesQuantity = normalizeDecimalInput(defaultSalesQuantityRaw);
|
|
251
|
+
const defaultSalesQuantity = normalizeDecimalInput(defaultSalesQuantityRaw, locale);
|
|
242
252
|
const unitPriceEnabled = Boolean(values.unitPriceEnabled);
|
|
243
253
|
const unitPriceReferenceUnit =
|
|
244
254
|
toTrimmedOrNull(values.unitPriceReferenceUnit) ?? "";
|
|
245
255
|
const unitPriceBaseQuantityRaw =
|
|
246
256
|
toTrimmedOrNull(values.unitPriceBaseQuantity) ?? "";
|
|
247
|
-
const unitPriceBaseQuantity = normalizeDecimalInput(unitPriceBaseQuantityRaw);
|
|
257
|
+
const unitPriceBaseQuantity = normalizeDecimalInput(unitPriceBaseQuantityRaw, locale);
|
|
248
258
|
|
|
249
259
|
const baseUnitLabel = findUnitLabel(defaultUnit) ?? defaultUnit;
|
|
250
260
|
const salesUnitLabel =
|
|
@@ -261,17 +271,17 @@ export function ProductUomSection({
|
|
|
261
271
|
(entry) =>
|
|
262
272
|
entry.isActive &&
|
|
263
273
|
entry.unitCode.toLowerCase() === defaultSalesKey &&
|
|
264
|
-
toPositiveNumber(entry.toBaseFactor) !== null,
|
|
274
|
+
toPositiveNumber(entry.toBaseFactor, locale) !== null,
|
|
265
275
|
);
|
|
266
|
-
return row ? toPositiveNumber(row.toBaseFactor) : null;
|
|
267
|
-
}, [conversions, defaultSalesUnit, defaultUnit]);
|
|
276
|
+
return row ? toPositiveNumber(row.toBaseFactor, locale) : null;
|
|
277
|
+
}, [conversions, defaultSalesUnit, defaultUnit, locale]);
|
|
268
278
|
|
|
269
|
-
const defaultSalesQuantityNumber = toPositiveNumber(defaultSalesQuantity);
|
|
279
|
+
const defaultSalesQuantityNumber = toPositiveNumber(defaultSalesQuantity, locale);
|
|
270
280
|
const defaultSalesQuantityNormalized =
|
|
271
281
|
defaultSalesQuantityNumber && defaultSalesFactor
|
|
272
282
|
? defaultSalesQuantityNumber * defaultSalesFactor
|
|
273
283
|
: null;
|
|
274
|
-
const unitPriceBaseQuantityNumber = toPositiveNumber(unitPriceBaseQuantity);
|
|
284
|
+
const unitPriceBaseQuantityNumber = toPositiveNumber(unitPriceBaseQuantity, locale);
|
|
275
285
|
|
|
276
286
|
const validConversions = conversions.filter(
|
|
277
287
|
(entry) =>
|
|
@@ -388,7 +398,7 @@ export function ProductUomSection({
|
|
|
388
398
|
onChange={(event) =>
|
|
389
399
|
setValue(
|
|
390
400
|
"defaultSalesUnitQuantity",
|
|
391
|
-
normalizeDecimalInput(event.target.value),
|
|
401
|
+
normalizeDecimalInput(event.target.value, locale),
|
|
392
402
|
)
|
|
393
403
|
}
|
|
394
404
|
placeholder="1"
|
|
@@ -541,7 +551,7 @@ export function ProductUomSection({
|
|
|
541
551
|
onChange={(event) =>
|
|
542
552
|
setValue(
|
|
543
553
|
"unitPriceBaseQuantity",
|
|
544
|
-
normalizeDecimalInput(event.target.value),
|
|
554
|
+
normalizeDecimalInput(event.target.value, locale),
|
|
545
555
|
)
|
|
546
556
|
}
|
|
547
557
|
placeholder="1"
|
|
@@ -595,7 +605,7 @@ export function ProductUomSection({
|
|
|
595
605
|
) : (
|
|
596
606
|
<div className="space-y-2">
|
|
597
607
|
{conversions.map((entry, index) => {
|
|
598
|
-
const conversionFactor = toPositiveNumber(entry.toBaseFactor);
|
|
608
|
+
const conversionFactor = toPositiveNumber(entry.toBaseFactor, locale);
|
|
599
609
|
const conversionPreviewText =
|
|
600
610
|
entry.unitCode && conversionFactor !== null
|
|
601
611
|
? t(
|
|
@@ -653,7 +663,7 @@ export function ProductUomSection({
|
|
|
653
663
|
value={entry.toBaseFactor}
|
|
654
664
|
onChange={(event) =>
|
|
655
665
|
updateConversion(index, {
|
|
656
|
-
toBaseFactor: normalizeDecimalInput(event.target.value),
|
|
666
|
+
toBaseFactor: normalizeDecimalInput(event.target.value, locale),
|
|
657
667
|
})
|
|
658
668
|
}
|
|
659
669
|
placeholder="1"
|
|
@@ -15,7 +15,8 @@ import { useDialogKeyHandler } from '@open-mercato/ui/hooks/useDialogKeyHandler'
|
|
|
15
15
|
import { Input } from '@open-mercato/ui/primitives/input'
|
|
16
16
|
import { Spinner } from '@open-mercato/ui/primitives/spinner'
|
|
17
17
|
import { E } from '#generated/entities.ids.generated'
|
|
18
|
-
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
18
|
+
import { useT, useLocale } from '@open-mercato/shared/lib/i18n/context'
|
|
19
|
+
import { parseLocaleNumber } from '@open-mercato/shared/lib/number'
|
|
19
20
|
import { normalizeCustomFieldSubmitValue, extractCustomFieldValues } from './customFieldHelpers'
|
|
20
21
|
import { createLogger } from '@open-mercato/shared/lib/logger'
|
|
21
22
|
|
|
@@ -66,13 +67,15 @@ type PaymentDialogProps = {
|
|
|
66
67
|
onSaved?: (totals?: PaymentTotals | null) => void | Promise<void>
|
|
67
68
|
}
|
|
68
69
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
return 0
|
|
70
|
+
// The amount field is a hand-rolled text input, so the raw string a user typed reaches
|
|
71
|
+
// the submit handler carrying the separator the surrounding UI displays — `110,70` under
|
|
72
|
+
// Polish (issue #5828, same defect class as #5552). A blank/absent value keeps the old
|
|
73
|
+
// "must be a positive amount" message; only genuinely unparseable input returns null.
|
|
74
|
+
const parseAmountInput = (value: unknown, locale?: string): number | null => {
|
|
75
|
+
if (typeof value === 'number') return Number.isFinite(value) ? value : null
|
|
76
|
+
if (typeof value !== 'string') return 0
|
|
77
|
+
if (!value.trim()) return 0
|
|
78
|
+
return parseLocaleNumber(value, locale)
|
|
76
79
|
}
|
|
77
80
|
|
|
78
81
|
export function PaymentDialog({
|
|
@@ -88,6 +91,7 @@ export function PaymentDialog({
|
|
|
88
91
|
onSaved,
|
|
89
92
|
}: PaymentDialogProps) {
|
|
90
93
|
const t = useT()
|
|
94
|
+
const locale = useLocale()
|
|
91
95
|
const dialogContentRef = React.useRef<HTMLDivElement | null>(null)
|
|
92
96
|
const [formResetKey, setFormResetKey] = React.useState(0)
|
|
93
97
|
const [paymentMethods, setPaymentMethods] = React.useState<PaymentMethodOption[]>([])
|
|
@@ -352,9 +356,8 @@ export function PaymentDialog({
|
|
|
352
356
|
return (
|
|
353
357
|
<div className="flex items-center gap-2">
|
|
354
358
|
<Input
|
|
355
|
-
type="
|
|
359
|
+
type="text"
|
|
356
360
|
inputMode="decimal"
|
|
357
|
-
step="0.01"
|
|
358
361
|
value={normalized as string | number}
|
|
359
362
|
onChange={(event) => setValue(event.target.value)}
|
|
360
363
|
placeholder="0.00"
|
|
@@ -493,10 +496,14 @@ export function PaymentDialog({
|
|
|
493
496
|
const handleSubmit = React.useCallback(
|
|
494
497
|
async (values: Record<string, unknown>) => {
|
|
495
498
|
const resolvedCurrency = currencyCode ? currencyCode.toUpperCase() : ''
|
|
496
|
-
const amountValue =
|
|
499
|
+
const amountValue = parseAmountInput(values.amount, locale)
|
|
497
500
|
if (!resolvedCurrency.trim()) {
|
|
498
501
|
throw createCrudFormError(t('sales.documents.payments.currencyRequired', 'Currency is required.'))
|
|
499
502
|
}
|
|
503
|
+
if (amountValue === null) {
|
|
504
|
+
const message = t('sales.documents.payments.amountInvalid', 'Enter the amount as a number.')
|
|
505
|
+
throw createCrudFormError(message, { amount: message })
|
|
506
|
+
}
|
|
500
507
|
if (amountValue <= 0) {
|
|
501
508
|
throw createCrudFormError(t('sales.documents.payments.amountRequired', 'Enter a positive amount.'), {
|
|
502
509
|
amount: t('sales.documents.payments.amountRequired', 'Enter a positive amount.'),
|
|
@@ -1139,6 +1139,7 @@
|
|
|
1139
1139
|
"sales.documents.list.table.unassigned": "Nicht zugewiesen",
|
|
1140
1140
|
"sales.documents.payments.add": "Zahlung hinzufügen",
|
|
1141
1141
|
"sales.documents.payments.amount": "Betrag",
|
|
1142
|
+
"sales.documents.payments.amountInvalid": "Geben Sie den Betrag als Zahl ein.",
|
|
1142
1143
|
"sales.documents.payments.amountRequired": "Geben Sie einen positiven Betrag ein.",
|
|
1143
1144
|
"sales.documents.payments.created": "Zahlung erfasst.",
|
|
1144
1145
|
"sales.documents.payments.createdAt": "Erstellt",
|
|
@@ -1139,6 +1139,7 @@
|
|
|
1139
1139
|
"sales.documents.list.table.unassigned": "Unassigned",
|
|
1140
1140
|
"sales.documents.payments.add": "Add payment",
|
|
1141
1141
|
"sales.documents.payments.amount": "Amount",
|
|
1142
|
+
"sales.documents.payments.amountInvalid": "Enter the amount as a number.",
|
|
1142
1143
|
"sales.documents.payments.amountRequired": "Enter a positive amount.",
|
|
1143
1144
|
"sales.documents.payments.created": "Payment recorded.",
|
|
1144
1145
|
"sales.documents.payments.createdAt": "Created",
|
|
@@ -1139,6 +1139,7 @@
|
|
|
1139
1139
|
"sales.documents.list.table.unassigned": "Sin asignar",
|
|
1140
1140
|
"sales.documents.payments.add": "Agregar pago",
|
|
1141
1141
|
"sales.documents.payments.amount": "Importe",
|
|
1142
|
+
"sales.documents.payments.amountInvalid": "Introduce el importe como un número.",
|
|
1142
1143
|
"sales.documents.payments.amountRequired": "Introduce un importe positivo.",
|
|
1143
1144
|
"sales.documents.payments.created": "Pago registrado.",
|
|
1144
1145
|
"sales.documents.payments.createdAt": "Creado",
|
|
@@ -1139,6 +1139,7 @@
|
|
|
1139
1139
|
"sales.documents.list.table.unassigned": "미지정",
|
|
1140
1140
|
"sales.documents.payments.add": "결제 추가",
|
|
1141
1141
|
"sales.documents.payments.amount": "금액",
|
|
1142
|
+
"sales.documents.payments.amountInvalid": "금액을 숫자로 입력하세요.",
|
|
1142
1143
|
"sales.documents.payments.amountRequired": "0보다 큰 금액을 입력하세요.",
|
|
1143
1144
|
"sales.documents.payments.created": "결제가 등록되었습니다.",
|
|
1144
1145
|
"sales.documents.payments.createdAt": "등록일",
|
|
@@ -1139,6 +1139,7 @@
|
|
|
1139
1139
|
"sales.documents.list.table.unassigned": "Nieprzypisany",
|
|
1140
1140
|
"sales.documents.payments.add": "Dodaj płatność",
|
|
1141
1141
|
"sales.documents.payments.amount": "Kwota",
|
|
1142
|
+
"sales.documents.payments.amountInvalid": "Podaj kwotę jako liczbę.",
|
|
1142
1143
|
"sales.documents.payments.amountRequired": "Wprowadź dodatnią kwotę.",
|
|
1143
1144
|
"sales.documents.payments.created": "Płatność zarejestrowana.",
|
|
1144
1145
|
"sales.documents.payments.createdAt": "Utworzono",
|
|
@@ -32,7 +32,8 @@ import {
|
|
|
32
32
|
} from '@open-mercato/ui/primitives/select'
|
|
33
33
|
import { StatusBadge } from '@open-mercato/ui/primitives/status-badge'
|
|
34
34
|
import { Textarea } from '@open-mercato/ui/primitives/textarea'
|
|
35
|
-
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
35
|
+
import { useT, useLocale } from '@open-mercato/shared/lib/i18n/context'
|
|
36
|
+
import { parseLocaleNumber } from '@open-mercato/shared/lib/number'
|
|
36
37
|
import { createLogger } from '@open-mercato/shared/lib/logger'
|
|
37
38
|
import { buildInventoryMutationReferenceId } from '../../lib/inventoryMutationUi'
|
|
38
39
|
import {
|
|
@@ -91,11 +92,10 @@ const EMPTY_FORM: AdjustFormValues = {
|
|
|
91
92
|
serialNumber: '',
|
|
92
93
|
}
|
|
93
94
|
|
|
94
|
-
function parseDeltaInput(value: string): number | null {
|
|
95
|
+
export function parseDeltaInput(value: string, locale?: string): number | null {
|
|
95
96
|
const trimmed = value.trim()
|
|
96
97
|
if (!trimmed || trimmed === '-' || trimmed === '+') return null
|
|
97
|
-
|
|
98
|
-
return Number.isFinite(parsed) ? parsed : null
|
|
98
|
+
return parseLocaleNumber(trimmed, locale)
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
export function AdjustInventoryDialog({
|
|
@@ -108,6 +108,7 @@ export function AdjustInventoryDialog({
|
|
|
108
108
|
initialLotId,
|
|
109
109
|
}: AdjustInventoryDialogProps) {
|
|
110
110
|
const t = useT()
|
|
111
|
+
const locale = useLocale()
|
|
111
112
|
const queryClient = useQueryClient()
|
|
112
113
|
const formRef = React.useRef<HTMLFormElement>(null)
|
|
113
114
|
const { runMutation, retryLastMutation } = useGuardedMutation<{
|
|
@@ -690,7 +691,7 @@ export function AdjustInventoryDialog({
|
|
|
690
691
|
inputMode="decimal"
|
|
691
692
|
value={String(form.delta)}
|
|
692
693
|
onChange={(event) => {
|
|
693
|
-
const parsed = parseDeltaInput(event.target.value)
|
|
694
|
+
const parsed = parseDeltaInput(event.target.value, locale)
|
|
694
695
|
if (parsed == null) return
|
|
695
696
|
patchForm({ delta: parsed })
|
|
696
697
|
}}
|