@open-mercato/core 0.4.5-develop-3ce83a8b24 → 0.4.5-develop-539cff4960

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 (184) hide show
  1. package/dist/generated/entities/catalog_product/index.js +16 -0
  2. package/dist/generated/entities/catalog_product/index.js.map +2 -2
  3. package/dist/generated/entities/catalog_product_unit_conversion/index.js +27 -0
  4. package/dist/generated/entities/catalog_product_unit_conversion/index.js.map +7 -0
  5. package/dist/generated/entities/sales_credit_memo_line/index.js +7 -1
  6. package/dist/generated/entities/sales_credit_memo_line/index.js.map +2 -2
  7. package/dist/generated/entities/sales_invoice_line/index.js +7 -1
  8. package/dist/generated/entities/sales_invoice_line/index.js.map +2 -2
  9. package/dist/generated/entities/sales_order_line/index.js +6 -0
  10. package/dist/generated/entities/sales_order_line/index.js.map +2 -2
  11. package/dist/generated/entities/sales_quote_line/index.js +6 -0
  12. package/dist/generated/entities/sales_quote_line/index.js.map +2 -2
  13. package/dist/generated/entities.ids.generated.js +1 -0
  14. package/dist/generated/entities.ids.generated.js.map +2 -2
  15. package/dist/generated/entity-fields-registry.js +2 -0
  16. package/dist/generated/entity-fields-registry.js.map +2 -2
  17. package/dist/modules/catalog/api/prices/route.js +123 -8
  18. package/dist/modules/catalog/api/prices/route.js.map +2 -2
  19. package/dist/modules/catalog/api/product-unit-conversions/route.js +194 -0
  20. package/dist/modules/catalog/api/product-unit-conversions/route.js.map +7 -0
  21. package/dist/modules/catalog/api/products/route.js +351 -201
  22. package/dist/modules/catalog/api/products/route.js.map +2 -2
  23. package/dist/modules/catalog/backend/catalog/products/[id]/page.js +1267 -497
  24. package/dist/modules/catalog/backend/catalog/products/[id]/page.js.map +2 -2
  25. package/dist/modules/catalog/backend/catalog/products/create/page.js +733 -210
  26. package/dist/modules/catalog/backend/catalog/products/create/page.js.map +2 -2
  27. package/dist/modules/catalog/commands/index.js +1 -0
  28. package/dist/modules/catalog/commands/index.js.map +2 -2
  29. package/dist/modules/catalog/commands/productUnitConversions.js +503 -0
  30. package/dist/modules/catalog/commands/productUnitConversions.js.map +7 -0
  31. package/dist/modules/catalog/commands/products.js +355 -73
  32. package/dist/modules/catalog/commands/products.js.map +2 -2
  33. package/dist/modules/catalog/commands/shared.js +18 -4
  34. package/dist/modules/catalog/commands/shared.js.map +2 -2
  35. package/dist/modules/catalog/components/products/ProductUomSection.js +591 -0
  36. package/dist/modules/catalog/components/products/ProductUomSection.js.map +7 -0
  37. package/dist/modules/catalog/components/products/productForm.js +66 -5
  38. package/dist/modules/catalog/components/products/productForm.js.map +2 -2
  39. package/dist/modules/catalog/components/products/productFormUtils.js +68 -0
  40. package/dist/modules/catalog/components/products/productFormUtils.js.map +7 -0
  41. package/dist/modules/catalog/data/entities.js +86 -0
  42. package/dist/modules/catalog/data/entities.js.map +2 -2
  43. package/dist/modules/catalog/data/validators.js +65 -3
  44. package/dist/modules/catalog/data/validators.js.map +2 -2
  45. package/dist/modules/catalog/events.js +3 -0
  46. package/dist/modules/catalog/events.js.map +2 -2
  47. package/dist/modules/catalog/lib/unitCodes.js +7 -0
  48. package/dist/modules/catalog/lib/unitCodes.js.map +7 -0
  49. package/dist/modules/catalog/lib/unitResolution.js +53 -0
  50. package/dist/modules/catalog/lib/unitResolution.js.map +7 -0
  51. package/dist/modules/catalog/migrations/Migration20260218225422.js +19 -0
  52. package/dist/modules/catalog/migrations/Migration20260218225422.js.map +7 -0
  53. package/dist/modules/catalog/migrations/Migration20260219084500.js +27 -0
  54. package/dist/modules/catalog/migrations/Migration20260219084500.js.map +7 -0
  55. package/dist/modules/catalog/search.js +69 -1
  56. package/dist/modules/catalog/search.js.map +2 -2
  57. package/dist/modules/catalog/seed/examples.js +91 -42
  58. package/dist/modules/catalog/seed/examples.js.map +2 -2
  59. package/dist/modules/dashboards/seed/analytics.js +3 -0
  60. package/dist/modules/dashboards/seed/analytics.js.map +2 -2
  61. package/dist/modules/sales/api/order-lines/route.js +98 -15
  62. package/dist/modules/sales/api/order-lines/route.js.map +2 -2
  63. package/dist/modules/sales/api/quote-lines/route.js +101 -14
  64. package/dist/modules/sales/api/quote-lines/route.js.map +2 -2
  65. package/dist/modules/sales/api/quotes/public/[token]/route.js +87 -12
  66. package/dist/modules/sales/api/quotes/public/[token]/route.js.map +2 -2
  67. package/dist/modules/sales/commands/documents.js +1424 -260
  68. package/dist/modules/sales/commands/documents.js.map +3 -3
  69. package/dist/modules/sales/commands/shared.js +6 -2
  70. package/dist/modules/sales/commands/shared.js.map +2 -2
  71. package/dist/modules/sales/components/documents/ItemsSection.js +216 -86
  72. package/dist/modules/sales/components/documents/ItemsSection.js.map +2 -2
  73. package/dist/modules/sales/components/documents/LineItemDialog.js +913 -241
  74. package/dist/modules/sales/components/documents/LineItemDialog.js.map +3 -3
  75. package/dist/modules/sales/components/documents/ShipmentsSection.js +15 -3
  76. package/dist/modules/sales/components/documents/ShipmentsSection.js.map +2 -2
  77. package/dist/modules/sales/data/entities.js +59 -3
  78. package/dist/modules/sales/data/entities.js.map +2 -2
  79. package/dist/modules/sales/data/validators.js +35 -0
  80. package/dist/modules/sales/data/validators.js.map +2 -2
  81. package/dist/modules/sales/frontend/quote/[token]/page.js +15 -1
  82. package/dist/modules/sales/frontend/quote/[token]/page.js.map +2 -2
  83. package/dist/modules/sales/migrations/Migration20260218225423.js +31 -0
  84. package/dist/modules/sales/migrations/Migration20260218225423.js.map +7 -0
  85. package/dist/modules/sales/migrations/Migration20260219084501.js +71 -0
  86. package/dist/modules/sales/migrations/Migration20260219084501.js.map +7 -0
  87. package/dist/modules/sales/search.js +28 -0
  88. package/dist/modules/sales/search.js.map +2 -2
  89. package/dist/modules/sales/seed/examples.js +14 -1
  90. package/dist/modules/sales/seed/examples.js.map +2 -2
  91. package/dist/modules/sales/widgets/injection/document-history/widget.client.js +1 -1
  92. package/dist/modules/sales/widgets/injection/document-history/widget.client.js.map +2 -2
  93. package/dist/modules/staff/backend/staff/team-members/[id]/page.js +28 -15
  94. package/dist/modules/staff/backend/staff/team-members/[id]/page.js.map +2 -2
  95. package/dist/modules/staff/translations.js +9 -0
  96. package/dist/modules/staff/translations.js.map +7 -0
  97. package/dist/modules/translations/components/TranslationDrawerAction.js +97 -0
  98. package/dist/modules/translations/components/TranslationDrawerAction.js.map +7 -0
  99. package/dist/modules/translations/lib/extract-record-id.js +31 -2
  100. package/dist/modules/translations/lib/extract-record-id.js.map +2 -2
  101. package/dist/modules/translations/lib/resolve-field-list.js +3 -0
  102. package/dist/modules/translations/lib/resolve-field-list.js.map +2 -2
  103. package/dist/modules/translations/widgets/injection/translation-manager/widget.client.js +105 -36
  104. package/dist/modules/translations/widgets/injection/translation-manager/widget.client.js.map +2 -2
  105. package/dist/modules/translations/widgets/injection-table.js +18 -29
  106. package/dist/modules/translations/widgets/injection-table.js.map +2 -2
  107. package/generated/entities/catalog_product/index.ts +8 -0
  108. package/generated/entities/catalog_product_unit_conversion/index.ts +12 -0
  109. package/generated/entities/sales_credit_memo_line/index.ts +3 -0
  110. package/generated/entities/sales_invoice_line/index.ts +3 -0
  111. package/generated/entities/sales_order_line/index.ts +3 -0
  112. package/generated/entities/sales_quote_line/index.ts +3 -0
  113. package/generated/entities.ids.generated.ts +1 -0
  114. package/generated/entity-fields-registry.ts +2 -0
  115. package/package.json +2 -2
  116. package/src/modules/auth/i18n/de.json +1 -1
  117. package/src/modules/auth/i18n/en.json +1 -1
  118. package/src/modules/auth/i18n/es.json +1 -1
  119. package/src/modules/auth/i18n/pl.json +1 -1
  120. package/src/modules/catalog/api/prices/route.ts +213 -81
  121. package/src/modules/catalog/api/product-unit-conversions/route.ts +195 -0
  122. package/src/modules/catalog/api/products/route.ts +638 -402
  123. package/src/modules/catalog/backend/catalog/products/[id]/page.tsx +2085 -1072
  124. package/src/modules/catalog/backend/catalog/products/create/page.tsx +1288 -593
  125. package/src/modules/catalog/commands/index.ts +1 -0
  126. package/src/modules/catalog/commands/productUnitConversions.ts +626 -0
  127. package/src/modules/catalog/commands/products.ts +1151 -693
  128. package/src/modules/catalog/commands/shared.ts +19 -5
  129. package/src/modules/catalog/components/products/ProductUomSection.tsx +745 -0
  130. package/src/modules/catalog/components/products/productForm.ts +369 -256
  131. package/src/modules/catalog/components/products/productFormUtils.ts +82 -0
  132. package/src/modules/catalog/data/entities.ts +82 -1
  133. package/src/modules/catalog/data/validators.ts +118 -34
  134. package/src/modules/catalog/events.ts +3 -0
  135. package/src/modules/catalog/i18n/de.json +56 -0
  136. package/src/modules/catalog/i18n/en.json +56 -0
  137. package/src/modules/catalog/i18n/es.json +56 -0
  138. package/src/modules/catalog/i18n/pl.json +56 -0
  139. package/src/modules/catalog/lib/unitCodes.ts +1 -0
  140. package/src/modules/catalog/lib/unitResolution.ts +62 -0
  141. package/src/modules/catalog/migrations/.snapshot-open-mercato.json +245 -0
  142. package/src/modules/catalog/migrations/Migration20260218225422.ts +21 -0
  143. package/src/modules/catalog/migrations/Migration20260219084500.ts +26 -0
  144. package/src/modules/catalog/search.ts +73 -1
  145. package/src/modules/catalog/seed/examples.ts +552 -479
  146. package/src/modules/dashboards/i18n/de.json +1 -1
  147. package/src/modules/dashboards/i18n/en.json +1 -1
  148. package/src/modules/dashboards/i18n/es.json +1 -1
  149. package/src/modules/dashboards/i18n/pl.json +1 -1
  150. package/src/modules/dashboards/seed/analytics.ts +3 -0
  151. package/src/modules/sales/api/order-lines/route.ts +158 -68
  152. package/src/modules/sales/api/quote-lines/route.ts +161 -67
  153. package/src/modules/sales/api/quotes/public/[token]/route.ts +122 -36
  154. package/src/modules/sales/commands/documents.ts +4250 -2424
  155. package/src/modules/sales/commands/shared.ts +7 -2
  156. package/src/modules/sales/components/documents/ItemsSection.tsx +580 -310
  157. package/src/modules/sales/components/documents/LineItemDialog.tsx +1988 -833
  158. package/src/modules/sales/components/documents/ShipmentsSection.tsx +17 -3
  159. package/src/modules/sales/components/documents/lineItemTypes.ts +6 -0
  160. package/src/modules/sales/data/entities.ts +53 -0
  161. package/src/modules/sales/data/validators.ts +36 -0
  162. package/src/modules/sales/frontend/quote/[token]/page.tsx +25 -1
  163. package/src/modules/sales/i18n/de.json +23 -3
  164. package/src/modules/sales/i18n/en.json +23 -3
  165. package/src/modules/sales/i18n/es.json +23 -3
  166. package/src/modules/sales/i18n/pl.json +23 -3
  167. package/src/modules/sales/lib/types.ts +30 -0
  168. package/src/modules/sales/migrations/.snapshot-open-mercato.json +172 -0
  169. package/src/modules/sales/migrations/Migration20260218225423.ts +37 -0
  170. package/src/modules/sales/migrations/Migration20260219084501.ts +73 -0
  171. package/src/modules/sales/search.ts +28 -0
  172. package/src/modules/sales/seed/examples.ts +20 -1
  173. package/src/modules/sales/widgets/injection/document-history/widget.client.tsx +1 -1
  174. package/src/modules/staff/backend/staff/team-members/[id]/page.tsx +8 -0
  175. package/src/modules/staff/translations.ts +5 -0
  176. package/src/modules/translations/components/TranslationDrawerAction.tsx +107 -0
  177. package/src/modules/translations/lib/extract-record-id.ts +47 -3
  178. package/src/modules/translations/lib/resolve-field-list.ts +4 -0
  179. package/src/modules/translations/widgets/injection/translation-manager/widget.client.tsx +108 -36
  180. package/src/modules/translations/widgets/injection-table.ts +19 -33
  181. package/src/modules/workflows/i18n/de.json +4 -4
  182. package/src/modules/workflows/i18n/en.json +4 -4
  183. package/src/modules/workflows/i18n/es.json +4 -4
  184. package/src/modules/workflows/i18n/pl.json +4 -4
