@open-mercato/core 0.10.1-develop.7722.1.901c72b9e9 → 0.10.1-develop.7724.1.95e4e7d38e
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/dist/generated/entities/sales_order/index.js +2 -0
- package/dist/generated/entities/sales_order/index.js.map +2 -2
- package/dist/generated/entities/sales_order_line/index.js +2 -0
- package/dist/generated/entities/sales_order_line/index.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +2 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/modules/sales/api/documents/factory.js +3 -0
- package/dist/modules/sales/api/documents/factory.js.map +2 -2
- package/dist/modules/sales/api/order-lines/route.js +2 -1
- package/dist/modules/sales/api/order-lines/route.js.map +2 -2
- package/dist/modules/sales/backend/sales/documents/[id]/page.js +83 -6
- package/dist/modules/sales/backend/sales/documents/[id]/page.js.map +2 -2
- package/dist/modules/sales/commands/documents.js +141 -18
- package/dist/modules/sales/commands/documents.js.map +2 -2
- package/dist/modules/sales/commands/returns.js +24 -7
- package/dist/modules/sales/commands/returns.js.map +2 -2
- package/dist/modules/sales/components/documents/AdjustmentsSection.js +17 -11
- package/dist/modules/sales/components/documents/AdjustmentsSection.js.map +2 -2
- package/dist/modules/sales/components/documents/DocumentTotals.js +21 -3
- package/dist/modules/sales/components/documents/DocumentTotals.js.map +2 -2
- package/dist/modules/sales/components/documents/ItemsSection.js +23 -12
- package/dist/modules/sales/components/documents/ItemsSection.js.map +2 -2
- package/dist/modules/sales/data/entities.js +10 -0
- package/dist/modules/sales/data/entities.js.map +2 -2
- package/dist/modules/sales/data/validators.js +9 -1
- package/dist/modules/sales/data/validators.js.map +2 -2
- package/dist/modules/sales/lib/calculations.js +85 -6
- package/dist/modules/sales/lib/calculations.js.map +2 -2
- package/dist/modules/sales/lib/externalAmounts.js +143 -0
- package/dist/modules/sales/lib/externalAmounts.js.map +7 -0
- package/dist/modules/sales/lib/lineSnapshots.js +6 -1
- package/dist/modules/sales/lib/lineSnapshots.js.map +2 -2
- package/dist/modules/sales/lib/makeSalesLineRoute.js +8 -2
- package/dist/modules/sales/lib/makeSalesLineRoute.js.map +2 -2
- package/dist/modules/sales/lib/providers/totals.js +3 -2
- package/dist/modules/sales/lib/providers/totals.js.map +2 -2
- package/dist/modules/sales/migrations/Migration20260910120000_sales_external_amounts_mode.js +19 -0
- package/dist/modules/sales/migrations/Migration20260910120000_sales_external_amounts_mode.js.map +7 -0
- package/generated/entities/sales_order/index.ts +1 -0
- package/generated/entities/sales_order_line/index.ts +1 -0
- package/generated/entity-fields-registry.ts +2 -0
- package/package.json +7 -7
- package/src/modules/sales/api/documents/factory.ts +3 -0
- package/src/modules/sales/api/order-lines/route.ts +2 -1
- package/src/modules/sales/backend/sales/documents/[id]/page.tsx +96 -0
- package/src/modules/sales/commands/documents.ts +219 -44
- package/src/modules/sales/commands/returns.ts +27 -3
- package/src/modules/sales/components/documents/AdjustmentsSection.tsx +34 -25
- package/src/modules/sales/components/documents/DocumentTotals.tsx +26 -2
- package/src/modules/sales/components/documents/ItemsSection.tsx +27 -12
- package/src/modules/sales/data/entities.ts +14 -0
- package/src/modules/sales/data/validators.ts +10 -0
- package/src/modules/sales/i18n/de.json +12 -0
- package/src/modules/sales/i18n/en.json +12 -0
- package/src/modules/sales/i18n/es.json +12 -0
- package/src/modules/sales/i18n/ko.json +12 -0
- package/src/modules/sales/i18n/pl.json +12 -0
- package/src/modules/sales/lib/calculations.ts +99 -4
- package/src/modules/sales/lib/externalAmounts.ts +174 -0
- package/src/modules/sales/lib/lineSnapshots.ts +6 -1
- package/src/modules/sales/lib/makeSalesLineRoute.ts +8 -0
- package/src/modules/sales/lib/providers/totals.ts +5 -2
- package/src/modules/sales/lib/types.ts +19 -2
- package/src/modules/sales/migrations/.snapshot-open-mercato.json +33 -1
- package/src/modules/sales/migrations/Migration20260910120000_sales_external_amounts_mode.ts +25 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/sales/lib/providers/totals.ts"],
|
|
4
|
-
"sourcesContent": ["import { registerSalesTotalsCalculator, rebuildDocumentResult } from '../calculations'\nimport type { SalesAdjustmentDraft, SalesDocumentCalculationResult } from '../types'\nimport {\n getPaymentProvider,\n getShippingProvider,\n normalizeProviderSettings,\n} from './registry'\nimport type {\n PaymentMethodContext,\n ProviderAdjustment,\n ProviderAdjustmentResult,\n ShippingMethodContext,\n ShippingMetrics,\n} from './types'\n\nconst SHIPPING_PREFIX = 'shipping-provider:'\nconst PAYMENT_PREFIX = 'payment-provider:'\n\nlet totalsRegistered = false\n\nfunction toNumber(value: unknown, fallback = 0): number {\n if (typeof value === 'number' && Number.isFinite(value)) return value\n if (typeof value === 'string' && value.trim() !== '' && !Number.isNaN(Number(value))) {\n return Number(value)\n }\n return fallback\n}\n\nfunction isProviderAdjustment(adjustment: SalesAdjustmentDraft): boolean {\n const key = adjustment.calculatorKey ?? ''\n return typeof key === 'string' && (key.startsWith(SHIPPING_PREFIX) || key.startsWith(PAYMENT_PREFIX))\n}\n\nfunction isManualOverride(adjustment: SalesAdjustmentDraft): boolean {\n const metadata = adjustment.metadata\n if (!metadata || typeof metadata !== 'object') return false\n const manual =\n (metadata as Record<string, unknown>).manualOverride ??\n (metadata as Record<string, unknown>).manual_override\n return manual === true\n}\n\nfunction withoutPrefix(adjustments: SalesAdjustmentDraft[], prefix: string) {\n return adjustments.filter((adj) => {\n if (!(adj.calculatorKey ?? '').startsWith(prefix)) return true\n return isManualOverride(adj)\n })\n}\n\nfunction extractProviderSettings(\n method: ShippingMethodContext | PaymentMethodContext | null | undefined\n): Record<string, unknown> | null {\n if (!method) return null\n if (method.providerSettings && typeof method.providerSettings === 'object') {\n return method.providerSettings as Record<string, unknown>\n }\n if (method.metadata && typeof method.metadata === 'object') {\n const meta = method.metadata as Record<string, unknown>\n if (meta.providerSettings && typeof meta.providerSettings === 'object') {\n return meta.providerSettings as Record<string, unknown>\n }\n }\n return null\n}\n\nfunction computeMetrics(lines: SalesDocumentCalculationResult['lines']): ShippingMetrics {\n let itemCount = 0\n let totalWeight = 0\n let totalVolume = 0\n let subtotalNet = 0\n let subtotalGross = 0\n\n for (const entry of lines) {\n const { line } = entry\n const qty = toNumber(line.quantity, 0)\n itemCount += qty\n subtotalNet += toNumber(entry.netAmount, 0)\n subtotalGross += toNumber(entry.grossAmount, 0)\n\n const meta = (line.metadata ?? {}) as Record<string, unknown>\n const configuration = (line.configuration ?? {}) as Record<string, unknown>\n const weight =\n toNumber((meta.weight as number | string | undefined) ?? (configuration.weight as number | string | undefined), 0) *\n qty\n const volume =\n toNumber(\n (meta.volume as number | string | undefined) ??\n (configuration.volume as number | string | undefined),\n 0\n ) * qty\n\n totalWeight += weight\n totalVolume += volume\n }\n\n return {\n itemCount,\n totalWeight,\n totalVolume,\n subtotalNet,\n subtotalGross,\n }\n}\n\nfunction normalizeAdjustments(params: {\n providerKey: string\n calculatorKey: string\n adjustments: ProviderAdjustment[]\n currencyCode: string\n defaultKind: SalesAdjustmentDraft['kind']\n}) {\n return params.adjustments.map<SalesAdjustmentDraft>((adj, index) => ({\n scope: 'order',\n kind: adj.kind ?? params.defaultKind,\n code: adj.code ?? params.providerKey,\n label: adj.label ?? params.providerKey,\n calculatorKey: params.calculatorKey,\n promotionId: null,\n rate: null,\n amountNet: toNumber(adj.amountNet, 0),\n amountGross:\n adj.amountGross === undefined || adj.amountGross === null\n ? toNumber(adj.amountNet, 0)\n : toNumber(adj.amountGross, 0),\n currencyCode: (adj.currencyCode || params.currencyCode || '').toUpperCase() || params.currencyCode,\n metadata: adj.metadata ?? null,\n position: 10_000 + index,\n }))\n}\n\nfunction mergeMetadata(\n base: Record<string, unknown> | undefined,\n key: string,\n value: unknown\n): Record<string, unknown> {\n const next = { ...(base ?? {}) }\n next[key] = value\n return next\n}\n\nfunction applyProviderResult(\n current: SalesDocumentCalculationResult,\n adjustments: SalesAdjustmentDraft[],\n providerKey: string,\n calculatorKey: string,\n result: ProviderAdjustmentResult | null | undefined,\n defaultKind: SalesAdjustmentDraft['kind']\n): SalesDocumentCalculationResult {\n if (!result?.adjustments?.length) {\n return rebuildDocumentResult({\n documentKind: current.kind,\n currencyCode: current.currencyCode,\n lines: current.lines,\n adjustments,\n metadata: current.metadata,\n })\n }\n const normalized = normalizeAdjustments({\n providerKey,\n calculatorKey,\n adjustments: result.adjustments,\n currencyCode: current.currencyCode,\n defaultKind,\n })\n const nextAdjustments = [...adjustments, ...normalized]\n const next = rebuildDocumentResult({\n documentKind: current.kind,\n currencyCode: current.currencyCode,\n lines: current.lines,\n adjustments: nextAdjustments,\n metadata: current.metadata,\n })\n if (result.metadata) {\n next.metadata = mergeMetadata(next.metadata, calculatorKey, result.metadata)\n }\n return next\n}\n\nexport function ensureProviderTotalsCalculator() {\n if (totalsRegistered) return\n totalsRegistered = true\n\n registerSalesTotalsCalculator(async ({ documentKind, lines, context, current, eventBus }) => {\n const metadata = (context.metadata ?? {}) as Record<string, unknown>\n const shippingMethod = (metadata.shippingMethod ?? null) as ShippingMethodContext | null\n const paymentMethod = (metadata.paymentMethod ?? null) as PaymentMethodContext | null\n\n let runningAdjustments = (current.adjustments ?? []).filter(\n (adj) => !isProviderAdjustment(adj) || isManualOverride(adj)\n )\n let working = rebuildDocumentResult({\n documentKind,\n currencyCode: current.currencyCode,\n lines,\n adjustments: runningAdjustments,\n metadata: current.metadata,\n })\n\n if (shippingMethod?.providerKey) {\n const provider = getShippingProvider(shippingMethod.providerKey)\n if (provider?.calculate) {\n const calculatorKey = `${SHIPPING_PREFIX}${provider.key}`\n const hasManualOverride = runningAdjustments.some(\n (adj) => (adj.calculatorKey ?? '').startsWith(calculatorKey) && isManualOverride(adj)\n )\n if (hasManualOverride) {\n // Respect user overrides: keep existing provider-tagged adjustments and skip regeneration.\n working = rebuildDocumentResult({\n documentKind,\n currencyCode: working.currencyCode,\n lines: working.lines,\n adjustments: runningAdjustments,\n metadata: working.metadata,\n })\n } else {\n const rawSettings = extractProviderSettings(shippingMethod)\n const settings =\n normalizeProviderSettings('shipping', provider.key, rawSettings) ?? rawSettings ?? {}\n const metrics = computeMetrics(working.lines)\n let result = await provider.calculate({\n method: shippingMethod,\n settings,\n document: working,\n lines: working.lines,\n context,\n metrics,\n })\n if (eventBus) {\n await eventBus.emitEvent('sales.shipping.adjustments.apply.before', {\n documentKind,\n providerKey: provider.key,\n calculatorKey,\n method: shippingMethod,\n settings,\n document: working,\n result,\n setResult(next: ProviderAdjustmentResult | null | undefined) {\n result = next ?? null\n },\n })\n }\n working = applyProviderResult(working, runningAdjustments, provider.key, calculatorKey, result, 'shipping')\n runningAdjustments = working.adjustments\n if (eventBus) {\n let nextDocument = working\n await eventBus.emitEvent('sales.shipping.adjustments.apply.after', {\n documentKind,\n providerKey: provider.key,\n calculatorKey,\n method: shippingMethod,\n settings,\n document: nextDocument,\n adjustments: nextDocument.adjustments.filter((adj) =>\n (adj.calculatorKey ?? '').startsWith(calculatorKey)\n ),\n setDocument(next: SalesDocumentCalculationResult | null | undefined) {\n if (next) {\n nextDocument = next\n }\n },\n setAdjustments(next: SalesAdjustmentDraft[] | null | undefined) {\n if (!next) return\n const preserved = nextDocument.adjustments.filter(\n (adj) => !(adj.calculatorKey ?? '').startsWith(calculatorKey)\n )\n nextDocument = rebuildDocumentResult({\n documentKind,\n currencyCode: nextDocument.currencyCode,\n lines: nextDocument.lines,\n adjustments: [...preserved, ...next],\n metadata: nextDocument.metadata,\n })\n },\n })\n working = nextDocument\n runningAdjustments = working.adjustments\n }\n }\n }\n }\n\n if (paymentMethod?.providerKey) {\n const provider = getPaymentProvider(paymentMethod.providerKey)\n if (provider?.calculate) {\n const calculatorKey = `${PAYMENT_PREFIX}${provider.key}`\n const hasManualOverride = runningAdjustments.some(\n (adj) => (adj.calculatorKey ?? '').startsWith(calculatorKey) && isManualOverride(adj)\n )\n if (hasManualOverride) {\n working = rebuildDocumentResult({\n documentKind,\n currencyCode: working.currencyCode,\n lines: working.lines,\n adjustments: runningAdjustments,\n metadata: working.metadata,\n })\n } else {\n runningAdjustments = withoutPrefix(runningAdjustments, PAYMENT_PREFIX)\n const rawSettings = extractProviderSettings(paymentMethod)\n const settings =\n normalizeProviderSettings('payment', provider.key, rawSettings) ?? rawSettings ?? {}\n let result = await provider.calculate({\n method: paymentMethod,\n settings,\n document: working,\n lines: working.lines,\n context,\n })\n if (eventBus) {\n await eventBus.emitEvent('sales.payment.adjustments.apply.before', {\n documentKind,\n providerKey: provider.key,\n calculatorKey,\n method: paymentMethod,\n settings,\n document: working,\n result,\n setResult(next: ProviderAdjustmentResult | null | undefined) {\n result = next ?? null\n },\n })\n }\n working = applyProviderResult(\n working,\n runningAdjustments,\n provider.key,\n calculatorKey,\n result,\n 'surcharge'\n )\n runningAdjustments = working.adjustments\n if (eventBus) {\n let nextDocument = working\n await eventBus.emitEvent('sales.payment.adjustments.apply.after', {\n documentKind,\n providerKey: provider.key,\n calculatorKey,\n method: paymentMethod,\n settings,\n document: nextDocument,\n adjustments: nextDocument.adjustments.filter((adj) =>\n (adj.calculatorKey ?? '').startsWith(calculatorKey)\n ),\n setDocument(next: SalesDocumentCalculationResult | null | undefined) {\n if (next) {\n nextDocument = next\n }\n },\n setAdjustments(next: SalesAdjustmentDraft[] | null | undefined) {\n if (!next) return\n const preserved = nextDocument.adjustments.filter(\n (adj) => !(adj.calculatorKey ?? '').startsWith(calculatorKey)\n )\n nextDocument = rebuildDocumentResult({\n documentKind,\n currencyCode: nextDocument.currencyCode,\n lines: nextDocument.lines,\n adjustments: [...preserved, ...next],\n metadata: nextDocument.metadata,\n })\n },\n })\n working = nextDocument\n runningAdjustments = working.adjustments\n }\n }\n }\n }\n\n return working\n })\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,+BAA+B,6BAA6B;
|
|
4
|
+
"sourcesContent": ["import { isExternalAmountsMode, registerSalesTotalsCalculator, rebuildDocumentResult } from '../calculations'\nimport type { SalesAdjustmentDraft, SalesDocumentCalculationResult } from '../types'\nimport {\n getPaymentProvider,\n getShippingProvider,\n normalizeProviderSettings,\n} from './registry'\nimport type {\n PaymentMethodContext,\n ProviderAdjustment,\n ProviderAdjustmentResult,\n ShippingMethodContext,\n ShippingMetrics,\n} from './types'\n\nconst SHIPPING_PREFIX = 'shipping-provider:'\nconst PAYMENT_PREFIX = 'payment-provider:'\n\nlet totalsRegistered = false\n\nfunction toNumber(value: unknown, fallback = 0): number {\n if (typeof value === 'number' && Number.isFinite(value)) return value\n if (typeof value === 'string' && value.trim() !== '' && !Number.isNaN(Number(value))) {\n return Number(value)\n }\n return fallback\n}\n\nfunction isProviderAdjustment(adjustment: SalesAdjustmentDraft): boolean {\n const key = adjustment.calculatorKey ?? ''\n return typeof key === 'string' && (key.startsWith(SHIPPING_PREFIX) || key.startsWith(PAYMENT_PREFIX))\n}\n\nfunction isManualOverride(adjustment: SalesAdjustmentDraft): boolean {\n const metadata = adjustment.metadata\n if (!metadata || typeof metadata !== 'object') return false\n const manual =\n (metadata as Record<string, unknown>).manualOverride ??\n (metadata as Record<string, unknown>).manual_override\n return manual === true\n}\n\nfunction withoutPrefix(adjustments: SalesAdjustmentDraft[], prefix: string) {\n return adjustments.filter((adj) => {\n if (!(adj.calculatorKey ?? '').startsWith(prefix)) return true\n return isManualOverride(adj)\n })\n}\n\nfunction extractProviderSettings(\n method: ShippingMethodContext | PaymentMethodContext | null | undefined\n): Record<string, unknown> | null {\n if (!method) return null\n if (method.providerSettings && typeof method.providerSettings === 'object') {\n return method.providerSettings as Record<string, unknown>\n }\n if (method.metadata && typeof method.metadata === 'object') {\n const meta = method.metadata as Record<string, unknown>\n if (meta.providerSettings && typeof meta.providerSettings === 'object') {\n return meta.providerSettings as Record<string, unknown>\n }\n }\n return null\n}\n\nfunction computeMetrics(lines: SalesDocumentCalculationResult['lines']): ShippingMetrics {\n let itemCount = 0\n let totalWeight = 0\n let totalVolume = 0\n let subtotalNet = 0\n let subtotalGross = 0\n\n for (const entry of lines) {\n const { line } = entry\n const qty = toNumber(line.quantity, 0)\n itemCount += qty\n subtotalNet += toNumber(entry.netAmount, 0)\n subtotalGross += toNumber(entry.grossAmount, 0)\n\n const meta = (line.metadata ?? {}) as Record<string, unknown>\n const configuration = (line.configuration ?? {}) as Record<string, unknown>\n const weight =\n toNumber((meta.weight as number | string | undefined) ?? (configuration.weight as number | string | undefined), 0) *\n qty\n const volume =\n toNumber(\n (meta.volume as number | string | undefined) ??\n (configuration.volume as number | string | undefined),\n 0\n ) * qty\n\n totalWeight += weight\n totalVolume += volume\n }\n\n return {\n itemCount,\n totalWeight,\n totalVolume,\n subtotalNet,\n subtotalGross,\n }\n}\n\nfunction normalizeAdjustments(params: {\n providerKey: string\n calculatorKey: string\n adjustments: ProviderAdjustment[]\n currencyCode: string\n defaultKind: SalesAdjustmentDraft['kind']\n}) {\n return params.adjustments.map<SalesAdjustmentDraft>((adj, index) => ({\n scope: 'order',\n kind: adj.kind ?? params.defaultKind,\n code: adj.code ?? params.providerKey,\n label: adj.label ?? params.providerKey,\n calculatorKey: params.calculatorKey,\n promotionId: null,\n rate: null,\n amountNet: toNumber(adj.amountNet, 0),\n amountGross:\n adj.amountGross === undefined || adj.amountGross === null\n ? toNumber(adj.amountNet, 0)\n : toNumber(adj.amountGross, 0),\n currencyCode: (adj.currencyCode || params.currencyCode || '').toUpperCase() || params.currencyCode,\n metadata: adj.metadata ?? null,\n position: 10_000 + index,\n }))\n}\n\nfunction mergeMetadata(\n base: Record<string, unknown> | undefined,\n key: string,\n value: unknown\n): Record<string, unknown> {\n const next = { ...(base ?? {}) }\n next[key] = value\n return next\n}\n\nfunction applyProviderResult(\n current: SalesDocumentCalculationResult,\n adjustments: SalesAdjustmentDraft[],\n providerKey: string,\n calculatorKey: string,\n result: ProviderAdjustmentResult | null | undefined,\n defaultKind: SalesAdjustmentDraft['kind']\n): SalesDocumentCalculationResult {\n if (!result?.adjustments?.length) {\n return rebuildDocumentResult({\n documentKind: current.kind,\n currencyCode: current.currencyCode,\n lines: current.lines,\n adjustments,\n metadata: current.metadata,\n })\n }\n const normalized = normalizeAdjustments({\n providerKey,\n calculatorKey,\n adjustments: result.adjustments,\n currencyCode: current.currencyCode,\n defaultKind,\n })\n const nextAdjustments = [...adjustments, ...normalized]\n const next = rebuildDocumentResult({\n documentKind: current.kind,\n currencyCode: current.currencyCode,\n lines: current.lines,\n adjustments: nextAdjustments,\n metadata: current.metadata,\n })\n if (result.metadata) {\n next.metadata = mergeMetadata(next.metadata, calculatorKey, result.metadata)\n }\n return next\n}\n\nexport function ensureProviderTotalsCalculator() {\n if (totalsRegistered) return\n totalsRegistered = true\n\n registerSalesTotalsCalculator(async ({ documentKind, lines, context, current, eventBus, totalsMode }) => {\n // A provider charge here could not move the caller's header, so it would show\n // in the breakdown and be absent from the total.\n if (isExternalAmountsMode(totalsMode)) return current\n const metadata = (context.metadata ?? {}) as Record<string, unknown>\n const shippingMethod = (metadata.shippingMethod ?? null) as ShippingMethodContext | null\n const paymentMethod = (metadata.paymentMethod ?? null) as PaymentMethodContext | null\n\n let runningAdjustments = (current.adjustments ?? []).filter(\n (adj) => !isProviderAdjustment(adj) || isManualOverride(adj)\n )\n let working = rebuildDocumentResult({\n documentKind,\n currencyCode: current.currencyCode,\n lines,\n adjustments: runningAdjustments,\n metadata: current.metadata,\n })\n\n if (shippingMethod?.providerKey) {\n const provider = getShippingProvider(shippingMethod.providerKey)\n if (provider?.calculate) {\n const calculatorKey = `${SHIPPING_PREFIX}${provider.key}`\n const hasManualOverride = runningAdjustments.some(\n (adj) => (adj.calculatorKey ?? '').startsWith(calculatorKey) && isManualOverride(adj)\n )\n if (hasManualOverride) {\n // Respect user overrides: keep existing provider-tagged adjustments and skip regeneration.\n working = rebuildDocumentResult({\n documentKind,\n currencyCode: working.currencyCode,\n lines: working.lines,\n adjustments: runningAdjustments,\n metadata: working.metadata,\n })\n } else {\n const rawSettings = extractProviderSettings(shippingMethod)\n const settings =\n normalizeProviderSettings('shipping', provider.key, rawSettings) ?? rawSettings ?? {}\n const metrics = computeMetrics(working.lines)\n let result = await provider.calculate({\n method: shippingMethod,\n settings,\n document: working,\n lines: working.lines,\n context,\n metrics,\n })\n if (eventBus) {\n await eventBus.emitEvent('sales.shipping.adjustments.apply.before', {\n documentKind,\n providerKey: provider.key,\n calculatorKey,\n method: shippingMethod,\n settings,\n document: working,\n result,\n setResult(next: ProviderAdjustmentResult | null | undefined) {\n result = next ?? null\n },\n })\n }\n working = applyProviderResult(working, runningAdjustments, provider.key, calculatorKey, result, 'shipping')\n runningAdjustments = working.adjustments\n if (eventBus) {\n let nextDocument = working\n await eventBus.emitEvent('sales.shipping.adjustments.apply.after', {\n documentKind,\n providerKey: provider.key,\n calculatorKey,\n method: shippingMethod,\n settings,\n document: nextDocument,\n adjustments: nextDocument.adjustments.filter((adj) =>\n (adj.calculatorKey ?? '').startsWith(calculatorKey)\n ),\n setDocument(next: SalesDocumentCalculationResult | null | undefined) {\n if (next) {\n nextDocument = next\n }\n },\n setAdjustments(next: SalesAdjustmentDraft[] | null | undefined) {\n if (!next) return\n const preserved = nextDocument.adjustments.filter(\n (adj) => !(adj.calculatorKey ?? '').startsWith(calculatorKey)\n )\n nextDocument = rebuildDocumentResult({\n documentKind,\n currencyCode: nextDocument.currencyCode,\n lines: nextDocument.lines,\n adjustments: [...preserved, ...next],\n metadata: nextDocument.metadata,\n })\n },\n })\n working = nextDocument\n runningAdjustments = working.adjustments\n }\n }\n }\n }\n\n if (paymentMethod?.providerKey) {\n const provider = getPaymentProvider(paymentMethod.providerKey)\n if (provider?.calculate) {\n const calculatorKey = `${PAYMENT_PREFIX}${provider.key}`\n const hasManualOverride = runningAdjustments.some(\n (adj) => (adj.calculatorKey ?? '').startsWith(calculatorKey) && isManualOverride(adj)\n )\n if (hasManualOverride) {\n working = rebuildDocumentResult({\n documentKind,\n currencyCode: working.currencyCode,\n lines: working.lines,\n adjustments: runningAdjustments,\n metadata: working.metadata,\n })\n } else {\n runningAdjustments = withoutPrefix(runningAdjustments, PAYMENT_PREFIX)\n const rawSettings = extractProviderSettings(paymentMethod)\n const settings =\n normalizeProviderSettings('payment', provider.key, rawSettings) ?? rawSettings ?? {}\n let result = await provider.calculate({\n method: paymentMethod,\n settings,\n document: working,\n lines: working.lines,\n context,\n })\n if (eventBus) {\n await eventBus.emitEvent('sales.payment.adjustments.apply.before', {\n documentKind,\n providerKey: provider.key,\n calculatorKey,\n method: paymentMethod,\n settings,\n document: working,\n result,\n setResult(next: ProviderAdjustmentResult | null | undefined) {\n result = next ?? null\n },\n })\n }\n working = applyProviderResult(\n working,\n runningAdjustments,\n provider.key,\n calculatorKey,\n result,\n 'surcharge'\n )\n runningAdjustments = working.adjustments\n if (eventBus) {\n let nextDocument = working\n await eventBus.emitEvent('sales.payment.adjustments.apply.after', {\n documentKind,\n providerKey: provider.key,\n calculatorKey,\n method: paymentMethod,\n settings,\n document: nextDocument,\n adjustments: nextDocument.adjustments.filter((adj) =>\n (adj.calculatorKey ?? '').startsWith(calculatorKey)\n ),\n setDocument(next: SalesDocumentCalculationResult | null | undefined) {\n if (next) {\n nextDocument = next\n }\n },\n setAdjustments(next: SalesAdjustmentDraft[] | null | undefined) {\n if (!next) return\n const preserved = nextDocument.adjustments.filter(\n (adj) => !(adj.calculatorKey ?? '').startsWith(calculatorKey)\n )\n nextDocument = rebuildDocumentResult({\n documentKind,\n currencyCode: nextDocument.currencyCode,\n lines: nextDocument.lines,\n adjustments: [...preserved, ...next],\n metadata: nextDocument.metadata,\n })\n },\n })\n working = nextDocument\n runningAdjustments = working.adjustments\n }\n }\n }\n }\n\n return working\n })\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Migration } from "@mikro-orm/migrations";
|
|
2
|
+
class Migration20260910120000_sales_external_amounts_mode extends Migration {
|
|
3
|
+
async up() {
|
|
4
|
+
this.addSql(
|
|
5
|
+
`alter table "sales_orders" add column if not exists "totals_mode" text not null default 'computed';`
|
|
6
|
+
);
|
|
7
|
+
this.addSql(
|
|
8
|
+
`alter table "sales_order_lines" add column if not exists "amounts_mode" text not null default 'computed';`
|
|
9
|
+
);
|
|
10
|
+
}
|
|
11
|
+
async down() {
|
|
12
|
+
this.addSql(`alter table "sales_order_lines" drop column if exists "amounts_mode";`);
|
|
13
|
+
this.addSql(`alter table "sales_orders" drop column if exists "totals_mode";`);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export {
|
|
17
|
+
Migration20260910120000_sales_external_amounts_mode
|
|
18
|
+
};
|
|
19
|
+
//# sourceMappingURL=Migration20260910120000_sales_external_amounts_mode.js.map
|
package/dist/modules/sales/migrations/Migration20260910120000_sales_external_amounts_mode.js.map
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/sales/migrations/Migration20260910120000_sales_external_amounts_mode.ts"],
|
|
4
|
+
"sourcesContent": ["import { Migration } from '@mikro-orm/migrations';\n\n// Opt-in `external` amounts mode for sales orders: the row-level statement that\n// the stored amounts are a caller's assertion mirrored from an external book of\n// record, not something core derived. Two defaulted columns, no backfill \u2014 every\n// existing row takes 'computed', which is exactly today's behaviour, so the\n// change is observably inert until a caller sets the mode.\n//\n// PostgreSQL has not rewritten a table for a defaulted column add since 11, so\n// both statements are metadata-only regardless of table size.\nexport class Migration20260910120000_sales_external_amounts_mode extends Migration {\n override async up(): Promise<void> {\n this.addSql(\n `alter table \"sales_orders\" add column if not exists \"totals_mode\" text not null default 'computed';`,\n );\n this.addSql(\n `alter table \"sales_order_lines\" add column if not exists \"amounts_mode\" text not null default 'computed';`,\n );\n }\n\n override async down(): Promise<void> {\n this.addSql(`alter table \"sales_order_lines\" drop column if exists \"amounts_mode\";`);\n this.addSql(`alter table \"sales_orders\" drop column if exists \"totals_mode\";`);\n }\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,iBAAiB;AAUnB,MAAM,4DAA4D,UAAU;AAAA,EACjF,MAAe,KAAoB;AACjC,SAAK;AAAA,MACH;AAAA,IACF;AACA,SAAK;AAAA,MACH;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAe,OAAsB;AACnC,SAAK,OAAO,uEAAuE;AACnF,SAAK,OAAO,iEAAiE;AAAA,EAC/E;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -43,6 +43,7 @@ export const totals_snapshot = "totals_snapshot";
|
|
|
43
43
|
export const paid_total_amount = "paid_total_amount";
|
|
44
44
|
export const refunded_total_amount = "refunded_total_amount";
|
|
45
45
|
export const outstanding_amount = "outstanding_amount";
|
|
46
|
+
export const totals_mode = "totals_mode";
|
|
46
47
|
export const line_item_count = "line_item_count";
|
|
47
48
|
export const metadata = "metadata";
|
|
48
49
|
export const custom_field_set_id = "custom_field_set_id";
|
|
@@ -30,6 +30,7 @@ export const tax_rate = "tax_rate";
|
|
|
30
30
|
export const tax_amount = "tax_amount";
|
|
31
31
|
export const total_net_amount = "total_net_amount";
|
|
32
32
|
export const total_gross_amount = "total_gross_amount";
|
|
33
|
+
export const amounts_mode = "amounts_mode";
|
|
33
34
|
export const configuration = "configuration";
|
|
34
35
|
export const promotion_code = "promotion_code";
|
|
35
36
|
export const promotion_snapshot = "promotion_snapshot";
|
|
@@ -3054,6 +3054,7 @@ export const entityFieldsRegistry: Record<string, Record<string, string>> = {
|
|
|
3054
3054
|
"paid_total_amount": "paid_total_amount",
|
|
3055
3055
|
"refunded_total_amount": "refunded_total_amount",
|
|
3056
3056
|
"outstanding_amount": "outstanding_amount",
|
|
3057
|
+
"totals_mode": "totals_mode",
|
|
3057
3058
|
"line_item_count": "line_item_count",
|
|
3058
3059
|
"metadata": "metadata",
|
|
3059
3060
|
"custom_field_set_id": "custom_field_set_id",
|
|
@@ -3136,6 +3137,7 @@ export const entityFieldsRegistry: Record<string, Record<string, string>> = {
|
|
|
3136
3137
|
"tax_amount": "tax_amount",
|
|
3137
3138
|
"total_net_amount": "total_net_amount",
|
|
3138
3139
|
"total_gross_amount": "total_gross_amount",
|
|
3140
|
+
"amounts_mode": "amounts_mode",
|
|
3139
3141
|
"configuration": "configuration",
|
|
3140
3142
|
"promotion_code": "promotion_code",
|
|
3141
3143
|
"promotion_snapshot": "promotion_snapshot",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/core",
|
|
3
|
-
"version": "0.10.1-develop.
|
|
3
|
+
"version": "0.10.1-develop.7724.1.95e4e7d38e",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -265,16 +265,16 @@
|
|
|
265
265
|
"zod": "^4.4.3"
|
|
266
266
|
},
|
|
267
267
|
"peerDependencies": {
|
|
268
|
-
"@open-mercato/ai-assistant": "0.10.1-develop.
|
|
269
|
-
"@open-mercato/shared": "0.10.1-develop.
|
|
270
|
-
"@open-mercato/ui": "0.10.1-develop.
|
|
268
|
+
"@open-mercato/ai-assistant": "0.10.1-develop.7724.1.95e4e7d38e",
|
|
269
|
+
"@open-mercato/shared": "0.10.1-develop.7724.1.95e4e7d38e",
|
|
270
|
+
"@open-mercato/ui": "0.10.1-develop.7724.1.95e4e7d38e",
|
|
271
271
|
"react": "^19.0.0",
|
|
272
272
|
"react-dom": "^19.0.0"
|
|
273
273
|
},
|
|
274
274
|
"devDependencies": {
|
|
275
|
-
"@open-mercato/ai-assistant": "0.10.1-develop.
|
|
276
|
-
"@open-mercato/shared": "0.10.1-develop.
|
|
277
|
-
"@open-mercato/ui": "0.10.1-develop.
|
|
275
|
+
"@open-mercato/ai-assistant": "0.10.1-develop.7724.1.95e4e7d38e",
|
|
276
|
+
"@open-mercato/shared": "0.10.1-develop.7724.1.95e4e7d38e",
|
|
277
|
+
"@open-mercato/ui": "0.10.1-develop.7724.1.95e4e7d38e",
|
|
278
278
|
"@testing-library/dom": "^10.4.1",
|
|
279
279
|
"@testing-library/jest-dom": "^7.0.1",
|
|
280
280
|
"@testing-library/react": "^16.3.3",
|
|
@@ -424,6 +424,7 @@ export function buildDocumentCrudOptions(binding: DocumentBinding) {
|
|
|
424
424
|
|
|
425
425
|
const orderOnlyFields = [
|
|
426
426
|
'expected_delivery_at',
|
|
427
|
+
'totals_mode',
|
|
427
428
|
'shipping_net_amount',
|
|
428
429
|
'shipping_gross_amount',
|
|
429
430
|
'surcharge_total_amount',
|
|
@@ -535,6 +536,7 @@ export function buildDocumentCrudOptions(binding: DocumentBinding) {
|
|
|
535
536
|
paidTotalAmount: toNumber(item.paid_total_amount),
|
|
536
537
|
refundedTotalAmount: toNumber(item.refunded_total_amount),
|
|
537
538
|
outstandingAmount: toNumber(item.outstanding_amount),
|
|
539
|
+
...(binding.kind === 'order' ? { totalsMode: item.totals_mode ?? 'computed' } : {}),
|
|
538
540
|
customerSnapshot: normalizeJsonRecord(item.customer_snapshot),
|
|
539
541
|
billingAddressSnapshot: normalizeJsonRecord(item.billing_address_snapshot),
|
|
540
542
|
shippingAddressSnapshot: normalizeJsonRecord(item.shipping_address_snapshot),
|
|
@@ -665,6 +667,7 @@ export function buildDocumentOpenApi(binding: DocumentBinding) {
|
|
|
665
667
|
paidTotalAmount: z.number().nullable().optional(),
|
|
666
668
|
refundedTotalAmount: z.number().nullable().optional(),
|
|
667
669
|
outstandingAmount: z.number().nullable().optional(),
|
|
670
|
+
totalsMode: z.enum(['computed', 'external']).optional(),
|
|
668
671
|
createdAt: z.string(),
|
|
669
672
|
updatedAt: z.string(),
|
|
670
673
|
customFields: z.record(z.string(), z.unknown()).optional(),
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { SalesOrderLine } from "../../data/entities";
|
|
2
|
-
import { orderLineCreateSchema } from "../../data/validators";
|
|
2
|
+
import { orderHeaderTotalsSchema, orderLineCreateSchema } from "../../data/validators";
|
|
3
3
|
import { E } from "#generated/entities.ids.generated";
|
|
4
4
|
import * as F from "#generated/entities/sales_order_line";
|
|
5
5
|
import { makeSalesLineRoute } from "../../lib/makeSalesLineRoute";
|
|
@@ -11,6 +11,7 @@ const route = makeSalesLineRoute({
|
|
|
11
11
|
parentFkColumn: "order_id",
|
|
12
12
|
parentFkParam: "orderId",
|
|
13
13
|
createSchema: orderLineCreateSchema,
|
|
14
|
+
writeExtensionShape: { orderTotals: orderHeaderTotalsSchema.optional() },
|
|
14
15
|
features: { view: "sales.orders.view", manage: "sales.orders.manage" },
|
|
15
16
|
commandPrefix: "sales.orders.lines",
|
|
16
17
|
openApi: {
|
|
@@ -77,6 +77,7 @@ import { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuarde
|
|
|
77
77
|
import { buildRecordInjectionContext, useSetCurrentRecordInjectionContext } from '@open-mercato/ui/backend/injection/recordContext'
|
|
78
78
|
import { useSalesChannelsEnabled } from '@open-mercato/core/modules/sales/components/useSalesChannelsEnabled'
|
|
79
79
|
import { createLogger } from '@open-mercato/shared/lib/logger'
|
|
80
|
+
import { getTelemetryRuntime } from '@open-mercato/shared/lib/telemetry/runtime'
|
|
80
81
|
|
|
81
82
|
const logger = createLogger('sales')
|
|
82
83
|
|
|
@@ -899,6 +900,7 @@ type DocumentRecord = {
|
|
|
899
900
|
paidTotalAmount?: number | null
|
|
900
901
|
refundedTotalAmount?: number | null
|
|
901
902
|
outstandingAmount?: number | null
|
|
903
|
+
totalsMode?: 'computed' | 'external' | null
|
|
902
904
|
createdAt?: string
|
|
903
905
|
updatedAt?: string
|
|
904
906
|
metadata?: Record<string, unknown> | null
|
|
@@ -1478,6 +1480,7 @@ function MethodInlineEditor({
|
|
|
1478
1480
|
emptyResultsLabel,
|
|
1479
1481
|
selectedHint,
|
|
1480
1482
|
icon,
|
|
1483
|
+
note,
|
|
1481
1484
|
allowClear = true,
|
|
1482
1485
|
}: {
|
|
1483
1486
|
label: string
|
|
@@ -1494,6 +1497,7 @@ function MethodInlineEditor({
|
|
|
1494
1497
|
emptyResultsLabel: string
|
|
1495
1498
|
selectedHint: (id: string) => string
|
|
1496
1499
|
icon: React.ReactNode
|
|
1500
|
+
note?: string | null
|
|
1497
1501
|
allowClear?: boolean
|
|
1498
1502
|
}) {
|
|
1499
1503
|
const t = useT()
|
|
@@ -1607,6 +1611,7 @@ function MethodInlineEditor({
|
|
|
1607
1611
|
<div className="flex items-start justify-between gap-2">
|
|
1608
1612
|
<div className="flex-1 min-w-0">
|
|
1609
1613
|
<p className="text-xs uppercase tracking-wide text-muted-foreground">{label}</p>
|
|
1614
|
+
{note ? <p className="mt-1 text-xs text-muted-foreground">{note}</p> : null}
|
|
1610
1615
|
{editing ? (
|
|
1611
1616
|
<div
|
|
1612
1617
|
className="mt-2 space-y-2"
|
|
@@ -1937,6 +1942,7 @@ export default function SalesDocumentDetailPage({
|
|
|
1937
1942
|
const [generating, setGenerating] = React.useState(false)
|
|
1938
1943
|
const [converting, setConverting] = React.useState(false)
|
|
1939
1944
|
const [deleting, setDeleting] = React.useState(false)
|
|
1945
|
+
const [switchingToComputedAmounts, setSwitchingToComputedAmounts] = React.useState(false)
|
|
1940
1946
|
const [sending, setSending] = React.useState(false)
|
|
1941
1947
|
const [sendOpen, setSendOpen] = React.useState(false)
|
|
1942
1948
|
const [validForDays, setValidForDays] = React.useState(DEFAULT_QUOTE_VALID_FOR_DAYS)
|
|
@@ -2844,6 +2850,8 @@ export default function SalesDocumentDetailPage({
|
|
|
2844
2850
|
},
|
|
2845
2851
|
[t]
|
|
2846
2852
|
)
|
|
2853
|
+
const amountsAreExternal = kind === 'order' && record?.totalsMode === 'external'
|
|
2854
|
+
|
|
2847
2855
|
const totalsItems = React.useMemo(() => {
|
|
2848
2856
|
if (!record) return []
|
|
2849
2857
|
const items: { key: string; label: string; amount: number | null | undefined; emphasize?: boolean }[] = [
|
|
@@ -3791,6 +3799,48 @@ export default function SalesDocumentDetailPage({
|
|
|
3791
3799
|
}
|
|
3792
3800
|
}, [fetchDocumentByKind, kind, record, runMutationWithContext, t, validForDays])
|
|
3793
3801
|
|
|
3802
|
+
const handleSwitchToComputedAmounts = React.useCallback(async () => {
|
|
3803
|
+
if (!record || kind !== 'order') return
|
|
3804
|
+
const ok = await confirm({
|
|
3805
|
+
title: t(
|
|
3806
|
+
'sales.documents.amountsSwitchConfirm',
|
|
3807
|
+
'Recompute this order\u2019s amounts from its lines? The totals supplied by the source system will be replaced and cannot be restored from here.',
|
|
3808
|
+
),
|
|
3809
|
+
variant: 'default',
|
|
3810
|
+
})
|
|
3811
|
+
if (!ok) return
|
|
3812
|
+
setSwitchingToComputedAmounts(true)
|
|
3813
|
+
try {
|
|
3814
|
+
await runMutationWithContext(async () => {
|
|
3815
|
+
await withScopedApiRequestHeaders(buildOptimisticLockHeader(record.updatedAt), () =>
|
|
3816
|
+
apiCallOrThrow('/api/sales/orders', {
|
|
3817
|
+
method: 'PUT',
|
|
3818
|
+
headers: { 'Content-Type': 'application/json' },
|
|
3819
|
+
body: JSON.stringify({ id: record.id, totalsMode: 'computed' }),
|
|
3820
|
+
}),
|
|
3821
|
+
)
|
|
3822
|
+
}, { id: record.id, totalsMode: 'computed' })
|
|
3823
|
+
flash(t('sales.documents.amountsSwitchDone', 'Amounts are now computed from the lines.'), 'success')
|
|
3824
|
+
await refreshDocumentTotals()
|
|
3825
|
+
} catch (err) {
|
|
3826
|
+
logger.error('sales.documents.amounts.switchToComputed', { err })
|
|
3827
|
+
try {
|
|
3828
|
+
getTelemetryRuntime()?.reportError(err, {
|
|
3829
|
+
module: 'sales',
|
|
3830
|
+
code: 'sales.amounts_switch_to_computed_failed',
|
|
3831
|
+
attributes: { orderId: record.id },
|
|
3832
|
+
})
|
|
3833
|
+
} catch (reportFailure) {
|
|
3834
|
+
logger.warn('sales.documents.amounts.switchToComputed.reportFailed', { err: reportFailure })
|
|
3835
|
+
}
|
|
3836
|
+
if (!handleDocumentMutationError(err, t, () => setReloadKey((prev) => prev + 1))) {
|
|
3837
|
+
flash(t('sales.documents.amountsSwitchFailed', 'Could not switch to computed amounts.'), 'error')
|
|
3838
|
+
}
|
|
3839
|
+
} finally {
|
|
3840
|
+
setSwitchingToComputedAmounts(false)
|
|
3841
|
+
}
|
|
3842
|
+
}, [confirm, kind, record, refreshDocumentTotals, runMutationWithContext, t])
|
|
3843
|
+
|
|
3794
3844
|
const handleDelete = React.useCallback(async () => {
|
|
3795
3845
|
if (!record) return
|
|
3796
3846
|
const ok = await confirm({
|
|
@@ -3851,6 +3901,14 @@ export default function SalesDocumentDetailPage({
|
|
|
3851
3901
|
t('sales.documents.detail.shippingMethod.selected', 'Selected shipping method: {{id}}', { id })
|
|
3852
3902
|
}
|
|
3853
3903
|
icon={<Truck className="h-5 w-5 text-muted-foreground" />}
|
|
3904
|
+
note={
|
|
3905
|
+
amountsAreExternal
|
|
3906
|
+
? t(
|
|
3907
|
+
'sales.documents.amountsExternalMethodNote',
|
|
3908
|
+
'The method is recorded, but no charge is added \u2014 this order\u2019s totals come from the source system.',
|
|
3909
|
+
)
|
|
3910
|
+
: null
|
|
3911
|
+
}
|
|
3854
3912
|
/>
|
|
3855
3913
|
),
|
|
3856
3914
|
},
|
|
@@ -3877,6 +3935,14 @@ export default function SalesDocumentDetailPage({
|
|
|
3877
3935
|
t('sales.documents.detail.paymentMethod.selected', 'Selected payment method: {{id}}', { id })
|
|
3878
3936
|
}
|
|
3879
3937
|
icon={<CreditCard className="h-5 w-5 text-muted-foreground" />}
|
|
3938
|
+
note={
|
|
3939
|
+
amountsAreExternal
|
|
3940
|
+
? t(
|
|
3941
|
+
'sales.documents.amountsExternalMethodNote',
|
|
3942
|
+
'The method is recorded, but no charge is added \u2014 this order\u2019s totals come from the source system.',
|
|
3943
|
+
)
|
|
3944
|
+
: null
|
|
3945
|
+
}
|
|
3880
3946
|
/>
|
|
3881
3947
|
),
|
|
3882
3948
|
},
|
|
@@ -3949,6 +4015,7 @@ export default function SalesDocumentDetailPage({
|
|
|
3949
4015
|
)
|
|
3950
4016
|
return fields
|
|
3951
4017
|
}, [
|
|
4018
|
+
amountsAreExternal,
|
|
3952
4019
|
handleUpdateComment,
|
|
3953
4020
|
handleUpdateCustomerReference,
|
|
3954
4021
|
handleUpdateExpectedDeliveryAt,
|
|
@@ -4266,6 +4333,9 @@ export default function SalesDocumentDetailPage({
|
|
|
4266
4333
|
if (activeTab === 'items') {
|
|
4267
4334
|
return (
|
|
4268
4335
|
<SalesDocumentItemsSection
|
|
4336
|
+
// Remount on a mode flip: the switch rewrites every line server-side, and the
|
|
4337
|
+
// section loads its lines only once per `documentId`, which does not change.
|
|
4338
|
+
key={`items:${amountsAreExternal ? 'external' : 'computed'}`}
|
|
4269
4339
|
documentId={record.id}
|
|
4270
4340
|
kind={kind}
|
|
4271
4341
|
currencyCode={record.currencyCode ?? null}
|
|
@@ -4274,6 +4344,7 @@ export default function SalesDocumentDetailPage({
|
|
|
4274
4344
|
tenantId={(record as any)?.tenantId ?? (record as any)?.tenant_id ?? null}
|
|
4275
4345
|
onActionChange={handleSectionActionChange}
|
|
4276
4346
|
onItemsChange={(items) => setHasItems(items.length > 0)}
|
|
4347
|
+
amountsReadOnly={amountsAreExternal}
|
|
4277
4348
|
/>
|
|
4278
4349
|
)
|
|
4279
4350
|
}
|
|
@@ -4332,6 +4403,7 @@ export default function SalesDocumentDetailPage({
|
|
|
4332
4403
|
tenantId={(record as any)?.tenantId ?? (record as any)?.tenant_id ?? null}
|
|
4333
4404
|
onActionChange={handleSectionActionChange}
|
|
4334
4405
|
onRowsChange={setAdjustmentRows}
|
|
4406
|
+
amountsReadOnly={amountsAreExternal}
|
|
4335
4407
|
/>
|
|
4336
4408
|
)
|
|
4337
4409
|
}
|
|
@@ -4914,6 +4986,30 @@ export default function SalesDocumentDetailPage({
|
|
|
4914
4986
|
title={t('sales.documents.detail.totals.title', 'Totals')}
|
|
4915
4987
|
currency={record.currencyCode ?? null}
|
|
4916
4988
|
items={totalsItems}
|
|
4989
|
+
sourceBadge={
|
|
4990
|
+
amountsAreExternal
|
|
4991
|
+
? {
|
|
4992
|
+
label: t('sales.documents.amountsExternal', 'Amounts from source'),
|
|
4993
|
+
hint: t(
|
|
4994
|
+
'sales.documents.amountsExternalHint',
|
|
4995
|
+
'Totals on this document come from an external system and are stored as supplied.',
|
|
4996
|
+
),
|
|
4997
|
+
}
|
|
4998
|
+
: null
|
|
4999
|
+
}
|
|
5000
|
+
footerAction={
|
|
5001
|
+
amountsAreExternal ? (
|
|
5002
|
+
<Button
|
|
5003
|
+
type="button"
|
|
5004
|
+
variant="outline"
|
|
5005
|
+
size="sm"
|
|
5006
|
+
disabled={switchingToComputedAmounts}
|
|
5007
|
+
onClick={handleSwitchToComputedAmounts}
|
|
5008
|
+
>
|
|
5009
|
+
{t('sales.documents.amountsSwitchToComputed', 'Switch to computed amounts')}
|
|
5010
|
+
</Button>
|
|
5011
|
+
) : null
|
|
5012
|
+
}
|
|
4917
5013
|
/>
|
|
4918
5014
|
|
|
4919
5015
|
<div className="space-y-4" ref={detailSectionRef}>
|