@dainamite/cpq 0.6.0 → 0.7.0

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 (164) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/modules/cpq/backend/cpq/inventory/AssetsTable.js +3 -9
  3. package/dist/modules/cpq/backend/cpq/inventory/AssetsTable.js.map +2 -2
  4. package/dist/modules/cpq/backend/cpq/inventory/SubscriptionsTable.js +21 -25
  5. package/dist/modules/cpq/backend/cpq/inventory/SubscriptionsTable.js.map +2 -2
  6. package/dist/modules/cpq/backend/cpq/inventory/assets/[id]/page.js +48 -47
  7. package/dist/modules/cpq/backend/cpq/inventory/assets/[id]/page.js.map +2 -2
  8. package/dist/modules/cpq/backend/cpq/inventory/assets/[id]/page.meta.js +1 -1
  9. package/dist/modules/cpq/backend/cpq/inventory/assets/[id]/page.meta.js.map +2 -2
  10. package/dist/modules/cpq/backend/cpq/inventory/page.meta.js +2 -2
  11. package/dist/modules/cpq/backend/cpq/inventory/page.meta.js.map +2 -2
  12. package/dist/modules/cpq/backend/cpq/inventory/shared.js +37 -2
  13. package/dist/modules/cpq/backend/cpq/inventory/shared.js.map +2 -2
  14. package/dist/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.js +154 -178
  15. package/dist/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.js.map +2 -2
  16. package/dist/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.meta.js +1 -1
  17. package/dist/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.meta.js.map +2 -2
  18. package/dist/modules/cpq/backend/cpq/offerings/[id]/page.js +168 -143
  19. package/dist/modules/cpq/backend/cpq/offerings/[id]/page.js.map +2 -2
  20. package/dist/modules/cpq/backend/cpq/offerings/page.js +36 -34
  21. package/dist/modules/cpq/backend/cpq/offerings/page.js.map +2 -2
  22. package/dist/modules/cpq/backend/cpq/orders/[id]/page.js +69 -66
  23. package/dist/modules/cpq/backend/cpq/orders/[id]/page.js.map +2 -2
  24. package/dist/modules/cpq/backend/cpq/orders/page.js +6 -6
  25. package/dist/modules/cpq/backend/cpq/orders/page.js.map +2 -2
  26. package/dist/modules/cpq/backend/cpq/price-rules/PriceRuleForm.js +41 -36
  27. package/dist/modules/cpq/backend/cpq/price-rules/PriceRuleForm.js.map +2 -2
  28. package/dist/modules/cpq/backend/cpq/price-rules/[id]/page.js +76 -72
  29. package/dist/modules/cpq/backend/cpq/price-rules/[id]/page.js.map +2 -2
  30. package/dist/modules/cpq/backend/cpq/price-rules/[id]/page.meta.js +1 -1
  31. package/dist/modules/cpq/backend/cpq/price-rules/[id]/page.meta.js.map +2 -2
  32. package/dist/modules/cpq/backend/cpq/price-rules/page.js +18 -15
  33. package/dist/modules/cpq/backend/cpq/price-rules/page.js.map +2 -2
  34. package/dist/modules/cpq/backend/cpq/pricing/[id]/EntriesEditor.js +23 -21
  35. package/dist/modules/cpq/backend/cpq/pricing/[id]/EntriesEditor.js.map +2 -2
  36. package/dist/modules/cpq/backend/cpq/pricing/[id]/ImportPreview.js +22 -50
  37. package/dist/modules/cpq/backend/cpq/pricing/[id]/ImportPreview.js.map +2 -2
  38. package/dist/modules/cpq/backend/cpq/pricing/[id]/page.js +74 -52
  39. package/dist/modules/cpq/backend/cpq/pricing/[id]/page.js.map +2 -2
  40. package/dist/modules/cpq/backend/cpq/pricing/[id]/xlsxImport.js +20 -17
  41. package/dist/modules/cpq/backend/cpq/pricing/[id]/xlsxImport.js.map +2 -2
  42. package/dist/modules/cpq/backend/cpq/pricing/page.js +1 -1
  43. package/dist/modules/cpq/backend/cpq/pricing/page.js.map +2 -2
  44. package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/ArcQuoteConfigurator.js +115 -123
  45. package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/ArcQuoteConfigurator.js.map +2 -2
  46. package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/PriceAdjustmentDialog.js +6 -3
  47. package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/PriceAdjustmentDialog.js.map +2 -2
  48. package/dist/modules/cpq/backend/cpq/quotes/[id]/page.js +129 -94
  49. package/dist/modules/cpq/backend/cpq/quotes/[id]/page.js.map +2 -2
  50. package/dist/modules/cpq/backend/cpq/quotes/page.js +23 -19
  51. package/dist/modules/cpq/backend/cpq/quotes/page.js.map +2 -2
  52. package/dist/modules/cpq/backend/cpq/specifications/[id]/DependsOnRulesEditor.js +19 -15
  53. package/dist/modules/cpq/backend/cpq/specifications/[id]/DependsOnRulesEditor.js.map +2 -2
  54. package/dist/modules/cpq/backend/cpq/specifications/[id]/page.js +171 -178
  55. package/dist/modules/cpq/backend/cpq/specifications/[id]/page.js.map +2 -2
  56. package/dist/modules/cpq/backend/cpq/specifications/page.js +2 -2
  57. package/dist/modules/cpq/backend/cpq/specifications/page.js.map +2 -2
  58. package/dist/modules/cpq/backend/cpq/wizards/[definitionCode]/detail/page.js +60 -50
  59. package/dist/modules/cpq/backend/cpq/wizards/[definitionCode]/detail/page.js.map +2 -2
  60. package/dist/modules/cpq/backend/cpq/wizards/[definitionCode]/page.js +22 -18
  61. package/dist/modules/cpq/backend/cpq/wizards/[definitionCode]/page.js.map +2 -2
  62. package/dist/modules/cpq/backend/cpq/wizards/page.js +1 -1
  63. package/dist/modules/cpq/backend/cpq/wizards/page.js.map +2 -2
  64. package/dist/modules/cpq/components/ArcLineDiff.js +2 -2
  65. package/dist/modules/cpq/components/ArcLineDiff.js.map +2 -2
  66. package/dist/modules/cpq/components/ChargeBreakdownTable.js +4 -10
  67. package/dist/modules/cpq/components/ChargeBreakdownTable.js.map +2 -2
  68. package/dist/modules/cpq/components/PriceWaterfall.js +3 -1
  69. package/dist/modules/cpq/components/PriceWaterfall.js.map +2 -2
  70. package/dist/modules/cpq/components/PricingSummaryCard.js +1 -6
  71. package/dist/modules/cpq/components/PricingSummaryCard.js.map +2 -2
  72. package/dist/modules/cpq/components/StatusPath.js +5 -3
  73. package/dist/modules/cpq/components/StatusPath.js.map +2 -2
  74. package/dist/modules/cpq/components/useCpqRowActions.js +10 -22
  75. package/dist/modules/cpq/components/useCpqRowActions.js.map +2 -2
  76. package/dist/modules/cpq/i18n/de.json +1220 -0
  77. package/dist/modules/cpq/i18n/en.json +1220 -0
  78. package/dist/modules/cpq/i18n/es.json +1220 -0
  79. package/dist/modules/cpq/i18n/pl.json +1220 -0
  80. package/dist/modules/cpq/widgets/injection/customer-inventory/widget.client.js +39 -31
  81. package/dist/modules/cpq/widgets/injection/customer-inventory/widget.client.js.map +2 -2
  82. package/dist/modules/cpq/widgets/injection/order-cpq-tab/widget.client.js +13 -5
  83. package/dist/modules/cpq/widgets/injection/order-cpq-tab/widget.client.js.map +2 -2
  84. package/dist/modules/cpq/widgets/injection/quote-configurator/widget.client.js +41 -21
  85. package/dist/modules/cpq/widgets/injection/quote-configurator/widget.client.js.map +2 -2
  86. package/dist/modules/cpq/widgets/injection-table.js +2 -2
  87. package/dist/modules/cpq/widgets/injection-table.js.map +2 -2
  88. package/dist/modules/cpq/workflows/WizardRunner.js +10 -8
  89. package/dist/modules/cpq/workflows/WizardRunner.js.map +2 -2
  90. package/dist/modules/cpq/workflows/WizardTrigger.js +15 -9
  91. package/dist/modules/cpq/workflows/WizardTrigger.js.map +2 -2
  92. package/dist/modules/cpq/workflows/steps/CustomerSelectStep.js +9 -7
  93. package/dist/modules/cpq/workflows/steps/CustomerSelectStep.js.map +2 -2
  94. package/dist/modules/cpq/workflows/steps/InventorySelectStep.js +9 -4
  95. package/dist/modules/cpq/workflows/steps/InventorySelectStep.js.map +2 -2
  96. package/dist/modules/cpq/workflows/steps/ItemListStep.js +16 -19
  97. package/dist/modules/cpq/workflows/steps/ItemListStep.js.map +2 -2
  98. package/dist/modules/cpq/workflows/steps/OfferingSelectStep.js +19 -32
  99. package/dist/modules/cpq/workflows/steps/OfferingSelectStep.js.map +2 -2
  100. package/dist/modules/cpq/workflows/steps/ProductConfigureStep.js +8 -6
  101. package/dist/modules/cpq/workflows/steps/ProductConfigureStep.js.map +2 -2
  102. package/dist/modules/cpq/workflows/steps/ReviewStep.js +32 -30
  103. package/dist/modules/cpq/workflows/steps/ReviewStep.js.map +2 -2
  104. package/dist/modules/cpq/workflows/steps/index.js +12 -0
  105. package/dist/modules/cpq/workflows/steps/index.js.map +2 -2
  106. package/package.json +1 -1
  107. package/src/modules/cpq/__tests__/i18n/dictionaries.test.ts +62 -0
  108. package/src/modules/cpq/__tests__/i18n/hardcodedStrings.test.ts +35 -0
  109. package/src/modules/cpq/__tests__/i18n/sourceScan.ts +109 -0
  110. package/src/modules/cpq/backend/cpq/inventory/AssetsTable.tsx +3 -9
  111. package/src/modules/cpq/backend/cpq/inventory/SubscriptionsTable.tsx +22 -26
  112. package/src/modules/cpq/backend/cpq/inventory/assets/[id]/page.meta.ts +1 -1
  113. package/src/modules/cpq/backend/cpq/inventory/assets/[id]/page.tsx +43 -46
  114. package/src/modules/cpq/backend/cpq/inventory/page.meta.ts +2 -2
  115. package/src/modules/cpq/backend/cpq/inventory/shared.tsx +37 -1
  116. package/src/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.meta.ts +1 -1
  117. package/src/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.tsx +134 -112
  118. package/src/modules/cpq/backend/cpq/offerings/[id]/page.tsx +169 -130
  119. package/src/modules/cpq/backend/cpq/offerings/page.tsx +34 -24
  120. package/src/modules/cpq/backend/cpq/orders/[id]/page.tsx +66 -43
  121. package/src/modules/cpq/backend/cpq/orders/page.tsx +6 -6
  122. package/src/modules/cpq/backend/cpq/price-rules/PriceRuleForm.tsx +48 -34
  123. package/src/modules/cpq/backend/cpq/price-rules/[id]/page.meta.ts +1 -1
  124. package/src/modules/cpq/backend/cpq/price-rules/[id]/page.tsx +76 -70
  125. package/src/modules/cpq/backend/cpq/price-rules/page.tsx +25 -16
  126. package/src/modules/cpq/backend/cpq/pricing/[id]/EntriesEditor.tsx +24 -18
  127. package/src/modules/cpq/backend/cpq/pricing/[id]/ImportPreview.tsx +29 -19
  128. package/src/modules/cpq/backend/cpq/pricing/[id]/page.tsx +95 -47
  129. package/src/modules/cpq/backend/cpq/pricing/[id]/xlsxImport.ts +23 -15
  130. package/src/modules/cpq/backend/cpq/pricing/page.tsx +1 -1
  131. package/src/modules/cpq/backend/cpq/quotes/[id]/_components/ArcQuoteConfigurator.tsx +111 -88
  132. package/src/modules/cpq/backend/cpq/quotes/[id]/_components/PriceAdjustmentDialog.tsx +3 -3
  133. package/src/modules/cpq/backend/cpq/quotes/[id]/page.tsx +134 -89
  134. package/src/modules/cpq/backend/cpq/quotes/page.tsx +29 -21
  135. package/src/modules/cpq/backend/cpq/specifications/[id]/DependsOnRulesEditor.tsx +19 -15
  136. package/src/modules/cpq/backend/cpq/specifications/[id]/page.tsx +171 -171
  137. package/src/modules/cpq/backend/cpq/specifications/page.tsx +2 -2
  138. package/src/modules/cpq/backend/cpq/wizards/[definitionCode]/detail/page.tsx +51 -41
  139. package/src/modules/cpq/backend/cpq/wizards/[definitionCode]/page.tsx +17 -13
  140. package/src/modules/cpq/backend/cpq/wizards/page.tsx +1 -1
  141. package/src/modules/cpq/components/ArcLineDiff.tsx +2 -2
  142. package/src/modules/cpq/components/ChargeBreakdownTable.tsx +4 -2
  143. package/src/modules/cpq/components/PriceWaterfall.tsx +4 -2
  144. package/src/modules/cpq/components/PricingSummaryCard.tsx +1 -1
  145. package/src/modules/cpq/components/StatusPath.tsx +7 -4
  146. package/src/modules/cpq/components/useCpqRowActions.tsx +17 -25
  147. package/src/modules/cpq/i18n/de.json +1220 -0
  148. package/src/modules/cpq/i18n/en.json +1220 -0
  149. package/src/modules/cpq/i18n/es.json +1220 -0
  150. package/src/modules/cpq/i18n/pl.json +1220 -0
  151. package/src/modules/cpq/widgets/injection/customer-inventory/widget.client.tsx +38 -31
  152. package/src/modules/cpq/widgets/injection/order-cpq-tab/widget.client.tsx +7 -5
  153. package/src/modules/cpq/widgets/injection/quote-configurator/widget.client.tsx +36 -21
  154. package/src/modules/cpq/widgets/injection-table.ts +8 -2
  155. package/src/modules/cpq/workflows/WizardRunner.tsx +8 -5
  156. package/src/modules/cpq/workflows/WizardTrigger.tsx +15 -9
  157. package/src/modules/cpq/workflows/steps/CustomerSelectStep.tsx +11 -7
  158. package/src/modules/cpq/workflows/steps/InventorySelectStep.tsx +9 -5
  159. package/src/modules/cpq/workflows/steps/ItemListStep.tsx +18 -12
  160. package/src/modules/cpq/workflows/steps/OfferingSelectStep.tsx +20 -16
  161. package/src/modules/cpq/workflows/steps/ProductConfigureStep.tsx +8 -6
  162. package/src/modules/cpq/workflows/steps/ReviewStep.tsx +33 -18
  163. package/src/modules/cpq/workflows/steps/index.ts +12 -0
  164. package/src/modules/cpq/workflows/types.ts +4 -0
