@open-mercato/core 0.7.1-develop.7153.1.7145d295e6 → 0.7.1-develop.7170.1.d95074d7ba

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 (116) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/AGENTS.md +2 -2
  3. package/dist/modules/auth/commands/users.js +14 -1
  4. package/dist/modules/auth/commands/users.js.map +2 -2
  5. package/dist/modules/auth/data/entities.js +2 -2
  6. package/dist/modules/auth/data/entities.js.map +2 -2
  7. package/dist/modules/auth/lib/backendChrome.js +26 -3
  8. package/dist/modules/auth/lib/backendChrome.js.map +2 -2
  9. package/dist/modules/auth/lib/profile-sections.js +1 -1
  10. package/dist/modules/auth/lib/profile-sections.js.map +2 -2
  11. package/dist/modules/auth/migrations/Migration20260907120000_auth.js +19 -0
  12. package/dist/modules/auth/migrations/Migration20260907120000_auth.js.map +7 -0
  13. package/dist/modules/communication_channels/backend/profile/communication-channels/page.meta.js +2 -2
  14. package/dist/modules/communication_channels/backend/profile/communication-channels/page.meta.js.map +1 -1
  15. package/dist/modules/customer_accounts/api/admin/demo-accounts.js +118 -0
  16. package/dist/modules/customer_accounts/api/admin/demo-accounts.js.map +7 -0
  17. package/dist/modules/customer_accounts/api/invitations/accept.js +23 -6
  18. package/dist/modules/customer_accounts/api/invitations/accept.js.map +2 -2
  19. package/dist/modules/customer_accounts/backend/customer_accounts/settings/CustomerAccountsSettingsPageClient.js +10 -13
  20. package/dist/modules/customer_accounts/backend/customer_accounts/settings/CustomerAccountsSettingsPageClient.js.map +2 -2
  21. package/dist/modules/customer_accounts/backend/customer_accounts/useDemoPortalAccounts.js +28 -0
  22. package/dist/modules/customer_accounts/backend/customer_accounts/useDemoPortalAccounts.js.map +7 -0
  23. package/dist/modules/customer_accounts/backend/customer_accounts/users/PortalUsersPageClient.js +6 -1
  24. package/dist/modules/customer_accounts/backend/customer_accounts/users/PortalUsersPageClient.js.map +2 -2
  25. package/dist/modules/customer_accounts/data/entities.js +4 -1
  26. package/dist/modules/customer_accounts/data/entities.js.map +2 -2
  27. package/dist/modules/customer_accounts/lib/exampleAccounts.js +9 -0
  28. package/dist/modules/customer_accounts/lib/exampleAccounts.js.map +7 -0
  29. package/dist/modules/customer_accounts/migrations/Migration20260827160000_customer_accounts.js +16 -0
  30. package/dist/modules/customer_accounts/migrations/Migration20260827160000_customer_accounts.js.map +7 -0
  31. package/dist/modules/customer_accounts/services/customerInvitationService.js +53 -6
  32. package/dist/modules/customer_accounts/services/customerInvitationService.js.map +2 -2
  33. package/dist/modules/customer_accounts/setup.js +2 -6
  34. package/dist/modules/customer_accounts/setup.js.map +2 -2
  35. package/dist/modules/customer_accounts/widgets/injection/account-status/widget.client.js +55 -40
  36. package/dist/modules/customer_accounts/widgets/injection/account-status/widget.client.js.map +2 -2
  37. package/dist/modules/customers/commands/people.js +16 -1
  38. package/dist/modules/customers/commands/people.js.map +2 -2
  39. package/dist/modules/data_sync/backend/data-sync/runs/[id]/page.js +18 -36
  40. package/dist/modules/data_sync/backend/data-sync/runs/[id]/page.js.map +2 -2
  41. package/dist/modules/data_sync/lib/sync-schedule-service.js +25 -23
  42. package/dist/modules/data_sync/lib/sync-schedule-service.js.map +2 -2
  43. package/dist/modules/entities/api/records.js +18 -15
  44. package/dist/modules/entities/api/records.js.map +3 -3
  45. package/dist/modules/entities/lib/scoped-field-defs.js +52 -0
  46. package/dist/modules/entities/lib/scoped-field-defs.js.map +7 -0
  47. package/dist/modules/entities/lib/validation.js +4 -35
  48. package/dist/modules/entities/lib/validation.js.map +2 -2
  49. package/dist/modules/integrations/backend/integrations/[id]/page.js +32 -54
  50. package/dist/modules/integrations/backend/integrations/[id]/page.js.map +2 -2
  51. package/dist/modules/integrations/backend/integrations/bundle/[id]/page.js +12 -29
  52. package/dist/modules/integrations/backend/integrations/bundle/[id]/page.js.map +2 -2
  53. package/dist/modules/notifications/backend/profile/notification-preferences/page.meta.js +2 -2
  54. package/dist/modules/notifications/backend/profile/notification-preferences/page.meta.js.map +1 -1
  55. package/dist/modules/progress/lib/progressServiceImpl.js +14 -3
  56. package/dist/modules/progress/lib/progressServiceImpl.js.map +2 -2
  57. package/dist/modules/sales/backend/sales/documents/[id]/page.js +9 -9
  58. package/dist/modules/sales/backend/sales/documents/[id]/page.js.map +2 -2
  59. package/dist/modules/sales/components/documents/LineItemDialog.js +50 -10
  60. package/dist/modules/sales/components/documents/LineItemDialog.js.map +2 -2
  61. package/dist/modules/sales/components/documents/PaymentsSection.js +8 -3
  62. package/dist/modules/sales/components/documents/PaymentsSection.js.map +2 -2
  63. package/dist/modules/sales/components/documents/ReturnEditDialog.js +2 -7
  64. package/dist/modules/sales/components/documents/ReturnEditDialog.js.map +2 -2
  65. package/dist/modules/sales/components/documents/ReturnsSection.js +2 -7
  66. package/dist/modules/sales/components/documents/ReturnsSection.js.map +2 -2
  67. package/dist/modules/sales/components/documents/ShipmentsSection.js +3 -8
  68. package/dist/modules/sales/components/documents/ShipmentsSection.js.map +2 -2
  69. package/package.json +7 -7
  70. package/src/modules/auth/commands/users.ts +14 -1
  71. package/src/modules/auth/data/entities.ts +2 -2
  72. package/src/modules/auth/lib/backendChrome.tsx +46 -2
  73. package/src/modules/auth/lib/profile-sections.tsx +14 -3
  74. package/src/modules/auth/migrations/.snapshot-open-mercato.json +4 -2
  75. package/src/modules/auth/migrations/Migration20260907120000_auth.ts +21 -0
  76. package/src/modules/communication_channels/backend/profile/communication-channels/page.meta.ts +2 -2
  77. package/src/modules/customer_accounts/AGENTS.md +3 -1
  78. package/src/modules/customer_accounts/api/admin/demo-accounts.ts +149 -0
  79. package/src/modules/customer_accounts/api/invitations/accept.ts +23 -6
  80. package/src/modules/customer_accounts/backend/customer_accounts/settings/CustomerAccountsSettingsPageClient.tsx +43 -41
  81. package/src/modules/customer_accounts/backend/customer_accounts/useDemoPortalAccounts.ts +42 -0
  82. package/src/modules/customer_accounts/backend/customer_accounts/users/PortalUsersPageClient.tsx +10 -3
  83. package/src/modules/customer_accounts/data/entities.ts +5 -1
  84. package/src/modules/customer_accounts/i18n/de.json +4 -2
  85. package/src/modules/customer_accounts/i18n/en.json +4 -2
  86. package/src/modules/customer_accounts/i18n/es.json +4 -2
  87. package/src/modules/customer_accounts/i18n/ko.json +4 -2
  88. package/src/modules/customer_accounts/i18n/pl.json +4 -2
  89. package/src/modules/customer_accounts/lib/exampleAccounts.ts +18 -0
  90. package/src/modules/customer_accounts/migrations/.snapshot-open-mercato.json +5 -7
  91. package/src/modules/customer_accounts/migrations/Migration20260827160000_customer_accounts.ts +23 -0
  92. package/src/modules/customer_accounts/services/customerInvitationService.ts +89 -5
  93. package/src/modules/customer_accounts/setup.ts +2 -6
  94. package/src/modules/customer_accounts/widgets/injection/account-status/widget.client.tsx +74 -41
  95. package/src/modules/customers/commands/people.ts +15 -0
  96. package/src/modules/data_sync/backend/data-sync/runs/[id]/page.tsx +19 -44
  97. package/src/modules/data_sync/lib/sync-schedule-service.ts +32 -26
  98. package/src/modules/entities/api/records.ts +24 -15
  99. package/src/modules/entities/lib/scoped-field-defs.ts +76 -0
  100. package/src/modules/entities/lib/validation.ts +5 -45
  101. package/src/modules/integrations/backend/integrations/[id]/page.tsx +33 -62
  102. package/src/modules/integrations/backend/integrations/bundle/[id]/page.tsx +13 -37
  103. package/src/modules/notifications/backend/profile/notification-preferences/page.meta.ts +2 -2
  104. package/src/modules/progress/AGENTS.md +1 -1
  105. package/src/modules/progress/lib/progressServiceImpl.ts +26 -3
  106. package/src/modules/sales/backend/sales/documents/[id]/page.tsx +19 -25
  107. package/src/modules/sales/components/documents/LineItemDialog.tsx +53 -9
  108. package/src/modules/sales/components/documents/PaymentsSection.tsx +8 -8
  109. package/src/modules/sales/components/documents/ReturnEditDialog.tsx +2 -8
  110. package/src/modules/sales/components/documents/ReturnsSection.tsx +8 -7
  111. package/src/modules/sales/components/documents/ShipmentsSection.tsx +9 -8
  112. package/src/modules/sales/i18n/de.json +2 -0
  113. package/src/modules/sales/i18n/en.json +2 -0
  114. package/src/modules/sales/i18n/es.json +2 -0
  115. package/src/modules/sales/i18n/ko.json +2 -0
  116. package/src/modules/sales/i18n/pl.json +2 -0
