@open-mercato/core 0.6.8-develop.7088.1.3873d81f42 → 0.6.8-develop.7090.1.4dbca7b350

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.
Files changed (51) hide show
  1. package/dist/modules/customers/components/DealsKpiStrip.js +7 -6
  2. package/dist/modules/customers/components/DealsKpiStrip.js.map +2 -2
  3. package/dist/modules/customers/components/detail/CompanyKpiBar.js +5 -4
  4. package/dist/modules/customers/components/detail/CompanyKpiBar.js.map +2 -2
  5. package/dist/modules/customers/components/detail/DealsSection.js +6 -5
  6. package/dist/modules/customers/components/detail/DealsSection.js.map +2 -2
  7. package/dist/modules/customers/components/detail/utils.js +5 -5
  8. package/dist/modules/customers/components/detail/utils.js.map +2 -2
  9. package/dist/modules/sales/backend/sales/documents/[id]/page.js +10 -8
  10. package/dist/modules/sales/backend/sales/documents/[id]/page.js.map +2 -2
  11. package/dist/modules/sales/components/PriceWithCurrency.js +7 -5
  12. package/dist/modules/sales/components/PriceWithCurrency.js.map +2 -2
  13. package/dist/modules/sales/components/documents/AdjustmentsSection.js +8 -6
  14. package/dist/modules/sales/components/documents/AdjustmentsSection.js.map +2 -2
  15. package/dist/modules/sales/components/documents/ItemsSection.js +14 -7
  16. package/dist/modules/sales/components/documents/ItemsSection.js.map +2 -2
  17. package/dist/modules/sales/components/documents/LineItemDialog.js +8 -7
  18. package/dist/modules/sales/components/documents/LineItemDialog.js.map +2 -2
  19. package/dist/modules/sales/components/documents/PaymentsSection.js +9 -8
  20. package/dist/modules/sales/components/documents/PaymentsSection.js.map +2 -2
  21. package/dist/modules/sales/components/documents/ReturnsSection.js +8 -6
  22. package/dist/modules/sales/components/documents/ReturnsSection.js.map +2 -2
  23. package/dist/modules/sales/components/documents/SalesOrderDraftLines.js +5 -4
  24. package/dist/modules/sales/components/documents/SalesOrderDraftLines.js.map +2 -2
  25. package/dist/modules/sales/components/documents/ShipmentDialog.js +5 -4
  26. package/dist/modules/sales/components/documents/ShipmentDialog.js.map +2 -2
  27. package/dist/modules/sales/components/documents/ShipmentsSection.js +8 -6
  28. package/dist/modules/sales/components/documents/ShipmentsSection.js.map +2 -2
  29. package/dist/modules/sales/components/documents/lineItemUtils.js +2 -2
  30. package/dist/modules/sales/components/documents/lineItemUtils.js.map +2 -2
  31. package/package.json +7 -7
  32. package/src/modules/auth/i18n/de.json +3 -0
  33. package/src/modules/auth/i18n/en.json +3 -0
  34. package/src/modules/auth/i18n/es.json +3 -0
  35. package/src/modules/auth/i18n/ko.json +3 -0
  36. package/src/modules/auth/i18n/pl.json +3 -0
  37. package/src/modules/customers/components/DealsKpiStrip.tsx +7 -7
  38. package/src/modules/customers/components/detail/CompanyKpiBar.tsx +5 -4
  39. package/src/modules/customers/components/detail/DealsSection.tsx +11 -5
  40. package/src/modules/customers/components/detail/utils.ts +15 -5
  41. package/src/modules/sales/backend/sales/documents/[id]/page.tsx +14 -8
  42. package/src/modules/sales/components/PriceWithCurrency.tsx +17 -5
  43. package/src/modules/sales/components/documents/AdjustmentsSection.tsx +6 -5
  44. package/src/modules/sales/components/documents/ItemsSection.tsx +8 -1
  45. package/src/modules/sales/components/documents/LineItemDialog.tsx +9 -8
  46. package/src/modules/sales/components/documents/PaymentsSection.tsx +9 -8
  47. package/src/modules/sales/components/documents/ReturnsSection.tsx +6 -5
  48. package/src/modules/sales/components/documents/SalesOrderDraftLines.tsx +5 -4
  49. package/src/modules/sales/components/documents/ShipmentDialog.tsx +5 -4
  50. package/src/modules/sales/components/documents/ShipmentsSection.tsx +6 -5
  51. package/src/modules/sales/components/documents/lineItemUtils.ts +7 -2