@@ -1,14 +1,17 @@
1
1
  import * as XLSX from "xlsx";
2
- async function parsePricingTableXlsx(file, table, existingEntries) {
2
+ async function parsePricingTableXlsx(file, table, existingEntries, t) {
3
3
  let workbook;
4
4
  try {
5
5
  const buffer = await file.arrayBuffer();
6
6
  workbook = XLSX.read(buffer, { type: "array" });
7
7
  } catch (err) {
8
- return { ok: false, error: err instanceof Error ? `Failed to read file: ${err.message}` : "Failed to read file" };
8
+ return {
9
+ ok: false,
10
+ error: err instanceof Error ? t("cpq.pricing.import.errors.readFailedWithReason", "Failed to read file: {reason}", { reason: err.message }) : t("cpq.pricing.import.errors.readFailed", "Failed to read file")
11
+ };
9
12
  }
10
13
  const sheetName = workbook.SheetNames[0];
11
- if (!sheetName) return { ok: false, error: "Workbook has no sheets" };
14
+ if (!sheetName) return { ok: false, error: t("cpq.pricing.import.errors.noSheets", "Workbook has no sheets") };
12
15
  const sheet = workbook.Sheets[sheetName];
13
16
  const rows = XLSX.utils.sheet_to_json(sheet, {
14
17
  defval: "",
@@ -16,7 +19,7 @@ async function parsePricingTableXlsx(file, table, existingEntries) {
16
19
  // keep strings, parse numbers ourselves
17
20
  });
18
21
  if (rows.length === 0) {
19
- return { ok: false, error: "Sheet has no data rows" };
22
+ return { ok: false, error: t("cpq.pricing.import.errors.noDataRows", "Sheet has no data rows") };
20
23
  }
21
24
  const headerToKey = /* @__PURE__ */ new Map();
22
25
  for (const d of table.dimensions) {
@@ -74,18 +77,18 @@ async function parsePricingTableXlsx(file, table, existingEntries) {
74
77
  ...priceHeaders.map((p) => p.header)
75
78
  ].filter((h) => h !== null));
76
79
  for (const h of headers) {
77
- if (!knownHeaders.has(h)) warnings.push(`Ignored unknown column "${h}"`);
80
+ if (!knownHeaders.has(h)) warnings.push(t("cpq.pricing.import.warnings.unknownColumn", 'Ignored unknown column "{column}"', { column: h }));
78
81
  }
79
82
  const missingDims = table.dimensions.filter((d) => !dimensionHeaders.some((h) => h.key === d.key));
80
83
  const missingPrices = table.priceColumns.filter((c) => !priceHeaders.some((h) => h.key === c.key));
81
84
  if (missingDims.length > 0) {
82
- return { ok: false, error: `Missing dimension column(s): ${missingDims.map((d) => d.label).join(", ")}` };
85
+ return { ok: false, error: t("cpq.pricing.import.errors.missingDimensionColumns", "Missing dimension column(s): {columns}", { columns: missingDims.map((d) => d.label).join(", ") }) };
83
86
  }
84
87
  if (missingPrices.length > 0) {
85
- return { ok: false, error: `Missing price column(s): ${missingPrices.map((c) => c.label).join(", ")}` };
88
+ return { ok: false, error: t("cpq.pricing.import.errors.missingPriceColumns", "Missing price column(s): {columns}", { columns: missingPrices.map((c) => c.label).join(", ") }) };
86
89
  }
87
90
  if (!currencyHeader) {
88
- return { ok: false, error: 'Missing "currency_code" column' };
91
+ return { ok: false, error: t("cpq.pricing.import.errors.missingCurrencyColumn", 'Missing "currency_code" column') };
89
92
  }
90
93
  const parsed = [];
91
94
  for (let i = 0; i < rows.length; i++) {
@@ -97,14 +100,14 @@ async function parsePricingTableXlsx(file, table, existingEntries) {
97
100
  const rangeToStr = rangeToHeader ? toStringOrNull(raw[rangeToHeader]) : null;
98
101
  const currency = toStringOrNull(raw[currencyHeader]) ?? "";
99
102
  if (!currency) {
100
- return { ok: false, error: `Row ${rowNumber}: missing currency_code` };
103
+ return { ok: false, error: t("cpq.pricing.import.errors.rowMissingCurrency", "Row {row}: missing currency_code", { row: rowNumber }) };
101
104
  }
102
105
  if (!table.currencyCodeList.includes(currency.toUpperCase())) {
103
- return { ok: false, error: `Row ${rowNumber}: currency "${currency}" is not in the table's currency list (${table.currencyCodeList.join(", ")})` };
106
+ return { ok: false, error: t("cpq.pricing.import.errors.rowCurrencyNotAllowed", `Row {row}: currency "{currency}" is not in the table's currency list ({currencies})`, { row: rowNumber, currency, currencies: table.currencyCodeList.join(", ") }) };
104
107
  }
105
108
  const tierNumber = tierStr ? Number(tierStr) : null;
106
109
  if (tierNumber !== null && Number.isNaN(tierNumber)) {
107
- return { ok: false, error: `Row ${rowNumber}: tier_number must be a number, got "${tierStr}"` };
110
+ return { ok: false, error: t("cpq.pricing.import.errors.rowTierNotNumber", 'Row {row}: tier_number must be a number, got "{value}"', { row: rowNumber, value: tierStr ?? "" }) };
108
111
  }
109
112
  const dimensionValues = {};
110
113
  for (const { header, key } of dimensionHeaders) {
@@ -119,7 +122,7 @@ async function parsePricingTableXlsx(file, table, existingEntries) {
119
122
  }
120
123
  const num = Number(v);
121
124
  if (Number.isNaN(num)) {
122
- return { ok: false, error: `Row ${rowNumber}: price column "${header}" must be a number, got "${String(v)}"` };
125
+ return { ok: false, error: t("cpq.pricing.import.errors.rowPriceNotNumber", 'Row {row}: price column "{column}" must be a number, got "{value}"', { row: rowNumber, column: header, value: String(v) }) };
123
126
  }
124
127
  prices[key] = num;
125
128
  }
@@ -146,7 +149,7 @@ async function parsePricingTableXlsx(file, table, existingEntries) {
146
149
  }
147
150
  const existing = existingById.get(row.id);
148
151
  if (!existing) {
149
- warnings.push(`Row ${row.rowNumber}: id "${row.id}" not found in current entries \u2014 will be inserted as new`);
152
+ warnings.push(t("cpq.pricing.import.warnings.idNotFound", 'Row {row}: id "{id}" not found in current entries \u2014 will be inserted as new', { row: row.rowNumber, id: row.id }));
150
153
  inserts.push({ ...row, id: null });
151
154
  continue;
152
155
  }
@@ -162,7 +165,7 @@ async function parsePricingTableXlsx(file, table, existingEntries) {
162
165
  diff: { inserts, updates, deletes, unchanged, warnings }
163
166
  };
164
167
  }
165
- async function commitImportDiff(pricingTableId, diff) {
168
+ async function commitImportDiff(pricingTableId, diff, t) {
166
169
  if (diff.inserts.length > 0) {
167
170
  const payload = diff.inserts.map((r) => ({
168
171
  pricingTableId,
@@ -180,7 +183,7 @@ async function commitImportDiff(pricingTableId, diff) {
180
183
  });
181
184
  if (!res.ok) {
182
185
  const body = await res.json().catch(() => ({}));
183
- return { ok: false, error: body.error ?? "Failed to insert entries" };
186
+ return { ok: false, error: body.error ?? t("cpq.pricing.import.errors.insertFailed", "Failed to insert entries") };
184
187
  }
185
188
  }
186
189
  for (const { before, after } of diff.updates) {
@@ -199,7 +202,7 @@ async function commitImportDiff(pricingTableId, diff) {
199
202
  });
200
203
  if (!res.ok) {
201
204
  const body = await res.json().catch(() => ({}));
202
- return { ok: false, error: body.error ?? `Failed to update entry ${before.id}` };
205
+ return { ok: false, error: body.error ?? t("cpq.pricing.entries.errors.updateFailed", "Failed to update entry {id}", { id: before.id }) };
203
206
  }
204
207
  }
205
208
  for (const entry of diff.deletes) {
@@ -210,7 +213,7 @@ async function commitImportDiff(pricingTableId, diff) {
210
213
  });
211
214
  if (!res.ok) {
212
215
  const body = await res.json().catch(() => ({}));
213
- return { ok: false, error: body.error ?? `Failed to delete entry ${entry.id}` };
216
+ return { ok: false, error: body.error ?? t("cpq.pricing.entries.errors.deleteFailed", "Failed to delete entry {id}", { id: entry.id }) };
214
217
  }
215
218
  }
216
219
  return { ok: true };
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../../src/modules/cpq/backend/cpq/pricing/%5Bid%5D/xlsxImport.ts"],
4
- "sourcesContent": ["// Browser-side Excel import for pricing tables.\n//\n// Parses an .xlsx produced by `exportPricingTableToXlsx` (or any sheet with\n// the matching column shape) and computes a diff against the current\n// in-memory entries. Reconciliation key is the `id` UUID column from the\n// export \u2014 rows without an id are treated as INSERTS, entries whose id is\n// absent from the file are treated as DELETES.\n\nimport * as XLSX from 'xlsx'\n\ntype Dimension = { key: string; label: string }\ntype PriceColumn = { key: string; label: string }\n\nexport type ImportEntry = {\n id: string\n dimensionValues: Record<string, string>\n tierNumber: number | null\n rangeFrom: string | null\n rangeTo: string | null\n currencyCode: string\n prices: Record<string, number>\n}\n\ntype TableShape = {\n dimensions: Dimension[]\n priceColumns: PriceColumn[]\n currencyCodeList: string[]\n}\n\ntype ParsedRow = {\n rowNumber: number // 1-indexed (excluding header)\n id: string | null\n dimensionValues: Record<string, string>\n tierNumber: number | null\n rangeFrom: string | null\n rangeTo: string | null\n currencyCode: string\n prices: Record<string, number>\n}\n\nexport type ImportDiff = {\n inserts: ParsedRow[]\n updates: Array<{ before: ImportEntry; after: ParsedRow }>\n deletes: ImportEntry[]\n unchanged: ImportEntry[]\n warnings: string[]\n}\n\nexport type ImportResult =\n | { ok: true; diff: ImportDiff }\n | { ok: false; error: string }\n\nexport async function parsePricingTableXlsx(\n file: File,\n table: TableShape,\n existingEntries: ImportEntry[],\n): Promise<ImportResult> {\n let workbook: XLSX.WorkBook\n try {\n const buffer = await file.arrayBuffer()\n workbook = XLSX.read(buffer, { type: 'array' })\n } catch (err) {\n return { ok: false, error: err instanceof Error ? `Failed to read file: ${err.message}` : 'Failed to read file' }\n }\n\n const sheetName = workbook.SheetNames[0]\n if (!sheetName) return { ok: false, error: 'Workbook has no sheets' }\n const sheet = workbook.Sheets[sheetName]\n\n // Two-row header strategy: row 1 is human labels (from export), row 2+ is\n // data. But during round-trip the original technical keys are not in the\n // file \u2014 only labels. Re-derive keys from the table's `dimensions` and\n // `priceColumns` by matching labels first; fall back to keys for the\n // fixed columns (id, tier_number, range_from, range_to, currency_code).\n const rows = XLSX.utils.sheet_to_json<Record<string, unknown>>(sheet, {\n defval: '',\n raw: false, // keep strings, parse numbers ourselves\n })\n\n if (rows.length === 0) {\n return { ok: false, error: 'Sheet has no data rows' }\n }\n\n // Build label\u2192key map for dimensions and price columns so we can match\n // either the label or the technical key (both are valid header forms).\n const headerToKey = new Map<string, { kind: 'dimension' | 'price'; key: string }>()\n for (const d of table.dimensions) {\n headerToKey.set(d.label.toLowerCase(), { kind: 'dimension', key: d.key })\n headerToKey.set(d.key.toLowerCase(), { kind: 'dimension', key: d.key })\n }\n for (const c of table.priceColumns) {\n headerToKey.set(c.label.toLowerCase(), { kind: 'price', key: c.key })\n headerToKey.set(c.key.toLowerCase(), { kind: 'price', key: c.key })\n }\n\n // Inspect first row to discover which technical keys each column maps to.\n const firstRow = rows[0]\n const headers = Object.keys(firstRow)\n const dimensionHeaders: Array<{ header: string; key: string }> = []\n const priceHeaders: Array<{ header: string; key: string }> = []\n let idHeader: string | null = null\n let tierHeader: string | null = null\n let rangeFromHeader: string | null = null\n let rangeToHeader: string | null = null\n let currencyHeader: string | null = null\n\n for (const header of headers) {\n const lower = header.trim().toLowerCase()\n if (lower === 'id') { idHeader = header; continue }\n if (lower === 'tier_number' || lower === 'tier #' || lower === 'tier number') { tierHeader = header; continue }\n if (lower === 'range_from' || lower === 'range from') { rangeFromHeader = header; continue }\n if (lower === 'range_to' || lower === 'range to') { rangeToHeader = header; continue }\n if (lower === 'currency_code' || lower === 'currency code' || lower === 'currency') { currencyHeader = header; continue }\n const mapped = headerToKey.get(lower)\n if (!mapped) continue // unknown column \u2014 silently ignored (with warning later)\n if (mapped.kind === 'dimension') dimensionHeaders.push({ header, key: mapped.key })\n else priceHeaders.push({ header, key: mapped.key })\n }\n\n const warnings: string[] = []\n\n // Unknown columns \u2192 warning (not error \u2014 Excel files often have stray\n // columns from user notes).\n const knownHeaders = new Set<string>([\n idHeader,\n tierHeader,\n rangeFromHeader,\n rangeToHeader,\n currencyHeader,\n ...dimensionHeaders.map((d) => d.header),\n ...priceHeaders.map((p) => p.header),\n ].filter((h): h is string => h !== null))\n for (const h of headers) {\n if (!knownHeaders.has(h)) warnings.push(`Ignored unknown column \"${h}\"`)\n }\n\n // Missing required columns from the table \u2192 error.\n const missingDims = table.dimensions.filter((d) => !dimensionHeaders.some((h) => h.key === d.key))\n const missingPrices = table.priceColumns.filter((c) => !priceHeaders.some((h) => h.key === c.key))\n if (missingDims.length > 0) {\n return { ok: false, error: `Missing dimension column(s): ${missingDims.map((d) => d.label).join(', ')}` }\n }\n if (missingPrices.length > 0) {\n return { ok: false, error: `Missing price column(s): ${missingPrices.map((c) => c.label).join(', ')}` }\n }\n if (!currencyHeader) {\n return { ok: false, error: 'Missing \"currency_code\" column' }\n }\n\n // \u2500\u2500\u2500 Parse rows \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n const parsed: ParsedRow[] = []\n for (let i = 0; i < rows.length; i++) {\n const raw = rows[i]\n const rowNumber = i + 2 // +1 for header, +1 because rows are 1-indexed in Excel\n\n const id = idHeader ? toStringOrNull(raw[idHeader]) : null\n const tierStr = tierHeader ? toStringOrNull(raw[tierHeader]) : null\n const rangeFromStr = rangeFromHeader ? toStringOrNull(raw[rangeFromHeader]) : null\n const rangeToStr = rangeToHeader ? toStringOrNull(raw[rangeToHeader]) : null\n const currency = toStringOrNull(raw[currencyHeader]) ?? ''\n\n if (!currency) {\n return { ok: false, error: `Row ${rowNumber}: missing currency_code` }\n }\n if (!table.currencyCodeList.includes(currency.toUpperCase())) {\n return { ok: false, error: `Row ${rowNumber}: currency \"${currency}\" is not in the table's currency list (${table.currencyCodeList.join(', ')})` }\n }\n\n const tierNumber = tierStr ? Number(tierStr) : null\n if (tierNumber !== null && Number.isNaN(tierNumber)) {\n return { ok: false, error: `Row ${rowNumber}: tier_number must be a number, got \"${tierStr}\"` }\n }\n\n const dimensionValues: Record<string, string> = {}\n for (const { header, key } of dimensionHeaders) {\n dimensionValues[key] = toStringOrNull(raw[header]) ?? ''\n }\n\n const prices: Record<string, number> = {}\n for (const { header, key } of priceHeaders) {\n const v = raw[header]\n if (v === '' || v === null || v === undefined) {\n prices[key] = 0\n continue\n }\n const num = Number(v)\n if (Number.isNaN(num)) {\n return { ok: false, error: `Row ${rowNumber}: price column \"${header}\" must be a number, got \"${String(v)}\"` }\n }\n prices[key] = num\n }\n\n parsed.push({\n rowNumber,\n id,\n dimensionValues,\n tierNumber,\n rangeFrom: rangeFromStr,\n rangeTo: rangeToStr,\n currencyCode: currency.toUpperCase(),\n prices,\n })\n }\n\n // \u2500\u2500\u2500 Compute diff \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n const existingById = new Map<string, ImportEntry>(existingEntries.map((e) => [e.id, e]))\n const fileIds = new Set<string>(parsed.map((p) => p.id).filter((id): id is string => !!id))\n\n const inserts: ParsedRow[] = []\n const updates: Array<{ before: ImportEntry; after: ParsedRow }> = []\n const unchanged: ImportEntry[] = []\n\n for (const row of parsed) {\n if (!row.id) {\n inserts.push(row)\n continue\n }\n const existing = existingById.get(row.id)\n if (!existing) {\n warnings.push(`Row ${row.rowNumber}: id \"${row.id}\" not found in current entries \u2014 will be inserted as new`)\n inserts.push({ ...row, id: null })\n continue\n }\n if (rowMatchesEntry(row, existing)) {\n unchanged.push(existing)\n } else {\n updates.push({ before: existing, after: row })\n }\n }\n\n const deletes = existingEntries.filter((e) => !fileIds.has(e.id))\n\n return {\n ok: true,\n diff: { inserts, updates, deletes, unchanged, warnings },\n }\n}\n\n// \u2500\u2500\u2500 Commit \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport async function commitImportDiff(\n pricingTableId: string,\n diff: ImportDiff,\n): Promise<{ ok: true } | { ok: false; error: string }> {\n // INSERTS (batch \u2014 route accepts arrays)\n if (diff.inserts.length > 0) {\n const payload = diff.inserts.map((r) => ({\n pricingTableId,\n dimensionValues: r.dimensionValues,\n tierNumber: r.tierNumber ?? undefined,\n rangeFrom: r.rangeFrom !== null ? Number(r.rangeFrom) : undefined,\n rangeTo: r.rangeTo !== null ? Number(r.rangeTo) : undefined,\n currencyCode: r.currencyCode,\n prices: r.prices,\n }))\n const res = await fetch('/api/cpq/pricing-table-entries', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n })\n if (!res.ok) {\n const body = await res.json().catch(() => ({}))\n return { ok: false, error: body.error ?? 'Failed to insert entries' }\n }\n }\n\n // UPDATES (one-by-one)\n for (const { before, after } of diff.updates) {\n const res = await fetch('/api/cpq/pricing-table-entries', {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n id: before.id,\n dimensionValues: after.dimensionValues,\n tierNumber: after.tierNumber ?? null,\n rangeFrom: after.rangeFrom !== null ? Number(after.rangeFrom) : null,\n rangeTo: after.rangeTo !== null ? Number(after.rangeTo) : null,\n currencyCode: after.currencyCode,\n prices: after.prices,\n }),\n })\n if (!res.ok) {\n const body = await res.json().catch(() => ({}))\n return { ok: false, error: body.error ?? `Failed to update entry ${before.id}` }\n }\n }\n\n // DELETES (one-by-one)\n for (const entry of diff.deletes) {\n const res = await fetch('/api/cpq/pricing-table-entries', {\n method: 'DELETE',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ id: entry.id }),\n })\n if (!res.ok) {\n const body = await res.json().catch(() => ({}))\n return { ok: false, error: body.error ?? `Failed to delete entry ${entry.id}` }\n }\n }\n\n return { ok: true }\n}\n\n// \u2500\u2500\u2500 helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction toStringOrNull(v: unknown): string | null {\n if (v === null || v === undefined) return null\n const s = String(v).trim()\n return s === '' ? null : s\n}\n\nfunction rowMatchesEntry(row: ParsedRow, entry: ImportEntry): boolean {\n if (row.currencyCode !== entry.currencyCode) return false\n if ((row.tierNumber ?? null) !== (entry.tierNumber ?? null)) return false\n if (!numericEqual(row.rangeFrom, entry.rangeFrom)) return false\n if (!numericEqual(row.rangeTo, entry.rangeTo)) return false\n // dimensions\n const dimKeys = new Set([...Object.keys(row.dimensionValues), ...Object.keys(entry.dimensionValues)])\n for (const k of dimKeys) {\n if ((row.dimensionValues[k] ?? '') !== (entry.dimensionValues[k] ?? '')) return false\n }\n // prices\n const priceKeys = new Set([...Object.keys(row.prices), ...Object.keys(entry.prices)])\n for (const k of priceKeys) {\n if ((row.prices[k] ?? 0) !== (entry.prices[k] ?? 0)) return false\n }\n return true\n}\n\nfunction numericEqual(a: string | null, b: string | null): boolean {\n const an = a == null ? null : Number(a)\n const bn = b == null ? null : Number(b)\n if (an === null && bn === null) return true\n if (an === null || bn === null) return false\n return an === bn\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["// Browser-side Excel import for pricing tables.\n//\n// Parses an .xlsx produced by `exportPricingTableToXlsx` (or any sheet with\n// the matching column shape) and computes a diff against the current\n// in-memory entries. Reconciliation key is the `id` UUID column from the\n// export \u2014 rows without an id are treated as INSERTS, entries whose id is\n// absent from the file are treated as DELETES.\n\nimport * as XLSX from 'xlsx'\nimport type { TranslateFn } from '@open-mercato/shared/lib/i18n/context'\n\ntype Dimension = { key: string; label: string }\ntype PriceColumn = { key: string; label: string }\n\nexport type ImportEntry = {\n id: string\n dimensionValues: Record<string, string>\n tierNumber: number | null\n rangeFrom: string | null\n rangeTo: string | null\n currencyCode: string\n prices: Record<string, number>\n}\n\ntype TableShape = {\n dimensions: Dimension[]\n priceColumns: PriceColumn[]\n currencyCodeList: string[]\n}\n\ntype ParsedRow = {\n rowNumber: number // 1-indexed (excluding header)\n id: string | null\n dimensionValues: Record<string, string>\n tierNumber: number | null\n rangeFrom: string | null\n rangeTo: string | null\n currencyCode: string\n prices: Record<string, number>\n}\n\nexport type ImportDiff = {\n inserts: ParsedRow[]\n updates: Array<{ before: ImportEntry; after: ParsedRow }>\n deletes: ImportEntry[]\n unchanged: ImportEntry[]\n warnings: string[]\n}\n\nexport type ImportResult =\n | { ok: true; diff: ImportDiff }\n | { ok: false; error: string }\n\nexport async function parsePricingTableXlsx(\n file: File,\n table: TableShape,\n existingEntries: ImportEntry[],\n t: TranslateFn,\n): Promise<ImportResult> {\n let workbook: XLSX.WorkBook\n try {\n const buffer = await file.arrayBuffer()\n workbook = XLSX.read(buffer, { type: 'array' })\n } catch (err) {\n return {\n ok: false,\n error: err instanceof Error\n ? t('cpq.pricing.import.errors.readFailedWithReason', 'Failed to read file: {reason}', { reason: err.message })\n : t('cpq.pricing.import.errors.readFailed', 'Failed to read file'),\n }\n }\n\n const sheetName = workbook.SheetNames[0]\n if (!sheetName) return { ok: false, error: t('cpq.pricing.import.errors.noSheets', 'Workbook has no sheets') }\n const sheet = workbook.Sheets[sheetName]\n\n // Two-row header strategy: row 1 is human labels (from export), row 2+ is\n // data. But during round-trip the original technical keys are not in the\n // file \u2014 only labels. Re-derive keys from the table's `dimensions` and\n // `priceColumns` by matching labels first; fall back to keys for the\n // fixed columns (id, tier_number, range_from, range_to, currency_code).\n const rows = XLSX.utils.sheet_to_json<Record<string, unknown>>(sheet, {\n defval: '',\n raw: false, // keep strings, parse numbers ourselves\n })\n\n if (rows.length === 0) {\n return { ok: false, error: t('cpq.pricing.import.errors.noDataRows', 'Sheet has no data rows') }\n }\n\n // Build label\u2192key map for dimensions and price columns so we can match\n // either the label or the technical key (both are valid header forms).\n const headerToKey = new Map<string, { kind: 'dimension' | 'price'; key: string }>()\n for (const d of table.dimensions) {\n headerToKey.set(d.label.toLowerCase(), { kind: 'dimension', key: d.key })\n headerToKey.set(d.key.toLowerCase(), { kind: 'dimension', key: d.key })\n }\n for (const c of table.priceColumns) {\n headerToKey.set(c.label.toLowerCase(), { kind: 'price', key: c.key })\n headerToKey.set(c.key.toLowerCase(), { kind: 'price', key: c.key })\n }\n\n // Inspect first row to discover which technical keys each column maps to.\n const firstRow = rows[0]\n const headers = Object.keys(firstRow)\n const dimensionHeaders: Array<{ header: string; key: string }> = []\n const priceHeaders: Array<{ header: string; key: string }> = []\n let idHeader: string | null = null\n let tierHeader: string | null = null\n let rangeFromHeader: string | null = null\n let rangeToHeader: string | null = null\n let currencyHeader: string | null = null\n\n for (const header of headers) {\n const lower = header.trim().toLowerCase()\n if (lower === 'id') { idHeader = header; continue }\n if (lower === 'tier_number' || lower === 'tier #' || lower === 'tier number') { tierHeader = header; continue }\n if (lower === 'range_from' || lower === 'range from') { rangeFromHeader = header; continue }\n if (lower === 'range_to' || lower === 'range to') { rangeToHeader = header; continue }\n if (lower === 'currency_code' || lower === 'currency code' || lower === 'currency') { currencyHeader = header; continue }\n const mapped = headerToKey.get(lower)\n if (!mapped) continue // unknown column \u2014 silently ignored (with warning later)\n if (mapped.kind === 'dimension') dimensionHeaders.push({ header, key: mapped.key })\n else priceHeaders.push({ header, key: mapped.key })\n }\n\n const warnings: string[] = []\n\n // Unknown columns \u2192 warning (not error \u2014 Excel files often have stray\n // columns from user notes).\n const knownHeaders = new Set<string>([\n idHeader,\n tierHeader,\n rangeFromHeader,\n rangeToHeader,\n currencyHeader,\n ...dimensionHeaders.map((d) => d.header),\n ...priceHeaders.map((p) => p.header),\n ].filter((h): h is string => h !== null))\n for (const h of headers) {\n if (!knownHeaders.has(h)) warnings.push(t('cpq.pricing.import.warnings.unknownColumn', 'Ignored unknown column \"{column}\"', { column: h }))\n }\n\n // Missing required columns from the table \u2192 error.\n const missingDims = table.dimensions.filter((d) => !dimensionHeaders.some((h) => h.key === d.key))\n const missingPrices = table.priceColumns.filter((c) => !priceHeaders.some((h) => h.key === c.key))\n if (missingDims.length > 0) {\n return { ok: false, error: t('cpq.pricing.import.errors.missingDimensionColumns', 'Missing dimension column(s): {columns}', { columns: missingDims.map((d) => d.label).join(', ') }) }\n }\n if (missingPrices.length > 0) {\n return { ok: false, error: t('cpq.pricing.import.errors.missingPriceColumns', 'Missing price column(s): {columns}', { columns: missingPrices.map((c) => c.label).join(', ') }) }\n }\n if (!currencyHeader) {\n return { ok: false, error: t('cpq.pricing.import.errors.missingCurrencyColumn', 'Missing \"currency_code\" column') }\n }\n\n // \u2500\u2500\u2500 Parse rows \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n const parsed: ParsedRow[] = []\n for (let i = 0; i < rows.length; i++) {\n const raw = rows[i]\n const rowNumber = i + 2 // +1 for header, +1 because rows are 1-indexed in Excel\n\n const id = idHeader ? toStringOrNull(raw[idHeader]) : null\n const tierStr = tierHeader ? toStringOrNull(raw[tierHeader]) : null\n const rangeFromStr = rangeFromHeader ? toStringOrNull(raw[rangeFromHeader]) : null\n const rangeToStr = rangeToHeader ? toStringOrNull(raw[rangeToHeader]) : null\n const currency = toStringOrNull(raw[currencyHeader]) ?? ''\n\n if (!currency) {\n return { ok: false, error: t('cpq.pricing.import.errors.rowMissingCurrency', 'Row {row}: missing currency_code', { row: rowNumber }) }\n }\n if (!table.currencyCodeList.includes(currency.toUpperCase())) {\n return { ok: false, error: t('cpq.pricing.import.errors.rowCurrencyNotAllowed', 'Row {row}: currency \"{currency}\" is not in the table\\'s currency list ({currencies})', { row: rowNumber, currency, currencies: table.currencyCodeList.join(', ') }) }\n }\n\n const tierNumber = tierStr ? Number(tierStr) : null\n if (tierNumber !== null && Number.isNaN(tierNumber)) {\n return { ok: false, error: t('cpq.pricing.import.errors.rowTierNotNumber', 'Row {row}: tier_number must be a number, got \"{value}\"', { row: rowNumber, value: tierStr ?? '' }) }\n }\n\n const dimensionValues: Record<string, string> = {}\n for (const { header, key } of dimensionHeaders) {\n dimensionValues[key] = toStringOrNull(raw[header]) ?? ''\n }\n\n const prices: Record<string, number> = {}\n for (const { header, key } of priceHeaders) {\n const v = raw[header]\n if (v === '' || v === null || v === undefined) {\n prices[key] = 0\n continue\n }\n const num = Number(v)\n if (Number.isNaN(num)) {\n return { ok: false, error: t('cpq.pricing.import.errors.rowPriceNotNumber', 'Row {row}: price column \"{column}\" must be a number, got \"{value}\"', { row: rowNumber, column: header, value: String(v) }) }\n }\n prices[key] = num\n }\n\n parsed.push({\n rowNumber,\n id,\n dimensionValues,\n tierNumber,\n rangeFrom: rangeFromStr,\n rangeTo: rangeToStr,\n currencyCode: currency.toUpperCase(),\n prices,\n })\n }\n\n // \u2500\u2500\u2500 Compute diff \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n const existingById = new Map<string, ImportEntry>(existingEntries.map((e) => [e.id, e]))\n const fileIds = new Set<string>(parsed.map((p) => p.id).filter((id): id is string => !!id))\n\n const inserts: ParsedRow[] = []\n const updates: Array<{ before: ImportEntry; after: ParsedRow }> = []\n const unchanged: ImportEntry[] = []\n\n for (const row of parsed) {\n if (!row.id) {\n inserts.push(row)\n continue\n }\n const existing = existingById.get(row.id)\n if (!existing) {\n warnings.push(t('cpq.pricing.import.warnings.idNotFound', 'Row {row}: id \"{id}\" not found in current entries \u2014 will be inserted as new', { row: row.rowNumber, id: row.id }))\n inserts.push({ ...row, id: null })\n continue\n }\n if (rowMatchesEntry(row, existing)) {\n unchanged.push(existing)\n } else {\n updates.push({ before: existing, after: row })\n }\n }\n\n const deletes = existingEntries.filter((e) => !fileIds.has(e.id))\n\n return {\n ok: true,\n diff: { inserts, updates, deletes, unchanged, warnings },\n }\n}\n\n// \u2500\u2500\u2500 Commit \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport async function commitImportDiff(\n pricingTableId: string,\n diff: ImportDiff,\n t: TranslateFn,\n): Promise<{ ok: true } | { ok: false; error: string }> {\n // INSERTS (batch \u2014 route accepts arrays)\n if (diff.inserts.length > 0) {\n const payload = diff.inserts.map((r) => ({\n pricingTableId,\n dimensionValues: r.dimensionValues,\n tierNumber: r.tierNumber ?? undefined,\n rangeFrom: r.rangeFrom !== null ? Number(r.rangeFrom) : undefined,\n rangeTo: r.rangeTo !== null ? Number(r.rangeTo) : undefined,\n currencyCode: r.currencyCode,\n prices: r.prices,\n }))\n const res = await fetch('/api/cpq/pricing-table-entries', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n })\n if (!res.ok) {\n const body = await res.json().catch(() => ({}))\n return { ok: false, error: body.error ?? t('cpq.pricing.import.errors.insertFailed', 'Failed to insert entries') }\n }\n }\n\n // UPDATES (one-by-one)\n for (const { before, after } of diff.updates) {\n const res = await fetch('/api/cpq/pricing-table-entries', {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n id: before.id,\n dimensionValues: after.dimensionValues,\n tierNumber: after.tierNumber ?? null,\n rangeFrom: after.rangeFrom !== null ? Number(after.rangeFrom) : null,\n rangeTo: after.rangeTo !== null ? Number(after.rangeTo) : null,\n currencyCode: after.currencyCode,\n prices: after.prices,\n }),\n })\n if (!res.ok) {\n const body = await res.json().catch(() => ({}))\n return { ok: false, error: body.error ?? t('cpq.pricing.entries.errors.updateFailed', 'Failed to update entry {id}', { id: before.id }) }\n }\n }\n\n // DELETES (one-by-one)\n for (const entry of diff.deletes) {\n const res = await fetch('/api/cpq/pricing-table-entries', {\n method: 'DELETE',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ id: entry.id }),\n })\n if (!res.ok) {\n const body = await res.json().catch(() => ({}))\n return { ok: false, error: body.error ?? t('cpq.pricing.entries.errors.deleteFailed', 'Failed to delete entry {id}', { id: entry.id }) }\n }\n }\n\n return { ok: true }\n}\n\n// \u2500\u2500\u2500 helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction toStringOrNull(v: unknown): string | null {\n if (v === null || v === undefined) return null\n const s = String(v).trim()\n return s === '' ? null : s\n}\n\nfunction rowMatchesEntry(row: ParsedRow, entry: ImportEntry): boolean {\n if (row.currencyCode !== entry.currencyCode) return false\n if ((row.tierNumber ?? null) !== (entry.tierNumber ?? null)) return false\n if (!numericEqual(row.rangeFrom, entry.rangeFrom)) return false\n if (!numericEqual(row.rangeTo, entry.rangeTo)) return false\n // dimensions\n const dimKeys = new Set([...Object.keys(row.dimensionValues), ...Object.keys(entry.dimensionValues)])\n for (const k of dimKeys) {\n if ((row.dimensionValues[k] ?? '') !== (entry.dimensionValues[k] ?? '')) return false\n }\n // prices\n const priceKeys = new Set([...Object.keys(row.prices), ...Object.keys(entry.prices)])\n for (const k of priceKeys) {\n if ((row.prices[k] ?? 0) !== (entry.prices[k] ?? 0)) return false\n }\n return true\n}\n\nfunction numericEqual(a: string | null, b: string | null): boolean {\n const an = a == null ? null : Number(a)\n const bn = b == null ? null : Number(b)\n if (an === null && bn === null) return true\n if (an === null || bn === null) return false\n return an === bn\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -55,7 +55,7 @@ function PricingTablesPage() {
55
55
  {
56
56
  accessorKey: "isActive",
57
57
  header: t("cpq.pricing.status", "Status"),
58
- cell: ({ row }) => /* @__PURE__ */ jsx(Tag, { variant: row.original.isActive ? "success" : "neutral", dot: true, children: row.original.isActive ? t("common.active", "Active") : t("common.inactive", "Inactive") })
58
+ cell: ({ row }) => /* @__PURE__ */ jsx(Tag, { variant: row.original.isActive ? "success" : "neutral", dot: true, children: row.original.isActive ? t("cpq.common.active", "Active") : t("cpq.common.inactive", "Inactive") })
59
59
  }
60
60
  ],
61
61
  [t]
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/cpq/backend/cpq/pricing/page.tsx"],
4
- "sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useRouter } from 'next/navigation'\nimport type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Tag } from '@open-mercato/ui/primitives/tag'\nimport type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterBar'\nimport { CpqListView, useCpqListData } from '../../../components/CpqListView'\n\ntype PricingTable = {\n id: string\n code: string\n name: string\n priceColumns: Array<{ key: string; label: string }>\n currencyCodeList: string[]\n isActive: boolean\n}\n\nconst PAGE_SIZE = 50\n\nfunction buildFilterParams(values: FilterValues, params: URLSearchParams) {\n if (values.isActive === true) params.set('isActive', 'true')\n if (values.isActive === false) params.set('isActive', 'false')\n}\n\nexport default function PricingTablesPage() {\n const t = useT()\n const router = useRouter()\n\n const data = useCpqListData<PricingTable>({\n endpoint: '/api/cpq/pricing-tables',\n pageSize: PAGE_SIZE,\n buildFilterParams,\n loadErrorMessage: t('cpq.pricing.list.error.load', 'Failed to load pricing tables'),\n })\n\n const filters = React.useMemo<FilterDef[]>(\n () => [\n {\n id: 'isActive',\n label: t('cpq.pricing.filters.isActive', 'Active'),\n type: 'checkbox',\n },\n ],\n [t],\n )\n\n const columns = React.useMemo<ColumnDef<PricingTable>[]>(\n () => [\n {\n accessorKey: 'name',\n header: t('cpq.pricing.name', 'Name'),\n cell: ({ row }) => <span className=\"font-medium\">{row.original.name}</span>,\n },\n {\n accessorKey: 'code',\n header: t('cpq.pricing.code', 'Code'),\n cell: ({ row }) => <span className=\"font-mono text-xs\">{row.original.code}</span>,\n },\n {\n id: 'priceColumns',\n header: t('cpq.pricing.priceColumns', 'Price Columns'),\n cell: ({ row }) => <span>{row.original.priceColumns?.length ?? 0}</span>,\n },\n {\n id: 'currencies',\n header: t('cpq.pricing.currencies', 'Currencies'),\n cell: ({ row }) => <span>{row.original.currencyCodeList?.join(', ') ?? '\u2014'}</span>,\n },\n {\n accessorKey: 'isActive',\n header: t('cpq.pricing.status', 'Status'),\n cell: ({ row }) => (\n <Tag variant={row.original.isActive ? 'success' : 'neutral'} dot>\n {row.original.isActive ? t('common.active', 'Active') : t('common.inactive', 'Inactive')}\n </Tag>\n ),\n },\n ],\n [t],\n )\n\n return (\n <CpqListView<PricingTable>\n title={t('cpq.pricing.list.title', 'Pricing Tables')}\n tableId=\"cpq.pricing.list\"\n data={data}\n columns={columns}\n filters={filters}\n pageSize={PAGE_SIZE}\n searchPlaceholder={t('cpq.pricing.search.placeholder', 'Search pricing tables...')}\n actions={\n <Button asChild>\n <a href=\"/backend/cpq/pricing/new\">{t('cpq.pricing.add', 'New Pricing Table')}</a>\n </Button>\n }\n crud={{\n endpoint: '/api/cpq/pricing-tables',\n entityName: t('cpq.pricing.entityName', 'pricing table'),\n editHref: (row) => `/backend/cpq/pricing/${row.id}`,\n }}\n onRowClick={(row) => router.push(`/backend/cpq/pricing/${row.id}`)}\n emptyState={\n <div className=\"rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground\">\n {t('cpq.pricing.empty', 'No pricing tables found.')}\n </div>\n }\n />\n )\n}\n"],
5
- "mappings": ";AAqD2B;AApD3B,YAAY,WAAW;AACvB,SAAS,YAAY;AACrB,SAAS,iBAAiB;AAE1B,SAAS,cAAc;AACvB,SAAS,WAAW;AAEpB,SAAS,aAAa,sBAAsB;AAW5C,MAAM,YAAY;AAElB,SAAS,kBAAkB,QAAsB,QAAyB;AACxE,MAAI,OAAO,aAAa,KAAM,QAAO,IAAI,YAAY,MAAM;AAC3D,MAAI,OAAO,aAAa,MAAO,QAAO,IAAI,YAAY,OAAO;AAC/D;AAEe,SAAR,oBAAqC;AAC1C,QAAM,IAAI,KAAK;AACf,QAAM,SAAS,UAAU;AAEzB,QAAM,OAAO,eAA6B;AAAA,IACxC,UAAU;AAAA,IACV,UAAU;AAAA,IACV;AAAA,IACA,kBAAkB,EAAE,+BAA+B,+BAA+B;AAAA,EACpF,CAAC;AAED,QAAM,UAAU,MAAM;AAAA,IACpB,MAAM;AAAA,MACJ;AAAA,QACE,IAAI;AAAA,QACJ,OAAO,EAAE,gCAAgC,QAAQ;AAAA,QACjD,MAAM;AAAA,MACR;AAAA,IACF;AAAA,IACA,CAAC,CAAC;AAAA,EACJ;AAEA,QAAM,UAAU,MAAM;AAAA,IACpB,MAAM;AAAA,MACJ;AAAA,QACE,aAAa;AAAA,QACb,QAAQ,EAAE,oBAAoB,MAAM;AAAA,QACpC,MAAM,CAAC,EAAE,IAAI,MAAM,oBAAC,UAAK,WAAU,eAAe,cAAI,SAAS,MAAK;AAAA,MACtE;AAAA,MACA;AAAA,QACE,aAAa;AAAA,QACb,QAAQ,EAAE,oBAAoB,MAAM;AAAA,QACpC,MAAM,CAAC,EAAE,IAAI,MAAM,oBAAC,UAAK,WAAU,qBAAqB,cAAI,SAAS,MAAK;AAAA,MAC5E;AAAA,MACA;AAAA,QACE,IAAI;AAAA,QACJ,QAAQ,EAAE,4BAA4B,eAAe;AAAA,QACrD,MAAM,CAAC,EAAE,IAAI,MAAM,oBAAC,UAAM,cAAI,SAAS,cAAc,UAAU,GAAE;AAAA,MACnE;AAAA,MACA;AAAA,QACE,IAAI;AAAA,QACJ,QAAQ,EAAE,0BAA0B,YAAY;AAAA,QAChD,MAAM,CAAC,EAAE,IAAI,MAAM,oBAAC,UAAM,cAAI,SAAS,kBAAkB,KAAK,IAAI,KAAK,UAAI;AAAA,MAC7E;AAAA,MACA;AAAA,QACE,aAAa;AAAA,QACb,QAAQ,EAAE,sBAAsB,QAAQ;AAAA,QACxC,MAAM,CAAC,EAAE,IAAI,MACX,oBAAC,OAAI,SAAS,IAAI,SAAS,WAAW,YAAY,WAAW,KAAG,MAC7D,cAAI,SAAS,WAAW,EAAE,iBAAiB,QAAQ,IAAI,EAAE,mBAAmB,UAAU,GACzF;AAAA,MAEJ;AAAA,IACF;AAAA,IACA,CAAC,CAAC;AAAA,EACJ;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,OAAO,EAAE,0BAA0B,gBAAgB;AAAA,MACnD,SAAQ;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,MACA,UAAU;AAAA,MACV,mBAAmB,EAAE,kCAAkC,0BAA0B;AAAA,MACjF,SACE,oBAAC,UAAO,SAAO,MACb,8BAAC,OAAE,MAAK,4BAA4B,YAAE,mBAAmB,mBAAmB,GAAE,GAChF;AAAA,MAEF,MAAM;AAAA,QACJ,UAAU;AAAA,QACV,YAAY,EAAE,0BAA0B,eAAe;AAAA,QACvD,UAAU,CAAC,QAAQ,wBAAwB,IAAI,EAAE;AAAA,MACnD;AAAA,MACA,YAAY,CAAC,QAAQ,OAAO,KAAK,wBAAwB,IAAI,EAAE,EAAE;AAAA,MACjE,YACE,oBAAC,SAAI,WAAU,2EACZ,YAAE,qBAAqB,0BAA0B,GACpD;AAAA;AAAA,EAEJ;AAEJ;",
4
+ "sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useRouter } from 'next/navigation'\nimport type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Tag } from '@open-mercato/ui/primitives/tag'\nimport type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterBar'\nimport { CpqListView, useCpqListData } from '../../../components/CpqListView'\n\ntype PricingTable = {\n id: string\n code: string\n name: string\n priceColumns: Array<{ key: string; label: string }>\n currencyCodeList: string[]\n isActive: boolean\n}\n\nconst PAGE_SIZE = 50\n\nfunction buildFilterParams(values: FilterValues, params: URLSearchParams) {\n if (values.isActive === true) params.set('isActive', 'true')\n if (values.isActive === false) params.set('isActive', 'false')\n}\n\nexport default function PricingTablesPage() {\n const t = useT()\n const router = useRouter()\n\n const data = useCpqListData<PricingTable>({\n endpoint: '/api/cpq/pricing-tables',\n pageSize: PAGE_SIZE,\n buildFilterParams,\n loadErrorMessage: t('cpq.pricing.list.error.load', 'Failed to load pricing tables'),\n })\n\n const filters = React.useMemo<FilterDef[]>(\n () => [\n {\n id: 'isActive',\n label: t('cpq.pricing.filters.isActive', 'Active'),\n type: 'checkbox',\n },\n ],\n [t],\n )\n\n const columns = React.useMemo<ColumnDef<PricingTable>[]>(\n () => [\n {\n accessorKey: 'name',\n header: t('cpq.pricing.name', 'Name'),\n cell: ({ row }) => <span className=\"font-medium\">{row.original.name}</span>,\n },\n {\n accessorKey: 'code',\n header: t('cpq.pricing.code', 'Code'),\n cell: ({ row }) => <span className=\"font-mono text-xs\">{row.original.code}</span>,\n },\n {\n id: 'priceColumns',\n header: t('cpq.pricing.priceColumns', 'Price Columns'),\n cell: ({ row }) => <span>{row.original.priceColumns?.length ?? 0}</span>,\n },\n {\n id: 'currencies',\n header: t('cpq.pricing.currencies', 'Currencies'),\n cell: ({ row }) => <span>{row.original.currencyCodeList?.join(', ') ?? '\u2014'}</span>,\n },\n {\n accessorKey: 'isActive',\n header: t('cpq.pricing.status', 'Status'),\n cell: ({ row }) => (\n <Tag variant={row.original.isActive ? 'success' : 'neutral'} dot>\n {row.original.isActive ? t('cpq.common.active', 'Active') : t('cpq.common.inactive', 'Inactive')}\n </Tag>\n ),\n },\n ],\n [t],\n )\n\n return (\n <CpqListView<PricingTable>\n title={t('cpq.pricing.list.title', 'Pricing Tables')}\n tableId=\"cpq.pricing.list\"\n data={data}\n columns={columns}\n filters={filters}\n pageSize={PAGE_SIZE}\n searchPlaceholder={t('cpq.pricing.search.placeholder', 'Search pricing tables...')}\n actions={\n <Button asChild>\n <a href=\"/backend/cpq/pricing/new\">{t('cpq.pricing.add', 'New Pricing Table')}</a>\n </Button>\n }\n crud={{\n endpoint: '/api/cpq/pricing-tables',\n entityName: t('cpq.pricing.entityName', 'pricing table'),\n editHref: (row) => `/backend/cpq/pricing/${row.id}`,\n }}\n onRowClick={(row) => router.push(`/backend/cpq/pricing/${row.id}`)}\n emptyState={\n <div className=\"rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground\">\n {t('cpq.pricing.empty', 'No pricing tables found.')}\n </div>\n }\n />\n )\n}\n"],
5
+ "mappings": ";AAqD2B;AApD3B,YAAY,WAAW;AACvB,SAAS,YAAY;AACrB,SAAS,iBAAiB;AAE1B,SAAS,cAAc;AACvB,SAAS,WAAW;AAEpB,SAAS,aAAa,sBAAsB;AAW5C,MAAM,YAAY;AAElB,SAAS,kBAAkB,QAAsB,QAAyB;AACxE,MAAI,OAAO,aAAa,KAAM,QAAO,IAAI,YAAY,MAAM;AAC3D,MAAI,OAAO,aAAa,MAAO,QAAO,IAAI,YAAY,OAAO;AAC/D;AAEe,SAAR,oBAAqC;AAC1C,QAAM,IAAI,KAAK;AACf,QAAM,SAAS,UAAU;AAEzB,QAAM,OAAO,eAA6B;AAAA,IACxC,UAAU;AAAA,IACV,UAAU;AAAA,IACV;AAAA,IACA,kBAAkB,EAAE,+BAA+B,+BAA+B;AAAA,EACpF,CAAC;AAED,QAAM,UAAU,MAAM;AAAA,IACpB,MAAM;AAAA,MACJ;AAAA,QACE,IAAI;AAAA,QACJ,OAAO,EAAE,gCAAgC,QAAQ;AAAA,QACjD,MAAM;AAAA,MACR;AAAA,IACF;AAAA,IACA,CAAC,CAAC;AAAA,EACJ;AAEA,QAAM,UAAU,MAAM;AAAA,IACpB,MAAM;AAAA,MACJ;AAAA,QACE,aAAa;AAAA,QACb,QAAQ,EAAE,oBAAoB,MAAM;AAAA,QACpC,MAAM,CAAC,EAAE,IAAI,MAAM,oBAAC,UAAK,WAAU,eAAe,cAAI,SAAS,MAAK;AAAA,MACtE;AAAA,MACA;AAAA,QACE,aAAa;AAAA,QACb,QAAQ,EAAE,oBAAoB,MAAM;AAAA,QACpC,MAAM,CAAC,EAAE,IAAI,MAAM,oBAAC,UAAK,WAAU,qBAAqB,cAAI,SAAS,MAAK;AAAA,MAC5E;AAAA,MACA;AAAA,QACE,IAAI;AAAA,QACJ,QAAQ,EAAE,4BAA4B,eAAe;AAAA,QACrD,MAAM,CAAC,EAAE,IAAI,MAAM,oBAAC,UAAM,cAAI,SAAS,cAAc,UAAU,GAAE;AAAA,MACnE;AAAA,MACA;AAAA,QACE,IAAI;AAAA,QACJ,QAAQ,EAAE,0BAA0B,YAAY;AAAA,QAChD,MAAM,CAAC,EAAE,IAAI,MAAM,oBAAC,UAAM,cAAI,SAAS,kBAAkB,KAAK,IAAI,KAAK,UAAI;AAAA,MAC7E;AAAA,MACA;AAAA,QACE,aAAa;AAAA,QACb,QAAQ,EAAE,sBAAsB,QAAQ;AAAA,QACxC,MAAM,CAAC,EAAE,IAAI,MACX,oBAAC,OAAI,SAAS,IAAI,SAAS,WAAW,YAAY,WAAW,KAAG,MAC7D,cAAI,SAAS,WAAW,EAAE,qBAAqB,QAAQ,IAAI,EAAE,uBAAuB,UAAU,GACjG;AAAA,MAEJ;AAAA,IACF;AAAA,IACA,CAAC,CAAC;AAAA,EACJ;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,OAAO,EAAE,0BAA0B,gBAAgB;AAAA,MACnD,SAAQ;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,MACA,UAAU;AAAA,MACV,mBAAmB,EAAE,kCAAkC,0BAA0B;AAAA,MACjF,SACE,oBAAC,UAAO,SAAO,MACb,8BAAC,OAAE,MAAK,4BAA4B,YAAE,mBAAmB,mBAAmB,GAAE,GAChF;AAAA,MAEF,MAAM;AAAA,QACJ,UAAU;AAAA,QACV,YAAY,EAAE,0BAA0B,eAAe;AAAA,QACvD,UAAU,CAAC,QAAQ,wBAAwB,IAAI,EAAE;AAAA,MACnD;AAAA,MACA,YAAY,CAAC,QAAQ,OAAO,KAAK,wBAAwB,IAAI,EAAE,EAAE;AAAA,MACjE,YACE,oBAAC,SAAI,WAAU,2EACZ,YAAE,qBAAqB,0BAA0B,GACpD;AAAA;AAAA,EAEJ;AAEJ;",
6
6
  "names": []
7
7
  }