@@ -11,7 +11,10 @@ import { deleteCrud } from '@open-mercato/ui/backend/utils/crud'
11
11
  import { useOrganizationScopeDetail } from '@open-mercato/shared/lib/frontend/useOrganizationScope'
12
12
  import { useT } from '@open-mercato/shared/lib/i18n/context'
13
13
  import { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'
14
- import { emitSalesDocumentTotalsRefresh } from '@open-mercato/core/modules/sales/lib/frontend/documentTotalsEvents'
14
+ import {
15
+ emitSalesDocumentTotalsRefresh,
16
+ subscribeSalesDocumentTotalsRefresh,
17
+ } from '@open-mercato/core/modules/sales/lib/frontend/documentTotalsEvents'
15
18
  import type { SectionAction } from '@open-mercato/core/modules/customers/components/detail/types'
16
19
  import { generateTempId } from '@open-mercato/core/modules/customers/lib/detailHelpers'
17
20
  import { formatAddressString, type AddressValue } from '@open-mercato/core/modules/customers/utils/addressFormat'
@@ -157,7 +160,7 @@ export function SalesShipmentsSection({
157
160
  )
158
161
 
159
162
  const loadLines = React.useCallback(async () => {
160
- const params = new URLSearchParams({ page: '1', pageSize: '200', orderId })
163
+ const params = new URLSearchParams({ page: '1', pageSize: '100', orderId })
161
164
  const response = await apiCall<{ items?: Array<Record<string, unknown>> }>(
162
165
  `/api/sales/order-lines?${params.toString()}`,
163
166
  undefined,
@@ -204,7 +207,7 @@ export function SalesShipmentsSection({
204
207
  setLoading(true)
205
208
  setError(null)
206
209
  try {
207
- const params = new URLSearchParams({ page: '1', pageSize: '200', orderId })
210
+ const params = new URLSearchParams({ page: '1', pageSize: '100', orderId })
208
211
  const response = await apiCall<{ items?: Array<Record<string, unknown>> }>(
209
212
  `/api/sales/shipments?${params.toString()}`,
210
213
  undefined,
@@ -357,6 +360,17 @@ export function SalesShipmentsSection({
357
360
  void loadShipments()
358
361
  }, [loadLines, loadShipments])
359
362
 
363
+ React.useEffect(
364
+ () =>
365
+ subscribeSalesDocumentTotalsRefresh((detail) => {
366
+ if (detail.documentId !== orderId) return
367
+ if (detail.kind && detail.kind !== 'order') return
368
+ void loadLines()
369
+ void loadShipments()
370
+ }),
371
+ [loadLines, loadShipments, orderId],
372
+ )
373
+
360
374
  const handleOpenCreate = React.useCallback(() => {
361
375
  setDialogState({ mode: 'create', shipment: null })
362
376
  }, [])
@@ -1,9 +1,14 @@
1
+ import type { SalesLineUomSnapshot } from '../../lib/types'
2
+
1
3
  export type SalesLineRecord = {
2
4
  id: string
3
5
  name: string | null
4
6
  productId: string | null
5
7
  productVariantId: string | null
6
8
  quantity: number
9
+ quantityUnit: string | null
10
+ normalizedQuantity: number
11
+ normalizedUnit: string | null
7
12
  currencyCode: string | null
8
13
  unitPriceNet: number
9
14
  unitPriceGross: number
@@ -11,6 +16,7 @@ export type SalesLineRecord = {
11
16
  totalNet: number
12
17
  totalGross: number
13
18
  priceMode: 'net' | 'gross'
19
+ uomSnapshot: SalesLineUomSnapshot | null
14
20
  metadata: Record<string, unknown> | null
15
21
  catalogSnapshot: Record<string, unknown> | null
16
22
  customFieldSetId?: string | null
@@ -11,6 +11,7 @@ import {
11
11
  } from '@mikro-orm/core'
12
12
  import { DEFAULT_ORDER_NUMBER_FORMAT, DEFAULT_QUOTE_NUMBER_FORMAT } from '../lib/documentNumberTokens'
13
13
  import type { ShipmentItemSnapshot } from '../lib/shipments/types'
14
+ import type { SalesLineUomSnapshot } from '../lib/types'
14
15
 
15
16
  export type SalesDocumentKind = 'order' | 'quote' | 'invoice' | 'credit_memo'
16
17
  export type SalesLineKind = 'product' | 'service' | 'shipping' | 'discount' | 'adjustment'
@@ -556,6 +557,10 @@ export class SalesOrder {
556
557
  @Entity({ tableName: 'sales_order_lines' })
557
558
  @Index({ name: 'sales_order_lines_scope_idx', properties: ['order', 'organizationId', 'tenantId'] })
558
559
  @Index({ name: 'sales_order_lines_status_idx', properties: ['organizationId', 'tenantId', 'status'] })
560
+ @Index({
561
+ name: 'sales_order_lines_normalized_idx',
562
+ properties: ['organizationId', 'tenantId', 'normalizedUnit', 'normalizedQuantity'],
563
+ })
559
564
  export class SalesOrderLine {
560
565
  @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })
561
566
  id!: string
@@ -605,6 +610,15 @@ export class SalesOrderLine {
605
610
  @Property({ name: 'quantity_unit', type: 'text', nullable: true })
606
611
  quantityUnit?: string | null
607
612
 
613
+ @Property({ name: 'normalized_quantity', type: 'numeric', precision: 18, scale: 6, default: '0' })
614
+ normalizedQuantity: string = '0'
615
+
616
+ @Property({ name: 'normalized_unit', type: 'text', nullable: true })
617
+ normalizedUnit?: string | null
618
+
619
+ @Property({ name: 'uom_snapshot', type: 'jsonb', nullable: true })
620
+ uomSnapshot?: SalesLineUomSnapshot | null
621
+
608
622
  @Property({ name: 'reserved_quantity', type: 'numeric', precision: 18, scale: 4, default: '0' })
609
623
  reservedQuantity: string = '0'
610
624
 
@@ -992,6 +1006,10 @@ export class SalesQuote {
992
1006
  @Entity({ tableName: 'sales_quote_lines' })
993
1007
  @Index({ name: 'sales_quote_lines_scope_idx', properties: ['quote', 'organizationId', 'tenantId'] })
994
1008
  @Index({ name: 'sales_quote_lines_status_idx', properties: ['organizationId', 'tenantId', 'status'] })
1009
+ @Index({
1010
+ name: 'sales_quote_lines_normalized_idx',
1011
+ properties: ['organizationId', 'tenantId', 'normalizedUnit', 'normalizedQuantity'],
1012
+ })
995
1013
  export class SalesQuoteLine {
996
1014
  @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })
997
1015
  id!: string
@@ -1041,6 +1059,15 @@ export class SalesQuoteLine {
1041
1059
  @Property({ name: 'quantity_unit', type: 'text', nullable: true })
1042
1060
  quantityUnit?: string | null
1043
1061
 
1062
+ @Property({ name: 'normalized_quantity', type: 'numeric', precision: 18, scale: 6, default: '0' })
1063
+ normalizedQuantity: string = '0'
1064
+
1065
+ @Property({ name: 'normalized_unit', type: 'text', nullable: true })
1066
+ normalizedUnit?: string | null
1067
+
1068
+ @Property({ name: 'uom_snapshot', type: 'jsonb', nullable: true })
1069
+ uomSnapshot?: SalesLineUomSnapshot | null
1070
+
1044
1071
  @Property({ name: 'currency_code', type: 'text' })
1045
1072
  currencyCode!: string
1046
1073
 
@@ -1345,6 +1372,10 @@ export class SalesInvoice {
1345
1372
 
1346
1373
  @Entity({ tableName: 'sales_invoice_lines' })
1347
1374
  @Index({ name: 'sales_invoice_lines_scope_idx', properties: ['invoice', 'organizationId', 'tenantId'] })
1375
+ @Index({
1376
+ name: 'sales_invoice_lines_normalized_idx',
1377
+ properties: ['organizationId', 'tenantId', 'normalizedUnit', 'normalizedQuantity'],
1378
+ })
1348
1379
  export class SalesInvoiceLine {
1349
1380
  @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })
1350
1381
  id!: string
@@ -1376,6 +1407,15 @@ export class SalesInvoiceLine {
1376
1407
  @Property({ name: 'quantity_unit', type: 'text', nullable: true })
1377
1408
  quantityUnit?: string | null
1378
1409
 
1410
+ @Property({ name: 'normalized_quantity', type: 'numeric', precision: 18, scale: 6, default: '0' })
1411
+ normalizedQuantity: string = '0'
1412
+
1413
+ @Property({ name: 'normalized_unit', type: 'text', nullable: true })
1414
+ normalizedUnit?: string | null
1415
+
1416
+ @Property({ name: 'uom_snapshot', type: 'jsonb', nullable: true })
1417
+ uomSnapshot?: SalesLineUomSnapshot | null
1418
+
1379
1419
  @Property({ name: 'currency_code', type: 'text' })
1380
1420
  currencyCode!: string
1381
1421
 
@@ -1484,6 +1524,10 @@ export class SalesCreditMemo {
1484
1524
  name: 'sales_credit_memo_lines_scope_idx',
1485
1525
  properties: ['creditMemo', 'organizationId', 'tenantId'],
1486
1526
  })
1527
+ @Index({
1528
+ name: 'sales_credit_memo_lines_normalized_idx',
1529
+ properties: ['organizationId', 'tenantId', 'normalizedUnit', 'normalizedQuantity'],
1530
+ })
1487
1531
  export class SalesCreditMemoLine {
1488
1532
  @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })
1489
1533
  id!: string
@@ -1512,6 +1556,15 @@ export class SalesCreditMemoLine {
1512
1556
  @Property({ name: 'quantity_unit', type: 'text', nullable: true })
1513
1557
  quantityUnit?: string | null
1514
1558
 
1559
+ @Property({ name: 'normalized_quantity', type: 'numeric', precision: 18, scale: 6, default: '0' })
1560
+ normalizedQuantity: string = '0'
1561
+
1562
+ @Property({ name: 'normalized_unit', type: 'text', nullable: true })
1563
+ normalizedUnit?: string | null
1564
+
1565
+ @Property({ name: 'uom_snapshot', type: 'jsonb', nullable: true })
1566
+ uomSnapshot?: SalesLineUomSnapshot | null
1567
+
1515
1568
  @Property({ name: 'currency_code', type: 'text' })
1516
1569
  currencyCode!: string
1517
1570
 
@@ -4,6 +4,7 @@ import {
4
4
  updateDictionaryEntrySchema,
5
5
  } from '@open-mercato/core/modules/dictionaries/data/validators'
6
6
  import { getPaymentProvider, getShippingProvider } from '../lib/providers'
7
+ import { REFERENCE_UNIT_CODES } from '@open-mercato/shared/lib/units/unitCodes'
7
8
 
8
9
  const uuid = () => z.string().uuid()
9
10
 
@@ -292,6 +293,8 @@ const adjustmentKindSchema = z.string().trim().min(1).max(150)
292
293
  const linePricingSchema = z.object({
293
294
  quantity: decimal({ min: 0 }),
294
295
  quantityUnit: z.string().trim().max(25).optional(),
296
+ normalizedQuantity: decimal({ min: 0 }).optional(),
297
+ normalizedUnit: z.string().trim().max(25).nullable().optional(),
295
298
  unitPriceNet: decimal({ min: 0 }).optional(),
296
299
  unitPriceGross: decimal({ min: 0 }).optional(),
297
300
  priceId: uuid().optional(),
@@ -305,6 +308,32 @@ const linePricingSchema = z.object({
305
308
  totalGrossAmount: decimal({ min: 0 }).optional(),
306
309
  })
307
310
 
311
+ const uomSnapshotSchema = z.object({
312
+ version: z.literal(1),
313
+ productId: z.string().nullable(),
314
+ productVariantId: z.string().nullable(),
315
+ baseUnitCode: z.string().nullable(),
316
+ enteredUnitCode: z.string().nullable(),
317
+ enteredQuantity: z.string(),
318
+ toBaseFactor: z.string(),
319
+ normalizedQuantity: z.string(),
320
+ rounding: z.object({
321
+ mode: z.enum(['half_up', 'down', 'up']),
322
+ scale: z.number().int(),
323
+ }),
324
+ source: z.object({
325
+ conversionId: z.string().nullable(),
326
+ resolvedAt: z.string(),
327
+ }),
328
+ unitPriceReference: z.object({
329
+ enabled: z.boolean(),
330
+ referenceUnitCode: z.enum(REFERENCE_UNIT_CODES).nullable(),
331
+ baseQuantity: z.string().nullable(),
332
+ grossPerReference: z.string().nullable().optional(),
333
+ netPerReference: z.string().nullable().optional(),
334
+ }).optional(),
335
+ }).nullable().optional()
336
+
308
337
  const lineSharedSchema = z.object({
309
338
  kind: lineKindSchema.optional(),
310
339
  statusEntryId: uuid().optional(),
@@ -317,6 +346,7 @@ const lineSharedSchema = z.object({
317
346
  configuration: z.record(z.string(), z.unknown()).optional(),
318
347
  promotionCode: z.string().trim().max(120).optional(),
319
348
  promotionSnapshot: z.record(z.string(), z.unknown()).optional(),
349
+ uomSnapshot: uomSnapshotSchema,
320
350
  metadata,
321
351
  customFieldSetId: uuid().optional(),
322
352
  customFields: z.record(z.string(), z.unknown()).optional(),
@@ -604,6 +634,9 @@ export const invoiceCreateSchema = scoped.extend({
604
634
  description: z.string().trim().max(4000).optional(),
605
635
  quantity: decimal({ min: 0 }),
606
636
  quantityUnit: z.string().trim().max(25).optional(),
637
+ normalizedQuantity: decimal({ min: 0 }).optional(),
638
+ normalizedUnit: z.string().trim().max(25).nullable().optional(),
639
+ uomSnapshot: uomSnapshotSchema,
607
640
  currencyCode,
608
641
  unitPriceNet: decimal({ min: 0 }).optional(),
609
642
  unitPriceGross: decimal({ min: 0 }).optional(),
@@ -650,6 +683,9 @@ export const creditMemoCreateSchema = scoped.extend({
650
683
  description: z.string().trim().max(4000).optional(),
651
684
  quantity: decimal({ min: 0 }),
652
685
  quantityUnit: z.string().trim().max(25).optional(),
686
+ normalizedQuantity: decimal({ min: 0 }).optional(),
687
+ normalizedUnit: z.string().trim().max(25).nullable().optional(),
688
+ uomSnapshot: uomSnapshotSchema,
653
689
  currencyCode,
654
690
  unitPriceNet: decimal({ min: 0 }).optional(),
655
691
  unitPriceGross: decimal({ min: 0 }).optional(),
@@ -27,6 +27,14 @@ type PublicQuoteResponse = {
27
27
  description: string | null
28
28
  quantity: string
29
29
  quantityUnit: string | null
30
+ normalizedQuantity: string
31
+ normalizedUnit: string | null
32
+ unitPriceReference?: {
33
+ referenceUnitCode?: string | null
34
+ referenceUnit?: string | null
35
+ grossPerReference?: string | null
36
+ netPerReference?: string | null
37
+ } | null
30
38
  currencyCode: string
31
39
  totalGrossAmount: string
32
40
  }>
@@ -144,6 +152,23 @@ export default function QuotePublicPage({ params }: { params: { token: string }
144
152
  {t('sales.quotes.public.qty', { quantity: line.quantity })}
145
153
  {line.quantityUnit ? ` ${line.quantityUnit}` : ''}
146
154
  </p>
155
+ {line.normalizedUnit && (line.normalizedUnit !== line.quantityUnit || line.normalizedQuantity !== line.quantity) ? (
156
+ <p className="text-xs text-muted-foreground">
157
+ {line.normalizedQuantity} {line.normalizedUnit}
158
+ </p>
159
+ ) : null}
160
+ {(line.unitPriceReference?.grossPerReference || line.unitPriceReference?.netPerReference) ? (
161
+ <p className="text-xs text-muted-foreground">
162
+ {(line.unitPriceReference?.grossPerReference ?? line.unitPriceReference?.netPerReference) ?? ''}{' '}
163
+ {line.currencyCode}{' '}
164
+ {t('sales.quotes.public.perReferenceUnit', 'per 1 {{unit}}', {
165
+ unit:
166
+ line.unitPriceReference?.referenceUnitCode ??
167
+ line.unitPriceReference?.referenceUnit ??
168
+ t('sales.quotes.public.defaultUnit', 'unit'),
169
+ })}
170
+ </p>
171
+ ) : null}
147
172
  </div>
148
173
  <div className="text-right text-sm">
149
174
  <p>
@@ -196,4 +221,3 @@ export default function QuotePublicPage({ params }: { params: { token: string }
196
221
  )
197
222
  }
198
223
 
199
-
@@ -894,6 +894,7 @@
894
894
  "sales.documents.items.add": "Position hinzufügen",
895
895
  "sales.documents.items.addLine": "Position hinzufügen",
896
896
  "sales.documents.items.addTitle": "Position hinzufügen",
897
+ "sales.documents.items.baseUnitTag": "Basis",
897
898
  "sales.documents.items.cancel": "Abbrechen",
898
899
  "sales.documents.items.created": "Position hinzugefügt.",
899
900
  "sales.documents.items.deleteConfirm": "Diese Position löschen?",
@@ -911,6 +912,13 @@
911
912
  "sales.documents.items.errorScope": "Organisation und Mandant sind erforderlich.",
912
913
  "sales.documents.items.errorUnitPrice": "Der Stückpreis muss größer als 0 sein.",
913
914
  "sales.documents.items.errorVariantRequired": "Wähle eine Variante aus, um fortzufahren.",
915
+ "sales.documents.items.errors.uom.conversionNotFound": "Keine Einheitenumrechnung für dieses Produkt gefunden.",
916
+ "sales.documents.items.errors.uom.defaultUnitMissing": "Dem Produkt fehlt eine Standard-Basiseinheit.",
917
+ "sales.documents.items.errors.uom.duplicateConversion": "Eine Umrechnung für diese Einheit existiert bereits.",
918
+ "sales.documents.items.errors.uom.invalidFactor": "Der Umrechnungsfaktor muss eine positive Zahl sein.",
919
+ "sales.documents.items.errors.uom.precisionOverflow": "Das Ergebnis überschreitet die zulässige Dezimalgenauigkeit.",
920
+ "sales.documents.items.errors.uom.referenceConfigInvalid": "Die Konfiguration des Einheitspreisreferenz ist ungültig.",
921
+ "sales.documents.items.errors.uom.unitNotFound": "Der angegebene Einheitencode wurde nicht gefunden.",
914
922
  "sales.documents.items.lineMode.catalog": "Katalogprodukt",
915
923
  "sales.documents.items.lineMode.custom": "Benutzerdefinierte Position",
916
924
  "sales.documents.items.lineMode.helper": "Nutze Katalogprodukte oder erstelle eine freie Position mit eigenem Preis.",
@@ -919,6 +927,9 @@
919
927
  "sales.documents.items.name": "Name",
920
928
  "sales.documents.items.namePlaceholder": "Optionaler Positionsname",
921
929
  "sales.documents.items.price": "Preis",
930
+ "sales.documents.items.priceBasisManual": "Manueller Stückpreismodus. Der Preis wird nicht automatisch umgerechnet, bis ein Katalogpreis ausgewählt wird.",
931
+ "sales.documents.items.priceBasisMissingConversion": "Die Grundlage des Katalogpreises ist die Basiseinheit, aber die Umrechnung für die ausgewählte Einheit fehlt.",
932
+ "sales.documents.items.priceBasisTemplate": "Katalogpreis-Basis: {{baseAmount}} / {{baseUnit}}. Umgerechnet für {{unit}}: {{baseAmount}} × {{factor}} = {{convertedAmount}}.",
922
933
  "sales.documents.items.priceGross": "Brutto",
923
934
  "sales.documents.items.priceNet": "Netto",
924
935
  "sales.documents.items.priceScope.channel": "Kanal",
@@ -934,6 +945,9 @@
934
945
  "sales.documents.items.product": "Produkt",
935
946
  "sales.documents.items.productSearch": "Produkt suchen",
936
947
  "sales.documents.items.quantity": "Menge",
948
+ "sales.documents.items.quantityUnit": "Einheit",
949
+ "sales.documents.items.quantityUnitPlaceholder": "z. B. Stk.",
950
+ "sales.documents.items.quantityUnitSelect": "Einheit wählen",
937
951
  "sales.documents.items.save": "Änderungen speichern",
938
952
  "sales.documents.items.selectedProduct": "Ausgewählt {{id}}",
939
953
  "sales.documents.items.selectedVariant": "Ausgewählt {{id}}",
@@ -952,12 +966,17 @@
952
966
  "sales.documents.items.table.statusEmpty": "Kein Status",
953
967
  "sales.documents.items.table.total": "Gesamt",
954
968
  "sales.documents.items.table.unit": "Stückpreis",
969
+ "sales.documents.items.table.unitPriceReference": "{{value}} pro 1 {{unit}}",
955
970
  "sales.documents.items.taxRate": "Steuerklasse",
956
971
  "sales.documents.items.taxRate.empty": "Keine Steuerklassen verfügbar",
957
972
  "sales.documents.items.taxRate.none": "Keine Steuerklasse ausgewählt",
958
973
  "sales.documents.items.title": "Positionen",
959
974
  "sales.documents.items.unitPrice": "Stückpreis",
960
975
  "sales.documents.items.untitled": "Ohne Titel",
976
+ "sales.documents.items.uomPreview": "Normalisierte Menge",
977
+ "sales.documents.items.uomPreviewEmpty": "Einheit und Menge wählen, um die Normalisierung anzuzeigen.",
978
+ "sales.documents.items.uomPreviewTemplate": "{{quantity}} {{unit}} → {{normalized}} {{baseUnit}}",
979
+ "sales.documents.items.uomPreviewUnavailable": "Fehlende Umrechnung zur Basiseinheit.",
961
980
  "sales.documents.items.updated": "Position aktualisiert.",
962
981
  "sales.documents.items.variant": "Variante",
963
982
  "sales.documents.items.variantSearch": "Variante suchen",
@@ -1197,7 +1216,7 @@
1197
1216
  "sales.quotes.list.title": "Angebote",
1198
1217
  "sales.quotes.public.acceptButton": "Angebot annehmen",
1199
1218
  "sales.quotes.public.acceptFailed": "Angebot konnte nicht angenommen werden.",
1200
- "sales.quotes.public.acceptedMessage": "Auftrag erstellt: {orderNumber}",
1219
+ "sales.quotes.public.acceptedMessage": "Auftrag erstellt: {{orderNumber}}",
1201
1220
  "sales.quotes.public.acceptedTitle": "Angebot angenommen",
1202
1221
  "sales.quotes.public.discount": "Rabatt",
1203
1222
  "sales.quotes.public.expired": "Dieses Angebot ist abgelaufen.",
@@ -1209,12 +1228,13 @@
1209
1228
  "sales.quotes.public.noValidityDate": "Kein Gültigkeitsdatum",
1210
1229
  "sales.quotes.public.notFound": "Angebot nicht gefunden.",
1211
1230
  "sales.quotes.public.pageTitle": "Angebot",
1212
- "sales.quotes.public.qty": "Menge: {quantity}",
1231
+ "sales.quotes.public.perReferenceUnit": "pro 1 {{unit}}",
1232
+ "sales.quotes.public.qty": "Menge: {{quantity}}",
1213
1233
  "sales.quotes.public.subtotalGross": "Zwischensumme (brutto)",
1214
1234
  "sales.quotes.public.tax": "Steuer",
1215
1235
  "sales.quotes.public.total": "Gesamt",
1216
1236
  "sales.quotes.public.totals": "Summen",
1217
- "sales.quotes.public.validUntil": "Gültig bis {date}",
1237
+ "sales.quotes.public.validUntil": "Gültig bis {{date}}",
1218
1238
  "sales.quotes.send.action": "An Kunden senden",
1219
1239
  "sales.quotes.send.cancel": "Abbrechen",
1220
1240
  "sales.quotes.send.canceled": "Stornierte Angebote können nicht gesendet werden.",
@@ -894,6 +894,7 @@
894
894
  "sales.documents.items.add": "Add item",
895
895
  "sales.documents.items.addLine": "Add item",
896
896
  "sales.documents.items.addTitle": "Add line",
897
+ "sales.documents.items.baseUnitTag": "base",
897
898
  "sales.documents.items.cancel": "Cancel",
898
899
  "sales.documents.items.created": "Line added.",
899
900
  "sales.documents.items.deleteConfirm": "Delete this line item?",
@@ -911,6 +912,13 @@
911
912
  "sales.documents.items.errorScope": "Organization and tenant are required.",
912
913
  "sales.documents.items.errorUnitPrice": "Unit price must be greater than 0.",
913
914
  "sales.documents.items.errorVariantRequired": "Select a variant to continue.",
915
+ "sales.documents.items.errors.uom.conversionNotFound": "No unit conversion found for this product.",
916
+ "sales.documents.items.errors.uom.defaultUnitMissing": "Product is missing a default base unit.",
917
+ "sales.documents.items.errors.uom.duplicateConversion": "A conversion for this unit already exists.",
918
+ "sales.documents.items.errors.uom.invalidFactor": "Conversion factor must be a positive number.",
919
+ "sales.documents.items.errors.uom.precisionOverflow": "Result exceeds the allowed decimal precision.",
920
+ "sales.documents.items.errors.uom.referenceConfigInvalid": "Unit price reference configuration is invalid.",
921
+ "sales.documents.items.errors.uom.unitNotFound": "The specified unit code was not found.",
914
922
  "sales.documents.items.lineMode.catalog": "Catalog item",
915
923
  "sales.documents.items.lineMode.custom": "Custom line",
916
924
  "sales.documents.items.lineMode.helper": "Use catalog products or create a freeform line with your own price.",
@@ -919,6 +927,9 @@
919
927
  "sales.documents.items.name": "Name",
920
928
  "sales.documents.items.namePlaceholder": "Optional line name",
921
929
  "sales.documents.items.price": "Price",
930
+ "sales.documents.items.priceBasisManual": "Manual unit price mode. Price will not auto-convert until you select a catalog price.",
931
+ "sales.documents.items.priceBasisMissingConversion": "Catalog price basis is base unit, but conversion for selected unit is missing.",
932
+ "sales.documents.items.priceBasisTemplate": "Catalog price basis: {{baseAmount}} / {{baseUnit}}. Converted for {{unit}}: {{baseAmount}} × {{factor}} = {{convertedAmount}}.",
922
933
  "sales.documents.items.priceGross": "Gross",
923
934
  "sales.documents.items.priceNet": "Net",
924
935
  "sales.documents.items.priceScope.channel": "Channel",
@@ -934,6 +945,9 @@
934
945
  "sales.documents.items.product": "Product",
935
946
  "sales.documents.items.productSearch": "Search product",
936
947
  "sales.documents.items.quantity": "Quantity",
948
+ "sales.documents.items.quantityUnit": "Unit",
949
+ "sales.documents.items.quantityUnitPlaceholder": "e.g. pc",
950
+ "sales.documents.items.quantityUnitSelect": "Select unit",
937
951
  "sales.documents.items.save": "Save changes",
938
952
  "sales.documents.items.selectedProduct": "Selected {{id}}",
939
953
  "sales.documents.items.selectedVariant": "Selected {{id}}",
@@ -952,12 +966,17 @@
952
966
  "sales.documents.items.table.statusEmpty": "No status",
953
967
  "sales.documents.items.table.total": "Total",
954
968
  "sales.documents.items.table.unit": "Unit price",
969
+ "sales.documents.items.table.unitPriceReference": "{{value}} per 1 {{unit}}",
955
970
  "sales.documents.items.taxRate": "Tax class",
956
971
  "sales.documents.items.taxRate.empty": "No tax classes available",
957
972
  "sales.documents.items.taxRate.none": "No tax class selected",
958
973
  "sales.documents.items.title": "Items",
959
974
  "sales.documents.items.unitPrice": "Unit price",
960
975
  "sales.documents.items.untitled": "Untitled",
976
+ "sales.documents.items.uomPreview": "Normalized quantity",
977
+ "sales.documents.items.uomPreviewEmpty": "Select unit and quantity to preview normalization.",
978
+ "sales.documents.items.uomPreviewTemplate": "{{quantity}} {{unit}} → {{normalized}} {{baseUnit}}",
979
+ "sales.documents.items.uomPreviewUnavailable": "Missing conversion to base unit.",
961
980
  "sales.documents.items.updated": "Line updated.",
962
981
  "sales.documents.items.variant": "Variant",
963
982
  "sales.documents.items.variantSearch": "Search variant",
@@ -1197,7 +1216,7 @@
1197
1216
  "sales.quotes.list.title": "Quotes",
1198
1217
  "sales.quotes.public.acceptButton": "Accept quote",
1199
1218
  "sales.quotes.public.acceptFailed": "Failed to accept quote.",
1200
- "sales.quotes.public.acceptedMessage": "Order created: {orderNumber}",
1219
+ "sales.quotes.public.acceptedMessage": "Order created: {{orderNumber}}",
1201
1220
  "sales.quotes.public.acceptedTitle": "Quote accepted",
1202
1221
  "sales.quotes.public.discount": "Discount",
1203
1222
  "sales.quotes.public.expired": "This quote has expired.",
@@ -1209,12 +1228,13 @@
1209
1228
  "sales.quotes.public.noValidityDate": "No validity date",
1210
1229
  "sales.quotes.public.notFound": "Quote not found.",
1211
1230
  "sales.quotes.public.pageTitle": "Quote",
1212
- "sales.quotes.public.qty": "Qty: {quantity}",
1231
+ "sales.quotes.public.perReferenceUnit": "per 1 {{unit}}",
1232
+ "sales.quotes.public.qty": "Qty: {{quantity}}",
1213
1233
  "sales.quotes.public.subtotalGross": "Subtotal (gross)",
1214
1234
  "sales.quotes.public.tax": "Tax",
1215
1235
  "sales.quotes.public.total": "Total",
1216
1236
  "sales.quotes.public.totals": "Totals",
1217
- "sales.quotes.public.validUntil": "Valid until {date}",
1237
+ "sales.quotes.public.validUntil": "Valid until {{date}}",
1218
1238
  "sales.quotes.send.action": "Send to customer",
1219
1239
  "sales.quotes.send.cancel": "Cancel",
1220
1240
  "sales.quotes.send.canceled": "Canceled quotes cannot be sent.",
@@ -894,6 +894,7 @@
894
894
  "sales.documents.items.add": "Agregar artículo",
895
895
  "sales.documents.items.addLine": "Agregar artículo",
896
896
  "sales.documents.items.addTitle": "Agregar línea",
897
+ "sales.documents.items.baseUnitTag": "base",
897
898
  "sales.documents.items.cancel": "Cancelar",
898
899
  "sales.documents.items.created": "Línea agregada.",
899
900
  "sales.documents.items.deleteConfirm": "¿Eliminar esta línea de artículo?",
@@ -911,6 +912,13 @@
911
912
  "sales.documents.items.errorScope": "Se requieren organización e inquilino.",
912
913
  "sales.documents.items.errorUnitPrice": "El precio unitario debe ser mayor que 0.",
913
914
  "sales.documents.items.errorVariantRequired": "Selecciona una variante para continuar.",
915
+ "sales.documents.items.errors.uom.conversionNotFound": "No se encontró conversión de unidad para este producto.",
916
+ "sales.documents.items.errors.uom.defaultUnitMissing": "El producto no tiene una unidad base predeterminada.",
917
+ "sales.documents.items.errors.uom.duplicateConversion": "Ya existe una conversión para esta unidad.",
918
+ "sales.documents.items.errors.uom.invalidFactor": "El factor de conversión debe ser un número positivo.",
919
+ "sales.documents.items.errors.uom.precisionOverflow": "El resultado excede la precisión decimal permitida.",
920
+ "sales.documents.items.errors.uom.referenceConfigInvalid": "La configuración de precio unitario de referencia no es válida.",
921
+ "sales.documents.items.errors.uom.unitNotFound": "No se encontró el código de unidad especificado.",
914
922
  "sales.documents.items.lineMode.catalog": "Artículo del catálogo",
915
923
  "sales.documents.items.lineMode.custom": "Línea personalizada",
916
924
  "sales.documents.items.lineMode.helper": "Usa productos del catálogo o crea una línea libre con tu propio precio.",
@@ -919,6 +927,9 @@
919
927
  "sales.documents.items.name": "Nombre",
920
928
  "sales.documents.items.namePlaceholder": "Nombre opcional de la línea",
921
929
  "sales.documents.items.price": "Precio",
930
+ "sales.documents.items.priceBasisManual": "Modo de precio unitario manual. El precio no se convertirá automáticamente hasta que selecciones un precio del catálogo.",
931
+ "sales.documents.items.priceBasisMissingConversion": "La base del precio del catálogo está en la unidad base, pero falta la conversión para la unidad seleccionada.",
932
+ "sales.documents.items.priceBasisTemplate": "Base del precio del catálogo: {{baseAmount}} / {{baseUnit}}. Convertido para {{unit}}: {{baseAmount}} × {{factor}} = {{convertedAmount}}.",
922
933
  "sales.documents.items.priceGross": "Bruto",
923
934
  "sales.documents.items.priceNet": "Neto",
924
935
  "sales.documents.items.priceScope.channel": "Canal",
@@ -934,6 +945,9 @@
934
945
  "sales.documents.items.product": "Producto",
935
946
  "sales.documents.items.productSearch": "Buscar producto",
936
947
  "sales.documents.items.quantity": "Cantidad",
948
+ "sales.documents.items.quantityUnit": "Unidad",
949
+ "sales.documents.items.quantityUnitPlaceholder": "p. ej. ud",
950
+ "sales.documents.items.quantityUnitSelect": "Seleccionar unidad",
937
951
  "sales.documents.items.save": "Guardar cambios",
938
952
  "sales.documents.items.selectedProduct": "Seleccionado {{id}}",
939
953
  "sales.documents.items.selectedVariant": "Seleccionado {{id}}",
@@ -952,12 +966,17 @@
952
966
  "sales.documents.items.table.statusEmpty": "Sin estado",
953
967
  "sales.documents.items.table.total": "Total",
954
968
  "sales.documents.items.table.unit": "Precio unitario",
969
+ "sales.documents.items.table.unitPriceReference": "{{value}} por 1 {{unit}}",
955
970
  "sales.documents.items.taxRate": "Clase de impuesto",
956
971
  "sales.documents.items.taxRate.empty": "No hay clases de impuestos disponibles",
957
972
  "sales.documents.items.taxRate.none": "Ninguna clase seleccionada",
958
973
  "sales.documents.items.title": "Artículos",
959
974
  "sales.documents.items.unitPrice": "Precio unitario",
960
975
  "sales.documents.items.untitled": "Sin título",
976
+ "sales.documents.items.uomPreview": "Cantidad normalizada",
977
+ "sales.documents.items.uomPreviewEmpty": "Selecciona unidad y cantidad para ver la normalización.",
978
+ "sales.documents.items.uomPreviewTemplate": "{{quantity}} {{unit}} → {{normalized}} {{baseUnit}}",
979
+ "sales.documents.items.uomPreviewUnavailable": "Falta la conversión a unidad base.",
961
980
  "sales.documents.items.updated": "Línea actualizada.",
962
981
  "sales.documents.items.variant": "Variante",
963
982
  "sales.documents.items.variantSearch": "Buscar variante",
@@ -1197,7 +1216,7 @@
1197
1216
  "sales.quotes.list.title": "Cotizaciones",
1198
1217
  "sales.quotes.public.acceptButton": "Aceptar cotización",
1199
1218
  "sales.quotes.public.acceptFailed": "No se pudo aceptar la cotización.",
1200
- "sales.quotes.public.acceptedMessage": "Pedido creado: {orderNumber}",
1219
+ "sales.quotes.public.acceptedMessage": "Pedido creado: {{orderNumber}}",
1201
1220
  "sales.quotes.public.acceptedTitle": "Cotización aceptada",
1202
1221
  "sales.quotes.public.discount": "Descuento",
1203
1222
  "sales.quotes.public.expired": "Esta cotización ha expirado.",
@@ -1209,12 +1228,13 @@
1209
1228
  "sales.quotes.public.noValidityDate": "Sin fecha de validez",
1210
1229
  "sales.quotes.public.notFound": "Cotización no encontrada.",
1211
1230
  "sales.quotes.public.pageTitle": "Cotización",
1212
- "sales.quotes.public.qty": "Cant.: {quantity}",
1231
+ "sales.quotes.public.perReferenceUnit": "por 1 {{unit}}",
1232
+ "sales.quotes.public.qty": "Cant.: {{quantity}}",
1213
1233
  "sales.quotes.public.subtotalGross": "Subtotal (bruto)",
1214
1234
  "sales.quotes.public.tax": "Impuesto",
1215
1235
  "sales.quotes.public.total": "Total",
1216
1236
  "sales.quotes.public.totals": "Totales",
1217
- "sales.quotes.public.validUntil": "Válida hasta {date}",
1237
+ "sales.quotes.public.validUntil": "Válida hasta {{date}}",
1218
1238
  "sales.quotes.send.action": "Enviar al cliente",
1219
1239
  "sales.quotes.send.cancel": "Cancelar",
1220
1240
  "sales.quotes.send.canceled": "No se pueden enviar cotizaciones canceladas.",
@@ -894,6 +894,7 @@
894
894
  "sales.documents.items.add": "Dodaj pozycję",
895
895
  "sales.documents.items.addLine": "Dodaj pozycję",
896
896
  "sales.documents.items.addTitle": "Dodaj pozycję",
897
+ "sales.documents.items.baseUnitTag": "bazowa",
897
898
  "sales.documents.items.cancel": "Anuluj",
898
899
  "sales.documents.items.created": "Pozycja dodana.",
899
900
  "sales.documents.items.deleteConfirm": "Usunąć tę pozycję?",
@@ -911,6 +912,13 @@
911
912
  "sales.documents.items.errorScope": "Wymagane są organizacja i najemca.",
912
913
  "sales.documents.items.errorUnitPrice": "Cena jednostkowa musi być większa niż 0.",
913
914
  "sales.documents.items.errorVariantRequired": "Wybierz wariant, aby kontynuować.",
915
+ "sales.documents.items.errors.uom.conversionNotFound": "Nie znaleziono przelicznika jednostek dla tego produktu.",
916
+ "sales.documents.items.errors.uom.defaultUnitMissing": "Produkt nie ma zdefiniowanej domyślnej jednostki bazowej.",
917
+ "sales.documents.items.errors.uom.duplicateConversion": "Przelicznik dla tej jednostki już istnieje.",
918
+ "sales.documents.items.errors.uom.invalidFactor": "Współczynnik przeliczenia musi być liczbą dodatnią.",
919
+ "sales.documents.items.errors.uom.precisionOverflow": "Wynik przekracza dozwoloną precyzję dziesiętną.",
920
+ "sales.documents.items.errors.uom.referenceConfigInvalid": "Konfiguracja ceny jednostkowej referencyjnej jest nieprawidłowa.",
921
+ "sales.documents.items.errors.uom.unitNotFound": "Podany kod jednostki nie został znaleziony.",
914
922
  "sales.documents.items.lineMode.catalog": "Produkt z katalogu",
915
923
  "sales.documents.items.lineMode.custom": "Pozycja niestandardowa",
916
924
  "sales.documents.items.lineMode.helper": "Użyj produktów z katalogu albo dodaj własną pozycję z ceną ręcznie.",
@@ -919,6 +927,9 @@
919
927
  "sales.documents.items.name": "Nazwa",
920
928
  "sales.documents.items.namePlaceholder": "Opcjonalna nazwa pozycji",
921
929
  "sales.documents.items.price": "Cena",
930
+ "sales.documents.items.priceBasisManual": "Tryb ręcznej ceny jednostkowej. Cena nie przeliczy się automatycznie, dopóki nie wybierzesz ceny katalogowej.",
931
+ "sales.documents.items.priceBasisMissingConversion": "Podstawa ceny katalogowej jest w jednostce bazowej, ale brakuje konwersji dla wybranej jednostki.",
932
+ "sales.documents.items.priceBasisTemplate": "Podstawa ceny katalogowej: {{baseAmount}} / {{baseUnit}}. Przeliczenie dla {{unit}}: {{baseAmount}} × {{factor}} = {{convertedAmount}}.",
922
933
  "sales.documents.items.priceGross": "Brutto",
923
934
  "sales.documents.items.priceNet": "Netto",
924
935
  "sales.documents.items.priceScope.channel": "Kanał",
@@ -934,6 +945,9 @@
934
945
  "sales.documents.items.product": "Produkt",
935
946
  "sales.documents.items.productSearch": "Szukaj produktu",
936
947
  "sales.documents.items.quantity": "Ilość",
948
+ "sales.documents.items.quantityUnit": "Jednostka",
949
+ "sales.documents.items.quantityUnitPlaceholder": "np. szt.",
950
+ "sales.documents.items.quantityUnitSelect": "Wybierz jednostkę",
937
951
  "sales.documents.items.save": "Zapisz zmiany",
938
952
  "sales.documents.items.selectedProduct": "Wybrano {{id}}",
939
953
  "sales.documents.items.selectedVariant": "Wybrano {{id}}",
@@ -952,12 +966,17 @@
952
966
  "sales.documents.items.table.statusEmpty": "Brak statusu",
953
967
  "sales.documents.items.table.total": "Suma",
954
968
  "sales.documents.items.table.unit": "Cena jednostkowa",
969
+ "sales.documents.items.table.unitPriceReference": "{{value}} za 1 {{unit}}",
955
970
  "sales.documents.items.taxRate": "Klasa podatkowa",
956
971
  "sales.documents.items.taxRate.empty": "Brak dostępnych klas podatkowych",
957
972
  "sales.documents.items.taxRate.none": "Brak wybranej klasy podatkowej",
958
973
  "sales.documents.items.title": "Pozycje",
959
974
  "sales.documents.items.unitPrice": "Cena jednostkowa",
960
975
  "sales.documents.items.untitled": "Bez nazwy",
976
+ "sales.documents.items.uomPreview": "Ilość po normalizacji",
977
+ "sales.documents.items.uomPreviewEmpty": "Wybierz jednostkę i ilość, aby zobaczyć podgląd normalizacji.",
978
+ "sales.documents.items.uomPreviewTemplate": "{{quantity}} {{unit}} → {{normalized}} {{baseUnit}}",
979
+ "sales.documents.items.uomPreviewUnavailable": "Brak konwersji do jednostki bazowej.",
961
980
  "sales.documents.items.updated": "Pozycja zaktualizowana.",
962
981
  "sales.documents.items.variant": "Wariant",
963
982
  "sales.documents.items.variantSearch": "Szukaj wariantu",
@@ -1197,7 +1216,7 @@
1197
1216
  "sales.quotes.list.title": "Oferty",
1198
1217
  "sales.quotes.public.acceptButton": "Zaakceptuj ofertę",
1199
1218
  "sales.quotes.public.acceptFailed": "Nie udało się zaakceptować oferty.",
1200
- "sales.quotes.public.acceptedMessage": "Utworzono zamówienie: {orderNumber}",
1219
+ "sales.quotes.public.acceptedMessage": "Utworzono zamówienie: {{orderNumber}}",
1201
1220
  "sales.quotes.public.acceptedTitle": "Oferta zaakceptowana",
1202
1221
  "sales.quotes.public.discount": "Rabat",
1203
1222
  "sales.quotes.public.expired": "Ta oferta wygasła.",
@@ -1209,12 +1228,13 @@
1209
1228
  "sales.quotes.public.noValidityDate": "Brak daty ważności",
1210
1229
  "sales.quotes.public.notFound": "Nie znaleziono oferty.",
1211
1230
  "sales.quotes.public.pageTitle": "Oferta",
1212
- "sales.quotes.public.qty": "Ilość: {quantity}",
1231
+ "sales.quotes.public.perReferenceUnit": "za 1 {{unit}}",
1232
+ "sales.quotes.public.qty": "Ilość: {{quantity}}",
1213
1233
  "sales.quotes.public.subtotalGross": "Suma częściowa (brutto)",
1214
1234
  "sales.quotes.public.tax": "Podatek",
1215
1235
  "sales.quotes.public.total": "Suma",
1216
1236
  "sales.quotes.public.totals": "Sumy",
1217
- "sales.quotes.public.validUntil": "Ważna do {date}",
1237
+ "sales.quotes.public.validUntil": "Ważna do {{date}}",
1218
1238
  "sales.quotes.send.action": "Wyślij do klienta",
1219
1239
  "sales.quotes.send.cancel": "Anuluj",
1220
1240
  "sales.quotes.send.canceled": "Anulowanych ofert nie można wysłać.",