@@ -23,7 +23,7 @@ import type { SectionAction } from '@open-mercato/core/modules/customers/compone
23
23
  import type { SalesAdjustmentKind } from '../../data/entities'
24
24
  import { PriceWithCurrency } from '../PriceWithCurrency'
25
25
  import { AdjustmentDialog, type AdjustmentRowData, type AdjustmentSubmitPayload } from './AdjustmentDialog'
26
- import { useT } from '@open-mercato/shared/lib/i18n/context'
26
+ import { useT, useLocale } from '@open-mercato/shared/lib/i18n/context'
27
27
  import { useOrganizationScopeDetail } from '@open-mercato/shared/lib/frontend/useOrganizationScope'
28
28
  import { extractCustomFieldValues } from './customFieldHelpers'
29
29
  import { createLogger } from '@open-mercato/shared/lib/logger'
@@ -60,9 +60,9 @@ function normalizeNumber(value: unknown, fallback = 0): number {
60
60
  return fallback
61
61
  }
62
62
 
63
- function formatPercent(value: number | null | undefined): string {
63
+ export function formatPercent(value: number | null | undefined, locale?: string): string {
64
64
  if (value === null || value === undefined) return '—'
65
- const formatter = new Intl.NumberFormat(undefined, { style: 'percent', maximumFractionDigits: 2 })
65
+ const formatter = new Intl.NumberFormat(locale, { style: 'percent', maximumFractionDigits: 2 })
66
66
  return formatter.format(value / 100)
67
67
  }
68
68
 
@@ -77,6 +77,7 @@ export function SalesDocumentAdjustmentsSection({
77
77
  onRowsChange,
78
78
  }: SalesDocumentAdjustmentsSectionProps) {
79
79
  const t = useT()
80
+ const locale = useLocale()
80
81
  const { organizationId, tenantId } = useOrganizationScopeDetail()
81
82
  const resolvedOrganizationId = orgFromProps ?? organizationId ?? null
82
83
  const resolvedTenantId = tenantFromProps ?? tenantId ?? null
@@ -433,7 +434,7 @@ export function SalesDocumentAdjustmentsSection({
433
434
  {
434
435
  id: 'rate',
435
436
  header: t('sales.documents.adjustments.rate', 'Rate'),
436
- cell: ({ row }) => <span className="font-mono text-sm text-muted-foreground">{formatPercent(row.original.rate)}</span>,
437
+ cell: ({ row }) => <span className="font-mono text-sm text-muted-foreground">{formatPercent(row.original.rate, locale)}</span>,
437
438
  },
438
439
  {
439
440
  id: 'position',
@@ -441,7 +442,7 @@ export function SalesDocumentAdjustmentsSection({
441
442
  cell: ({ row }) => <span className="text-sm text-muted-foreground">{row.original.position}</span>,
442
443
  },
443
444
  ],
444
- [currencyCode, resolveKindLabel, t]
445
+ [currencyCode, locale, resolveKindLabel, t]
445
446
  )
446
447
 
447
448
  const showLoadingState = loading && rows.length === 0
@@ -16,7 +16,7 @@ import {
16
16
  createDictionaryMap,
17
17
  normalizeDictionaryEntries,
18
18
  } from "@open-mercato/core/modules/dictionaries/components/dictionaryAppearance";
19
- import { useT } from "@open-mercato/shared/lib/i18n/context";
19
+ import { useT, useLocale } from "@open-mercato/shared/lib/i18n/context";
20
20
  import { useOrganizationScopeDetail } from "@open-mercato/shared/lib/frontend/useOrganizationScope";
21
21
  import { useConfirmDialog } from "@open-mercato/ui/backend/confirm-dialog";
22
22
  import { emitSalesDocumentTotalsRefresh } from "@open-mercato/core/modules/sales/lib/frontend/documentTotalsEvents";
@@ -158,6 +158,7 @@ export function SalesDocumentItemsSection({
158
158
  onItemsChange,
159
159
  }: SalesDocumentItemsSectionProps) {
160
160
  const t = useT();
161
+ const locale = useLocale();
161
162
  const { organizationId, tenantId } = useOrganizationScopeDetail();
162
163
  const { confirm, ConfirmDialogElement } = useConfirmDialog();
163
164
  const resolvedOrganizationId = orgFromProps ?? organizationId ?? null;
@@ -827,6 +828,7 @@ export function SalesDocumentItemsSection({
827
828
  {formatMoney(
828
829
  item.unitPriceGross,
829
830
  item.currencyCode ?? currencyCode ?? undefined,
831
+ locale,
830
832
  )}{" "}
831
833
  <span className="text-xs text-muted-foreground">
832
834
  {t("sales.documents.items.table.gross", "gross")}
@@ -836,6 +838,7 @@ export function SalesDocumentItemsSection({
836
838
  {formatMoney(
837
839
  item.unitPriceNet,
838
840
  item.currencyCode ?? currencyCode ?? undefined,
841
+ locale,
839
842
  )}{" "}
840
843
  {t("sales.documents.items.table.net", "net")}
841
844
  </span>
@@ -850,6 +853,7 @@ export function SalesDocumentItemsSection({
850
853
  item.currencyCode ??
851
854
  currencyCode ??
852
855
  undefined,
856
+ locale,
853
857
  ),
854
858
  unit: unitPriceReference.referenceUnitCode,
855
859
  },
@@ -873,6 +877,7 @@ export function SalesDocumentItemsSection({
873
877
  item.currencyCode ??
874
878
  currencyCode ??
875
879
  undefined,
880
+ locale,
876
881
  ),
877
882
  },
878
883
  )}
@@ -903,6 +908,7 @@ export function SalesDocumentItemsSection({
903
908
  {formatMoney(
904
909
  item.totalGross,
905
910
  item.currencyCode ?? currencyCode ?? undefined,
911
+ locale,
906
912
  )}{" "}
907
913
  <span className="text-xs font-normal text-muted-foreground">
908
914
  {t("sales.documents.items.table.gross", "gross")}
@@ -912,6 +918,7 @@ export function SalesDocumentItemsSection({
912
918
  {formatMoney(
913
919
  item.totalNet,
914
920
  item.currencyCode ?? currencyCode ?? undefined,
921
+ locale,
915
922
  )}{" "}
916
923
  {t("sales.documents.items.table.net", "net")}
917
924
  </span>
@@ -46,7 +46,7 @@ import {
46
46
  renderDictionaryColor,
47
47
  } from "@open-mercato/core/modules/dictionaries/components/dictionaryAppearance";
48
48
  import { E } from "#generated/entities.ids.generated";
49
- import { useT } from "@open-mercato/shared/lib/i18n/context";
49
+ import { useT, useLocale } from "@open-mercato/shared/lib/i18n/context";
50
50
  import { useOrganizationScopeDetail } from "@open-mercato/shared/lib/frontend/useOrganizationScope";
51
51
  import { formatMoney, normalizeNumber } from "./lineItemUtils";
52
52
  import type { SalesLineRecord } from "./lineItemTypes";
@@ -489,6 +489,7 @@ export function LineItemDialog({
489
489
  onDraftSaved,
490
490
  }: SalesLineDialogProps) {
491
491
  const t = useT();
492
+ const locale = useLocale();
492
493
  const scope = useOrganizationScopeDetail();
493
494
  const resolvedOrganizationId = organizationId ?? scope.organizationId ?? null;
494
495
  const resolvedTenantId = tenantId ?? scope.tenantId ?? null;
@@ -1044,10 +1045,10 @@ export function LineItemDialog({
1044
1045
  displayMode === "including-tax" &&
1045
1046
  amountGross !== null &&
1046
1047
  currency
1047
- ? formatMoney(amountGross, currency)
1048
+ ? formatMoney(amountGross, currency, locale)
1048
1049
  : null,
1049
1050
  displayMode === "excluding-tax" && amountNet !== null && currency
1050
- ? formatMoney(amountNet, currency)
1051
+ ? formatMoney(amountNet, currency, locale)
1051
1052
  : null,
1052
1053
  displayMode
1053
1054
  ? displayMode === "including-tax"
@@ -1063,9 +1064,9 @@ export function LineItemDialog({
1063
1064
  labelParts.length > 0
1064
1065
  ? labelParts.join(" • ")
1065
1066
  : amountGross !== null && currency
1066
- ? formatMoney(amountGross, currency)
1067
+ ? formatMoney(amountGross, currency, locale)
1067
1068
  : amountNet !== null && currency
1068
- ? formatMoney(amountNet, currency)
1069
+ ? formatMoney(amountNet, currency, locale)
1069
1070
  : id;
1070
1071
  return {
1071
1072
  id,
@@ -2059,7 +2060,7 @@ export function LineItemDialog({
2059
2060
  ? t("sales.documents.items.priceNet", "Net")
2060
2061
  : t("sales.documents.items.priceGross", "Gross");
2061
2062
  const lockedAmountLabel = Number.isFinite(lockedAmount)
2062
- ? `${formatMoney(lockedAmount, lockedCurrency)} — ${lockedModeLabel}`
2063
+ ? `${formatMoney(lockedAmount, lockedCurrency, locale)} — ${lockedModeLabel}`
2063
2064
  : lockedModeLabel;
2064
2065
  const lockedPriceDetail =
2065
2066
  selectedPrice?.priceKindTitle ??
@@ -2260,11 +2261,11 @@ export function LineItemDialog({
2260
2261
  "sales.documents.items.priceBasisTemplate",
2261
2262
  "Catalog price basis: {{baseAmount}} / {{baseUnit}}. Converted for {{unit}}: {{baseAmount}} × {{factor}} = {{convertedAmount}}.",
2262
2263
  {
2263
- baseAmount: formatMoney(selectedBaseAmount as number, selectedCurrency),
2264
+ baseAmount: formatMoney(selectedBaseAmount as number, selectedCurrency, locale),
2264
2265
  baseUnit: baseUnitCode,
2265
2266
  unit: quantityUnitCode,
2266
2267
  factor: unitFactor,
2267
- convertedAmount: formatMoney(convertedAmount, selectedCurrency),
2268
+ convertedAmount: formatMoney(convertedAmount, selectedCurrency, locale),
2268
2269
  },
2269
2270
  )}
2270
2271
  </p>
@@ -13,7 +13,7 @@ import { RowActions } from '@open-mercato/ui/backend/RowActions'
13
13
  import { Button } from '@open-mercato/ui/primitives/button'
14
14
  import { flash } from '@open-mercato/ui/backend/FlashMessages'
15
15
  import { useOrganizationScopeDetail } from '@open-mercato/shared/lib/frontend/useOrganizationScope'
16
- import { useT } from '@open-mercato/shared/lib/i18n/context'
16
+ import { useT, useLocale } from '@open-mercato/shared/lib/i18n/context'
17
17
  import { emitSalesDocumentTotalsRefresh } from '@open-mercato/core/modules/sales/lib/frontend/documentTotalsEvents'
18
18
  import { extensionPoints } from '@open-mercato/core/modules/sales/extension-points'
19
19
  import { PaymentDialog, type PaymentFormData, type PaymentTotals } from './PaymentDialog'
@@ -60,10 +60,10 @@ function normalizeNumber(value: unknown): number {
60
60
  return 0
61
61
  }
62
62
 
63
- function formatMoney(value: number, currency: string | null | undefined): string {
63
+ function formatMoney(value: number, currency: string | null | undefined, locale?: string): string {
64
64
  if (!currency || currency.trim().length !== 3) return value.toFixed(2)
65
65
  try {
66
- return new Intl.NumberFormat(undefined, { style: 'currency', currency }).format(value)
66
+ return new Intl.NumberFormat(locale, { style: 'currency', currency }).format(value)
67
67
  } catch {
68
68
  return `${currency.toUpperCase()} ${value.toFixed(2)}`
69
69
  }
@@ -80,6 +80,7 @@ export function SalesDocumentPaymentsSection({
80
80
  onPaymentsChange,
81
81
  }: SalesDocumentPaymentsSectionProps) {
82
82
  const t = useT()
83
+ const locale = useLocale()
83
84
  const { organizationId, tenantId } = useOrganizationScopeDetail()
84
85
  const resolvedOrganizationId = orgFromProps ?? organizationId ?? null
85
86
  const resolvedTenantId = tenantFromProps ?? tenantId ?? null
@@ -287,24 +288,24 @@ export function SalesDocumentPaymentsSection({
287
288
  {
288
289
  accessorKey: 'amount',
289
290
  header: t('sales.documents.payments.amount', 'Amount'),
290
- cell: ({ row }) => formatMoney(row.original.amount, row.original.currencyCode ?? currencyCode),
291
+ cell: ({ row }) => formatMoney(row.original.amount, row.original.currencyCode ?? currencyCode, locale),
291
292
  },
292
293
  {
293
294
  accessorKey: 'receivedAt',
294
295
  header: t('sales.documents.payments.receivedAt', 'Received'),
295
296
  cell: ({ row }) =>
296
297
  row.original.receivedAt
297
- ? new Date(row.original.receivedAt).toLocaleDateString()
298
+ ? new Date(row.original.receivedAt).toLocaleDateString(locale)
298
299
  : '—',
299
300
  },
300
301
  {
301
302
  accessorKey: 'createdAt',
302
303
  header: t('sales.documents.payments.createdAt', 'Created'),
303
304
  cell: ({ row }) =>
304
- row.original.createdAt ? new Date(row.original.createdAt).toLocaleString() : '—',
305
+ row.original.createdAt ? new Date(row.original.createdAt).toLocaleString(locale) : '—',
305
306
  meta: {
306
307
  tooltipContent: (row: PaymentRow) =>
307
- row.createdAt ? new Date(row.createdAt).toLocaleString() : undefined,
308
+ row.createdAt ? new Date(row.createdAt).toLocaleString(locale) : undefined,
308
309
  },
309
310
  },
310
311
  {
@@ -327,7 +328,7 @@ export function SalesDocumentPaymentsSection({
327
328
  },
328
329
  },
329
330
  ],
330
- [currencyCode, deleteActionLabel, editActionLabel, handleDelete, openEditPayment, t]
331
+ [currencyCode, deleteActionLabel, editActionLabel, handleDelete, locale, openEditPayment, t]
331
332
  )
332
333
 
333
334
  if (loading) {
@@ -12,7 +12,7 @@ import { flash } from '@open-mercato/ui/backend/FlashMessages'
12
12
  import { RowActions } from '@open-mercato/ui/backend/RowActions'
13
13
  import { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'
14
14
  import { useOrganizationScopeDetail } from '@open-mercato/shared/lib/frontend/useOrganizationScope'
15
- import { useT } from '@open-mercato/shared/lib/i18n/context'
15
+ import { useT, useLocale } from '@open-mercato/shared/lib/i18n/context'
16
16
  import {
17
17
  emitSalesDocumentTotalsRefresh,
18
18
  subscribeSalesDocumentTotalsRefresh,
@@ -41,15 +41,16 @@ type SalesReturnsSectionProps = {
41
41
  documentUpdatedAt?: string | null
42
42
  }
43
43
 
44
- function formatDisplayDate(value: string | null | undefined): string | null {
44
+ export function formatDisplayDate(value: string | null | undefined, locale?: string): string | null {
45
45
  if (!value) return null
46
46
  const date = new Date(value)
47
47
  if (Number.isNaN(date.getTime())) return null
48
- return new Intl.DateTimeFormat(undefined, { dateStyle: 'medium' }).format(date)
48
+ return new Intl.DateTimeFormat(locale, { dateStyle: 'medium' }).format(date)
49
49
  }
50
50
 
51
51
  export function SalesReturnsSection({ orderId, currencyCode, documentUpdatedAt }: SalesReturnsSectionProps) {
52
52
  const t = useT()
53
+ const locale = useLocale()
53
54
  const { organizationId, tenantId } = useOrganizationScopeDetail()
54
55
  const { confirm, ConfirmDialogElement } = useConfirmDialog()
55
56
  const [returns, setReturns] = React.useState<ReturnRow[]>([])
@@ -328,10 +329,10 @@ export function SalesReturnsSection({ orderId, currencyCode, documentUpdatedAt }
328
329
  </div>
329
330
  </div>
330
331
  <div className="whitespace-nowrap text-right text-sm text-muted-foreground">
331
- {formatDisplayDate(ret.returnedAt) ?? t('sales.returns.notSet', 'Not set')}
332
+ {formatDisplayDate(ret.returnedAt, locale) ?? t('sales.returns.notSet', 'Not set')}
332
333
  </div>
333
334
  <div className="whitespace-nowrap text-right text-sm font-medium">
334
- {formatMoney(ret.total, currencyCode ?? null)}
335
+ {formatMoney(ret.total, currencyCode ?? null, locale)}
335
336
  </div>
336
337
  <div className="flex justify-end">
337
338
  <RowActions
@@ -7,7 +7,7 @@ import { DataTable } from '@open-mercato/ui/backend/DataTable'
7
7
  import { RowActions } from '@open-mercato/ui/backend/RowActions'
8
8
  import { EmptyState } from '@open-mercato/ui/primitives/empty-state'
9
9
  import { Button } from '@open-mercato/ui/primitives/button'
10
- import { useT } from '@open-mercato/shared/lib/i18n/context'
10
+ import { useLocale, useT } from '@open-mercato/shared/lib/i18n/context'
11
11
  import { LineItemDialog } from './LineItemDialog'
12
12
  import type { SalesLineRecord } from './lineItemTypes'
13
13
  import { formatMoney, normalizeNumber } from './lineItemUtils'
@@ -93,6 +93,7 @@ export function SalesOrderDraftLines({
93
93
  onChange,
94
94
  }: SalesOrderDraftLinesProps) {
95
95
  const t = useT()
96
+ const locale = useLocale()
96
97
  const [dialogOpen, setDialogOpen] = React.useState(false)
97
98
  const [editing, setEditing] = React.useState<SalesOrderLineDraft | null>(null)
98
99
 
@@ -112,14 +113,14 @@ export function SalesOrderDraftLines({
112
113
  {
113
114
  id: 'unitPrice',
114
115
  header: t('sales.documents.items.table.unit', 'Unit price'),
115
- cell: ({ row }) => formatMoney(row.original.record.unitPriceGross, row.original.record.currencyCode ?? currencyCode ?? undefined),
116
+ cell: ({ row }) => formatMoney(row.original.record.unitPriceGross, row.original.record.currencyCode ?? currencyCode ?? undefined, locale),
116
117
  },
117
118
  {
118
119
  id: 'total',
119
120
  header: t('sales.documents.items.table.total', 'Total'),
120
- cell: ({ row }) => formatMoney(row.original.record.totalGross, row.original.record.currencyCode ?? currencyCode ?? undefined),
121
+ cell: ({ row }) => formatMoney(row.original.record.totalGross, row.original.record.currencyCode ?? currencyCode ?? undefined, locale),
121
122
  },
122
- ], [currencyCode, t])
123
+ ], [currencyCode, locale, t])
123
124
 
124
125
  const openCreate = React.useCallback(() => {
125
126
  setEditing(null)
@@ -20,7 +20,7 @@ import { flash } from '@open-mercato/ui/backend/FlashMessages'
20
20
  import { cn } from '@open-mercato/shared/lib/utils'
21
21
  import { E } from '#generated/entities.ids.generated'
22
22
  import { emitSalesDocumentTotalsRefresh } from '@open-mercato/core/modules/sales/lib/frontend/documentTotalsEvents'
23
- import { useT } from '@open-mercato/shared/lib/i18n/context'
23
+ import { useT, useLocale } from '@open-mercato/shared/lib/i18n/context'
24
24
  import { formatMoney, normalizeNumber } from './lineItemUtils'
25
25
  import type { OrderLine, ShipmentRow } from './shipmentTypes'
26
26
  import { formatAddressString, type AddressFormatStrategy, type AddressValue } from '@open-mercato/core/modules/customers/utils/addressFormat'
@@ -290,6 +290,7 @@ export function ShipmentDialog({
290
290
  onAddComment,
291
291
  }: ShipmentDialogProps) {
292
292
  const t = useT()
293
+ const locale = useLocale()
293
294
  const [formResetKey, setFormResetKey] = React.useState(0)
294
295
  const [shippingMethods, setShippingMethods] = React.useState<ShippingMethodOption[]>([])
295
296
  const [shippingMethodLoading, setShippingMethodLoading] = React.useState(false)
@@ -540,21 +541,21 @@ export function ShipmentDialog({
540
541
  if (option.avgPrice !== null) {
541
542
  parts.push(
542
543
  t('sales.documents.shipments.shippingMethodAvg', 'Avg {{price}}', {
543
- price: formatMoney(option.avgPrice, currency ?? null),
544
+ price: formatMoney(option.avgPrice, currency ?? null, locale),
544
545
  }),
545
546
  )
546
547
  }
547
548
  if (option.minPrice !== null) {
548
549
  parts.push(
549
550
  t('sales.documents.shipments.shippingMethodMin', 'Min {{price}}', {
550
- price: formatMoney(option.minPrice, currency ?? null),
551
+ price: formatMoney(option.minPrice, currency ?? null, locale),
551
552
  }),
552
553
  )
553
554
  }
554
555
  if (parts.length === 0) return undefined
555
556
  return parts.join(' · ')
556
557
  },
557
- [currencyCode, t],
558
+ [currencyCode, locale, t],
558
559
  )
559
560
 
560
561
  const mapShippingMethod = React.useCallback(
@@ -10,7 +10,7 @@ import { apiCall, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/u
10
10
  import { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'
11
11
  import { deleteCrud } from '@open-mercato/ui/backend/utils/crud'
12
12
  import { useOrganizationScopeDetail } from '@open-mercato/shared/lib/frontend/useOrganizationScope'
13
- import { useT } from '@open-mercato/shared/lib/i18n/context'
13
+ import { useT, useLocale } from '@open-mercato/shared/lib/i18n/context'
14
14
  import { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'
15
15
  import {
16
16
  emitSalesDocumentTotalsRefresh,
@@ -41,11 +41,11 @@ type SalesShipmentsSectionProps = {
41
41
  onAddComment?: (body: string) => Promise<void>
42
42
  }
43
43
 
44
- function formatDisplayDate(value: string | null | undefined): string | null {
44
+ export function formatDisplayDate(value: string | null | undefined, locale?: string): string | null {
45
45
  if (!value) return null
46
46
  const date = new Date(value)
47
47
  if (Number.isNaN(date.getTime())) return null
48
- return new Intl.DateTimeFormat(undefined, { dateStyle: 'medium' }).format(date)
48
+ return new Intl.DateTimeFormat(locale, { dateStyle: 'medium' }).format(date)
49
49
  }
50
50
 
51
51
  const formatShipmentAddress = (metadata?: Record<string, unknown> | null): string | null => {
@@ -123,6 +123,7 @@ export function SalesShipmentsSection({
123
123
  onAddComment,
124
124
  }: SalesShipmentsSectionProps) {
125
125
  const t = useT()
126
+ const locale = useLocale()
126
127
  const { organizationId, tenantId } = useOrganizationScopeDetail()
127
128
  const { confirm, ConfirmDialogElement } = useConfirmDialog()
128
129
  const resolvedOrganizationId = organizationIdProp ?? organizationId ?? null
@@ -486,8 +487,8 @@ export function SalesShipmentsSection({
486
487
  ) : (
487
488
  <div className="grid gap-3 md:grid-cols-2">
488
489
  {shipments.map((shipment) => {
489
- const shippedAt = formatDisplayDate(shipment.shippedAt)
490
- const deliveredAt = formatDisplayDate(shipment.deliveredAt)
490
+ const shippedAt = formatDisplayDate(shipment.shippedAt, locale)
491
+ const deliveredAt = formatDisplayDate(shipment.deliveredAt, locale)
491
492
  const addressSummary = formatShipmentAddress(shipment.metadata)
492
493
  const statusLabel =
493
494
  shipment.statusLabel ??
@@ -9,9 +9,14 @@ export function normalizeNumber(value: unknown, fallback = 0): number {
9
9
  return fallback
10
10
  }
11
11
 
12
- export function formatMoney(value: number, currency: string | null | undefined): string {
12
+ /**
13
+ * Pass `locale` to format in the application locale (`useLocale()` in client components).
14
+ * Omitting it keeps the runtime default, which varies per machine and is therefore not
15
+ * assertable in tests.
16
+ */
17
+ export function formatMoney(value: number, currency: string | null | undefined, locale?: string): string {
13
18
  if (!currency) return value.toFixed(2)
14
- return new Intl.NumberFormat(undefined, { style: 'currency', currency }).format(value)
19
+ return new Intl.NumberFormat(locale, { style: 'currency', currency }).format(value)
15
20
  }
16
21
 
17
22
  export type LineDiscountDisplay = {