@@ -56,6 +56,7 @@ import {
56
56
  extractCustomFieldValues,
57
57
  } from "./customFieldHelpers";
58
58
  import { canonicalizeUnitCode } from "@open-mercato/shared/lib/units/unitCodes";
59
+ import { parseLocaleNumber } from "@open-mercato/shared/lib/number";
59
60
  import { createLogger } from '@open-mercato/shared/lib/logger'
60
61
 
61
62
  const logger = createLogger('sales')
@@ -490,6 +491,30 @@ export function LineItemDialog({
490
491
  }: SalesLineDialogProps) {
491
492
  const t = useT();
492
493
  const locale = useLocale();
494
+ // These fields are hand-rolled text inputs, so the raw string a user typed reaches the
495
+ // submit handler. It carries the separator the surrounding UI displays, which follows the
496
+ // application locale — `110,70` under Polish (issue #5552). A blank field keeps its old
497
+ // "must be greater than 0" message; only genuinely unparseable input returns null.
498
+ const parseUserNumber = React.useCallback(
499
+ (value: unknown): number | null => {
500
+ if (value == null) return 0;
501
+ if (typeof value === "number") return Number.isFinite(value) ? value : null;
502
+ if (typeof value !== "string") return null;
503
+ if (!value.trim()) return 0;
504
+ return parseLocaleNumber(value, locale);
505
+ },
506
+ [locale],
507
+ );
508
+ const numberExample = React.useMemo(() => {
509
+ try {
510
+ return new Intl.NumberFormat(locale, {
511
+ minimumFractionDigits: 2,
512
+ maximumFractionDigits: 2,
513
+ }).format(110.7);
514
+ } catch {
515
+ return "110.70";
516
+ }
517
+ }, [locale]);
493
518
  const scope = useOrganizationScopeDetail();
494
519
  const resolvedOrganizationId = organizationId ?? scope.organizationId ?? null;
495
520
  const resolvedTenantId = tenantId ?? scope.tenantId ?? null;
@@ -952,7 +977,7 @@ export function LineItemDialog({
952
977
  try {
953
978
  const params = new URLSearchParams({ productId, pageSize: "20" });
954
979
  if (variantId) params.set("variantId", variantId);
955
- const quantityValue = normalizeNumber(quantity, Number.NaN);
980
+ const quantityValue = parseUserNumber(quantity) ?? Number.NaN;
956
981
  if (Number.isFinite(quantityValue) && quantityValue > 0) {
957
982
  params.set("quantity", String(quantityValue));
958
983
  }
@@ -1093,7 +1118,7 @@ export function LineItemDialog({
1093
1118
  setPriceLoading(false);
1094
1119
  }
1095
1120
  },
1096
- [t],
1121
+ [parseUserNumber, t],
1097
1122
  );
1098
1123
 
1099
1124
  const selectPriceAfterRefresh = React.useCallback(
@@ -1136,7 +1161,7 @@ export function LineItemDialog({
1136
1161
  fromUnit: string | null | undefined,
1137
1162
  toUnit: string | null | undefined,
1138
1163
  ): string | null => {
1139
- const amount = normalizeNumber(rawUnitPrice, Number.NaN);
1164
+ const amount = parseUserNumber(rawUnitPrice) ?? Number.NaN;
1140
1165
  if (!Number.isFinite(amount) || amount <= 0) return null;
1141
1166
  const fromCode = normalizeUnitCode(fromUnit);
1142
1167
  const toCode = normalizeUnitCode(toUnit);
@@ -1156,7 +1181,7 @@ export function LineItemDialog({
1156
1181
  if (!Number.isFinite(convertedAmount) || convertedAmount <= 0) return null;
1157
1182
  return normalizeUnitPriceInputValue(convertedAmount);
1158
1183
  },
1159
- [resolveUnitPriceFactor],
1184
+ [parseUserNumber, resolveUnitPriceFactor],
1160
1185
  );
1161
1186
 
1162
1187
  const applyPriceSelection = React.useCallback(
@@ -1342,8 +1367,16 @@ export function LineItemDialog({
1342
1367
  );
1343
1368
  }
1344
1369
 
1345
- const qtyNumber = Number(values.quantity ?? 0);
1346
- if (!Number.isFinite(qtyNumber) || qtyNumber <= 0) {
1370
+ const qtyNumber = parseUserNumber(values.quantity);
1371
+ if (qtyNumber === null) {
1372
+ const message = t(
1373
+ "sales.documents.items.errorQuantityInvalid",
1374
+ "Enter the quantity as a number, for example {{example}}.",
1375
+ { example: numberExample },
1376
+ );
1377
+ throw createCrudFormError(message, { quantity: message });
1378
+ }
1379
+ if (qtyNumber <= 0) {
1347
1380
  throw createCrudFormError(
1348
1381
  t(
1349
1382
  "sales.documents.items.errorQuantity",
@@ -1380,8 +1413,16 @@ export function LineItemDialog({
1380
1413
  );
1381
1414
  })();
1382
1415
 
1383
- const unitPriceNumber = Number(values.unitPrice ?? 0);
1384
- if (!Number.isFinite(unitPriceNumber) || unitPriceNumber <= 0) {
1416
+ const unitPriceNumber = parseUserNumber(values.unitPrice);
1417
+ if (unitPriceNumber === null) {
1418
+ const message = t(
1419
+ "sales.documents.items.errorUnitPriceInvalid",
1420
+ "Enter the unit price as a number, for example {{example}}.",
1421
+ { example: numberExample },
1422
+ );
1423
+ throw createCrudFormError(message, { unitPrice: message });
1424
+ }
1425
+ if (unitPriceNumber <= 0) {
1385
1426
  throw createCrudFormError(
1386
1427
  t(
1387
1428
  "sales.documents.items.errorUnitPrice",
@@ -1610,6 +1651,8 @@ export function LineItemDialog({
1610
1651
  variantOption,
1611
1652
  onSaved,
1612
1653
  closeDialog,
1654
+ numberExample,
1655
+ parseUserNumber,
1613
1656
  resolvedOrganizationId,
1614
1657
  resolvedTenantId,
1615
1658
  ],
@@ -2541,7 +2584,7 @@ export function LineItemDialog({
2541
2584
  layout: "full",
2542
2585
  component: ({ values }: FieldRenderProps) => {
2543
2586
  if (isCustomLine) return null;
2544
- const quantity = normalizeNumber(values?.quantity, Number.NaN);
2587
+ const quantity = parseUserNumber(values?.quantity) ?? Number.NaN;
2545
2588
  const enteredUnit = normalizeUnitCode(values?.quantityUnit);
2546
2589
  if (!Number.isFinite(quantity) || quantity <= 0 || !enteredUnit) {
2547
2590
  return (
@@ -2651,6 +2694,7 @@ export function LineItemDialog({
2651
2694
  selectPriceAfterRefresh,
2652
2695
  hasTaxMetadata,
2653
2696
  isShippedOrderLine,
2697
+ parseUserNumber,
2654
2698
  ]);
2655
2699
 
2656
2700
  const groups = React.useMemo<CrudFormGroup[]>(() => {
@@ -11,6 +11,7 @@ import { handleSectionMutationError, readRowUpdatedAt } from './optimisticLock'
11
11
  import type { SectionAction } from '@open-mercato/ui/backend/detail'
12
12
  import { RowActions } from '@open-mercato/ui/backend/RowActions'
13
13
  import { Button } from '@open-mercato/ui/primitives/button'
14
+ import { formatDisplayDate, formatDisplayDateTime, toUtcDateInputValue } from '@open-mercato/ui/primitives/date-format'
14
15
  import { flash } from '@open-mercato/ui/backend/FlashMessages'
15
16
  import { useOrganizationScopeDetail } from '@open-mercato/shared/lib/frontend/useOrganizationScope'
16
17
  import { useT, useLocale } from '@open-mercato/shared/lib/i18n/context'
@@ -293,19 +294,18 @@ export function SalesDocumentPaymentsSection({
293
294
  {
294
295
  accessorKey: 'receivedAt',
295
296
  header: t('sales.documents.payments.receivedAt', 'Received'),
296
- cell: ({ row }) =>
297
- row.original.receivedAt
298
- ? new Date(row.original.receivedAt).toLocaleDateString(locale)
299
- : '—',
297
+ // `receivedAt` is written from a date input (`PaymentDialog`), coerced with
298
+ // `z.coerce.date()` and stored as UTC midnight, so its day must be read in UTC.
299
+ // Reading it locally names the previous day west of UTC — and disagrees with the
300
+ // Edit dialog, which seeds from `receivedAt.slice(0, 10)`, i.e. the UTC day.
301
+ cell: ({ row }) => formatDisplayDate(toUtcDateInputValue(row.original.receivedAt), locale) ?? '—',
300
302
  },
301
303
  {
302
304
  accessorKey: 'createdAt',
303
305
  header: t('sales.documents.payments.createdAt', 'Created'),
304
- cell: ({ row }) =>
305
- row.original.createdAt ? new Date(row.original.createdAt).toLocaleString(locale) : '—',
306
+ cell: ({ row }) => formatDisplayDateTime(row.original.createdAt, locale) ?? '—',
306
307
  meta: {
307
- tooltipContent: (row: PaymentRow) =>
308
- row.createdAt ? new Date(row.createdAt).toLocaleString(locale) : undefined,
308
+ tooltipContent: (row: PaymentRow) => formatDisplayDateTime(row.createdAt, locale) ?? undefined,
309
309
  },
310
310
  },
311
311
  {
@@ -1,6 +1,7 @@
1
1
  "use client"
2
2
 
3
3
  import * as React from 'react'
4
+ import { toUtcDateInputValue } from '@open-mercato/ui/primitives/date-format'
4
5
  import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@open-mercato/ui/primitives/dialog'
5
6
  import { useDialogKeyHandler } from '@open-mercato/ui/hooks/useDialogKeyHandler'
6
7
  import { CrudForm, type CrudField } from '@open-mercato/ui/backend/CrudForm'
@@ -36,13 +37,6 @@ type ReturnEditDialogProps = {
36
37
  onSaved: () => Promise<void>
37
38
  }
38
39
 
39
- function toDateInputValue(value: string | null | undefined): string {
40
- if (!value) return ''
41
- const date = new Date(value)
42
- if (Number.isNaN(date.getTime())) return ''
43
- return date.toISOString().slice(0, 10)
44
- }
45
-
46
40
  export function ReturnEditDialog({
47
41
  open,
48
42
  returnRecord,
@@ -62,7 +56,7 @@ export function ReturnEditDialog({
62
56
  updatedAt: returnRecord?.updatedAt ?? null,
63
57
  reason: returnRecord?.reason ?? '',
64
58
  notes: returnRecord?.notes ?? '',
65
- returnedAt: toDateInputValue(returnRecord?.returnedAt),
59
+ returnedAt: toUtcDateInputValue(returnRecord?.returnedAt) ?? '',
66
60
  }),
67
61
  [returnRecord],
68
62
  )
@@ -1,6 +1,7 @@
1
1
  "use client"
2
2
 
3
3
  import * as React from 'react'
4
+ import { formatDisplayDate, toUtcDateInputValue } from '@open-mercato/ui/primitives/date-format'
4
5
  import { Undo2, Plus } from 'lucide-react'
5
6
  import { Button } from '@open-mercato/ui/primitives/button'
6
7
  import { Badge } from '@open-mercato/ui/primitives/badge'
@@ -41,12 +42,8 @@ type SalesReturnsSectionProps = {
41
42
  documentUpdatedAt?: string | null
42
43
  }
43
44
 
44
- export function formatDisplayDate(value: string | null | undefined, locale?: string): string | null {
45
- if (!value) return null
46
- const date = new Date(value)
47
- if (Number.isNaN(date.getTime())) return null
48
- return new Intl.DateTimeFormat(locale, { dateStyle: 'medium' }).format(date)
49
- }
45
+ // One date formatter for this page rather than three with incompatible semantics.
46
+ export { formatDisplayDate }
50
47
 
51
48
  export function SalesReturnsSection({ orderId, currencyCode, documentUpdatedAt }: SalesReturnsSectionProps) {
52
49
  const t = useT()
@@ -329,7 +326,11 @@ export function SalesReturnsSection({ orderId, currencyCode, documentUpdatedAt }
329
326
  </div>
330
327
  </div>
331
328
  <div className="whitespace-nowrap text-right text-sm text-muted-foreground">
332
- {formatDisplayDate(ret.returnedAt, locale) ?? t('sales.returns.notSet', 'Not set')}
329
+ {/* `returnedAt` is submitted as a bare `yyyy-MM-dd` by `ReturnEditDialog`, coerced by
330
+ `z.coerce.date()` and returned via `.toISOString()`, and that dialog seeds itself
331
+ back from the UTC day — unconditionally, however the row was written. Matching the
332
+ dialog is the invariant, so the row cannot contradict itself west of UTC. */}
333
+ {formatDisplayDate(toUtcDateInputValue(ret.returnedAt), locale) ?? t('sales.returns.notSet', 'Not set')}
333
334
  </div>
334
335
  <div className="whitespace-nowrap text-right text-sm font-medium">
335
336
  {formatMoney(ret.total, currencyCode ?? null, locale)}
@@ -1,6 +1,7 @@
1
1
  "use client"
2
2
 
3
3
  import * as React from 'react'
4
+ import { formatDisplayDate, toUtcDateInputValue } from '@open-mercato/ui/primitives/date-format'
4
5
  import { Pencil, Plus, Trash2, Truck } from 'lucide-react'
5
6
  import { Button } from '@open-mercato/ui/primitives/button'
6
7
  import { Badge } from '@open-mercato/ui/primitives/badge'
@@ -41,12 +42,8 @@ type SalesShipmentsSectionProps = {
41
42
  onAddComment?: (body: string) => Promise<void>
42
43
  }
43
44
 
44
- export function formatDisplayDate(value: string | null | undefined, locale?: string): string | null {
45
- if (!value) return null
46
- const date = new Date(value)
47
- if (Number.isNaN(date.getTime())) return null
48
- return new Intl.DateTimeFormat(locale, { dateStyle: 'medium' }).format(date)
49
- }
45
+ // One date formatter for this page rather than three with incompatible semantics.
46
+ export { formatDisplayDate }
50
47
 
51
48
  const formatShipmentAddress = (metadata?: Record<string, unknown> | null): string | null => {
52
49
  if (!metadata || typeof metadata !== 'object') return null
@@ -487,8 +484,12 @@ export function SalesShipmentsSection({
487
484
  ) : (
488
485
  <div className="grid gap-3 md:grid-cols-2">
489
486
  {shipments.map((shipment) => {
490
- const shippedAt = formatDisplayDate(shipment.shippedAt, locale)
491
- const deliveredAt = formatDisplayDate(shipment.deliveredAt, locale)
487
+ // `shippedAt` / `deliveredAt` are written from date inputs (`ShipmentDialog`), coerced
488
+ // by `z.coerce.date()` and stored as UTC midnight, and the dialog seeds itself back from
489
+ // `.slice(0, 10)` — the UTC day. Reading them locally names the previous day west of UTC
490
+ // and disagrees with that dialog on the same row.
491
+ const shippedAt = formatDisplayDate(toUtcDateInputValue(shipment.shippedAt), locale)
492
+ const deliveredAt = formatDisplayDate(toUtcDateInputValue(shipment.deliveredAt), locale)
492
493
  const addressSummary = formatShipmentAddress(shipment.metadata)
493
494
  const statusLabel =
494
495
  shipment.statusLabel ??
@@ -1016,10 +1016,12 @@
1016
1016
  "sales.documents.items.errorProductRequired": "Wähle ein Produkt aus, um fortzufahren.",
1017
1017
  "sales.documents.items.errorQuantity": "Die Menge muss größer als 0 sein.",
1018
1018
  "sales.documents.items.errorQuantityBelowShipped": "Die Menge kann nicht unter die bereits versandten {{shipped}} gesenkt werden.",
1019
+ "sales.documents.items.errorQuantityInvalid": "Geben Sie die Menge als Zahl ein, zum Beispiel {{example}}.",
1019
1020
  "sales.documents.items.errorQuantityShipmentsUnknown": "Die Menge kann erst gesenkt werden, wenn die Sendungen dieses Auftrags geladen wurden. Öffnen Sie den Auftrag erneut, um es noch einmal zu versuchen.",
1020
1021
  "sales.documents.items.errorSave": "Position konnte nicht gespeichert werden.",
1021
1022
  "sales.documents.items.errorScope": "Organisation und Mandant sind erforderlich.",
1022
1023
  "sales.documents.items.errorUnitPrice": "Der Stückpreis muss größer als 0 sein.",
1024
+ "sales.documents.items.errorUnitPriceInvalid": "Geben Sie den Stückpreis als Zahl ein, zum Beispiel {{example}}.",
1023
1025
  "sales.documents.items.errorVariantRequired": "Wähle eine Variante aus, um fortzufahren.",
1024
1026
  "sales.documents.items.errors.uom.conversionNotFound": "Keine Einheitenumrechnung für dieses Produkt gefunden.",
1025
1027
  "sales.documents.items.errors.uom.defaultUnitMissing": "Dem Produkt fehlt eine Standard-Basiseinheit.",
@@ -1016,10 +1016,12 @@
1016
1016
  "sales.documents.items.errorProductRequired": "Select a product to continue.",
1017
1017
  "sales.documents.items.errorQuantity": "Quantity must be greater than 0.",
1018
1018
  "sales.documents.items.errorQuantityBelowShipped": "You cannot lower the quantity below the {{shipped}} already shipped.",
1019
+ "sales.documents.items.errorQuantityInvalid": "Enter the quantity as a number, for example {{example}}.",
1019
1020
  "sales.documents.items.errorQuantityShipmentsUnknown": "The quantity cannot be lowered until this order's shipments have been read. Reopen the order to try again.",
1020
1021
  "sales.documents.items.errorSave": "Failed to save line.",
1021
1022
  "sales.documents.items.errorScope": "Organization and tenant are required.",
1022
1023
  "sales.documents.items.errorUnitPrice": "Unit price must be greater than 0.",
1024
+ "sales.documents.items.errorUnitPriceInvalid": "Enter the unit price as a number, for example {{example}}.",
1023
1025
  "sales.documents.items.errorVariantRequired": "Select a variant to continue.",
1024
1026
  "sales.documents.items.errors.uom.conversionNotFound": "No unit conversion found for this product.",
1025
1027
  "sales.documents.items.errors.uom.defaultUnitMissing": "Product is missing a default base unit.",
@@ -1016,10 +1016,12 @@
1016
1016
  "sales.documents.items.errorProductRequired": "Selecciona un producto para continuar.",
1017
1017
  "sales.documents.items.errorQuantity": "La cantidad debe ser mayor que 0.",
1018
1018
  "sales.documents.items.errorQuantityBelowShipped": "No puedes reducir la cantidad por debajo de las {{shipped}} ya enviadas.",
1019
+ "sales.documents.items.errorQuantityInvalid": "Introduce la cantidad como un número, por ejemplo {{example}}.",
1019
1020
  "sales.documents.items.errorQuantityShipmentsUnknown": "La cantidad no se puede reducir hasta que se carguen los envíos de este pedido. Vuelve a abrir el pedido para intentarlo de nuevo.",
1020
1021
  "sales.documents.items.errorSave": "No se pudo guardar la línea.",
1021
1022
  "sales.documents.items.errorScope": "Se requieren organización e inquilino.",
1022
1023
  "sales.documents.items.errorUnitPrice": "El precio unitario debe ser mayor que 0.",
1024
+ "sales.documents.items.errorUnitPriceInvalid": "Introduce el precio unitario como un número, por ejemplo {{example}}.",
1023
1025
  "sales.documents.items.errorVariantRequired": "Selecciona una variante para continuar.",
1024
1026
  "sales.documents.items.errors.uom.conversionNotFound": "No se encontró conversión de unidad para este producto.",
1025
1027
  "sales.documents.items.errors.uom.defaultUnitMissing": "El producto no tiene una unidad base predeterminada.",
@@ -1016,10 +1016,12 @@
1016
1016
  "sales.documents.items.errorProductRequired": "계속하려면 상품을 선택하세요.",
1017
1017
  "sales.documents.items.errorQuantity": "수량은 0보다 커야 합니다.",
1018
1018
  "sales.documents.items.errorQuantityBelowShipped": "이미 배송된 {{shipped}} 이하로 수량을 줄일 수 없습니다.",
1019
+ "sales.documents.items.errorQuantityInvalid": "수량을 숫자로 입력하세요. 예: {{example}}.",
1019
1020
  "sales.documents.items.errorQuantityShipmentsUnknown": "이 주문의 배송 정보를 불러올 때까지 수량을 줄일 수 없습니다. 주문을 다시 열어 재시도하세요.",
1020
1021
  "sales.documents.items.errorSave": "품목을 저장하지 못했습니다.",
1021
1022
  "sales.documents.items.errorScope": "조직 및 테넌트 정보가 필요합니다.",
1022
1023
  "sales.documents.items.errorUnitPrice": "단가는 0보다 커야 합니다.",
1024
+ "sales.documents.items.errorUnitPriceInvalid": "단가를 숫자로 입력하세요. 예: {{example}}.",
1023
1025
  "sales.documents.items.errorVariantRequired": "계속하려면 옵션(변형)을 선택하세요.",
1024
1026
  "sales.documents.items.errors.uom.conversionNotFound": "이 상품에 대한 단위 변환 정보를 찾을 수 없습니다.",
1025
1027
  "sales.documents.items.errors.uom.defaultUnitMissing": "상품에 기본 단위가 설정되어 있지 않습니다.",
@@ -1016,10 +1016,12 @@
1016
1016
  "sales.documents.items.errorProductRequired": "Wybierz produkt, aby kontynuować.",
1017
1017
  "sales.documents.items.errorQuantity": "Ilość musi być większa niż 0.",
1018
1018
  "sales.documents.items.errorQuantityBelowShipped": "Nie można obniżyć ilości poniżej {{shipped}} już wysłanych.",
1019
+ "sales.documents.items.errorQuantityInvalid": "Podaj ilość jako liczbę, na przykład {{example}}.",
1019
1020
  "sales.documents.items.errorQuantityShipmentsUnknown": "Nie można obniżyć ilości, dopóki wysyłki tego zamówienia nie zostaną wczytane. Otwórz zamówienie ponownie, aby spróbować jeszcze raz.",
1020
1021
  "sales.documents.items.errorSave": "Nie udało się zapisać pozycji.",
1021
1022
  "sales.documents.items.errorScope": "Wymagane są organizacja i najemca.",
1022
1023
  "sales.documents.items.errorUnitPrice": "Cena jednostkowa musi być większa niż 0.",
1024
+ "sales.documents.items.errorUnitPriceInvalid": "Podaj cenę jednostkową jako liczbę, na przykład {{example}}.",
1023
1025
  "sales.documents.items.errorVariantRequired": "Wybierz wariant, aby kontynuować.",
1024
1026
  "sales.documents.items.errors.uom.conversionNotFound": "Nie znaleziono przelicznika jednostek dla tego produktu.",
1025
1027
  "sales.documents.items.errors.uom.defaultUnitMissing": "Produkt nie ma zdefiniowanej domyślnej jednostki bazowej.",