@open-mercato/core 0.6.8-develop.6912.1.36161dc9ac → 0.6.8-develop.6913.1.721e521f55
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/sales/components/documents/ItemsSection.js +42 -1
- package/dist/modules/sales/components/documents/ItemsSection.js.map +2 -2
- package/dist/modules/sales/components/documents/SalesOrderDraftLines.js +2 -0
- package/dist/modules/sales/components/documents/SalesOrderDraftLines.js.map +2 -2
- package/dist/modules/sales/components/documents/lineItemUtils.js +27 -1
- package/dist/modules/sales/components/documents/lineItemUtils.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/sales/components/documents/ItemsSection.tsx +63 -1
- package/src/modules/sales/components/documents/SalesOrderDraftLines.tsx +2 -0
- package/src/modules/sales/components/documents/lineItemTypes.ts +4 -0
- package/src/modules/sales/components/documents/lineItemUtils.ts +46 -0
- package/src/modules/sales/i18n/de.json +3 -0
- package/src/modules/sales/i18n/en.json +3 -0
- package/src/modules/sales/i18n/es.json +3 -0
- package/src/modules/sales/i18n/ko.json +3 -0
- package/src/modules/sales/i18n/pl.json +3 -0
|
@@ -21,7 +21,11 @@ import { useConfirmDialog } from "@open-mercato/ui/backend/confirm-dialog";
|
|
|
21
21
|
import { emitSalesDocumentTotalsRefresh } from "@open-mercato/core/modules/sales/lib/frontend/documentTotalsEvents";
|
|
22
22
|
import { LineItemDialog } from "./LineItemDialog.js";
|
|
23
23
|
import { handleSectionMutationError } from "./optimisticLock.js";
|
|
24
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
formatMoney,
|
|
26
|
+
normalizeNumber,
|
|
27
|
+
resolveLineDiscountDisplay
|
|
28
|
+
} from "./lineItemUtils.js";
|
|
25
29
|
import { extractCustomFieldValues } from "./customFieldHelpers.js";
|
|
26
30
|
import { canonicalizeUnitCode } from "@open-mercato/shared/lib/units/unitCodes";
|
|
27
31
|
import { useInjectionDataWidgets } from "@open-mercato/ui/backend/injection/useInjectionDataWidgets";
|
|
@@ -206,6 +210,14 @@ function SalesDocumentItemsSection({
|
|
|
206
210
|
currencyCode: typeof item.currency_code === "string" ? item.currency_code : typeof currencyCode === "string" ? currencyCode : null,
|
|
207
211
|
unitPriceNet,
|
|
208
212
|
unitPriceGross,
|
|
213
|
+
discountAmount: normalizeNumber(
|
|
214
|
+
item.discount_amount ?? item.discountAmount,
|
|
215
|
+
0
|
|
216
|
+
),
|
|
217
|
+
discountPercent: normalizeNumber(
|
|
218
|
+
item.discount_percent ?? item.discountPercent,
|
|
219
|
+
0
|
|
220
|
+
),
|
|
209
221
|
taxRate,
|
|
210
222
|
totalNet,
|
|
211
223
|
totalGross,
|
|
@@ -394,6 +406,10 @@ function SalesDocumentItemsSection({
|
|
|
394
406
|
},
|
|
395
407
|
[lineStatusMap, t]
|
|
396
408
|
);
|
|
409
|
+
const showDiscountColumn = React.useMemo(
|
|
410
|
+
() => items.some((item) => resolveLineDiscountDisplay(item) !== null),
|
|
411
|
+
[items]
|
|
412
|
+
);
|
|
397
413
|
const renderImage = (record) => {
|
|
398
414
|
const meta = record.metadata ?? {};
|
|
399
415
|
const snapshot = record.catalogSnapshot ?? {};
|
|
@@ -440,6 +456,7 @@ function SalesDocumentItemsSection({
|
|
|
440
456
|
/* @__PURE__ */ jsx("th", { className: "px-3 py-2 font-medium", children: t("sales.documents.items.table.status", "Status") }),
|
|
441
457
|
/* @__PURE__ */ jsx("th", { className: "px-3 py-2 font-medium", children: t("sales.documents.items.table.quantity", "Qty") }),
|
|
442
458
|
/* @__PURE__ */ jsx("th", { className: "px-3 py-2 font-medium", children: t("sales.documents.items.table.unit", "Unit price") }),
|
|
459
|
+
showDiscountColumn ? /* @__PURE__ */ jsx("th", { className: "px-3 py-2 font-medium whitespace-nowrap", children: t("sales.documents.items.table.discount", "Discount") }) : null,
|
|
443
460
|
/* @__PURE__ */ jsx("th", { className: "px-3 py-2 font-medium", children: t("sales.documents.items.table.total", "Total") }),
|
|
444
461
|
injectedColumns.map((col) => /* @__PURE__ */ jsx(
|
|
445
462
|
"th",
|
|
@@ -467,6 +484,7 @@ function SalesDocumentItemsSection({
|
|
|
467
484
|
const unitPriceReference = resolveUnitPriceReference(
|
|
468
485
|
item.uomSnapshot
|
|
469
486
|
);
|
|
487
|
+
const discount = resolveLineDiscountDisplay(item);
|
|
470
488
|
return /* @__PURE__ */ jsxs(
|
|
471
489
|
"tr",
|
|
472
490
|
{
|
|
@@ -538,6 +556,29 @@ function SalesDocumentItemsSection({
|
|
|
538
556
|
}
|
|
539
557
|
) }) : null
|
|
540
558
|
] }) }),
|
|
559
|
+
showDiscountColumn ? /* @__PURE__ */ jsx("td", { className: "px-3 py-3", children: discount ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-0.5", children: [
|
|
560
|
+
discount.amount !== null ? /* @__PURE__ */ jsx("span", { className: "font-mono text-sm", children: t(
|
|
561
|
+
"sales.documents.items.table.discountAmount",
|
|
562
|
+
"\u2212{{value}}",
|
|
563
|
+
{
|
|
564
|
+
value: formatMoney(
|
|
565
|
+
discount.amount,
|
|
566
|
+
item.currencyCode ?? currencyCode ?? void 0
|
|
567
|
+
)
|
|
568
|
+
}
|
|
569
|
+
) }) : null,
|
|
570
|
+
discount.percent !== null ? /* @__PURE__ */ jsx(
|
|
571
|
+
"span",
|
|
572
|
+
{
|
|
573
|
+
className: discount.amount !== null ? "font-mono text-xs text-muted-foreground" : "font-mono text-sm",
|
|
574
|
+
children: t(
|
|
575
|
+
"sales.documents.items.table.discountPercent",
|
|
576
|
+
"{{value}}%",
|
|
577
|
+
{ value: discount.percent }
|
|
578
|
+
)
|
|
579
|
+
}
|
|
580
|
+
) : null
|
|
581
|
+
] }) : null }) : null,
|
|
541
582
|
/* @__PURE__ */ jsx("td", { className: "px-3 py-3 font-semibold", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-0.5", children: [
|
|
542
583
|
/* @__PURE__ */ jsxs("span", { children: [
|
|
543
584
|
formatMoney(
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/sales/components/documents/ItemsSection.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\";\n\nimport * as React from \"react\";\nimport { extensionPoints } from \"@open-mercato/core/modules/sales/extension-points\";\nimport { apiCall, withScopedApiRequestHeaders } from \"@open-mercato/ui/backend/utils/apiCall\";\nimport { buildOptimisticLockHeader } from \"@open-mercato/ui/backend/utils/optimisticLock\";\nimport { deleteCrud } from \"@open-mercato/ui/backend/utils/crud\";\nimport { normalizeCrudServerError } from \"@open-mercato/ui/backend/utils/serverErrors\";\nimport { LoadingMessage, TabEmptyState } from \"@open-mercato/ui/backend/detail\";\nimport { Button } from \"@open-mercato/ui/primitives/button\";\nimport { flash } from \"@open-mercato/ui/backend/FlashMessages\";\nimport { Pencil, Trash2 } from \"lucide-react\";\nimport {\n DictionaryValue,\n type DictionaryMap,\n createDictionaryMap,\n normalizeDictionaryEntries,\n} from \"@open-mercato/core/modules/dictionaries/components/dictionaryAppearance\";\nimport { useT } from \"@open-mercato/shared/lib/i18n/context\";\nimport { useOrganizationScopeDetail } from \"@open-mercato/shared/lib/frontend/useOrganizationScope\";\nimport { useConfirmDialog } from \"@open-mercato/ui/backend/confirm-dialog\";\nimport { emitSalesDocumentTotalsRefresh } from \"@open-mercato/core/modules/sales/lib/frontend/documentTotalsEvents\";\nimport { LineItemDialog } from \"./LineItemDialog\";\nimport { handleSectionMutationError } from \"./optimisticLock\";\nimport type { SalesLineRecord } from \"./lineItemTypes\";\nimport { formatMoney, normalizeNumber } from \"./lineItemUtils\";\nimport type { SectionAction } from \"@open-mercato/ui/backend/detail\";\nimport { extractCustomFieldValues } from \"./customFieldHelpers\";\nimport { canonicalizeUnitCode } from \"@open-mercato/shared/lib/units/unitCodes\";\nimport type { SalesLineUomSnapshot } from \"../../lib/types\";\nimport { useInjectionDataWidgets } from \"@open-mercato/ui/backend/injection/useInjectionDataWidgets\";\nimport type { InjectionColumnDefinition } from \"@open-mercato/shared/modules/widgets/injection\";\nimport { OrderItemsInjectionContext } from \"../../widgets/injection/order-items-context\";\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('sales')\n\ntype ResolvedUnitPriceReference = {\n grossPerReference: number;\n referenceUnitCode: string;\n};\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction isSalesLineUomSnapshot(\n value: unknown,\n): value is SalesLineUomSnapshot {\n if (!isPlainObject(value)) return false;\n return (\n value.version === 1 &&\n typeof value.enteredQuantity === \"string\" &&\n typeof value.toBaseFactor === \"string\" &&\n typeof value.normalizedQuantity === \"string\"\n );\n}\n\nfunction extractUomSnapshot(\n item: Record<string, unknown>,\n): SalesLineUomSnapshot | null {\n const raw = item.uom_snapshot ?? item.uomSnapshot;\n if (isSalesLineUomSnapshot(raw)) return raw;\n return null;\n}\n\nfunction resolveUnitPriceReference(\n snapshot: SalesLineUomSnapshot | Record<string, unknown> | null,\n): ResolvedUnitPriceReference | null {\n if (!snapshot) return null;\n\n const ref = isSalesLineUomSnapshot(snapshot)\n ? snapshot.unitPriceReference\n : isPlainObject(snapshot)\n ? isPlainObject(snapshot.unitPriceReference)\n ? snapshot.unitPriceReference\n : isPlainObject(snapshot.unit_price_reference)\n ? snapshot.unit_price_reference\n : null\n : null;\n\n if (!ref || !isPlainObject(ref)) return null;\n\n const refRecord = ref as Record<string, unknown>;\n const grossPerReference = normalizeNumber(\n refRecord.grossPerReference ?? refRecord.gross_per_reference,\n Number.NaN,\n );\n if (!Number.isFinite(grossPerReference)) return null;\n\n const referenceUnitCode =\n typeof refRecord.referenceUnitCode === \"string\"\n ? refRecord.referenceUnitCode\n : typeof refRecord.reference_unit_code === \"string\"\n ? refRecord.reference_unit_code\n : typeof refRecord.referenceUnit === \"string\"\n ? refRecord.referenceUnit\n : null;\n if (!referenceUnitCode) return null;\n\n return { grossPerReference, referenceUnitCode };\n}\n\nfunction getUomFields(item: Record<string, unknown>) {\n const uomSnapshot = extractUomSnapshot(item);\n return {\n normalizedQuantity: (item.normalized_quantity ?? item.normalizedQuantity ?? null) as\n | number\n | string\n | null,\n normalizedUnit: (item.normalized_unit ?? item.normalizedUnit ?? null) as string | null,\n quantityUnit: (item.quantity_unit ?? item.quantityUnit ?? null) as string | null,\n uomSnapshot,\n };\n}\n\nfunction resolveInjectedColumnValue(\n row: Record<string, unknown>,\n accessorKey: string,\n): unknown {\n const segments = accessorKey.split(\".\").filter(Boolean);\n let current: unknown = row;\n for (const segment of segments) {\n if (!current || typeof current !== \"object\") return undefined;\n current = (current as Record<string, unknown>)[segment];\n }\n return current;\n}\n\ntype SalesDocumentItemsSectionProps = {\n documentId: string;\n kind: \"order\" | \"quote\";\n currencyCode: string | null | undefined;\n documentUpdatedAt?: string | null;\n organizationId?: string | null;\n tenantId?: string | null;\n onActionChange?: (action: SectionAction | null) => void;\n onItemsChange?: (items: SalesLineRecord[]) => void;\n};\n\nexport function SalesDocumentItemsSection({\n documentId,\n kind,\n currencyCode,\n documentUpdatedAt,\n organizationId: orgFromProps,\n tenantId: tenantFromProps,\n onActionChange,\n onItemsChange,\n}: SalesDocumentItemsSectionProps) {\n const t = useT();\n const { organizationId, tenantId } = useOrganizationScopeDetail();\n const { confirm, ConfirmDialogElement } = useConfirmDialog();\n const resolvedOrganizationId = orgFromProps ?? organizationId ?? null;\n const resolvedTenantId = tenantFromProps ?? tenantId ?? null;\n const [items, setItems] = React.useState<SalesLineRecord[]>([]);\n const [loading, setLoading] = React.useState(false);\n const [error, setError] = React.useState<string | null>(null);\n const [dialogOpen, setDialogOpen] = React.useState(false);\n const [lineForEdit, setLineForEdit] = React.useState<SalesLineRecord | null>(\n null,\n );\n const [lineStatusMap, setLineStatusMap] = React.useState<DictionaryMap>({});\n const [shippedTotals, setShippedTotals] = React.useState<Map<string, number>>(\n new Map(),\n );\n\n const { widgets: allColumnWidgets } = useInjectionDataWidgets(\n extensionPoints.hosts.orderItemColumns.spotId,\n );\n const columnWidgets = kind === \"order\" ? allColumnWidgets : [];\n const injectedColumns = React.useMemo<InjectionColumnDefinition[]>(() => {\n const cols: InjectionColumnDefinition[] = [];\n for (const widget of columnWidgets) {\n if (!(\"columns\" in widget)) continue;\n for (const def of (widget as { columns?: InjectionColumnDefinition[] })\n .columns ?? []) {\n cols.push(def);\n }\n }\n return cols;\n }, [columnWidgets]);\n\n const resourcePath = React.useMemo(\n () => (kind === \"order\" ? \"sales/order-lines\" : \"sales/quote-lines\"),\n [kind],\n );\n const documentKey = kind === \"order\" ? \"orderId\" : \"quoteId\";\n const lineStatusesLoaded = React.useRef(false);\n const itemsLoadedForDocument = React.useRef<string | null>(null);\n const shipmentsLoadedForDocument = React.useRef<string | null>(null);\n const loadLineStatuses = React.useCallback(async () => {\n try {\n const params = new URLSearchParams({ page: \"1\", pageSize: \"100\" });\n const response = await apiCall<{\n items?: Array<Record<string, unknown>>;\n }>(`/api/sales/order-line-statuses?${params.toString()}`, undefined, {\n fallback: { items: [] },\n });\n const entries = normalizeDictionaryEntries(response.result?.items ?? [], { sort: false });\n setLineStatusMap(createDictionaryMap(entries));\n } catch (err) {\n logger.error('sales.document.line-statuses.load', { err });\n setLineStatusMap({});\n }\n }, []);\n\n const loadItems = React.useCallback(async () => {\n setLoading(true);\n setError(null);\n try {\n const params = new URLSearchParams({\n page: \"1\",\n pageSize: \"100\",\n [documentKey]: documentId,\n });\n const response = await apiCall<{\n items?: Array<Record<string, unknown>>;\n }>(`/api/${resourcePath}?${params.toString()}`, undefined, {\n fallback: { items: [] },\n });\n if (response.ok && Array.isArray(response.result?.items)) {\n const mapped = response.result.items.flatMap<SalesLineRecord>(\n (item) => {\n const id = typeof item.id === \"string\" ? item.id : null;\n if (!id) return [];\n const taxRate = normalizeNumber(\n item.tax_rate ?? item.taxRate,\n 0,\n );\n const customFields = extractCustomFieldValues(\n item as Record<string, unknown>,\n );\n const name =\n typeof item.name === \"string\"\n ? item.name\n : typeof item.catalog_snapshot === \"object\" &&\n item.catalog_snapshot &&\n typeof (item.catalog_snapshot as Record<string, unknown>).name === \"string\"\n ? (item.catalog_snapshot as Record<string, unknown>).name as string\n : null;\n const description =\n typeof item.description === \"string\" && item.description.trim()\n ? item.description\n : null;\n const quantity = normalizeNumber(item.quantity, 0);\n const uomFields = getUomFields(item);\n const quantityUnit = canonicalizeUnitCode(uomFields.quantityUnit);\n const normalizedQuantity = normalizeNumber(\n uomFields.normalizedQuantity,\n quantity,\n );\n const normalizedUnit =\n canonicalizeUnitCode(uomFields.normalizedUnit) ?? quantityUnit;\n const uomSnapshot = uomFields.uomSnapshot;\n const unitPriceNetRaw = normalizeNumber(\n item.unit_price_net ?? item.unitPriceNet,\n Number.NaN,\n );\n const unitPriceGrossRaw = normalizeNumber(\n item.unit_price_gross ?? item.unitPriceGross,\n Number.NaN,\n );\n const unitPriceNet = Number.isFinite(unitPriceNetRaw)\n ? unitPriceNetRaw\n : Number.isFinite(unitPriceGrossRaw)\n ? unitPriceGrossRaw / (1 + taxRate / 100)\n : 0;\n const unitPriceGross = Number.isFinite(unitPriceGrossRaw)\n ? unitPriceGrossRaw\n : Number.isFinite(unitPriceNetRaw)\n ? unitPriceNetRaw * (1 + taxRate / 100)\n : 0;\n const totalNetRaw = normalizeNumber(\n item.total_net_amount ?? item.totalNetAmount,\n Number.NaN,\n );\n const totalGrossRaw = normalizeNumber(\n item.total_gross_amount ?? item.totalGrossAmount,\n Number.NaN,\n );\n const totalNet = Number.isFinite(totalNetRaw)\n ? totalNetRaw\n : unitPriceNet * quantity;\n const totalGross = Number.isFinite(totalGrossRaw)\n ? totalGrossRaw\n : unitPriceGross * quantity;\n const priceModeRaw =\n item.metadata &&\n typeof item.metadata === \"object\"\n ? (item.metadata as Record<string, unknown>).priceMode\n : null;\n const priceMode = priceModeRaw === \"net\" ? \"net\" : \"gross\";\n const customFieldSetId =\n typeof item.custom_field_set_id === \"string\"\n ? item.custom_field_set_id\n : typeof item.customFieldSetId === \"string\"\n ? item.customFieldSetId\n : null;\n const statusEntryId =\n typeof item.status_entry_id === \"string\"\n ? item.status_entry_id\n : typeof item.statusEntryId === \"string\"\n ? item.statusEntryId\n : null;\n const status = typeof item.status === \"string\" ? item.status : null;\n const record: SalesLineRecord = {\n id,\n name,\n description,\n productId:\n typeof item.product_id === \"string\" ? item.product_id : null,\n productVariantId:\n typeof item.product_variant_id === \"string\"\n ? item.product_variant_id\n : null,\n quantity,\n quantityUnit,\n normalizedQuantity,\n normalizedUnit,\n currencyCode:\n typeof item.currency_code === \"string\"\n ? item.currency_code\n : typeof currencyCode === \"string\"\n ? currencyCode\n : null,\n unitPriceNet,\n unitPriceGross,\n taxRate,\n totalNet,\n totalGross,\n priceMode,\n uomSnapshot,\n metadata:\n (item.metadata as Record<string, unknown> | null | undefined) ??\n null,\n catalogSnapshot:\n (item.catalog_snapshot as\n | Record<string, unknown>\n | null\n | undefined) ?? null,\n customFieldSetId,\n customFields: Object.keys(customFields).length\n ? customFields\n : null,\n status,\n statusEntryId,\n };\n return [record];\n },\n );\n setItems(mapped);\n if (onItemsChange) onItemsChange(mapped);\n } else {\n setItems([]);\n if (onItemsChange) onItemsChange([]);\n }\n } catch (err) {\n logger.error('sales.document.items.load', { err });\n setError(t(\"sales.documents.items.errorLoad\", \"Failed to load items.\"));\n if (onItemsChange) onItemsChange([]);\n } finally {\n setLoading(false);\n }\n }, [currencyCode, documentId, documentKey, onItemsChange, resourcePath, t]);\n\n const loadShippedTotals = React.useCallback(async () => {\n if (kind !== \"order\") {\n setShippedTotals(new Map());\n return;\n }\n try {\n const params = new URLSearchParams({\n page: \"1\",\n pageSize: \"100\",\n orderId: documentId,\n });\n const response = await apiCall<{\n items?: Array<Record<string, unknown>>;\n }>(`/api/sales/shipments?${params.toString()}`, undefined, {\n fallback: { items: [] },\n });\n if (response.ok && Array.isArray(response.result?.items)) {\n const totals = new Map<string, number>();\n response.result.items.forEach((shipment) => {\n const entries = Array.isArray(shipment.items)\n ? (shipment.items as Array<Record<string, unknown>>)\n : [];\n entries.forEach((entry) => {\n const lineId =\n typeof entry.orderLineId === \"string\"\n ? entry.orderLineId\n : typeof entry.order_line_id === \"string\"\n ? entry.order_line_id\n : null;\n if (!lineId) return;\n const quantity = normalizeNumber(entry.quantity, 0);\n if (!Number.isFinite(quantity) || quantity <= 0) return;\n const current = totals.get(lineId) ?? 0;\n totals.set(lineId, current + quantity);\n });\n });\n setShippedTotals(totals);\n } else {\n setShippedTotals(new Map());\n }\n } catch (err) {\n logger.error('sales.document.shipments.load', { err });\n setShippedTotals(new Map());\n }\n }, [documentId, kind]);\n\n React.useEffect(() => {\n if (lineStatusesLoaded.current) return;\n lineStatusesLoaded.current = true;\n void loadLineStatuses();\n }, [loadLineStatuses]);\n\n React.useEffect(() => {\n if (!documentId) return;\n if (itemsLoadedForDocument.current === documentId) return;\n itemsLoadedForDocument.current = documentId;\n void loadItems();\n }, [documentId, loadItems]);\n\n React.useEffect(() => {\n if (kind !== \"order\") {\n shipmentsLoadedForDocument.current = null;\n setShippedTotals(new Map());\n return;\n }\n const key = `${kind}:${documentId}`;\n if (shipmentsLoadedForDocument.current === key) return;\n shipmentsLoadedForDocument.current = key;\n void loadShippedTotals();\n }, [documentId, kind, loadShippedTotals]);\n\n const openCreate = React.useCallback(() => {\n setLineForEdit(null);\n setDialogOpen(true);\n }, []);\n\n React.useEffect(() => {\n if (!onActionChange) return;\n onActionChange({\n label: t(\"sales.documents.items.add\", \"Add item\"),\n onClick: openCreate,\n disabled: false,\n });\n return () => onActionChange(null);\n }, [onActionChange, openCreate, t]);\n\n const handleEdit = React.useCallback((line: SalesLineRecord) => {\n setLineForEdit(line);\n setDialogOpen(true);\n }, []);\n\n const resolveVariantInfo = React.useCallback((record: SalesLineRecord) => {\n const meta =\n (record.metadata as Record<string, unknown> | null | undefined) ?? null;\n const snapshot =\n (record.catalogSnapshot as Record<string, unknown> | null | undefined) ??\n null;\n const variantSnapshot =\n snapshot &&\n typeof snapshot.variant === \"object\" &&\n snapshot.variant\n ? (snapshot.variant as Record<string, unknown>)\n : null;\n const variantTitle =\n meta && typeof meta.variantTitle === \"string\"\n ? meta.variantTitle\n : variantSnapshot && typeof variantSnapshot.name === \"string\"\n ? variantSnapshot.name\n : null;\n const variantSku =\n meta && typeof meta.variantSku === \"string\"\n ? meta.variantSku\n : variantSnapshot && typeof variantSnapshot.sku === \"string\"\n ? variantSnapshot.sku\n : null;\n\n return { variantTitle, variantSku };\n }, []);\n\n const handleDelete = React.useCallback(\n async (line: SalesLineRecord) => {\n const confirmed = await confirm({\n title: t(\n \"sales.documents.items.deleteConfirm\",\n \"Delete this line item?\",\n ),\n variant: \"destructive\",\n });\n if (!confirmed) return;\n try {\n const result = await withScopedApiRequestHeaders(\n buildOptimisticLockHeader(documentUpdatedAt),\n () =>\n deleteCrud(resourcePath, {\n body: {\n id: line.id,\n [documentKey]: documentId,\n organizationId: resolvedOrganizationId ?? undefined,\n tenantId: resolvedTenantId ?? undefined,\n },\n errorMessage: t(\n \"sales.documents.items.errorDelete\",\n \"Failed to delete line.\",\n ),\n }),\n );\n if (result.ok) {\n flash(t(\"sales.documents.items.deleted\", \"Line removed.\"), \"success\");\n await loadItems();\n emitSalesDocumentTotalsRefresh({ documentId, kind });\n }\n } catch (err) {\n if (handleSectionMutationError(err, t, () => void loadItems())) return;\n logger.error('sales.document.items.delete', { err });\n const normalized = normalizeCrudServerError(err);\n const fallback = t(\n \"sales.documents.items.errorDelete\",\n \"Failed to delete line.\",\n );\n flash(normalized.message || fallback, \"error\");\n }\n },\n [\n confirm,\n documentId,\n documentKey,\n documentUpdatedAt,\n kind,\n loadItems,\n resolvedOrganizationId,\n resourcePath,\n t,\n resolvedTenantId,\n ],\n );\n\n const renderStatus = React.useCallback(\n (line: SalesLineRecord) => {\n const value = line.status ?? null;\n if (!value) {\n return (\n <span className=\"text-xs text-muted-foreground\">\n {t(\"sales.documents.items.table.statusEmpty\", \"No status\")}\n </span>\n );\n }\n return (\n <DictionaryValue\n value={value}\n map={lineStatusMap}\n fallback={<span className=\"text-xs font-medium\">{value}</span>}\n className=\"text-xs font-medium\"\n iconWrapperClassName=\"inline-flex h-6 w-6 items-center justify-center rounded bg-muted text-muted-foreground\"\n iconClassName=\"h-3.5 w-3.5\"\n colorClassName=\"h-4 w-4 rounded-full border border-border/70\"\n />\n );\n },\n [lineStatusMap, t],\n );\n\n const renderImage = (record: SalesLineRecord) => {\n const meta =\n (record.metadata as Record<string, unknown> | null | undefined) ?? {};\n const snapshot =\n (record.catalogSnapshot as Record<string, unknown> | null | undefined) ??\n {};\n const productSnapshot: Record<string, unknown> =\n typeof snapshot === \"object\" && snapshot\n ? ((snapshot.product as Record<string, unknown>) ?? {})\n : {};\n const variantSnapshot: Record<string, unknown> =\n typeof snapshot === \"object\" && snapshot\n ? ((snapshot.variant as Record<string, unknown>) ?? {})\n : {};\n const productThumb =\n (meta &&\n typeof meta.productThumbnail === \"string\" &&\n meta.productThumbnail) ||\n (productSnapshot &&\n typeof productSnapshot.thumbnailUrl === \"string\" &&\n productSnapshot.thumbnailUrl) ||\n null;\n const variantThumb =\n (meta &&\n typeof meta.variantThumbnail === \"string\" &&\n meta.variantThumbnail) ||\n (variantSnapshot &&\n typeof variantSnapshot.thumbnailUrl === \"string\" &&\n variantSnapshot.thumbnailUrl) ||\n null;\n const thumbnail = variantThumb ?? productThumb;\n if (thumbnail) {\n return (\n <img\n src={thumbnail}\n alt={record.name ?? record.id}\n className=\"h-10 w-10 rounded border object-cover\"\n />\n );\n }\n return (\n <div className=\"flex h-10 w-10 items-center justify-center rounded border bg-muted text-xs text-muted-foreground\">\n N/A\n </div>\n );\n };\n\n return (\n <OrderItemsInjectionContext.Provider value={{ documentId, kind }}>\n <div className=\"space-y-4\">\n {loading ? (\n <LoadingMessage\n label={t(\"sales.documents.items.loading\", \"Loading items\u2026\")}\n className=\"border-0 bg-transparent p-0 py-8 justify-center\"\n />\n ) : error ? (\n <p className=\"text-sm text-destructive\">{error}</p>\n ) : items.length === 0 ? (\n <TabEmptyState\n title={t(\"sales.documents.items.empty\", \"No items yet.\")}\n description={t(\n \"sales.documents.items.subtitle\",\n \"Add products and configure pricing for this document.\",\n )}\n action={{\n label: t(\"sales.documents.items.add\", \"Add item\"),\n onClick: openCreate,\n }}\n />\n ) : (\n <div className=\"overflow-hidden rounded border\">\n <table className=\"w-full text-sm\">\n <thead className=\"bg-muted\">\n <tr className=\"text-left\">\n <th className=\"px-3 py-2 font-medium\">\n {t(\"sales.documents.items.table.product\", \"Product\")}\n </th>\n <th className=\"px-3 py-2 font-medium\">\n {t(\"sales.documents.items.table.status\", \"Status\")}\n </th>\n <th className=\"px-3 py-2 font-medium\">\n {t(\"sales.documents.items.table.quantity\", \"Qty\")}\n </th>\n <th className=\"px-3 py-2 font-medium\">\n {t(\"sales.documents.items.table.unit\", \"Unit price\")}\n </th>\n <th className=\"px-3 py-2 font-medium\">\n {t(\"sales.documents.items.table.total\", \"Total\")}\n </th>\n {injectedColumns.map((col) => (\n <th\n key={col.id}\n className=\"px-3 py-2 font-medium whitespace-nowrap\"\n >\n {col.headerKey ? t(col.headerKey, col.header) : col.header}\n </th>\n ))}\n <th className=\"px-3 py-2 font-medium sr-only\">\n {t(\"sales.documents.items.table.actions\", \"Actions\")}\n </th>\n </tr>\n </thead>\n <tbody>\n {items.map((item) => {\n const meta =\n (item.metadata as\n | Record<string, unknown>\n | null\n | undefined) ?? null;\n const { variantTitle, variantSku } = resolveVariantInfo(item);\n const productSku =\n meta && typeof meta.productSku === \"string\"\n ? meta.productSku\n : null;\n const variantLabel = variantTitle ?? variantSku;\n const variantSuffix =\n variantSku && variantLabel && variantSku !== variantLabel\n ? ` \u2022 ${variantSku}`\n : \"\";\n const showProductSku =\n productSku && productSku !== variantSku ? productSku : null;\n const shippedQuantity = Math.max(\n 0,\n shippedTotals.get(item.id) ?? 0,\n );\n const quantityLabel = item.quantityUnit\n ? `${item.quantity} ${item.quantityUnit}`\n : String(item.quantity);\n const showNormalized =\n Number.isFinite(item.normalizedQuantity) &&\n item.normalizedQuantity > 0 &&\n (item.normalizedUnit ?? null) &&\n (Math.abs(item.normalizedQuantity - item.quantity) >\n 0.000001 ||\n (item.normalizedUnit ?? null) !==\n (item.quantityUnit ?? null));\n const unitPriceReference = resolveUnitPriceReference(\n item.uomSnapshot,\n );\n\n return (\n <tr\n key={item.id}\n className=\"border-t hover:bg-muted/50 cursor-pointer transition-colors\"\n onClick={() => handleEdit(item)}\n >\n <td className=\"px-3 py-3\">\n <div className=\"flex items-center gap-3\">\n {renderImage(item)}\n <div className=\"min-w-0\">\n <div className=\"truncate font-medium\">\n {item.name ??\n t(\"sales.documents.items.untitled\", \"Untitled\")}\n </div>\n {variantLabel ? (\n <div className=\"text-xs text-muted-foreground truncate\">\n {variantLabel}\n {variantSuffix}\n </div>\n ) : null}\n {showProductSku ? (\n <div className=\"text-xs text-muted-foreground truncate\">\n {showProductSku}\n </div>\n ) : null}\n {item.description ? (\n <div\n className=\"text-xs text-muted-foreground line-clamp-2\"\n title={item.description}\n >\n {item.description}\n </div>\n ) : null}\n </div>\n </div>\n </td>\n <td className=\"px-3 py-3\">\n <div className=\"flex items-center\">\n {renderStatus(item)}\n </div>\n </td>\n <td className=\"px-3 py-3\">\n <div className=\"flex flex-col gap-0.5\">\n <span className=\"font-medium\">{quantityLabel}</span>\n {showNormalized ? (\n <span className=\"text-xs text-muted-foreground\">\n {item.normalizedQuantity} {item.normalizedUnit}\n </span>\n ) : null}\n {shippedQuantity > 0 ? (\n <span className=\"text-xs text-muted-foreground\">\n {t(\n \"sales.documents.items.table.shipped\",\n \"{{shipped}} / {{total}} shipped\",\n {\n shipped: shippedQuantity,\n total: item.quantity,\n },\n )}\n </span>\n ) : null}\n </div>\n </td>\n <td className=\"px-3 py-3\">\n <div className=\"flex flex-col gap-0.5\">\n <span className=\"font-mono text-sm\">\n {formatMoney(\n item.unitPriceGross,\n item.currencyCode ?? currencyCode ?? undefined,\n )}{\" \"}\n <span className=\"text-xs text-muted-foreground\">\n {t(\"sales.documents.items.table.gross\", \"gross\")}\n </span>\n </span>\n <span className=\"font-mono text-xs text-muted-foreground\">\n {formatMoney(\n item.unitPriceNet,\n item.currencyCode ?? currencyCode ?? undefined,\n )}{\" \"}\n {t(\"sales.documents.items.table.net\", \"net\")}\n </span>\n {unitPriceReference ? (\n <span className=\"text-xs text-muted-foreground\">\n {t(\n \"sales.documents.items.table.unitPriceReference\",\n \"{{value}} per 1 {{unit}}\",\n {\n value: formatMoney(\n unitPriceReference.grossPerReference,\n item.currencyCode ??\n currencyCode ??\n undefined,\n ),\n unit: unitPriceReference.referenceUnitCode,\n },\n )}\n </span>\n ) : null}\n </div>\n </td>\n <td className=\"px-3 py-3 font-semibold\">\n <div className=\"flex flex-col gap-0.5\">\n <span>\n {formatMoney(\n item.totalGross,\n item.currencyCode ?? currencyCode ?? undefined,\n )}{\" \"}\n <span className=\"text-xs font-normal text-muted-foreground\">\n {t(\"sales.documents.items.table.gross\", \"gross\")}\n </span>\n </span>\n <span className=\"text-xs font-medium text-muted-foreground\">\n {formatMoney(\n item.totalNet,\n item.currencyCode ?? currencyCode ?? undefined,\n )}{\" \"}\n {t(\"sales.documents.items.table.net\", \"net\")}\n </span>\n </div>\n </td>\n {injectedColumns.map((col) => {\n const colValue = resolveInjectedColumnValue(\n item as unknown as Record<string, unknown>,\n col.accessorKey,\n );\n const Cell = col.cell;\n return (\n <td\n key={col.id}\n className=\"px-3 py-3\"\n onClick={(e) => e.stopPropagation()}\n >\n {Cell ? <Cell getValue={() => colValue} /> : null}\n </td>\n );\n })}\n <td className=\"px-3 py-3\">\n <div className=\"flex items-center gap-2 justify-end\">\n <Button\n size=\"icon\"\n variant=\"ghost\"\n className=\"h-8 w-8\"\n aria-label={t('ui.actions.edit', 'Edit')}\n onClick={(event) => {\n event.stopPropagation();\n handleEdit(item);\n }}\n >\n <Pencil className=\"h-4 w-4\" />\n </Button>\n <span\n title={kind === 'order' && items.length === 1\n ? t('sales.documents.items.errorDeleteLast', 'An order must contain at least one line item.')\n : undefined}\n >\n <Button\n size=\"icon\"\n variant=\"ghost\"\n className=\"h-8 w-8 text-destructive\"\n aria-label={t('ui.actions.delete', 'Delete')}\n disabled={kind === 'order' && items.length === 1}\n onClick={(event) => {\n event.stopPropagation();\n void handleDelete(item);\n }}\n >\n <Trash2 className=\"h-4 w-4\" />\n </Button>\n </span>\n </div>\n </td>\n </tr>\n );\n })}\n </tbody>\n </table>\n </div>\n )}\n\n <LineItemDialog\n open={dialogOpen}\n onOpenChange={(next) => {\n setDialogOpen(next);\n if (!next) setLineForEdit(null);\n }}\n kind={kind}\n documentId={documentId}\n currencyCode={currencyCode}\n documentUpdatedAt={documentUpdatedAt}\n organizationId={resolvedOrganizationId}\n tenantId={resolvedTenantId}\n initialLine={lineForEdit}\n shippedQuantity={\n lineForEdit\n ? Math.max(0, shippedTotals.get(lineForEdit.id) ?? 0)\n : 0\n }\n onSaved={async () => {\n await loadItems();\n emitSalesDocumentTotalsRefresh({ documentId, kind });\n }}\n />\n {ConfirmDialogElement}\n </div>\n </OrderItemsInjectionContext.Provider>\n );\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["\"use client\";\n\nimport * as React from \"react\";\nimport { extensionPoints } from \"@open-mercato/core/modules/sales/extension-points\";\nimport { apiCall, withScopedApiRequestHeaders } from \"@open-mercato/ui/backend/utils/apiCall\";\nimport { buildOptimisticLockHeader } from \"@open-mercato/ui/backend/utils/optimisticLock\";\nimport { deleteCrud } from \"@open-mercato/ui/backend/utils/crud\";\nimport { normalizeCrudServerError } from \"@open-mercato/ui/backend/utils/serverErrors\";\nimport { LoadingMessage, TabEmptyState } from \"@open-mercato/ui/backend/detail\";\nimport { Button } from \"@open-mercato/ui/primitives/button\";\nimport { flash } from \"@open-mercato/ui/backend/FlashMessages\";\nimport { Pencil, Trash2 } from \"lucide-react\";\nimport {\n DictionaryValue,\n type DictionaryMap,\n createDictionaryMap,\n normalizeDictionaryEntries,\n} from \"@open-mercato/core/modules/dictionaries/components/dictionaryAppearance\";\nimport { useT } from \"@open-mercato/shared/lib/i18n/context\";\nimport { useOrganizationScopeDetail } from \"@open-mercato/shared/lib/frontend/useOrganizationScope\";\nimport { useConfirmDialog } from \"@open-mercato/ui/backend/confirm-dialog\";\nimport { emitSalesDocumentTotalsRefresh } from \"@open-mercato/core/modules/sales/lib/frontend/documentTotalsEvents\";\nimport { LineItemDialog } from \"./LineItemDialog\";\nimport { handleSectionMutationError } from \"./optimisticLock\";\nimport type { SalesLineRecord } from \"./lineItemTypes\";\nimport {\n formatMoney,\n normalizeNumber,\n resolveLineDiscountDisplay,\n} from \"./lineItemUtils\";\nimport type { SectionAction } from \"@open-mercato/ui/backend/detail\";\nimport { extractCustomFieldValues } from \"./customFieldHelpers\";\nimport { canonicalizeUnitCode } from \"@open-mercato/shared/lib/units/unitCodes\";\nimport type { SalesLineUomSnapshot } from \"../../lib/types\";\nimport { useInjectionDataWidgets } from \"@open-mercato/ui/backend/injection/useInjectionDataWidgets\";\nimport type { InjectionColumnDefinition } from \"@open-mercato/shared/modules/widgets/injection\";\nimport { OrderItemsInjectionContext } from \"../../widgets/injection/order-items-context\";\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('sales')\n\ntype ResolvedUnitPriceReference = {\n grossPerReference: number;\n referenceUnitCode: string;\n};\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction isSalesLineUomSnapshot(\n value: unknown,\n): value is SalesLineUomSnapshot {\n if (!isPlainObject(value)) return false;\n return (\n value.version === 1 &&\n typeof value.enteredQuantity === \"string\" &&\n typeof value.toBaseFactor === \"string\" &&\n typeof value.normalizedQuantity === \"string\"\n );\n}\n\nfunction extractUomSnapshot(\n item: Record<string, unknown>,\n): SalesLineUomSnapshot | null {\n const raw = item.uom_snapshot ?? item.uomSnapshot;\n if (isSalesLineUomSnapshot(raw)) return raw;\n return null;\n}\n\nfunction resolveUnitPriceReference(\n snapshot: SalesLineUomSnapshot | Record<string, unknown> | null,\n): ResolvedUnitPriceReference | null {\n if (!snapshot) return null;\n\n const ref = isSalesLineUomSnapshot(snapshot)\n ? snapshot.unitPriceReference\n : isPlainObject(snapshot)\n ? isPlainObject(snapshot.unitPriceReference)\n ? snapshot.unitPriceReference\n : isPlainObject(snapshot.unit_price_reference)\n ? snapshot.unit_price_reference\n : null\n : null;\n\n if (!ref || !isPlainObject(ref)) return null;\n\n const refRecord = ref as Record<string, unknown>;\n const grossPerReference = normalizeNumber(\n refRecord.grossPerReference ?? refRecord.gross_per_reference,\n Number.NaN,\n );\n if (!Number.isFinite(grossPerReference)) return null;\n\n const referenceUnitCode =\n typeof refRecord.referenceUnitCode === \"string\"\n ? refRecord.referenceUnitCode\n : typeof refRecord.reference_unit_code === \"string\"\n ? refRecord.reference_unit_code\n : typeof refRecord.referenceUnit === \"string\"\n ? refRecord.referenceUnit\n : null;\n if (!referenceUnitCode) return null;\n\n return { grossPerReference, referenceUnitCode };\n}\n\nfunction getUomFields(item: Record<string, unknown>) {\n const uomSnapshot = extractUomSnapshot(item);\n return {\n normalizedQuantity: (item.normalized_quantity ?? item.normalizedQuantity ?? null) as\n | number\n | string\n | null,\n normalizedUnit: (item.normalized_unit ?? item.normalizedUnit ?? null) as string | null,\n quantityUnit: (item.quantity_unit ?? item.quantityUnit ?? null) as string | null,\n uomSnapshot,\n };\n}\n\nfunction resolveInjectedColumnValue(\n row: Record<string, unknown>,\n accessorKey: string,\n): unknown {\n const segments = accessorKey.split(\".\").filter(Boolean);\n let current: unknown = row;\n for (const segment of segments) {\n if (!current || typeof current !== \"object\") return undefined;\n current = (current as Record<string, unknown>)[segment];\n }\n return current;\n}\n\ntype SalesDocumentItemsSectionProps = {\n documentId: string;\n kind: \"order\" | \"quote\";\n currencyCode: string | null | undefined;\n documentUpdatedAt?: string | null;\n organizationId?: string | null;\n tenantId?: string | null;\n onActionChange?: (action: SectionAction | null) => void;\n onItemsChange?: (items: SalesLineRecord[]) => void;\n};\n\nexport function SalesDocumentItemsSection({\n documentId,\n kind,\n currencyCode,\n documentUpdatedAt,\n organizationId: orgFromProps,\n tenantId: tenantFromProps,\n onActionChange,\n onItemsChange,\n}: SalesDocumentItemsSectionProps) {\n const t = useT();\n const { organizationId, tenantId } = useOrganizationScopeDetail();\n const { confirm, ConfirmDialogElement } = useConfirmDialog();\n const resolvedOrganizationId = orgFromProps ?? organizationId ?? null;\n const resolvedTenantId = tenantFromProps ?? tenantId ?? null;\n const [items, setItems] = React.useState<SalesLineRecord[]>([]);\n const [loading, setLoading] = React.useState(false);\n const [error, setError] = React.useState<string | null>(null);\n const [dialogOpen, setDialogOpen] = React.useState(false);\n const [lineForEdit, setLineForEdit] = React.useState<SalesLineRecord | null>(\n null,\n );\n const [lineStatusMap, setLineStatusMap] = React.useState<DictionaryMap>({});\n const [shippedTotals, setShippedTotals] = React.useState<Map<string, number>>(\n new Map(),\n );\n\n const { widgets: allColumnWidgets } = useInjectionDataWidgets(\n extensionPoints.hosts.orderItemColumns.spotId,\n );\n const columnWidgets = kind === \"order\" ? allColumnWidgets : [];\n const injectedColumns = React.useMemo<InjectionColumnDefinition[]>(() => {\n const cols: InjectionColumnDefinition[] = [];\n for (const widget of columnWidgets) {\n if (!(\"columns\" in widget)) continue;\n for (const def of (widget as { columns?: InjectionColumnDefinition[] })\n .columns ?? []) {\n cols.push(def);\n }\n }\n return cols;\n }, [columnWidgets]);\n\n const resourcePath = React.useMemo(\n () => (kind === \"order\" ? \"sales/order-lines\" : \"sales/quote-lines\"),\n [kind],\n );\n const documentKey = kind === \"order\" ? \"orderId\" : \"quoteId\";\n const lineStatusesLoaded = React.useRef(false);\n const itemsLoadedForDocument = React.useRef<string | null>(null);\n const shipmentsLoadedForDocument = React.useRef<string | null>(null);\n const loadLineStatuses = React.useCallback(async () => {\n try {\n const params = new URLSearchParams({ page: \"1\", pageSize: \"100\" });\n const response = await apiCall<{\n items?: Array<Record<string, unknown>>;\n }>(`/api/sales/order-line-statuses?${params.toString()}`, undefined, {\n fallback: { items: [] },\n });\n const entries = normalizeDictionaryEntries(response.result?.items ?? [], { sort: false });\n setLineStatusMap(createDictionaryMap(entries));\n } catch (err) {\n logger.error('sales.document.line-statuses.load', { err });\n setLineStatusMap({});\n }\n }, []);\n\n const loadItems = React.useCallback(async () => {\n setLoading(true);\n setError(null);\n try {\n const params = new URLSearchParams({\n page: \"1\",\n pageSize: \"100\",\n [documentKey]: documentId,\n });\n const response = await apiCall<{\n items?: Array<Record<string, unknown>>;\n }>(`/api/${resourcePath}?${params.toString()}`, undefined, {\n fallback: { items: [] },\n });\n if (response.ok && Array.isArray(response.result?.items)) {\n const mapped = response.result.items.flatMap<SalesLineRecord>(\n (item) => {\n const id = typeof item.id === \"string\" ? item.id : null;\n if (!id) return [];\n const taxRate = normalizeNumber(\n item.tax_rate ?? item.taxRate,\n 0,\n );\n const customFields = extractCustomFieldValues(\n item as Record<string, unknown>,\n );\n const name =\n typeof item.name === \"string\"\n ? item.name\n : typeof item.catalog_snapshot === \"object\" &&\n item.catalog_snapshot &&\n typeof (item.catalog_snapshot as Record<string, unknown>).name === \"string\"\n ? (item.catalog_snapshot as Record<string, unknown>).name as string\n : null;\n const description =\n typeof item.description === \"string\" && item.description.trim()\n ? item.description\n : null;\n const quantity = normalizeNumber(item.quantity, 0);\n const uomFields = getUomFields(item);\n const quantityUnit = canonicalizeUnitCode(uomFields.quantityUnit);\n const normalizedQuantity = normalizeNumber(\n uomFields.normalizedQuantity,\n quantity,\n );\n const normalizedUnit =\n canonicalizeUnitCode(uomFields.normalizedUnit) ?? quantityUnit;\n const uomSnapshot = uomFields.uomSnapshot;\n const unitPriceNetRaw = normalizeNumber(\n item.unit_price_net ?? item.unitPriceNet,\n Number.NaN,\n );\n const unitPriceGrossRaw = normalizeNumber(\n item.unit_price_gross ?? item.unitPriceGross,\n Number.NaN,\n );\n const unitPriceNet = Number.isFinite(unitPriceNetRaw)\n ? unitPriceNetRaw\n : Number.isFinite(unitPriceGrossRaw)\n ? unitPriceGrossRaw / (1 + taxRate / 100)\n : 0;\n const unitPriceGross = Number.isFinite(unitPriceGrossRaw)\n ? unitPriceGrossRaw\n : Number.isFinite(unitPriceNetRaw)\n ? unitPriceNetRaw * (1 + taxRate / 100)\n : 0;\n const totalNetRaw = normalizeNumber(\n item.total_net_amount ?? item.totalNetAmount,\n Number.NaN,\n );\n const totalGrossRaw = normalizeNumber(\n item.total_gross_amount ?? item.totalGrossAmount,\n Number.NaN,\n );\n const totalNet = Number.isFinite(totalNetRaw)\n ? totalNetRaw\n : unitPriceNet * quantity;\n const totalGross = Number.isFinite(totalGrossRaw)\n ? totalGrossRaw\n : unitPriceGross * quantity;\n const priceModeRaw =\n item.metadata &&\n typeof item.metadata === \"object\"\n ? (item.metadata as Record<string, unknown>).priceMode\n : null;\n const priceMode = priceModeRaw === \"net\" ? \"net\" : \"gross\";\n const customFieldSetId =\n typeof item.custom_field_set_id === \"string\"\n ? item.custom_field_set_id\n : typeof item.customFieldSetId === \"string\"\n ? item.customFieldSetId\n : null;\n const statusEntryId =\n typeof item.status_entry_id === \"string\"\n ? item.status_entry_id\n : typeof item.statusEntryId === \"string\"\n ? item.statusEntryId\n : null;\n const status = typeof item.status === \"string\" ? item.status : null;\n const record: SalesLineRecord = {\n id,\n name,\n description,\n productId:\n typeof item.product_id === \"string\" ? item.product_id : null,\n productVariantId:\n typeof item.product_variant_id === \"string\"\n ? item.product_variant_id\n : null,\n quantity,\n quantityUnit,\n normalizedQuantity,\n normalizedUnit,\n currencyCode:\n typeof item.currency_code === \"string\"\n ? item.currency_code\n : typeof currencyCode === \"string\"\n ? currencyCode\n : null,\n unitPriceNet,\n unitPriceGross,\n discountAmount: normalizeNumber(\n item.discount_amount ?? item.discountAmount,\n 0,\n ),\n discountPercent: normalizeNumber(\n item.discount_percent ?? item.discountPercent,\n 0,\n ),\n taxRate,\n totalNet,\n totalGross,\n priceMode,\n uomSnapshot,\n metadata:\n (item.metadata as Record<string, unknown> | null | undefined) ??\n null,\n catalogSnapshot:\n (item.catalog_snapshot as\n | Record<string, unknown>\n | null\n | undefined) ?? null,\n customFieldSetId,\n customFields: Object.keys(customFields).length\n ? customFields\n : null,\n status,\n statusEntryId,\n };\n return [record];\n },\n );\n setItems(mapped);\n if (onItemsChange) onItemsChange(mapped);\n } else {\n setItems([]);\n if (onItemsChange) onItemsChange([]);\n }\n } catch (err) {\n logger.error('sales.document.items.load', { err });\n setError(t(\"sales.documents.items.errorLoad\", \"Failed to load items.\"));\n if (onItemsChange) onItemsChange([]);\n } finally {\n setLoading(false);\n }\n }, [currencyCode, documentId, documentKey, onItemsChange, resourcePath, t]);\n\n const loadShippedTotals = React.useCallback(async () => {\n if (kind !== \"order\") {\n setShippedTotals(new Map());\n return;\n }\n try {\n const params = new URLSearchParams({\n page: \"1\",\n pageSize: \"100\",\n orderId: documentId,\n });\n const response = await apiCall<{\n items?: Array<Record<string, unknown>>;\n }>(`/api/sales/shipments?${params.toString()}`, undefined, {\n fallback: { items: [] },\n });\n if (response.ok && Array.isArray(response.result?.items)) {\n const totals = new Map<string, number>();\n response.result.items.forEach((shipment) => {\n const entries = Array.isArray(shipment.items)\n ? (shipment.items as Array<Record<string, unknown>>)\n : [];\n entries.forEach((entry) => {\n const lineId =\n typeof entry.orderLineId === \"string\"\n ? entry.orderLineId\n : typeof entry.order_line_id === \"string\"\n ? entry.order_line_id\n : null;\n if (!lineId) return;\n const quantity = normalizeNumber(entry.quantity, 0);\n if (!Number.isFinite(quantity) || quantity <= 0) return;\n const current = totals.get(lineId) ?? 0;\n totals.set(lineId, current + quantity);\n });\n });\n setShippedTotals(totals);\n } else {\n setShippedTotals(new Map());\n }\n } catch (err) {\n logger.error('sales.document.shipments.load', { err });\n setShippedTotals(new Map());\n }\n }, [documentId, kind]);\n\n React.useEffect(() => {\n if (lineStatusesLoaded.current) return;\n lineStatusesLoaded.current = true;\n void loadLineStatuses();\n }, [loadLineStatuses]);\n\n React.useEffect(() => {\n if (!documentId) return;\n if (itemsLoadedForDocument.current === documentId) return;\n itemsLoadedForDocument.current = documentId;\n void loadItems();\n }, [documentId, loadItems]);\n\n React.useEffect(() => {\n if (kind !== \"order\") {\n shipmentsLoadedForDocument.current = null;\n setShippedTotals(new Map());\n return;\n }\n const key = `${kind}:${documentId}`;\n if (shipmentsLoadedForDocument.current === key) return;\n shipmentsLoadedForDocument.current = key;\n void loadShippedTotals();\n }, [documentId, kind, loadShippedTotals]);\n\n const openCreate = React.useCallback(() => {\n setLineForEdit(null);\n setDialogOpen(true);\n }, []);\n\n React.useEffect(() => {\n if (!onActionChange) return;\n onActionChange({\n label: t(\"sales.documents.items.add\", \"Add item\"),\n onClick: openCreate,\n disabled: false,\n });\n return () => onActionChange(null);\n }, [onActionChange, openCreate, t]);\n\n const handleEdit = React.useCallback((line: SalesLineRecord) => {\n setLineForEdit(line);\n setDialogOpen(true);\n }, []);\n\n const resolveVariantInfo = React.useCallback((record: SalesLineRecord) => {\n const meta =\n (record.metadata as Record<string, unknown> | null | undefined) ?? null;\n const snapshot =\n (record.catalogSnapshot as Record<string, unknown> | null | undefined) ??\n null;\n const variantSnapshot =\n snapshot &&\n typeof snapshot.variant === \"object\" &&\n snapshot.variant\n ? (snapshot.variant as Record<string, unknown>)\n : null;\n const variantTitle =\n meta && typeof meta.variantTitle === \"string\"\n ? meta.variantTitle\n : variantSnapshot && typeof variantSnapshot.name === \"string\"\n ? variantSnapshot.name\n : null;\n const variantSku =\n meta && typeof meta.variantSku === \"string\"\n ? meta.variantSku\n : variantSnapshot && typeof variantSnapshot.sku === \"string\"\n ? variantSnapshot.sku\n : null;\n\n return { variantTitle, variantSku };\n }, []);\n\n const handleDelete = React.useCallback(\n async (line: SalesLineRecord) => {\n const confirmed = await confirm({\n title: t(\n \"sales.documents.items.deleteConfirm\",\n \"Delete this line item?\",\n ),\n variant: \"destructive\",\n });\n if (!confirmed) return;\n try {\n const result = await withScopedApiRequestHeaders(\n buildOptimisticLockHeader(documentUpdatedAt),\n () =>\n deleteCrud(resourcePath, {\n body: {\n id: line.id,\n [documentKey]: documentId,\n organizationId: resolvedOrganizationId ?? undefined,\n tenantId: resolvedTenantId ?? undefined,\n },\n errorMessage: t(\n \"sales.documents.items.errorDelete\",\n \"Failed to delete line.\",\n ),\n }),\n );\n if (result.ok) {\n flash(t(\"sales.documents.items.deleted\", \"Line removed.\"), \"success\");\n await loadItems();\n emitSalesDocumentTotalsRefresh({ documentId, kind });\n }\n } catch (err) {\n if (handleSectionMutationError(err, t, () => void loadItems())) return;\n logger.error('sales.document.items.delete', { err });\n const normalized = normalizeCrudServerError(err);\n const fallback = t(\n \"sales.documents.items.errorDelete\",\n \"Failed to delete line.\",\n );\n flash(normalized.message || fallback, \"error\");\n }\n },\n [\n confirm,\n documentId,\n documentKey,\n documentUpdatedAt,\n kind,\n loadItems,\n resolvedOrganizationId,\n resourcePath,\n t,\n resolvedTenantId,\n ],\n );\n\n const renderStatus = React.useCallback(\n (line: SalesLineRecord) => {\n const value = line.status ?? null;\n if (!value) {\n return (\n <span className=\"text-xs text-muted-foreground\">\n {t(\"sales.documents.items.table.statusEmpty\", \"No status\")}\n </span>\n );\n }\n return (\n <DictionaryValue\n value={value}\n map={lineStatusMap}\n fallback={<span className=\"text-xs font-medium\">{value}</span>}\n className=\"text-xs font-medium\"\n iconWrapperClassName=\"inline-flex h-6 w-6 items-center justify-center rounded bg-muted text-muted-foreground\"\n iconClassName=\"h-3.5 w-3.5\"\n colorClassName=\"h-4 w-4 rounded-full border border-border/70\"\n />\n );\n },\n [lineStatusMap, t],\n );\n\n const showDiscountColumn = React.useMemo(\n () => items.some((item) => resolveLineDiscountDisplay(item) !== null),\n [items],\n );\n\n const renderImage = (record: SalesLineRecord) => {\n const meta =\n (record.metadata as Record<string, unknown> | null | undefined) ?? {};\n const snapshot =\n (record.catalogSnapshot as Record<string, unknown> | null | undefined) ??\n {};\n const productSnapshot: Record<string, unknown> =\n typeof snapshot === \"object\" && snapshot\n ? ((snapshot.product as Record<string, unknown>) ?? {})\n : {};\n const variantSnapshot: Record<string, unknown> =\n typeof snapshot === \"object\" && snapshot\n ? ((snapshot.variant as Record<string, unknown>) ?? {})\n : {};\n const productThumb =\n (meta &&\n typeof meta.productThumbnail === \"string\" &&\n meta.productThumbnail) ||\n (productSnapshot &&\n typeof productSnapshot.thumbnailUrl === \"string\" &&\n productSnapshot.thumbnailUrl) ||\n null;\n const variantThumb =\n (meta &&\n typeof meta.variantThumbnail === \"string\" &&\n meta.variantThumbnail) ||\n (variantSnapshot &&\n typeof variantSnapshot.thumbnailUrl === \"string\" &&\n variantSnapshot.thumbnailUrl) ||\n null;\n const thumbnail = variantThumb ?? productThumb;\n if (thumbnail) {\n return (\n <img\n src={thumbnail}\n alt={record.name ?? record.id}\n className=\"h-10 w-10 rounded border object-cover\"\n />\n );\n }\n return (\n <div className=\"flex h-10 w-10 items-center justify-center rounded border bg-muted text-xs text-muted-foreground\">\n N/A\n </div>\n );\n };\n\n return (\n <OrderItemsInjectionContext.Provider value={{ documentId, kind }}>\n <div className=\"space-y-4\">\n {loading ? (\n <LoadingMessage\n label={t(\"sales.documents.items.loading\", \"Loading items\u2026\")}\n className=\"border-0 bg-transparent p-0 py-8 justify-center\"\n />\n ) : error ? (\n <p className=\"text-sm text-destructive\">{error}</p>\n ) : items.length === 0 ? (\n <TabEmptyState\n title={t(\"sales.documents.items.empty\", \"No items yet.\")}\n description={t(\n \"sales.documents.items.subtitle\",\n \"Add products and configure pricing for this document.\",\n )}\n action={{\n label: t(\"sales.documents.items.add\", \"Add item\"),\n onClick: openCreate,\n }}\n />\n ) : (\n <div className=\"overflow-hidden rounded border\">\n <table className=\"w-full text-sm\">\n <thead className=\"bg-muted\">\n <tr className=\"text-left\">\n <th className=\"px-3 py-2 font-medium\">\n {t(\"sales.documents.items.table.product\", \"Product\")}\n </th>\n <th className=\"px-3 py-2 font-medium\">\n {t(\"sales.documents.items.table.status\", \"Status\")}\n </th>\n <th className=\"px-3 py-2 font-medium\">\n {t(\"sales.documents.items.table.quantity\", \"Qty\")}\n </th>\n <th className=\"px-3 py-2 font-medium\">\n {t(\"sales.documents.items.table.unit\", \"Unit price\")}\n </th>\n {showDiscountColumn ? (\n <th className=\"px-3 py-2 font-medium whitespace-nowrap\">\n {t(\"sales.documents.items.table.discount\", \"Discount\")}\n </th>\n ) : null}\n <th className=\"px-3 py-2 font-medium\">\n {t(\"sales.documents.items.table.total\", \"Total\")}\n </th>\n {injectedColumns.map((col) => (\n <th\n key={col.id}\n className=\"px-3 py-2 font-medium whitespace-nowrap\"\n >\n {col.headerKey ? t(col.headerKey, col.header) : col.header}\n </th>\n ))}\n <th className=\"px-3 py-2 font-medium sr-only\">\n {t(\"sales.documents.items.table.actions\", \"Actions\")}\n </th>\n </tr>\n </thead>\n <tbody>\n {items.map((item) => {\n const meta =\n (item.metadata as\n | Record<string, unknown>\n | null\n | undefined) ?? null;\n const { variantTitle, variantSku } = resolveVariantInfo(item);\n const productSku =\n meta && typeof meta.productSku === \"string\"\n ? meta.productSku\n : null;\n const variantLabel = variantTitle ?? variantSku;\n const variantSuffix =\n variantSku && variantLabel && variantSku !== variantLabel\n ? ` \u2022 ${variantSku}`\n : \"\";\n const showProductSku =\n productSku && productSku !== variantSku ? productSku : null;\n const shippedQuantity = Math.max(\n 0,\n shippedTotals.get(item.id) ?? 0,\n );\n const quantityLabel = item.quantityUnit\n ? `${item.quantity} ${item.quantityUnit}`\n : String(item.quantity);\n const showNormalized =\n Number.isFinite(item.normalizedQuantity) &&\n item.normalizedQuantity > 0 &&\n (item.normalizedUnit ?? null) &&\n (Math.abs(item.normalizedQuantity - item.quantity) >\n 0.000001 ||\n (item.normalizedUnit ?? null) !==\n (item.quantityUnit ?? null));\n const unitPriceReference = resolveUnitPriceReference(\n item.uomSnapshot,\n );\n const discount = resolveLineDiscountDisplay(item);\n\n return (\n <tr\n key={item.id}\n className=\"border-t hover:bg-muted/50 cursor-pointer transition-colors\"\n onClick={() => handleEdit(item)}\n >\n <td className=\"px-3 py-3\">\n <div className=\"flex items-center gap-3\">\n {renderImage(item)}\n <div className=\"min-w-0\">\n <div className=\"truncate font-medium\">\n {item.name ??\n t(\"sales.documents.items.untitled\", \"Untitled\")}\n </div>\n {variantLabel ? (\n <div className=\"text-xs text-muted-foreground truncate\">\n {variantLabel}\n {variantSuffix}\n </div>\n ) : null}\n {showProductSku ? (\n <div className=\"text-xs text-muted-foreground truncate\">\n {showProductSku}\n </div>\n ) : null}\n {item.description ? (\n <div\n className=\"text-xs text-muted-foreground line-clamp-2\"\n title={item.description}\n >\n {item.description}\n </div>\n ) : null}\n </div>\n </div>\n </td>\n <td className=\"px-3 py-3\">\n <div className=\"flex items-center\">\n {renderStatus(item)}\n </div>\n </td>\n <td className=\"px-3 py-3\">\n <div className=\"flex flex-col gap-0.5\">\n <span className=\"font-medium\">{quantityLabel}</span>\n {showNormalized ? (\n <span className=\"text-xs text-muted-foreground\">\n {item.normalizedQuantity} {item.normalizedUnit}\n </span>\n ) : null}\n {shippedQuantity > 0 ? (\n <span className=\"text-xs text-muted-foreground\">\n {t(\n \"sales.documents.items.table.shipped\",\n \"{{shipped}} / {{total}} shipped\",\n {\n shipped: shippedQuantity,\n total: item.quantity,\n },\n )}\n </span>\n ) : null}\n </div>\n </td>\n <td className=\"px-3 py-3\">\n <div className=\"flex flex-col gap-0.5\">\n <span className=\"font-mono text-sm\">\n {formatMoney(\n item.unitPriceGross,\n item.currencyCode ?? currencyCode ?? undefined,\n )}{\" \"}\n <span className=\"text-xs text-muted-foreground\">\n {t(\"sales.documents.items.table.gross\", \"gross\")}\n </span>\n </span>\n <span className=\"font-mono text-xs text-muted-foreground\">\n {formatMoney(\n item.unitPriceNet,\n item.currencyCode ?? currencyCode ?? undefined,\n )}{\" \"}\n {t(\"sales.documents.items.table.net\", \"net\")}\n </span>\n {unitPriceReference ? (\n <span className=\"text-xs text-muted-foreground\">\n {t(\n \"sales.documents.items.table.unitPriceReference\",\n \"{{value}} per 1 {{unit}}\",\n {\n value: formatMoney(\n unitPriceReference.grossPerReference,\n item.currencyCode ??\n currencyCode ??\n undefined,\n ),\n unit: unitPriceReference.referenceUnitCode,\n },\n )}\n </span>\n ) : null}\n </div>\n </td>\n {showDiscountColumn ? (\n <td className=\"px-3 py-3\">\n {discount ? (\n <div className=\"flex flex-col gap-0.5\">\n {discount.amount !== null ? (\n <span className=\"font-mono text-sm\">\n {t(\n \"sales.documents.items.table.discountAmount\",\n \"\u2212{{value}}\",\n {\n value: formatMoney(\n discount.amount,\n item.currencyCode ??\n currencyCode ??\n undefined,\n ),\n },\n )}\n </span>\n ) : null}\n {discount.percent !== null ? (\n <span\n className={\n discount.amount !== null\n ? \"font-mono text-xs text-muted-foreground\"\n : \"font-mono text-sm\"\n }\n >\n {t(\n \"sales.documents.items.table.discountPercent\",\n \"{{value}}%\",\n { value: discount.percent },\n )}\n </span>\n ) : null}\n </div>\n ) : null}\n </td>\n ) : null}\n <td className=\"px-3 py-3 font-semibold\">\n <div className=\"flex flex-col gap-0.5\">\n <span>\n {formatMoney(\n item.totalGross,\n item.currencyCode ?? currencyCode ?? undefined,\n )}{\" \"}\n <span className=\"text-xs font-normal text-muted-foreground\">\n {t(\"sales.documents.items.table.gross\", \"gross\")}\n </span>\n </span>\n <span className=\"text-xs font-medium text-muted-foreground\">\n {formatMoney(\n item.totalNet,\n item.currencyCode ?? currencyCode ?? undefined,\n )}{\" \"}\n {t(\"sales.documents.items.table.net\", \"net\")}\n </span>\n </div>\n </td>\n {injectedColumns.map((col) => {\n const colValue = resolveInjectedColumnValue(\n item as unknown as Record<string, unknown>,\n col.accessorKey,\n );\n const Cell = col.cell;\n return (\n <td\n key={col.id}\n className=\"px-3 py-3\"\n onClick={(e) => e.stopPropagation()}\n >\n {Cell ? <Cell getValue={() => colValue} /> : null}\n </td>\n );\n })}\n <td className=\"px-3 py-3\">\n <div className=\"flex items-center gap-2 justify-end\">\n <Button\n size=\"icon\"\n variant=\"ghost\"\n className=\"h-8 w-8\"\n aria-label={t('ui.actions.edit', 'Edit')}\n onClick={(event) => {\n event.stopPropagation();\n handleEdit(item);\n }}\n >\n <Pencil className=\"h-4 w-4\" />\n </Button>\n <span\n title={kind === 'order' && items.length === 1\n ? t('sales.documents.items.errorDeleteLast', 'An order must contain at least one line item.')\n : undefined}\n >\n <Button\n size=\"icon\"\n variant=\"ghost\"\n className=\"h-8 w-8 text-destructive\"\n aria-label={t('ui.actions.delete', 'Delete')}\n disabled={kind === 'order' && items.length === 1}\n onClick={(event) => {\n event.stopPropagation();\n void handleDelete(item);\n }}\n >\n <Trash2 className=\"h-4 w-4\" />\n </Button>\n </span>\n </div>\n </td>\n </tr>\n );\n })}\n </tbody>\n </table>\n </div>\n )}\n\n <LineItemDialog\n open={dialogOpen}\n onOpenChange={(next) => {\n setDialogOpen(next);\n if (!next) setLineForEdit(null);\n }}\n kind={kind}\n documentId={documentId}\n currencyCode={currencyCode}\n documentUpdatedAt={documentUpdatedAt}\n organizationId={resolvedOrganizationId}\n tenantId={resolvedTenantId}\n initialLine={lineForEdit}\n shippedQuantity={\n lineForEdit\n ? Math.max(0, shippedTotals.get(lineForEdit.id) ?? 0)\n : 0\n }\n onSaved={async () => {\n await loadItems();\n emitSalesDocumentTotalsRefresh({ documentId, kind });\n }}\n />\n {ConfirmDialogElement}\n </div>\n </OrderItemsInjectionContext.Provider>\n );\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -36,6 +36,8 @@ function createSalesOrderLineDraft(payload, id = draftId()) {
|
|
|
36
36
|
currencyCode: typeof payload.currencyCode === "string" ? payload.currencyCode : null,
|
|
37
37
|
unitPriceNet,
|
|
38
38
|
unitPriceGross,
|
|
39
|
+
discountAmount: normalizeNumber(payload.discountAmount, 0) * quantity,
|
|
40
|
+
discountPercent: normalizeNumber(payload.discountPercent, 0),
|
|
39
41
|
taxRate,
|
|
40
42
|
totalNet,
|
|
41
43
|
totalGross,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/sales/components/documents/SalesOrderDraftLines.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'\nimport { Plus } from 'lucide-react'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport { RowActions } from '@open-mercato/ui/backend/RowActions'\nimport { EmptyState } from '@open-mercato/ui/primitives/empty-state'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { LineItemDialog } from './LineItemDialog'\nimport type { SalesLineRecord } from './lineItemTypes'\nimport { formatMoney, normalizeNumber } from './lineItemUtils'\n\nexport type SalesOrderLineDraft = {\n id: string\n payload: Record<string, unknown>\n record: SalesLineRecord\n}\n\ntype SalesOrderDraftLinesProps = {\n currencyCode: string | null | undefined\n organizationId: string | null\n tenantId: string | null\n lines: SalesOrderLineDraft[]\n error?: string | null\n onChange: (lines: SalesOrderLineDraft[]) => void\n}\n\nfunction draftId(): string {\n return typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function'\n ? crypto.randomUUID()\n : `draft-${Date.now()}-${Math.round(performance.now())}`\n}\n\nexport function createSalesOrderLineDraft(\n payload: Record<string, unknown>,\n id = draftId(),\n): SalesOrderLineDraft {\n const quantity = normalizeNumber(payload.quantity, 0)\n const unitPriceNet = normalizeNumber(payload.unitPriceNet, 0)\n const unitPriceGross = normalizeNumber(payload.unitPriceGross, unitPriceNet)\n const taxRate = normalizeNumber(payload.taxRate, 0)\n const totalNet = normalizeNumber(payload.totalNetAmount, unitPriceNet * quantity)\n const totalGross = normalizeNumber(payload.totalGrossAmount, unitPriceGross * quantity)\n const metadata = payload.metadata && typeof payload.metadata === 'object'\n ? payload.metadata as Record<string, unknown>\n : null\n const catalogSnapshot = payload.catalogSnapshot && typeof payload.catalogSnapshot === 'object'\n ? payload.catalogSnapshot as Record<string, unknown>\n : null\n\n return {\n id,\n payload: { ...payload },\n record: {\n id,\n name: typeof payload.name === 'string' ? payload.name : null,\n productId: typeof payload.productId === 'string' ? payload.productId : null,\n productVariantId: typeof payload.productVariantId === 'string' ? payload.productVariantId : null,\n quantity,\n quantityUnit: typeof payload.quantityUnit === 'string' ? payload.quantityUnit : null,\n normalizedQuantity: normalizeNumber(payload.normalizedQuantity, quantity),\n normalizedUnit: typeof payload.normalizedUnit === 'string' ? payload.normalizedUnit : null,\n currencyCode: typeof payload.currencyCode === 'string' ? payload.currencyCode : null,\n unitPriceNet,\n unitPriceGross,\n taxRate,\n totalNet,\n totalGross,\n priceMode: payload.priceMode === 'net' ? 'net' : 'gross',\n uomSnapshot: null,\n metadata,\n catalogSnapshot,\n customFieldSetId: typeof payload.customFieldSetId === 'string' ? payload.customFieldSetId : null,\n customFields: payload.customFields && typeof payload.customFields === 'object'\n ? payload.customFields as Record<string, unknown>\n : null,\n status: null,\n statusEntryId: typeof payload.statusEntryId === 'string' ? payload.statusEntryId : null,\n },\n }\n}\n\nexport function SalesOrderDraftLines({\n currencyCode,\n organizationId,\n tenantId,\n lines,\n error,\n onChange,\n}: SalesOrderDraftLinesProps) {\n const t = useT()\n const [dialogOpen, setDialogOpen] = React.useState(false)\n const [editing, setEditing] = React.useState<SalesOrderLineDraft | null>(null)\n\n const columns = React.useMemo<ColumnDef<SalesOrderLineDraft>[]>(() => [\n {\n id: 'name',\n header: t('sales.documents.items.table.product', 'Product'),\n cell: ({ row }) => row.original.record.name ?? t('sales.documents.items.untitled', 'Untitled'),\n },\n {\n id: 'quantity',\n header: t('sales.documents.items.table.quantity', 'Qty'),\n cell: ({ row }) => row.original.record.quantityUnit\n ? `${row.original.record.quantity} ${row.original.record.quantityUnit}`\n : row.original.record.quantity,\n },\n {\n id: 'unitPrice',\n header: t('sales.documents.items.table.unit', 'Unit price'),\n cell: ({ row }) => formatMoney(row.original.record.unitPriceGross, row.original.record.currencyCode ?? currencyCode ?? undefined),\n },\n {\n id: 'total',\n header: t('sales.documents.items.table.total', 'Total'),\n cell: ({ row }) => formatMoney(row.original.record.totalGross, row.original.record.currencyCode ?? currencyCode ?? undefined),\n },\n ], [currencyCode, t])\n\n const openCreate = React.useCallback(() => {\n setEditing(null)\n setDialogOpen(true)\n }, [])\n\n const rowActions = React.useCallback((line: SalesOrderLineDraft) => (\n <RowActions\n items={[\n {\n id: 'edit',\n label: t('ui.actions.edit', 'Edit'),\n onSelect: () => {\n setEditing(line)\n setDialogOpen(true)\n },\n },\n {\n id: 'delete',\n label: t('ui.actions.delete', 'Delete'),\n destructive: true,\n onSelect: () => onChange(lines.filter((candidate) => candidate.id !== line.id)),\n },\n ]}\n />\n ), [lines, onChange, t])\n\n return (\n <div className=\"space-y-3\">\n <div className=\"flex items-center justify-between gap-4\">\n <div>\n <h3 className=\"text-sm font-medium\">{t('sales.orders.form.lines', 'Line Items')}</h3>\n <p className=\"text-sm text-muted-foreground\">\n {t('sales.orders.linesRequired', 'Add at least one line item before creating the order.')}\n </p>\n </div>\n <Button type=\"button\" variant=\"outline\" onClick={openCreate}>\n <Plus className=\"h-4 w-4\" aria-hidden=\"true\" />\n {t('sales.documents.items.add', 'Add item')}\n </Button>\n </div>\n {error ? <p className=\"text-sm text-destructive\" role=\"alert\">{error}</p> : null}\n <DataTable\n columns={columns}\n data={lines}\n embedded\n disableRowClick\n rowActions={rowActions}\n emptyState={(\n <EmptyState\n size=\"sm\"\n title={t('sales.documents.items.empty', 'No items yet.')}\n description={t('sales.orders.linesRequired', 'Add at least one line item before creating the order.')}\n actions={<Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={openCreate}>{t('sales.documents.items.add', 'Add item')}</Button>}\n />\n )}\n />\n <LineItemDialog\n open={dialogOpen}\n onOpenChange={(open) => {\n setDialogOpen(open)\n if (!open) setEditing(null)\n }}\n kind=\"order\"\n currencyCode={currencyCode}\n organizationId={organizationId}\n tenantId={tenantId}\n initialLine={editing?.record ?? null}\n onDraftSaved={(payload, lineId) => {\n const draft = createSalesOrderLineDraft(payload, lineId ?? undefined)\n onChange(lineId\n ? lines.map((line) => line.id === lineId ? draft : line)\n : [...lines, draft])\n }}\n />\n </div>\n )\n}\n"],
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'\nimport { Plus } from 'lucide-react'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport { RowActions } from '@open-mercato/ui/backend/RowActions'\nimport { EmptyState } from '@open-mercato/ui/primitives/empty-state'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { LineItemDialog } from './LineItemDialog'\nimport type { SalesLineRecord } from './lineItemTypes'\nimport { formatMoney, normalizeNumber } from './lineItemUtils'\n\nexport type SalesOrderLineDraft = {\n id: string\n payload: Record<string, unknown>\n record: SalesLineRecord\n}\n\ntype SalesOrderDraftLinesProps = {\n currencyCode: string | null | undefined\n organizationId: string | null\n tenantId: string | null\n lines: SalesOrderLineDraft[]\n error?: string | null\n onChange: (lines: SalesOrderLineDraft[]) => void\n}\n\nfunction draftId(): string {\n return typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function'\n ? crypto.randomUUID()\n : `draft-${Date.now()}-${Math.round(performance.now())}`\n}\n\nexport function createSalesOrderLineDraft(\n payload: Record<string, unknown>,\n id = draftId(),\n): SalesOrderLineDraft {\n const quantity = normalizeNumber(payload.quantity, 0)\n const unitPriceNet = normalizeNumber(payload.unitPriceNet, 0)\n const unitPriceGross = normalizeNumber(payload.unitPriceGross, unitPriceNet)\n const taxRate = normalizeNumber(payload.taxRate, 0)\n const totalNet = normalizeNumber(payload.totalNetAmount, unitPriceNet * quantity)\n const totalGross = normalizeNumber(payload.totalGrossAmount, unitPriceGross * quantity)\n const metadata = payload.metadata && typeof payload.metadata === 'object'\n ? payload.metadata as Record<string, unknown>\n : null\n const catalogSnapshot = payload.catalogSnapshot && typeof payload.catalogSnapshot === 'object'\n ? payload.catalogSnapshot as Record<string, unknown>\n : null\n\n return {\n id,\n payload: { ...payload },\n record: {\n id,\n name: typeof payload.name === 'string' ? payload.name : null,\n productId: typeof payload.productId === 'string' ? payload.productId : null,\n productVariantId: typeof payload.productVariantId === 'string' ? payload.productVariantId : null,\n quantity,\n quantityUnit: typeof payload.quantityUnit === 'string' ? payload.quantityUnit : null,\n normalizedQuantity: normalizeNumber(payload.normalizedQuantity, quantity),\n normalizedUnit: typeof payload.normalizedUnit === 'string' ? payload.normalizedUnit : null,\n currencyCode: typeof payload.currencyCode === 'string' ? payload.currencyCode : null,\n unitPriceNet,\n unitPriceGross,\n discountAmount: normalizeNumber(payload.discountAmount, 0) * quantity,\n discountPercent: normalizeNumber(payload.discountPercent, 0),\n taxRate,\n totalNet,\n totalGross,\n priceMode: payload.priceMode === 'net' ? 'net' : 'gross',\n uomSnapshot: null,\n metadata,\n catalogSnapshot,\n customFieldSetId: typeof payload.customFieldSetId === 'string' ? payload.customFieldSetId : null,\n customFields: payload.customFields && typeof payload.customFields === 'object'\n ? payload.customFields as Record<string, unknown>\n : null,\n status: null,\n statusEntryId: typeof payload.statusEntryId === 'string' ? payload.statusEntryId : null,\n },\n }\n}\n\nexport function SalesOrderDraftLines({\n currencyCode,\n organizationId,\n tenantId,\n lines,\n error,\n onChange,\n}: SalesOrderDraftLinesProps) {\n const t = useT()\n const [dialogOpen, setDialogOpen] = React.useState(false)\n const [editing, setEditing] = React.useState<SalesOrderLineDraft | null>(null)\n\n const columns = React.useMemo<ColumnDef<SalesOrderLineDraft>[]>(() => [\n {\n id: 'name',\n header: t('sales.documents.items.table.product', 'Product'),\n cell: ({ row }) => row.original.record.name ?? t('sales.documents.items.untitled', 'Untitled'),\n },\n {\n id: 'quantity',\n header: t('sales.documents.items.table.quantity', 'Qty'),\n cell: ({ row }) => row.original.record.quantityUnit\n ? `${row.original.record.quantity} ${row.original.record.quantityUnit}`\n : row.original.record.quantity,\n },\n {\n id: 'unitPrice',\n header: t('sales.documents.items.table.unit', 'Unit price'),\n cell: ({ row }) => formatMoney(row.original.record.unitPriceGross, row.original.record.currencyCode ?? currencyCode ?? undefined),\n },\n {\n id: 'total',\n header: t('sales.documents.items.table.total', 'Total'),\n cell: ({ row }) => formatMoney(row.original.record.totalGross, row.original.record.currencyCode ?? currencyCode ?? undefined),\n },\n ], [currencyCode, t])\n\n const openCreate = React.useCallback(() => {\n setEditing(null)\n setDialogOpen(true)\n }, [])\n\n const rowActions = React.useCallback((line: SalesOrderLineDraft) => (\n <RowActions\n items={[\n {\n id: 'edit',\n label: t('ui.actions.edit', 'Edit'),\n onSelect: () => {\n setEditing(line)\n setDialogOpen(true)\n },\n },\n {\n id: 'delete',\n label: t('ui.actions.delete', 'Delete'),\n destructive: true,\n onSelect: () => onChange(lines.filter((candidate) => candidate.id !== line.id)),\n },\n ]}\n />\n ), [lines, onChange, t])\n\n return (\n <div className=\"space-y-3\">\n <div className=\"flex items-center justify-between gap-4\">\n <div>\n <h3 className=\"text-sm font-medium\">{t('sales.orders.form.lines', 'Line Items')}</h3>\n <p className=\"text-sm text-muted-foreground\">\n {t('sales.orders.linesRequired', 'Add at least one line item before creating the order.')}\n </p>\n </div>\n <Button type=\"button\" variant=\"outline\" onClick={openCreate}>\n <Plus className=\"h-4 w-4\" aria-hidden=\"true\" />\n {t('sales.documents.items.add', 'Add item')}\n </Button>\n </div>\n {error ? <p className=\"text-sm text-destructive\" role=\"alert\">{error}</p> : null}\n <DataTable\n columns={columns}\n data={lines}\n embedded\n disableRowClick\n rowActions={rowActions}\n emptyState={(\n <EmptyState\n size=\"sm\"\n title={t('sales.documents.items.empty', 'No items yet.')}\n description={t('sales.orders.linesRequired', 'Add at least one line item before creating the order.')}\n actions={<Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={openCreate}>{t('sales.documents.items.add', 'Add item')}</Button>}\n />\n )}\n />\n <LineItemDialog\n open={dialogOpen}\n onOpenChange={(open) => {\n setDialogOpen(open)\n if (!open) setEditing(null)\n }}\n kind=\"order\"\n currencyCode={currencyCode}\n organizationId={organizationId}\n tenantId={tenantId}\n initialLine={editing?.record ?? null}\n onDraftSaved={(payload, lineId) => {\n const draft = createSalesOrderLineDraft(payload, lineId ?? undefined)\n onChange(lineId\n ? lines.map((line) => line.id === lineId ? draft : line)\n : [...lines, draft])\n }}\n />\n </div>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -11,8 +11,34 @@ function formatMoney(value, currency) {
|
|
|
11
11
|
if (!currency) return value.toFixed(2);
|
|
12
12
|
return new Intl.NumberFormat(void 0, { style: "currency", currency }).format(value);
|
|
13
13
|
}
|
|
14
|
+
const DISCOUNT_MATCH_TOLERANCE = 0.01;
|
|
15
|
+
const DISCOUNT_MATCH_TOLERANCE_PER_UNIT = 1e-4;
|
|
16
|
+
function percentAccountsForAmount(line, percent, amount) {
|
|
17
|
+
if (percent <= 0) return false;
|
|
18
|
+
const quantity = normalizeNumber(line.quantity, 0);
|
|
19
|
+
const netBeforeDiscount = normalizeNumber(line.unitPriceNet, 0) * quantity;
|
|
20
|
+
if (netBeforeDiscount <= 0) return false;
|
|
21
|
+
const tolerance = Math.max(
|
|
22
|
+
DISCOUNT_MATCH_TOLERANCE,
|
|
23
|
+
quantity * DISCOUNT_MATCH_TOLERANCE_PER_UNIT
|
|
24
|
+
);
|
|
25
|
+
return Math.abs(percent / 100 * netBeforeDiscount - amount) <= tolerance;
|
|
26
|
+
}
|
|
27
|
+
function resolveLineDiscountDisplay(line) {
|
|
28
|
+
const amount = normalizeNumber(line.discountAmount, 0);
|
|
29
|
+
const percent = normalizeNumber(line.discountPercent, 0);
|
|
30
|
+
if (amount > 0) {
|
|
31
|
+
return {
|
|
32
|
+
amount,
|
|
33
|
+
percent: percentAccountsForAmount(line, percent, amount) ? percent : null
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
if (percent > 0) return { amount: null, percent };
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
14
39
|
export {
|
|
15
40
|
formatMoney,
|
|
16
|
-
normalizeNumber
|
|
41
|
+
normalizeNumber,
|
|
42
|
+
resolveLineDiscountDisplay
|
|
17
43
|
};
|
|
18
44
|
//# sourceMappingURL=lineItemUtils.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/sales/components/documents/lineItemUtils.ts"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nexport function normalizeNumber(value: unknown, fallback = 0): number {\n if (typeof value === 'number' && Number.isFinite(value)) return value\n if (typeof value === 'string' && value.trim().length) {\n const parsed = Number(value)\n if (!Number.isNaN(parsed)) return parsed\n }\n return fallback\n}\n\nexport function formatMoney(value: number, currency: string | null | undefined): string {\n if (!currency) return value.toFixed(2)\n return new Intl.NumberFormat(undefined, { style: 'currency', currency }).format(value)\n}\n"],
|
|
5
|
-
"mappings": ";AAEO,SAAS,gBAAgB,OAAgB,WAAW,GAAW;AACpE,MAAI,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK,EAAG,QAAO;AAChE,MAAI,OAAO,UAAU,YAAY,MAAM,KAAK,EAAE,QAAQ;AACpD,UAAM,SAAS,OAAO,KAAK;AAC3B,QAAI,CAAC,OAAO,MAAM,MAAM,EAAG,QAAO;AAAA,EACpC;AACA,SAAO;AACT;AAEO,SAAS,YAAY,OAAe,UAA6C;AACtF,MAAI,CAAC,SAAU,QAAO,MAAM,QAAQ,CAAC;AACrC,SAAO,IAAI,KAAK,aAAa,QAAW,EAAE,OAAO,YAAY,SAAS,CAAC,EAAE,OAAO,KAAK;AACvF;",
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nexport function normalizeNumber(value: unknown, fallback = 0): number {\n if (typeof value === 'number' && Number.isFinite(value)) return value\n if (typeof value === 'string' && value.trim().length) {\n const parsed = Number(value)\n if (!Number.isNaN(parsed)) return parsed\n }\n return fallback\n}\n\nexport function formatMoney(value: number, currency: string | null | undefined): string {\n if (!currency) return value.toFixed(2)\n return new Intl.NumberFormat(undefined, { style: 'currency', currency }).format(value)\n}\n\nexport type LineDiscountDisplay = {\n amount: number | null\n percent: number | null\n}\n\ntype LineDiscountSource = {\n discountAmount?: unknown\n discountPercent?: unknown\n unitPriceNet?: unknown\n quantity?: unknown\n}\n\nconst DISCOUNT_MATCH_TOLERANCE = 0.01\nconst DISCOUNT_MATCH_TOLERANCE_PER_UNIT = 0.0001\n\nfunction percentAccountsForAmount(\n line: LineDiscountSource,\n percent: number,\n amount: number,\n): boolean {\n if (percent <= 0) return false\n const quantity = normalizeNumber(line.quantity, 0)\n const netBeforeDiscount = normalizeNumber(line.unitPriceNet, 0) * quantity\n if (netBeforeDiscount <= 0) return false\n const tolerance = Math.max(\n DISCOUNT_MATCH_TOLERANCE,\n quantity * DISCOUNT_MATCH_TOLERANCE_PER_UNIT,\n )\n return Math.abs((percent / 100) * netBeforeDiscount - amount) <= tolerance\n}\n\nexport function resolveLineDiscountDisplay(\n line: LineDiscountSource,\n): LineDiscountDisplay | null {\n const amount = normalizeNumber(line.discountAmount, 0)\n const percent = normalizeNumber(line.discountPercent, 0)\n if (amount > 0) {\n return {\n amount,\n percent: percentAccountsForAmount(line, percent, amount) ? percent : null,\n }\n }\n if (percent > 0) return { amount: null, percent }\n return null\n}\n"],
|
|
5
|
+
"mappings": ";AAEO,SAAS,gBAAgB,OAAgB,WAAW,GAAW;AACpE,MAAI,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK,EAAG,QAAO;AAChE,MAAI,OAAO,UAAU,YAAY,MAAM,KAAK,EAAE,QAAQ;AACpD,UAAM,SAAS,OAAO,KAAK;AAC3B,QAAI,CAAC,OAAO,MAAM,MAAM,EAAG,QAAO;AAAA,EACpC;AACA,SAAO;AACT;AAEO,SAAS,YAAY,OAAe,UAA6C;AACtF,MAAI,CAAC,SAAU,QAAO,MAAM,QAAQ,CAAC;AACrC,SAAO,IAAI,KAAK,aAAa,QAAW,EAAE,OAAO,YAAY,SAAS,CAAC,EAAE,OAAO,KAAK;AACvF;AAcA,MAAM,2BAA2B;AACjC,MAAM,oCAAoC;AAE1C,SAAS,yBACP,MACA,SACA,QACS;AACT,MAAI,WAAW,EAAG,QAAO;AACzB,QAAM,WAAW,gBAAgB,KAAK,UAAU,CAAC;AACjD,QAAM,oBAAoB,gBAAgB,KAAK,cAAc,CAAC,IAAI;AAClE,MAAI,qBAAqB,EAAG,QAAO;AACnC,QAAM,YAAY,KAAK;AAAA,IACrB;AAAA,IACA,WAAW;AAAA,EACb;AACA,SAAO,KAAK,IAAK,UAAU,MAAO,oBAAoB,MAAM,KAAK;AACnE;AAEO,SAAS,2BACd,MAC4B;AAC5B,QAAM,SAAS,gBAAgB,KAAK,gBAAgB,CAAC;AACrD,QAAM,UAAU,gBAAgB,KAAK,iBAAiB,CAAC;AACvD,MAAI,SAAS,GAAG;AACd,WAAO;AAAA,MACL;AAAA,MACA,SAAS,yBAAyB,MAAM,SAAS,MAAM,IAAI,UAAU;AAAA,IACvE;AAAA,EACF;AACA,MAAI,UAAU,EAAG,QAAO,EAAE,QAAQ,MAAM,QAAQ;AAChD,SAAO;AACT;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/core",
|
|
3
|
-
"version": "0.6.8-develop.
|
|
3
|
+
"version": "0.6.8-develop.6913.1.721e521f55",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -254,16 +254,16 @@
|
|
|
254
254
|
"zod": "^4.4.3"
|
|
255
255
|
},
|
|
256
256
|
"peerDependencies": {
|
|
257
|
-
"@open-mercato/ai-assistant": "0.6.8-develop.
|
|
258
|
-
"@open-mercato/shared": "0.6.8-develop.
|
|
259
|
-
"@open-mercato/ui": "0.6.8-develop.
|
|
257
|
+
"@open-mercato/ai-assistant": "0.6.8-develop.6913.1.721e521f55",
|
|
258
|
+
"@open-mercato/shared": "0.6.8-develop.6913.1.721e521f55",
|
|
259
|
+
"@open-mercato/ui": "0.6.8-develop.6913.1.721e521f55",
|
|
260
260
|
"react": "^19.0.0",
|
|
261
261
|
"react-dom": "^19.0.0"
|
|
262
262
|
},
|
|
263
263
|
"devDependencies": {
|
|
264
|
-
"@open-mercato/ai-assistant": "0.6.8-develop.
|
|
265
|
-
"@open-mercato/shared": "0.6.8-develop.
|
|
266
|
-
"@open-mercato/ui": "0.6.8-develop.
|
|
264
|
+
"@open-mercato/ai-assistant": "0.6.8-develop.6913.1.721e521f55",
|
|
265
|
+
"@open-mercato/shared": "0.6.8-develop.6913.1.721e521f55",
|
|
266
|
+
"@open-mercato/ui": "0.6.8-develop.6913.1.721e521f55",
|
|
267
267
|
"@testing-library/dom": "^10.4.1",
|
|
268
268
|
"@testing-library/jest-dom": "^7.0.0",
|
|
269
269
|
"@testing-library/react": "^16.3.1",
|
|
@@ -23,7 +23,11 @@ import { emitSalesDocumentTotalsRefresh } from "@open-mercato/core/modules/sales
|
|
|
23
23
|
import { LineItemDialog } from "./LineItemDialog";
|
|
24
24
|
import { handleSectionMutationError } from "./optimisticLock";
|
|
25
25
|
import type { SalesLineRecord } from "./lineItemTypes";
|
|
26
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
formatMoney,
|
|
28
|
+
normalizeNumber,
|
|
29
|
+
resolveLineDiscountDisplay,
|
|
30
|
+
} from "./lineItemUtils";
|
|
27
31
|
import type { SectionAction } from "@open-mercato/ui/backend/detail";
|
|
28
32
|
import { extractCustomFieldValues } from "./customFieldHelpers";
|
|
29
33
|
import { canonicalizeUnitCode } from "@open-mercato/shared/lib/units/unitCodes";
|
|
@@ -326,6 +330,14 @@ export function SalesDocumentItemsSection({
|
|
|
326
330
|
: null,
|
|
327
331
|
unitPriceNet,
|
|
328
332
|
unitPriceGross,
|
|
333
|
+
discountAmount: normalizeNumber(
|
|
334
|
+
item.discount_amount ?? item.discountAmount,
|
|
335
|
+
0,
|
|
336
|
+
),
|
|
337
|
+
discountPercent: normalizeNumber(
|
|
338
|
+
item.discount_percent ?? item.discountPercent,
|
|
339
|
+
0,
|
|
340
|
+
),
|
|
329
341
|
taxRate,
|
|
330
342
|
totalNet,
|
|
331
343
|
totalGross,
|
|
@@ -565,6 +577,11 @@ export function SalesDocumentItemsSection({
|
|
|
565
577
|
[lineStatusMap, t],
|
|
566
578
|
);
|
|
567
579
|
|
|
580
|
+
const showDiscountColumn = React.useMemo(
|
|
581
|
+
() => items.some((item) => resolveLineDiscountDisplay(item) !== null),
|
|
582
|
+
[items],
|
|
583
|
+
);
|
|
584
|
+
|
|
568
585
|
const renderImage = (record: SalesLineRecord) => {
|
|
569
586
|
const meta =
|
|
570
587
|
(record.metadata as Record<string, unknown> | null | undefined) ?? {};
|
|
@@ -651,6 +668,11 @@ export function SalesDocumentItemsSection({
|
|
|
651
668
|
<th className="px-3 py-2 font-medium">
|
|
652
669
|
{t("sales.documents.items.table.unit", "Unit price")}
|
|
653
670
|
</th>
|
|
671
|
+
{showDiscountColumn ? (
|
|
672
|
+
<th className="px-3 py-2 font-medium whitespace-nowrap">
|
|
673
|
+
{t("sales.documents.items.table.discount", "Discount")}
|
|
674
|
+
</th>
|
|
675
|
+
) : null}
|
|
654
676
|
<th className="px-3 py-2 font-medium">
|
|
655
677
|
{t("sales.documents.items.table.total", "Total")}
|
|
656
678
|
</th>
|
|
@@ -704,6 +726,7 @@ export function SalesDocumentItemsSection({
|
|
|
704
726
|
const unitPriceReference = resolveUnitPriceReference(
|
|
705
727
|
item.uomSnapshot,
|
|
706
728
|
);
|
|
729
|
+
const discount = resolveLineDiscountDisplay(item);
|
|
707
730
|
|
|
708
731
|
return (
|
|
709
732
|
<tr
|
|
@@ -805,6 +828,45 @@ export function SalesDocumentItemsSection({
|
|
|
805
828
|
) : null}
|
|
806
829
|
</div>
|
|
807
830
|
</td>
|
|
831
|
+
{showDiscountColumn ? (
|
|
832
|
+
<td className="px-3 py-3">
|
|
833
|
+
{discount ? (
|
|
834
|
+
<div className="flex flex-col gap-0.5">
|
|
835
|
+
{discount.amount !== null ? (
|
|
836
|
+
<span className="font-mono text-sm">
|
|
837
|
+
{t(
|
|
838
|
+
"sales.documents.items.table.discountAmount",
|
|
839
|
+
"−{{value}}",
|
|
840
|
+
{
|
|
841
|
+
value: formatMoney(
|
|
842
|
+
discount.amount,
|
|
843
|
+
item.currencyCode ??
|
|
844
|
+
currencyCode ??
|
|
845
|
+
undefined,
|
|
846
|
+
),
|
|
847
|
+
},
|
|
848
|
+
)}
|
|
849
|
+
</span>
|
|
850
|
+
) : null}
|
|
851
|
+
{discount.percent !== null ? (
|
|
852
|
+
<span
|
|
853
|
+
className={
|
|
854
|
+
discount.amount !== null
|
|
855
|
+
? "font-mono text-xs text-muted-foreground"
|
|
856
|
+
: "font-mono text-sm"
|
|
857
|
+
}
|
|
858
|
+
>
|
|
859
|
+
{t(
|
|
860
|
+
"sales.documents.items.table.discountPercent",
|
|
861
|
+
"{{value}}%",
|
|
862
|
+
{ value: discount.percent },
|
|
863
|
+
)}
|
|
864
|
+
</span>
|
|
865
|
+
) : null}
|
|
866
|
+
</div>
|
|
867
|
+
) : null}
|
|
868
|
+
</td>
|
|
869
|
+
) : null}
|
|
808
870
|
<td className="px-3 py-3 font-semibold">
|
|
809
871
|
<div className="flex flex-col gap-0.5">
|
|
810
872
|
<span>
|
|
@@ -65,6 +65,8 @@ export function createSalesOrderLineDraft(
|
|
|
65
65
|
currencyCode: typeof payload.currencyCode === 'string' ? payload.currencyCode : null,
|
|
66
66
|
unitPriceNet,
|
|
67
67
|
unitPriceGross,
|
|
68
|
+
discountAmount: normalizeNumber(payload.discountAmount, 0) * quantity,
|
|
69
|
+
discountPercent: normalizeNumber(payload.discountPercent, 0),
|
|
68
70
|
taxRate,
|
|
69
71
|
totalNet,
|
|
70
72
|
totalGross,
|
|
@@ -13,6 +13,10 @@ export type SalesLineRecord = {
|
|
|
13
13
|
currencyCode: string | null
|
|
14
14
|
unitPriceNet: number
|
|
15
15
|
unitPriceGross: number
|
|
16
|
+
/** Resolved discount for the whole line, not per unit. */
|
|
17
|
+
discountAmount?: number
|
|
18
|
+
/** Percentage the discount was requested as, if it was requested that way. */
|
|
19
|
+
discountPercent?: number
|
|
16
20
|
taxRate: number
|
|
17
21
|
totalNet: number
|
|
18
22
|
totalGross: number
|
|
@@ -13,3 +13,49 @@ export function formatMoney(value: number, currency: string | null | undefined):
|
|
|
13
13
|
if (!currency) return value.toFixed(2)
|
|
14
14
|
return new Intl.NumberFormat(undefined, { style: 'currency', currency }).format(value)
|
|
15
15
|
}
|
|
16
|
+
|
|
17
|
+
export type LineDiscountDisplay = {
|
|
18
|
+
amount: number | null
|
|
19
|
+
percent: number | null
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
type LineDiscountSource = {
|
|
23
|
+
discountAmount?: unknown
|
|
24
|
+
discountPercent?: unknown
|
|
25
|
+
unitPriceNet?: unknown
|
|
26
|
+
quantity?: unknown
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const DISCOUNT_MATCH_TOLERANCE = 0.01
|
|
30
|
+
const DISCOUNT_MATCH_TOLERANCE_PER_UNIT = 0.0001
|
|
31
|
+
|
|
32
|
+
function percentAccountsForAmount(
|
|
33
|
+
line: LineDiscountSource,
|
|
34
|
+
percent: number,
|
|
35
|
+
amount: number,
|
|
36
|
+
): boolean {
|
|
37
|
+
if (percent <= 0) return false
|
|
38
|
+
const quantity = normalizeNumber(line.quantity, 0)
|
|
39
|
+
const netBeforeDiscount = normalizeNumber(line.unitPriceNet, 0) * quantity
|
|
40
|
+
if (netBeforeDiscount <= 0) return false
|
|
41
|
+
const tolerance = Math.max(
|
|
42
|
+
DISCOUNT_MATCH_TOLERANCE,
|
|
43
|
+
quantity * DISCOUNT_MATCH_TOLERANCE_PER_UNIT,
|
|
44
|
+
)
|
|
45
|
+
return Math.abs((percent / 100) * netBeforeDiscount - amount) <= tolerance
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function resolveLineDiscountDisplay(
|
|
49
|
+
line: LineDiscountSource,
|
|
50
|
+
): LineDiscountDisplay | null {
|
|
51
|
+
const amount = normalizeNumber(line.discountAmount, 0)
|
|
52
|
+
const percent = normalizeNumber(line.discountPercent, 0)
|
|
53
|
+
if (amount > 0) {
|
|
54
|
+
return {
|
|
55
|
+
amount,
|
|
56
|
+
percent: percentAccountsForAmount(line, percent, amount) ? percent : null,
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (percent > 0) return { amount: null, percent }
|
|
60
|
+
return null
|
|
61
|
+
}
|
|
@@ -1027,6 +1027,9 @@
|
|
|
1027
1027
|
"sales.documents.items.statusPlaceholder": "Status auswählen",
|
|
1028
1028
|
"sales.documents.items.subtitle": "Produkte und Preise für dieses Dokument hinzufügen.",
|
|
1029
1029
|
"sales.documents.items.table.actions": "Aktionen",
|
|
1030
|
+
"sales.documents.items.table.discount": "Rabatt",
|
|
1031
|
+
"sales.documents.items.table.discountAmount": "−{{value}}",
|
|
1032
|
+
"sales.documents.items.table.discountPercent": "{{value}} %",
|
|
1030
1033
|
"sales.documents.items.table.gross": "brutto",
|
|
1031
1034
|
"sales.documents.items.table.net": "netto",
|
|
1032
1035
|
"sales.documents.items.table.product": "Produkt",
|
|
@@ -1027,6 +1027,9 @@
|
|
|
1027
1027
|
"sales.documents.items.statusPlaceholder": "Select status",
|
|
1028
1028
|
"sales.documents.items.subtitle": "Add products and prices to this document.",
|
|
1029
1029
|
"sales.documents.items.table.actions": "Actions",
|
|
1030
|
+
"sales.documents.items.table.discount": "Discount",
|
|
1031
|
+
"sales.documents.items.table.discountAmount": "−{{value}}",
|
|
1032
|
+
"sales.documents.items.table.discountPercent": "{{value}}%",
|
|
1030
1033
|
"sales.documents.items.table.gross": "gross",
|
|
1031
1034
|
"sales.documents.items.table.net": "net",
|
|
1032
1035
|
"sales.documents.items.table.product": "Product",
|
|
@@ -1027,6 +1027,9 @@
|
|
|
1027
1027
|
"sales.documents.items.statusPlaceholder": "Seleccionar estado",
|
|
1028
1028
|
"sales.documents.items.subtitle": "Agrega productos y precios a este documento.",
|
|
1029
1029
|
"sales.documents.items.table.actions": "Acciones",
|
|
1030
|
+
"sales.documents.items.table.discount": "Descuento",
|
|
1031
|
+
"sales.documents.items.table.discountAmount": "−{{value}}",
|
|
1032
|
+
"sales.documents.items.table.discountPercent": "{{value}} %",
|
|
1030
1033
|
"sales.documents.items.table.gross": "bruto",
|
|
1031
1034
|
"sales.documents.items.table.net": "neto",
|
|
1032
1035
|
"sales.documents.items.table.product": "Producto",
|
|
@@ -1027,6 +1027,9 @@
|
|
|
1027
1027
|
"sales.documents.items.statusPlaceholder": "상태 선택",
|
|
1028
1028
|
"sales.documents.items.subtitle": "이 문서에 상품과 가격을 추가하세요.",
|
|
1029
1029
|
"sales.documents.items.table.actions": "작업",
|
|
1030
|
+
"sales.documents.items.table.discount": "할인",
|
|
1031
|
+
"sales.documents.items.table.discountAmount": "−{{value}}",
|
|
1032
|
+
"sales.documents.items.table.discountPercent": "{{value}}%",
|
|
1030
1033
|
"sales.documents.items.table.gross": "세금 포함",
|
|
1031
1034
|
"sales.documents.items.table.net": "세금 제외",
|
|
1032
1035
|
"sales.documents.items.table.product": "상품",
|
|
@@ -1027,6 +1027,9 @@
|
|
|
1027
1027
|
"sales.documents.items.statusPlaceholder": "Wybierz status",
|
|
1028
1028
|
"sales.documents.items.subtitle": "Dodaj produkty i skonfiguruj ceny dla tego dokumentu.",
|
|
1029
1029
|
"sales.documents.items.table.actions": "Akcje",
|
|
1030
|
+
"sales.documents.items.table.discount": "Rabat",
|
|
1031
|
+
"sales.documents.items.table.discountAmount": "−{{value}}",
|
|
1032
|
+
"sales.documents.items.table.discountPercent": "{{value}}%",
|
|
1030
1033
|
"sales.documents.items.table.gross": "brutto",
|
|
1031
1034
|
"sales.documents.items.table.net": "netto",
|
|
1032
1035
|
"sales.documents.items.table.product": "Produkt",
|