@dainamite/cpq 0.2.2 → 0.4.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 (59) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/MIGRATION.md +34 -1
  3. package/README.md +17 -12
  4. package/dist/modules/cpq/api/product-attributes/route.js +28 -53
  5. package/dist/modules/cpq/api/product-attributes/route.js.map +2 -2
  6. package/dist/modules/cpq/api/product-charges/route.js +12 -2
  7. package/dist/modules/cpq/api/product-charges/route.js.map +2 -2
  8. package/dist/modules/cpq/api/product-offerings/route.js +2 -0
  9. package/dist/modules/cpq/api/product-offerings/route.js.map +2 -2
  10. package/dist/modules/cpq/backend/cpq/offerings/[id]/page.js +128 -59
  11. package/dist/modules/cpq/backend/cpq/offerings/[id]/page.js.map +2 -2
  12. package/dist/modules/cpq/backend/cpq/pricing/[id]/EntriesEditor.js +11 -11
  13. package/dist/modules/cpq/backend/cpq/pricing/[id]/EntriesEditor.js.map +2 -2
  14. package/dist/modules/cpq/backend/cpq/specifications/[id]/page.js +55 -1
  15. package/dist/modules/cpq/backend/cpq/specifications/[id]/page.js.map +2 -2
  16. package/dist/modules/cpq/cli.js +58 -4
  17. package/dist/modules/cpq/cli.js.map +3 -3
  18. package/dist/modules/cpq/data/charge-pricing.js +12 -0
  19. package/dist/modules/cpq/data/charge-pricing.js.map +7 -0
  20. package/dist/modules/cpq/data/entities.js +3 -0
  21. package/dist/modules/cpq/data/entities.js.map +2 -2
  22. package/dist/modules/cpq/data/validators.js +36 -15
  23. package/dist/modules/cpq/data/validators.js.map +2 -2
  24. package/dist/modules/cpq/events.js +7 -0
  25. package/dist/modules/cpq/events.js.map +2 -2
  26. package/dist/modules/cpq/lib/seeds/demo.js +103 -0
  27. package/dist/modules/cpq/lib/seeds/demo.js.map +7 -0
  28. package/dist/modules/cpq/migrations/Migration20260602000000_cpq_charge_model_split.js +28 -0
  29. package/dist/modules/cpq/migrations/Migration20260602000000_cpq_charge_model_split.js.map +7 -0
  30. package/dist/modules/cpq/services/cpqOrderService.js +130 -76
  31. package/dist/modules/cpq/services/cpqOrderService.js.map +2 -2
  32. package/dist/modules/cpq/services/cpqPricingService.js +53 -29
  33. package/dist/modules/cpq/services/cpqPricingService.js.map +3 -3
  34. package/dist/modules/cpq/services/types.js.map +2 -2
  35. package/package.json +11 -9
  36. package/src/modules/cpq/__integration__/TC-CPQ-012.spec.ts +34 -4
  37. package/src/modules/cpq/__tests__/package-isolation.test.ts +8 -3
  38. package/src/modules/cpq/api/product-attributes/__tests__/serializeAttribute.test.ts +62 -0
  39. package/src/modules/cpq/api/product-attributes/route.ts +34 -52
  40. package/src/modules/cpq/api/product-charges/route.ts +23 -2
  41. package/src/modules/cpq/api/product-offerings/route.ts +2 -0
  42. package/src/modules/cpq/backend/cpq/offerings/[id]/page.tsx +151 -62
  43. package/src/modules/cpq/backend/cpq/pricing/[id]/EntriesEditor.tsx +24 -22
  44. package/src/modules/cpq/backend/cpq/specifications/[id]/page.tsx +73 -1
  45. package/src/modules/cpq/cli.ts +83 -4
  46. package/src/modules/cpq/data/__tests__/charge-pricing.test.ts +49 -0
  47. package/src/modules/cpq/data/__tests__/validators.test.ts +60 -5
  48. package/src/modules/cpq/data/charge-pricing.ts +48 -0
  49. package/src/modules/cpq/data/entities.ts +10 -0
  50. package/src/modules/cpq/data/validators.ts +56 -26
  51. package/src/modules/cpq/events.ts +11 -3
  52. package/src/modules/cpq/lib/seeds/__tests__/demo.test.ts +78 -0
  53. package/src/modules/cpq/lib/seeds/demo.ts +145 -0
  54. package/src/modules/cpq/migrations/.snapshot-open-mercato.json +16 -0
  55. package/src/modules/cpq/migrations/Migration20260602000000_cpq_charge_model_split.ts +57 -0
  56. package/src/modules/cpq/services/__tests__/cpqPricingService.test.ts +172 -1
  57. package/src/modules/cpq/services/cpqOrderService.ts +214 -103
  58. package/src/modules/cpq/services/cpqPricingService.ts +82 -34
  59. package/src/modules/cpq/services/types.ts +4 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,67 @@
1
1
  # @dainamite/cpq
2
2
 
3
+ ## 0.4.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 8834b8b: Require Open Mercato `0.6.7`. Peer ranges move from `^0.6.0` to `^0.6.7` for
8
+ `@open-mercato/core`, `shared`, `ui`, `events` and `queue`, and the MikroORM
9
+ peers from `^7.0.0` to `^7.1.5` (7.1 split the SQL layer into
10
+ `@mikro-orm/sql`, so a shared 7.1 instance is required).
11
+
12
+ No package source changes — this only narrows the supported framework range.
13
+ Consumers on Open Mercato `0.6.0`–`0.6.6` must upgrade before taking these
14
+ versions.
15
+
16
+ Note for consumers running commands with a system identity (`ctx.auth: null`):
17
+ `0.6.7` hardened `ensureTenantScope` / `ensureOrganizationScope`. Those calls
18
+ are still no-ops with `auth: null`, but an unscoped organization command now
19
+ logs a warning and will throw `403` if `OM_ENFORCE_ORG_SCOPE_STRICT=true`.
20
+
21
+ ## 0.3.0
22
+
23
+ ### Minor Changes
24
+
25
+ - f111f9d: Add `mercato cpq seed` — a one-command loader for a minimal, neutral CPQ test catalog (a configurable product with a Specification, an Offering, and a fixed monthly charge) into a single tenant.
26
+
27
+ - **Idempotent**: every row is matched by a stable `DEMO-CPQ-*` code/sku, so re-running is a no-op rather than a duplicate. The charge find-or-create keys on `(offering_id, code)` to match the DB uniqueness constraint.
28
+ - **Scope**: resolves the primary organization created by `mercato init` when no `--tenant`/`--org` flag is given.
29
+ - **`--reset`**: clears the seed's own rows before re-seeding.
30
+
31
+ The seed is intentionally generic so it ships with the package; rich, vertical demo catalogs continue to live in app-level `demo_<x>` modules + `mercato demo_tenants seed`. Pairs with `mercato billing seed` (`@dainamite/billing`) and the consumer-side `yarn dainamite-cpq-test` / `yarn dainamite-test` aliases.
32
+
33
+ ### Patch Changes
34
+
35
+ - 2261dfa: XD-289: ship `xlsx` as a runtime dependency so the pricing-table detail page works out of the box on fresh `create-mercato-app` scaffolds.
36
+
37
+ `xlsx` was previously declared only in `peerDependencies` (alongside the rest of the React / MikroORM / `@open-mercato/*` peer set). That works in this monorepo because the root `package.json` already lists `xlsx`, but it broke for external consumers: installing `@dainamite/cpq` into a vanilla `create-mercato-app` project did not pull `xlsx` into `node_modules`, and the very first `yarn dev` after `yarn add @dainamite/cpq` crashed every page with
38
+
39
+ ```
40
+ Module not found: Can't resolve 'xlsx'
41
+ > 1 | import * as XLSX from "xlsx";
42
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
43
+ @dainamite/cpq/dist/modules/cpq/backend/cpq/pricing/[id]/xlsxExport.js
44
+ @dainamite/cpq/dist/modules/cpq/backend/cpq/pricing/[id]/xlsxImport.js
45
+ ```
46
+
47
+ (The pricing-table `[id]/page.tsx` statically imports both files, so Next.js
48
+ bundles them for Client SSR and Client Browser regardless of which route the
49
+ visitor actually opens — a single broken import takes down `/login` and the
50
+ whole backend.)
51
+
52
+ The peer-dependency rule from SPEC-001 ("never `dependencies` for sibling
53
+ `@dainamite/*` / `@open-mercato/*`") exists to keep React, MikroORM, Next.js,
54
+ and the rest of the framework as a single shared instance. `xlsx` is a
55
+ CPQ-internal leaf library (used only by `pricing/[id]/xlsxExport.ts` and
56
+ `xlsxImport.ts`); it has no decorator/context/hook contract that breaks under
57
+ duplication, so promoting it to `dependencies` is safe and matches how leaf
58
+ libs are normally distributed on npm.
59
+
60
+ Consumers no longer need to `yarn add xlsx` after installing `@dainamite/cpq`.
61
+ The package-isolation test (`__tests__/package-isolation.test.ts`) was updated
62
+ in the same change so its allow-list comment mentions both `peerDependencies`
63
+ and `dependencies` instead of just peers.
64
+
3
65
  ## 0.2.2
4
66
 
5
67
  ### Patch Changes
package/MIGRATION.md CHANGED
@@ -10,7 +10,7 @@ the `@dainamite/cpq` package, per
10
10
  - Registered as `{ id: 'cpq', from: '@app' }` in
11
11
  [`src/modules.ts`](../../src/modules.ts).
12
12
  - `packages/cpq/` is a structural placeholder only (this directory).
13
- - `dainamite-core` repo is the L3 customer demo (Demo Netia) per SPEC-001 Q4.
13
+ - `dainamite-core` repo is the L3 customer demo per SPEC-001 Q4.
14
14
  - **Customer demo data does NOT live in `cpq/`** (since XD-276). GIX and
15
15
  Puffin Cloud catalog seeds live in `src/modules/demo_gix/` and
16
16
  `src/modules/demo_puffin/`, registered as use cases via
@@ -56,6 +56,9 @@ the `@dainamite/cpq` package, per
56
56
  `.yarn/patches/@open-mercato-cli-npm-0.5.0-*.patch`. **Will be
57
57
  fixed upstream in the next framework release** — patch can be
58
58
  removed at that point.
59
+ **Update (0.6.7, 2026-08-25): done.** Upstream now sanitizes any
60
+ npm scope generically, so the patch was deleted along with the
61
+ whole `.yarn/patches/` directory.
59
62
  - [x] Rewrite cross-module imports in `demo_gix`, `demo_puffin`,
60
63
  `demo_tenants`: `'../../cpq/...'` → `'@dainamite/cpq/modules/cpq/...'`
61
64
  (26 files). Demo modules now consume CPQ via the public package
@@ -163,6 +166,36 @@ the `@dainamite/cpq` package, per
163
166
 
164
167
  ## Schema / API changelog (track breaking changes here)
165
168
 
169
+ ### 2026-06-02 — XD-297 Charge model / pricing method split
170
+
171
+ Splits the conflated `cpq_product_charges.pricing_method` into two orthogonal
172
+ axes. **Additive + backfilled; back-compat preserved at runtime.**
173
+
174
+ **New column** (additive, migration `Migration20260602000000_cpq_charge_model_split`):
175
+ - `cpq_product_charges.charge_model` (nullable text) — `flat | per_unit | volume | tiered`.
176
+
177
+ **Repurposed column** (values migrated):
178
+ - `cpq_product_charges.pricing_method` now holds the **pricing source**
179
+ `fixed | table`. The migration backfills existing rows:
180
+ `flat → (flat, fixed)`, `per_unit → (per_unit, table)`,
181
+ `tiered → (tiered, table)`.
182
+
183
+ **API shape** (`/api/cpq/product-charges`, offering GET): charges now carry a
184
+ `chargeModel` field alongside `pricingMethod`. POST/PUT accept **either** the
185
+ new split (`chargeModel` + `pricingMethod ∈ {fixed, table}`) **or** a legacy
186
+ combined `pricingMethod` (`flat | per_unit | tiered`) and persist the canonical
187
+ split shape.
188
+
189
+ **New capabilities**: `volume` charge model (whole quantity billed at the rate
190
+ of the tier containing the total) and `per_unit + fixed` (quantity × a fixed
191
+ unit price). Valid combinations: `fixed` → `flat | per_unit`; `table` → all four.
192
+
193
+ **Backwards compatibility**: `normalizeChargePricing()` (exported from
194
+ `data/charge-pricing`) resolves the `{model, source}` from either shape, so rows that
195
+ predate the migration — and any client still sending the legacy `pricingMethod`
196
+ — price and render correctly without a migration. `ResolvedCharge.pricingMethod`
197
+ now carries the charge **model** (adds `'volume'`) for display.
198
+
166
199
  ### 2026-05-04 — XD-250 ARC (Amend / Renew / Cancel)
167
200
 
168
201
  Additive only. Every existing CPQ contract is preserved.
package/README.md CHANGED
@@ -7,25 +7,30 @@ quoting wizards, customer inventory & subscription tracking, ARC
7
7
 
8
8
  ## Install
9
9
 
10
- Requires access to the `@dainamite` GitHub Packages scope. Configure
11
- `.npmrc` to point `@dainamite` at `https://npm.pkg.github.com`, then:
10
+ Published to the **public npm registry** — no scope auth or `.npmrc`
11
+ changes needed:
12
12
 
13
13
  ```sh
14
14
  yarn add @dainamite/cpq
15
15
  ```
16
16
 
17
- Register the module in your host app's `src/modules.ts`:
17
+ Register the module in your host app's `src/modules.ts` (after its
18
+ required core modules — see below):
18
19
 
19
20
  ```ts
20
21
  { id: 'cpq', from: '@dainamite/cpq' }
21
22
  ```
22
23
 
23
- Run framework migrations to install CPQ's schema:
24
+ Regenerate the module graph, then run framework migrations to install
25
+ CPQ's schema:
24
26
 
25
27
  ```sh
28
+ yarn generate
26
29
  yarn mercato db migrate
27
30
  ```
28
31
 
32
+ CPQ admin pages appear under `/backend/cpq/*`.
33
+
29
34
  ## What's inside
30
35
 
31
36
  ```
@@ -55,11 +60,11 @@ directly to source.
55
60
 
56
61
  - **L1** — Open Mercato core consumed from `@open-mercato/*` (npm).
57
62
  - **L2** — Dainamite product modules (this package + sibling
58
- `@dainamite/*`), published to **GitHub Packages** under the
59
- `@dainamite` scope.
60
- - **L3** — `dainamite-core` is the first L3 customer app
61
- ("Demo Netia"); other customer repos consume `@dainamite/cpq` via
62
- `yarn add` and register it the same way.
63
+ `@dainamite/*`), published to **public npm** under the `@dainamite`
64
+ scope.
65
+ - **L3** — `dainamite-core` is the first L3 customer app; other customer
66
+ repos consume `@dainamite/cpq` via `yarn add` and register it the same
67
+ way.
63
68
 
64
69
  ## Hard rules (carried over from SPEC-001)
65
70
 
@@ -100,9 +105,9 @@ Add a changeset alongside any change in `packages/cpq/`:
100
105
  yarn changeset
101
106
  ```
102
107
 
103
- Bumps and the GitHub Packages publish are handled by the release
104
- workflow on push to `main`.
108
+ Bumps and the public-npm publish are handled by the release workflow on
109
+ push to `main`.
105
110
 
106
111
  ## License
107
112
 
108
- Proprietary — internal Dainamite use only.
113
+ MIT.
@@ -9,6 +9,29 @@ const metadata = {
9
9
  PUT: { requireAuth: true, requireFeatures: ["cpq.products.manage"] },
10
10
  DELETE: { requireAuth: true, requireFeatures: ["cpq.products.manage"] }
11
11
  };
12
+ function serializeAttribute(item) {
13
+ return {
14
+ id: item.id,
15
+ productId: item.productId,
16
+ specId: item.specId,
17
+ code: item.code,
18
+ name: item.name,
19
+ attributeType: item.attributeType,
20
+ resolutionTime: item.resolutionTime,
21
+ options: item.options,
22
+ constraints: item.constraints,
23
+ referenceEntity: item.referenceEntity,
24
+ referenceFilter: item.referenceFilter,
25
+ dependsOn: item.dependsOn,
26
+ defaultValue: item.defaultValue,
27
+ helpText: item.helpText,
28
+ sortOrder: item.sortOrder,
29
+ isRequired: item.isRequired,
30
+ isActive: item.isActive,
31
+ createdAt: item.createdAt,
32
+ updatedAt: item.updatedAt
33
+ };
34
+ }
12
35
  async function GET(req) {
13
36
  try {
14
37
  const ctx = await resolveCpqRouteContext(req);
@@ -28,27 +51,7 @@ async function GET(req) {
28
51
  orderBy: { sortOrder: "asc" }
29
52
  });
30
53
  return NextResponse.json({
31
- items: items.map((item) => ({
32
- id: item.id,
33
- productId: item.productId,
34
- specId: item.specId,
35
- code: item.code,
36
- name: item.name,
37
- attributeType: item.attributeType,
38
- resolutionTime: item.resolutionTime,
39
- options: item.options,
40
- constraints: item.constraints,
41
- referenceEntity: item.referenceEntity,
42
- referenceFilter: item.referenceFilter,
43
- dependsOn: item.dependsOn,
44
- defaultValue: item.defaultValue,
45
- helpText: item.helpText,
46
- sortOrder: item.sortOrder,
47
- isRequired: item.isRequired,
48
- isActive: item.isActive,
49
- createdAt: item.createdAt,
50
- updatedAt: item.updatedAt
51
- })),
54
+ items: items.map(serializeAttribute),
52
55
  total,
53
56
  page,
54
57
  pageSize,
@@ -75,23 +78,7 @@ async function POST(req) {
75
78
  }
76
79
  const entity = ctx.em.create(CpqProductAttribute, { ...body, productId, ...scope });
77
80
  await ctx.em.flush();
78
- return NextResponse.json(
79
- {
80
- id: entity.id,
81
- productId: entity.productId,
82
- code: entity.code,
83
- name: entity.name,
84
- attributeType: entity.attributeType,
85
- options: entity.options,
86
- constraints: entity.constraints,
87
- sortOrder: entity.sortOrder,
88
- isRequired: entity.isRequired,
89
- isActive: entity.isActive,
90
- createdAt: entity.createdAt,
91
- updatedAt: entity.updatedAt
92
- },
93
- { status: 201 }
94
- );
81
+ return NextResponse.json(serializeAttribute(entity), { status: 201 });
95
82
  } catch (err) {
96
83
  if (err instanceof z.ZodError) {
97
84
  return NextResponse.json({ error: "Validation error", details: err.issues }, { status: 400 });
@@ -111,20 +98,7 @@ async function PUT(req) {
111
98
  if (!entity) return NextResponse.json({ error: "Not found" }, { status: 404 });
112
99
  ctx.em.assign(entity, updates);
113
100
  await ctx.em.flush();
114
- return NextResponse.json({
115
- id: entity.id,
116
- productId: entity.productId,
117
- code: entity.code,
118
- name: entity.name,
119
- attributeType: entity.attributeType,
120
- options: entity.options,
121
- constraints: entity.constraints,
122
- sortOrder: entity.sortOrder,
123
- isRequired: entity.isRequired,
124
- isActive: entity.isActive,
125
- createdAt: entity.createdAt,
126
- updatedAt: entity.updatedAt
127
- });
101
+ return NextResponse.json(serializeAttribute(entity));
128
102
  } catch (err) {
129
103
  if (err instanceof z.ZodError) {
130
104
  return NextResponse.json({ error: "Validation error", details: err.issues }, { status: 400 });
@@ -155,6 +129,7 @@ export {
155
129
  GET,
156
130
  POST,
157
131
  PUT,
158
- metadata
132
+ metadata,
133
+ serializeAttribute
159
134
  };
160
135
  //# sourceMappingURL=route.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/cpq/api/product-attributes/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { resolveCpqRouteContext } from '../context'\nimport { CpqProductAttribute, CpqProductSpecification } from '../../data/entities'\nimport { cpqProductAttributeCreateSchema, cpqProductAttributeUpdateSchema } from '../../data/validators'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['cpq.products.view'] },\n POST: { requireAuth: true, requireFeatures: ['cpq.products.manage'] },\n PUT: { requireAuth: true, requireFeatures: ['cpq.products.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['cpq.products.manage'] },\n}\n\nexport async function GET(req: Request) {\n try {\n const ctx = await resolveCpqRouteContext(req)\n if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const url = new URL(req.url)\n const page = Math.max(1, Number(url.searchParams.get('page') ?? '1'))\n const pageSize = Math.min(100, Math.max(1, Number(url.searchParams.get('pageSize') ?? '50')))\n const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId, deletedAt: null }\n\n const filters: Record<string, unknown> = { ...scope }\n\n const productId = url.searchParams.get('productId')\n if (productId) filters.productId = productId\n const specId = url.searchParams.get('specId')\n if (specId) filters.specId = specId\n\n const [items, total] = await ctx.em.findAndCount(CpqProductAttribute, filters, {\n limit: pageSize,\n offset: (page - 1) * pageSize,\n orderBy: { sortOrder: 'asc' },\n })\n\n return NextResponse.json({\n items: items.map((item) => ({\n id: item.id,\n productId: item.productId,\n specId: item.specId,\n code: item.code,\n name: item.name,\n attributeType: item.attributeType,\n resolutionTime: item.resolutionTime,\n options: item.options,\n constraints: item.constraints,\n referenceEntity: item.referenceEntity,\n referenceFilter: item.referenceFilter,\n dependsOn: item.dependsOn,\n defaultValue: item.defaultValue,\n helpText: item.helpText,\n sortOrder: item.sortOrder,\n isRequired: item.isRequired,\n isActive: item.isActive,\n createdAt: item.createdAt,\n updatedAt: item.updatedAt,\n })),\n total,\n page,\n pageSize,\n totalPages: Math.ceil(total / pageSize),\n })\n } catch (err) {\n console.error('[cpq/product-attributes.GET]', err)\n return NextResponse.json({ error: 'Internal error' }, { status: 500 })\n }\n}\n\nexport async function POST(req: Request) {\n try {\n const ctx = await resolveCpqRouteContext(req)\n if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const body = cpqProductAttributeCreateSchema.parse(await req.json())\n const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId }\n\n // Resolve productId from spec if not provided\n let productId = body.productId\n if (!productId && body.specId) {\n const spec = await ctx.em.findOne(CpqProductSpecification, { id: body.specId, ...scope, deletedAt: null })\n if (spec) productId = spec.productId\n }\n if (!productId) {\n return NextResponse.json({ error: 'productId or specId with valid specification required' }, { status: 400 })\n }\n\n const entity = ctx.em.create(CpqProductAttribute, { ...body, productId, ...scope })\n await ctx.em.flush()\n\n return NextResponse.json(\n {\n id: entity.id,\n productId: entity.productId,\n code: entity.code,\n name: entity.name,\n attributeType: entity.attributeType,\n options: entity.options,\n constraints: entity.constraints,\n sortOrder: entity.sortOrder,\n isRequired: entity.isRequired,\n isActive: entity.isActive,\n createdAt: entity.createdAt,\n updatedAt: entity.updatedAt,\n },\n { status: 201 },\n )\n } catch (err) {\n if (err instanceof z.ZodError) {\n return NextResponse.json({ error: 'Validation error', details: err.issues }, { status: 400 })\n }\n console.error('[cpq/product-attributes.POST]', err)\n return NextResponse.json({ error: 'Internal error' }, { status: 500 })\n }\n}\n\nexport async function PUT(req: Request) {\n try {\n const ctx = await resolveCpqRouteContext(req)\n if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const body = cpqProductAttributeUpdateSchema.parse(await req.json())\n const { id, ...updates } = body\n const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId, deletedAt: null }\n\n const entity = await ctx.em.findOne(CpqProductAttribute, { id, ...scope })\n if (!entity) return NextResponse.json({ error: 'Not found' }, { status: 404 })\n\n ctx.em.assign(entity, updates)\n await ctx.em.flush()\n\n return NextResponse.json({\n id: entity.id,\n productId: entity.productId,\n code: entity.code,\n name: entity.name,\n attributeType: entity.attributeType,\n options: entity.options,\n constraints: entity.constraints,\n sortOrder: entity.sortOrder,\n isRequired: entity.isRequired,\n isActive: entity.isActive,\n createdAt: entity.createdAt,\n updatedAt: entity.updatedAt,\n })\n } catch (err) {\n if (err instanceof z.ZodError) {\n return NextResponse.json({ error: 'Validation error', details: err.issues }, { status: 400 })\n }\n console.error('[cpq/product-attributes.PUT]', err)\n return NextResponse.json({ error: 'Internal error' }, { status: 500 })\n }\n}\n\nexport async function DELETE(req: Request) {\n try {\n const ctx = await resolveCpqRouteContext(req)\n if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const { id } = (await req.json()) as { id?: string }\n if (!id) return NextResponse.json({ error: 'Missing id' }, { status: 400 })\n\n const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId, deletedAt: null }\n const entity = await ctx.em.findOne(CpqProductAttribute, { id, ...scope })\n if (!entity) return NextResponse.json({ error: 'Not found' }, { status: 404 })\n\n entity.deletedAt = new Date()\n await ctx.em.flush()\n\n return NextResponse.json({ success: true })\n } catch (err) {\n console.error('[cpq/product-attributes.DELETE]', err)\n return NextResponse.json({ error: 'Internal error' }, { status: 500 })\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { resolveCpqRouteContext } from '../context'\nimport { CpqProductAttribute, CpqProductSpecification } from '../../data/entities'\nimport { cpqProductAttributeCreateSchema, cpqProductAttributeUpdateSchema } from '../../data/validators'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['cpq.products.view'] },\n POST: { requireAuth: true, requireFeatures: ['cpq.products.manage'] },\n PUT: { requireAuth: true, requireFeatures: ['cpq.products.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['cpq.products.manage'] },\n}\n\n/**\n * Single source of truth for the attribute API shape. GET, POST and PUT MUST\n * all return this exact projection so the client never sees a partial record.\n * Omitting fields here (notably `resolutionTime`) caused XD-292: a freshly\n * created design-time attribute rendered as \"run\" until a page refresh, since\n * the create response lacked the field the table reads.\n */\nexport function serializeAttribute(item: CpqProductAttribute) {\n return {\n id: item.id,\n productId: item.productId,\n specId: item.specId,\n code: item.code,\n name: item.name,\n attributeType: item.attributeType,\n resolutionTime: item.resolutionTime,\n options: item.options,\n constraints: item.constraints,\n referenceEntity: item.referenceEntity,\n referenceFilter: item.referenceFilter,\n dependsOn: item.dependsOn,\n defaultValue: item.defaultValue,\n helpText: item.helpText,\n sortOrder: item.sortOrder,\n isRequired: item.isRequired,\n isActive: item.isActive,\n createdAt: item.createdAt,\n updatedAt: item.updatedAt,\n }\n}\n\nexport async function GET(req: Request) {\n try {\n const ctx = await resolveCpqRouteContext(req)\n if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const url = new URL(req.url)\n const page = Math.max(1, Number(url.searchParams.get('page') ?? '1'))\n const pageSize = Math.min(100, Math.max(1, Number(url.searchParams.get('pageSize') ?? '50')))\n const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId, deletedAt: null }\n\n const filters: Record<string, unknown> = { ...scope }\n\n const productId = url.searchParams.get('productId')\n if (productId) filters.productId = productId\n const specId = url.searchParams.get('specId')\n if (specId) filters.specId = specId\n\n const [items, total] = await ctx.em.findAndCount(CpqProductAttribute, filters, {\n limit: pageSize,\n offset: (page - 1) * pageSize,\n orderBy: { sortOrder: 'asc' },\n })\n\n return NextResponse.json({\n items: items.map(serializeAttribute),\n total,\n page,\n pageSize,\n totalPages: Math.ceil(total / pageSize),\n })\n } catch (err) {\n console.error('[cpq/product-attributes.GET]', err)\n return NextResponse.json({ error: 'Internal error' }, { status: 500 })\n }\n}\n\nexport async function POST(req: Request) {\n try {\n const ctx = await resolveCpqRouteContext(req)\n if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const body = cpqProductAttributeCreateSchema.parse(await req.json())\n const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId }\n\n // Resolve productId from spec if not provided\n let productId = body.productId\n if (!productId && body.specId) {\n const spec = await ctx.em.findOne(CpqProductSpecification, { id: body.specId, ...scope, deletedAt: null })\n if (spec) productId = spec.productId\n }\n if (!productId) {\n return NextResponse.json({ error: 'productId or specId with valid specification required' }, { status: 400 })\n }\n\n const entity = ctx.em.create(CpqProductAttribute, { ...body, productId, ...scope })\n await ctx.em.flush()\n\n return NextResponse.json(serializeAttribute(entity), { status: 201 })\n } catch (err) {\n if (err instanceof z.ZodError) {\n return NextResponse.json({ error: 'Validation error', details: err.issues }, { status: 400 })\n }\n console.error('[cpq/product-attributes.POST]', err)\n return NextResponse.json({ error: 'Internal error' }, { status: 500 })\n }\n}\n\nexport async function PUT(req: Request) {\n try {\n const ctx = await resolveCpqRouteContext(req)\n if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const body = cpqProductAttributeUpdateSchema.parse(await req.json())\n const { id, ...updates } = body\n const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId, deletedAt: null }\n\n const entity = await ctx.em.findOne(CpqProductAttribute, { id, ...scope })\n if (!entity) return NextResponse.json({ error: 'Not found' }, { status: 404 })\n\n ctx.em.assign(entity, updates)\n await ctx.em.flush()\n\n return NextResponse.json(serializeAttribute(entity))\n } catch (err) {\n if (err instanceof z.ZodError) {\n return NextResponse.json({ error: 'Validation error', details: err.issues }, { status: 400 })\n }\n console.error('[cpq/product-attributes.PUT]', err)\n return NextResponse.json({ error: 'Internal error' }, { status: 500 })\n }\n}\n\nexport async function DELETE(req: Request) {\n try {\n const ctx = await resolveCpqRouteContext(req)\n if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const { id } = (await req.json()) as { id?: string }\n if (!id) return NextResponse.json({ error: 'Missing id' }, { status: 400 })\n\n const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId, deletedAt: null }\n const entity = await ctx.em.findOne(CpqProductAttribute, { id, ...scope })\n if (!entity) return NextResponse.json({ error: 'Not found' }, { status: 404 })\n\n entity.deletedAt = new Date()\n await ctx.em.flush()\n\n return NextResponse.json({ success: true })\n } catch (err) {\n console.error('[cpq/product-attributes.DELETE]', err)\n return NextResponse.json({ error: 'Internal error' }, { status: 500 })\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -3,6 +3,7 @@ import { z } from "zod";
3
3
  import { resolveCpqRouteContext } from "../context.js";
4
4
  import { CpqProductCharge, CpqProductOffering, CpqProductSpecification } from "../../data/entities.js";
5
5
  import { cpqProductChargeCreateSchema, cpqProductChargeUpdateSchema } from "../../data/validators.js";
6
+ import { normalizeChargePricing } from "../../data/charge-pricing.js";
6
7
  const metadata = {
7
8
  GET: { requireAuth: true, requireFeatures: ["cpq.products.view"] },
8
9
  POST: { requireAuth: true, requireFeatures: ["cpq.products.manage"] },
@@ -36,6 +37,7 @@ async function GET(req) {
36
37
  name: item.name,
37
38
  description: item.description,
38
39
  chargeType: item.chargeType,
40
+ chargeModel: item.chargeModel,
39
41
  pricingMethod: item.pricingMethod,
40
42
  pricingTableId: item.pricingTableId,
41
43
  priceColumnKey: item.priceColumnKey,
@@ -75,7 +77,8 @@ async function POST(req) {
75
77
  if (!productId) {
76
78
  return NextResponse.json({ error: "productId or offeringId with valid offering required" }, { status: 400 });
77
79
  }
78
- const entity = ctx.em.create(CpqProductCharge, { ...body, productId, ...scope });
80
+ const { model, source } = normalizeChargePricing(body);
81
+ const entity = ctx.em.create(CpqProductCharge, { ...body, chargeModel: model, pricingMethod: source, productId, ...scope });
79
82
  await ctx.em.flush();
80
83
  return NextResponse.json(
81
84
  {
@@ -84,6 +87,7 @@ async function POST(req) {
84
87
  code: entity.code,
85
88
  name: entity.name,
86
89
  chargeType: entity.chargeType,
90
+ chargeModel: entity.chargeModel,
87
91
  pricingMethod: entity.pricingMethod,
88
92
  pricingTableId: entity.pricingTableId,
89
93
  priceColumnKey: entity.priceColumnKey,
@@ -113,7 +117,12 @@ async function PUT(req) {
113
117
  const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId, deletedAt: null };
114
118
  const entity = await ctx.em.findOne(CpqProductCharge, { id, ...scope });
115
119
  if (!entity) return NextResponse.json({ error: "Not found" }, { status: 404 });
116
- ctx.em.assign(entity, updates);
120
+ const normalizedUpdates = updates.pricingMethod !== void 0 || updates.chargeModel !== void 0 ? (() => {
121
+ const { model, source } = normalizeChargePricing({ ...entity, ...updates });
122
+ const sideCleared = source === "fixed" ? { pricingTableId: null, priceColumnKey: null } : { fixedPrice: null };
123
+ return { ...updates, chargeModel: model, pricingMethod: source, ...sideCleared };
124
+ })() : updates;
125
+ ctx.em.assign(entity, normalizedUpdates);
117
126
  await ctx.em.flush();
118
127
  return NextResponse.json({
119
128
  id: entity.id,
@@ -121,6 +130,7 @@ async function PUT(req) {
121
130
  code: entity.code,
122
131
  name: entity.name,
123
132
  chargeType: entity.chargeType,
133
+ chargeModel: entity.chargeModel,
124
134
  pricingMethod: entity.pricingMethod,
125
135
  pricingTableId: entity.pricingTableId,
126
136
  priceColumnKey: entity.priceColumnKey,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/cpq/api/product-charges/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { resolveCpqRouteContext } from '../context'\nimport { CpqProductCharge, CpqProductOffering, CpqProductSpecification } from '../../data/entities'\nimport { cpqProductChargeCreateSchema, cpqProductChargeUpdateSchema } from '../../data/validators'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['cpq.products.view'] },\n POST: { requireAuth: true, requireFeatures: ['cpq.products.manage'] },\n PUT: { requireAuth: true, requireFeatures: ['cpq.products.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['cpq.products.manage'] },\n}\n\nexport async function GET(req: Request) {\n try {\n const ctx = await resolveCpqRouteContext(req)\n if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const url = new URL(req.url)\n const page = Math.max(1, Number(url.searchParams.get('page') ?? '1'))\n const pageSize = Math.min(100, Math.max(1, Number(url.searchParams.get('pageSize') ?? '50')))\n const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId, deletedAt: null }\n\n const filters: Record<string, unknown> = { ...scope }\n\n const productId = url.searchParams.get('productId')\n if (productId) filters.productId = productId\n const offeringId = url.searchParams.get('offeringId')\n if (offeringId) filters.offeringId = offeringId\n\n const [items, total] = await ctx.em.findAndCount(CpqProductCharge, filters, {\n limit: pageSize,\n offset: (page - 1) * pageSize,\n orderBy: { sortOrder: 'asc' },\n })\n\n return NextResponse.json({\n items: items.map((item) => ({\n id: item.id,\n productId: item.productId,\n offeringId: item.offeringId,\n code: item.code,\n name: item.name,\n description: item.description,\n chargeType: item.chargeType,\n pricingMethod: item.pricingMethod,\n pricingTableId: item.pricingTableId,\n priceColumnKey: item.priceColumnKey,\n fixedPrice: item.fixedPrice,\n currencyCode: item.currencyCode,\n quantityAttributeCode: item.quantityAttributeCode,\n applicabilityCondition: item.applicabilityCondition,\n sortOrder: item.sortOrder,\n isActive: item.isActive,\n createdAt: item.createdAt,\n updatedAt: item.updatedAt,\n })),\n total,\n page,\n pageSize,\n totalPages: Math.ceil(total / pageSize),\n })\n } catch (err) {\n console.error('[cpq/product-charges.GET]', err)\n return NextResponse.json({ error: 'Internal error' }, { status: 500 })\n }\n}\n\nexport async function POST(req: Request) {\n try {\n const ctx = await resolveCpqRouteContext(req)\n if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const body = cpqProductChargeCreateSchema.parse(await req.json())\n const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId }\n\n // Resolve productId from offering if not provided\n let productId = body.productId\n if (!productId && body.offeringId) {\n const offering = await ctx.em.findOne(CpqProductOffering, { id: body.offeringId, ...scope, deletedAt: null })\n if (offering) {\n const spec = await ctx.em.findOne(CpqProductSpecification, { id: offering.specId, ...scope, deletedAt: null })\n if (spec) productId = spec.productId\n }\n }\n if (!productId) {\n return NextResponse.json({ error: 'productId or offeringId with valid offering required' }, { status: 400 })\n }\n\n const entity = ctx.em.create(CpqProductCharge, { ...body, productId, ...scope })\n await ctx.em.flush()\n\n return NextResponse.json(\n {\n id: entity.id,\n productId: entity.productId,\n code: entity.code,\n name: entity.name,\n chargeType: entity.chargeType,\n pricingMethod: entity.pricingMethod,\n pricingTableId: entity.pricingTableId,\n priceColumnKey: entity.priceColumnKey,\n quantityAttributeCode: entity.quantityAttributeCode,\n applicabilityCondition: entity.applicabilityCondition,\n sortOrder: entity.sortOrder,\n isActive: entity.isActive,\n createdAt: entity.createdAt,\n updatedAt: entity.updatedAt,\n },\n { status: 201 },\n )\n } catch (err) {\n if (err instanceof z.ZodError) {\n return NextResponse.json({ error: 'Validation error', details: err.issues }, { status: 400 })\n }\n console.error('[cpq/product-charges.POST]', err)\n return NextResponse.json({ error: 'Internal error' }, { status: 500 })\n }\n}\n\nexport async function PUT(req: Request) {\n try {\n const ctx = await resolveCpqRouteContext(req)\n if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const body = cpqProductChargeUpdateSchema.parse(await req.json())\n const { id, ...updates } = body\n const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId, deletedAt: null }\n\n const entity = await ctx.em.findOne(CpqProductCharge, { id, ...scope })\n if (!entity) return NextResponse.json({ error: 'Not found' }, { status: 404 })\n\n ctx.em.assign(entity, updates)\n await ctx.em.flush()\n\n return NextResponse.json({\n id: entity.id,\n productId: entity.productId,\n code: entity.code,\n name: entity.name,\n chargeType: entity.chargeType,\n pricingMethod: entity.pricingMethod,\n pricingTableId: entity.pricingTableId,\n priceColumnKey: entity.priceColumnKey,\n quantityAttributeCode: entity.quantityAttributeCode,\n applicabilityCondition: entity.applicabilityCondition,\n sortOrder: entity.sortOrder,\n isActive: entity.isActive,\n createdAt: entity.createdAt,\n updatedAt: entity.updatedAt,\n })\n } catch (err) {\n if (err instanceof z.ZodError) {\n return NextResponse.json({ error: 'Validation error', details: err.issues }, { status: 400 })\n }\n console.error('[cpq/product-charges.PUT]', err)\n return NextResponse.json({ error: 'Internal error' }, { status: 500 })\n }\n}\n\nexport async function DELETE(req: Request) {\n try {\n const ctx = await resolveCpqRouteContext(req)\n if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const { id } = (await req.json()) as { id?: string }\n if (!id) return NextResponse.json({ error: 'Missing id' }, { status: 400 })\n\n const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId, deletedAt: null }\n const entity = await ctx.em.findOne(CpqProductCharge, { id, ...scope })\n if (!entity) return NextResponse.json({ error: 'Not found' }, { status: 404 })\n\n entity.deletedAt = new Date()\n await ctx.em.flush()\n\n return NextResponse.json({ success: true })\n } catch (err) {\n console.error('[cpq/product-charges.DELETE]', err)\n return NextResponse.json({ error: 'Internal error' }, { status: 500 })\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { resolveCpqRouteContext } from '../context'\nimport { CpqProductCharge, CpqProductOffering, CpqProductSpecification } from '../../data/entities'\nimport { cpqProductChargeCreateSchema, cpqProductChargeUpdateSchema } from '../../data/validators'\nimport { normalizeChargePricing } from '../../data/charge-pricing'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['cpq.products.view'] },\n POST: { requireAuth: true, requireFeatures: ['cpq.products.manage'] },\n PUT: { requireAuth: true, requireFeatures: ['cpq.products.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['cpq.products.manage'] },\n}\n\nexport async function GET(req: Request) {\n try {\n const ctx = await resolveCpqRouteContext(req)\n if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const url = new URL(req.url)\n const page = Math.max(1, Number(url.searchParams.get('page') ?? '1'))\n const pageSize = Math.min(100, Math.max(1, Number(url.searchParams.get('pageSize') ?? '50')))\n const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId, deletedAt: null }\n\n const filters: Record<string, unknown> = { ...scope }\n\n const productId = url.searchParams.get('productId')\n if (productId) filters.productId = productId\n const offeringId = url.searchParams.get('offeringId')\n if (offeringId) filters.offeringId = offeringId\n\n const [items, total] = await ctx.em.findAndCount(CpqProductCharge, filters, {\n limit: pageSize,\n offset: (page - 1) * pageSize,\n orderBy: { sortOrder: 'asc' },\n })\n\n return NextResponse.json({\n items: items.map((item) => ({\n id: item.id,\n productId: item.productId,\n offeringId: item.offeringId,\n code: item.code,\n name: item.name,\n description: item.description,\n chargeType: item.chargeType,\n chargeModel: item.chargeModel,\n pricingMethod: item.pricingMethod,\n pricingTableId: item.pricingTableId,\n priceColumnKey: item.priceColumnKey,\n fixedPrice: item.fixedPrice,\n currencyCode: item.currencyCode,\n quantityAttributeCode: item.quantityAttributeCode,\n applicabilityCondition: item.applicabilityCondition,\n sortOrder: item.sortOrder,\n isActive: item.isActive,\n createdAt: item.createdAt,\n updatedAt: item.updatedAt,\n })),\n total,\n page,\n pageSize,\n totalPages: Math.ceil(total / pageSize),\n })\n } catch (err) {\n console.error('[cpq/product-charges.GET]', err)\n return NextResponse.json({ error: 'Internal error' }, { status: 500 })\n }\n}\n\nexport async function POST(req: Request) {\n try {\n const ctx = await resolveCpqRouteContext(req)\n if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const body = cpqProductChargeCreateSchema.parse(await req.json())\n const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId }\n\n // Resolve productId from offering if not provided\n let productId = body.productId\n if (!productId && body.offeringId) {\n const offering = await ctx.em.findOne(CpqProductOffering, { id: body.offeringId, ...scope, deletedAt: null })\n if (offering) {\n const spec = await ctx.em.findOne(CpqProductSpecification, { id: offering.specId, ...scope, deletedAt: null })\n if (spec) productId = spec.productId\n }\n }\n if (!productId) {\n return NextResponse.json({ error: 'productId or offeringId with valid offering required' }, { status: 400 })\n }\n\n // Persist the canonical split shape regardless of whether the client sent\n // the new fields or a legacy combined pricingMethod.\n const { model, source } = normalizeChargePricing(body)\n const entity = ctx.em.create(CpqProductCharge, { ...body, chargeModel: model, pricingMethod: source, productId, ...scope })\n await ctx.em.flush()\n\n return NextResponse.json(\n {\n id: entity.id,\n productId: entity.productId,\n code: entity.code,\n name: entity.name,\n chargeType: entity.chargeType,\n chargeModel: entity.chargeModel,\n pricingMethod: entity.pricingMethod,\n pricingTableId: entity.pricingTableId,\n priceColumnKey: entity.priceColumnKey,\n quantityAttributeCode: entity.quantityAttributeCode,\n applicabilityCondition: entity.applicabilityCondition,\n sortOrder: entity.sortOrder,\n isActive: entity.isActive,\n createdAt: entity.createdAt,\n updatedAt: entity.updatedAt,\n },\n { status: 201 },\n )\n } catch (err) {\n if (err instanceof z.ZodError) {\n return NextResponse.json({ error: 'Validation error', details: err.issues }, { status: 400 })\n }\n console.error('[cpq/product-charges.POST]', err)\n return NextResponse.json({ error: 'Internal error' }, { status: 500 })\n }\n}\n\nexport async function PUT(req: Request) {\n try {\n const ctx = await resolveCpqRouteContext(req)\n if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const body = cpqProductChargeUpdateSchema.parse(await req.json())\n const { id, ...updates } = body\n const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId, deletedAt: null }\n\n const entity = await ctx.em.findOne(CpqProductCharge, { id, ...scope })\n if (!entity) return NextResponse.json({ error: 'Not found' }, { status: 404 })\n\n // Re-derive the canonical split shape when pricing fields are being changed,\n // and clear the opposite axis' fields so a partial PATCH can't leave a\n // stale pricingTableId on a fixed charge (or a stale fixedPrice on a table\n // charge) that the patch-only validator never sees.\n const normalizedUpdates =\n updates.pricingMethod !== undefined || updates.chargeModel !== undefined\n ? (() => {\n const { model, source } = normalizeChargePricing({ ...entity, ...updates })\n const sideCleared = source === 'fixed'\n ? { pricingTableId: null, priceColumnKey: null }\n : { fixedPrice: null }\n return { ...updates, chargeModel: model, pricingMethod: source, ...sideCleared }\n })()\n : updates\n ctx.em.assign(entity, normalizedUpdates)\n await ctx.em.flush()\n\n return NextResponse.json({\n id: entity.id,\n productId: entity.productId,\n code: entity.code,\n name: entity.name,\n chargeType: entity.chargeType,\n chargeModel: entity.chargeModel,\n pricingMethod: entity.pricingMethod,\n pricingTableId: entity.pricingTableId,\n priceColumnKey: entity.priceColumnKey,\n quantityAttributeCode: entity.quantityAttributeCode,\n applicabilityCondition: entity.applicabilityCondition,\n sortOrder: entity.sortOrder,\n isActive: entity.isActive,\n createdAt: entity.createdAt,\n updatedAt: entity.updatedAt,\n })\n } catch (err) {\n if (err instanceof z.ZodError) {\n return NextResponse.json({ error: 'Validation error', details: err.issues }, { status: 400 })\n }\n console.error('[cpq/product-charges.PUT]', err)\n return NextResponse.json({ error: 'Internal error' }, { status: 500 })\n }\n}\n\nexport async function DELETE(req: Request) {\n try {\n const ctx = await resolveCpqRouteContext(req)\n if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const { id } = (await req.json()) as { id?: string }\n if (!id) return NextResponse.json({ error: 'Missing id' }, { status: 400 })\n\n const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId, deletedAt: null }\n const entity = await ctx.em.findOne(CpqProductCharge, { id, ...scope })\n if (!entity) return NextResponse.json({ error: 'Not found' }, { status: 404 })\n\n entity.deletedAt = new Date()\n await ctx.em.flush()\n\n return NextResponse.json({ success: true })\n } catch (err) {\n console.error('[cpq/product-charges.DELETE]', err)\n return NextResponse.json({ error: 'Internal error' }, { status: 500 })\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -80,6 +80,7 @@ async function GET(req) {
80
80
  name: c.name,
81
81
  description: c.description,
82
82
  chargeType: c.chargeType,
83
+ chargeModel: c.chargeModel,
83
84
  pricingMethod: c.pricingMethod,
84
85
  pricingTableId: c.pricingTableId,
85
86
  priceColumnKey: c.priceColumnKey,
@@ -141,6 +142,7 @@ async function GET(req) {
141
142
  name: c.name,
142
143
  description: c.description,
143
144
  chargeType: c.chargeType,
145
+ chargeModel: c.chargeModel,
144
146
  pricingMethod: c.pricingMethod,
145
147
  pricingTableId: c.pricingTableId,
146
148
  priceColumnKey: c.priceColumnKey,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/cpq/api/product-offerings/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { resolveCpqRouteContext } from '../context'\nimport { CpqProductOffering, CpqProductCharge, CpqProductSpecification, CpqOfferingComponent, CpqBundleSlot } from '../../data/entities'\nimport { cpqProductOfferingCreateSchema, cpqProductOfferingUpdateSchema } from '../../data/validators'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['cpq.offerings.view'] },\n POST: { requireAuth: true, requireFeatures: ['cpq.offerings.manage'] },\n PUT: { requireAuth: true, requireFeatures: ['cpq.offerings.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['cpq.offerings.manage'] },\n}\n\nexport async function GET(req: Request) {\n try {\n const ctx = await resolveCpqRouteContext(req)\n if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const url = new URL(req.url)\n const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId, deletedAt: null }\n\n // Detail view \u2014 load offering with spec and charges\n const id = url.searchParams.get('id')\n if (id) {\n const offering = await ctx.em.findOne(CpqProductOffering, { id, ...scope })\n if (!offering) return NextResponse.json({ error: 'Not found' }, { status: 404 })\n\n const [spec, charges] = await Promise.all([\n ctx.em.findOne(CpqProductSpecification, { id: offering.specId, ...scope }),\n ctx.em.find(CpqProductCharge, { offeringId: offering.id, ...scope }, { orderBy: { sortOrder: 'asc' } }),\n ])\n\n // Load components for bundle offerings\n let componentsData: Array<Record<string, unknown>> = []\n if (offering.offeringType === 'bundle') {\n const components = await ctx.em.find(CpqOfferingComponent, { offeringId: offering.id, ...scope }, { orderBy: { sortOrder: 'asc' } })\n if (components.length > 0) {\n const slotIds = [...new Set(components.map((c) => c.slotId))]\n const childOfferingIds = [...new Set(components.map((c) => c.childOfferingId))]\n const [slots, childOfferings] = await Promise.all([\n ctx.em.find(CpqBundleSlot, { id: { $in: slotIds }, ...scope }),\n ctx.em.find(CpqProductOffering, { id: { $in: childOfferingIds }, ...scope }),\n ])\n const slotMap = new Map(slots.map((s) => [s.id, s]))\n const offeringMap = new Map(childOfferings.map((o) => [o.id, o]))\n componentsData = components.map((c) => {\n const sl = slotMap.get(c.slotId)\n const ch = offeringMap.get(c.childOfferingId)\n return {\n id: c.id,\n slotId: c.slotId,\n slot: sl ? { id: sl.id, componentGroup: sl.componentGroup, name: sl.name, cardinalityMin: sl.cardinalityMin, cardinalityMax: sl.cardinalityMax } : null,\n childOfferingId: c.childOfferingId,\n childOffering: ch ? { id: ch.id, code: ch.code, name: ch.name, offeringType: ch.offeringType, specId: ch.specId } : null,\n isDefault: c.isDefault,\n sortOrder: c.sortOrder,\n isActive: c.isActive,\n }\n })\n }\n }\n\n return NextResponse.json({\n id: offering.id,\n specId: offering.specId,\n catalogOfferId: offering.catalogOfferId,\n code: offering.code,\n name: offering.name,\n description: offering.description,\n offeringType: offering.offeringType,\n designTimeValues: offering.designTimeValues,\n lifecycleStatus: offering.lifecycleStatus,\n effectiveFrom: offering.effectiveFrom,\n effectiveTo: offering.effectiveTo,\n metadata: offering.metadata,\n isActive: offering.isActive,\n createdAt: offering.createdAt,\n updatedAt: offering.updatedAt,\n specification: spec\n ? {\n id: spec.id,\n code: spec.code,\n name: spec.name,\n productId: spec.productId,\n specType: spec.specType,\n }\n : null,\n charges: charges.map((c) => ({\n id: c.id,\n code: c.code,\n name: c.name,\n description: c.description,\n chargeType: c.chargeType,\n pricingMethod: c.pricingMethod,\n pricingTableId: c.pricingTableId,\n priceColumnKey: c.priceColumnKey,\n fixedPrice: c.fixedPrice,\n currencyCode: c.currencyCode,\n quantityAttributeCode: c.quantityAttributeCode,\n applicabilityCondition: c.applicabilityCondition,\n sortOrder: c.sortOrder,\n isActive: c.isActive,\n })),\n ...(offering.offeringType === 'bundle' ? { components: componentsData } : {}),\n })\n }\n\n // List view\n const page = Math.max(1, Number(url.searchParams.get('page') ?? '1'))\n const pageSize = Math.min(100, Math.max(1, Number(url.searchParams.get('pageSize') ?? '50')))\n\n const filters: Record<string, unknown> = { ...scope }\n const specId = url.searchParams.get('specId')\n if (specId) filters.specId = specId\n const lifecycleStatus = url.searchParams.get('lifecycleStatus')\n if (lifecycleStatus) filters.lifecycleStatus = lifecycleStatus\n const code = url.searchParams.get('code')\n if (code) filters.code = code\n const offeringType = url.searchParams.get('offeringType')\n if (offeringType) filters.offeringType = offeringType\n const isActiveParam = url.searchParams.get('isActive')\n if (isActiveParam === 'true') filters.isActive = true\n else if (isActiveParam === 'false') filters.isActive = false\n\n // Free-text search across code, name, description (case-insensitive)\n const search = url.searchParams.get('search')?.trim()\n if (search) {\n filters.$or = [\n { code: { $ilike: `%${search}%` } },\n { name: { $ilike: `%${search}%` } },\n { description: { $ilike: `%${search}%` } },\n ]\n }\n\n // Configurable sort\n const ALLOWED_SORT_FIELDS = ['createdAt', 'updatedAt', 'code', 'name', 'lifecycleStatus', 'offeringType'] as const\n const sortFieldParam = url.searchParams.get('sortField') ?? ''\n const sortField = (ALLOWED_SORT_FIELDS as readonly string[]).includes(sortFieldParam)\n ? (sortFieldParam as (typeof ALLOWED_SORT_FIELDS)[number])\n : 'createdAt'\n const sortDir = url.searchParams.get('sortDir') === 'asc' ? 'asc' : 'desc'\n\n const [items, total] = await ctx.em.findAndCount(CpqProductOffering, filters, {\n limit: pageSize,\n offset: (page - 1) * pageSize,\n orderBy: { [sortField]: sortDir },\n })\n\n // Batch-load charges for all visible offerings in a single query\n // (replaces an N+1 pattern where the UI fetched each offering's detail separately).\n const offeringIds = items.map((item) => item.id)\n const chargesByOffering = new Map<string, Array<Record<string, unknown>>>()\n if (offeringIds.length > 0) {\n const allCharges = await ctx.em.find(\n CpqProductCharge,\n { offeringId: { $in: offeringIds }, ...scope },\n { orderBy: { sortOrder: 'asc' } },\n )\n for (const c of allCharges) {\n if (!c.offeringId) continue\n const list = chargesByOffering.get(c.offeringId) ?? []\n list.push({\n id: c.id,\n code: c.code,\n name: c.name,\n description: c.description,\n chargeType: c.chargeType,\n pricingMethod: c.pricingMethod,\n pricingTableId: c.pricingTableId,\n priceColumnKey: c.priceColumnKey,\n fixedPrice: c.fixedPrice,\n currencyCode: c.currencyCode,\n quantityAttributeCode: c.quantityAttributeCode,\n applicabilityCondition: c.applicabilityCondition,\n sortOrder: c.sortOrder,\n isActive: c.isActive,\n })\n chargesByOffering.set(c.offeringId, list)\n }\n }\n\n return NextResponse.json({\n items: items.map((item) => ({\n id: item.id,\n specId: item.specId,\n catalogOfferId: item.catalogOfferId,\n code: item.code,\n name: item.name,\n description: item.description,\n offeringType: item.offeringType,\n designTimeValues: item.designTimeValues,\n lifecycleStatus: item.lifecycleStatus,\n effectiveFrom: item.effectiveFrom,\n effectiveTo: item.effectiveTo,\n isActive: item.isActive,\n createdAt: item.createdAt,\n updatedAt: item.updatedAt,\n charges: chargesByOffering.get(item.id) ?? [],\n })),\n total,\n page,\n pageSize,\n totalPages: Math.ceil(total / pageSize),\n })\n } catch (err) {\n console.error('[cpq/product-offerings.GET]', err)\n return NextResponse.json({ error: 'Internal error' }, { status: 500 })\n }\n}\n\nexport async function POST(req: Request) {\n try {\n const ctx = await resolveCpqRouteContext(req)\n if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const body = cpqProductOfferingCreateSchema.parse(await req.json())\n const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId }\n\n // Validate that the spec exists\n const spec = await ctx.em.findOne(CpqProductSpecification, {\n id: body.specId,\n ...scope,\n deletedAt: null,\n })\n if (!spec) return NextResponse.json({ error: 'Specification not found' }, { status: 400 })\n\n // V-OFF-7: offeringType must match spec's specType\n const offeringType = body.offeringType ?? 'simple'\n if (offeringType !== spec.specType) {\n return NextResponse.json(\n { error: `V-OFF-7: offeringType '${offeringType}' must match specification's specType '${spec.specType}'` },\n { status: 400 },\n )\n }\n\n const entity = ctx.em.create(CpqProductOffering, { ...body, ...scope })\n await ctx.em.flush()\n\n return NextResponse.json(\n {\n id: entity.id,\n specId: entity.specId,\n catalogOfferId: entity.catalogOfferId,\n code: entity.code,\n name: entity.name,\n description: entity.description,\n offeringType: entity.offeringType,\n designTimeValues: entity.designTimeValues,\n lifecycleStatus: entity.lifecycleStatus,\n effectiveFrom: entity.effectiveFrom,\n effectiveTo: entity.effectiveTo,\n metadata: entity.metadata,\n isActive: entity.isActive,\n createdAt: entity.createdAt,\n updatedAt: entity.updatedAt,\n },\n { status: 201 },\n )\n } catch (err) {\n if (err instanceof z.ZodError) {\n return NextResponse.json({ error: 'Validation error', details: err.issues }, { status: 400 })\n }\n console.error('[cpq/product-offerings.POST]', err)\n return NextResponse.json({ error: 'Internal error' }, { status: 500 })\n }\n}\n\nexport async function PUT(req: Request) {\n try {\n const ctx = await resolveCpqRouteContext(req)\n if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const body = cpqProductOfferingUpdateSchema.parse(await req.json())\n const { id, ...updates } = body\n const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId, deletedAt: null }\n\n const entity = await ctx.em.findOne(CpqProductOffering, { id, ...scope })\n if (!entity) return NextResponse.json({ error: 'Not found' }, { status: 404 })\n\n ctx.em.assign(entity, updates)\n await ctx.em.flush()\n\n return NextResponse.json({\n id: entity.id,\n specId: entity.specId,\n catalogOfferId: entity.catalogOfferId,\n code: entity.code,\n name: entity.name,\n description: entity.description,\n offeringType: entity.offeringType,\n designTimeValues: entity.designTimeValues,\n lifecycleStatus: entity.lifecycleStatus,\n effectiveFrom: entity.effectiveFrom,\n effectiveTo: entity.effectiveTo,\n metadata: entity.metadata,\n isActive: entity.isActive,\n createdAt: entity.createdAt,\n updatedAt: entity.updatedAt,\n })\n } catch (err) {\n if (err instanceof z.ZodError) {\n return NextResponse.json({ error: 'Validation error', details: err.issues }, { status: 400 })\n }\n console.error('[cpq/product-offerings.PUT]', err)\n return NextResponse.json({ error: 'Internal error' }, { status: 500 })\n }\n}\n\nexport async function DELETE(req: Request) {\n try {\n const ctx = await resolveCpqRouteContext(req)\n if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const { id } = (await req.json()) as { id?: string }\n if (!id) return NextResponse.json({ error: 'Missing id' }, { status: 400 })\n\n const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId, deletedAt: null }\n const entity = await ctx.em.findOne(CpqProductOffering, { id, ...scope })\n if (!entity) return NextResponse.json({ error: 'Not found' }, { status: 404 })\n\n entity.deletedAt = new Date()\n await ctx.em.flush()\n\n return NextResponse.json({ success: true })\n } catch (err) {\n console.error('[cpq/product-offerings.DELETE]', err)\n return NextResponse.json({ error: 'Internal error' }, { status: 500 })\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { resolveCpqRouteContext } from '../context'\nimport { CpqProductOffering, CpqProductCharge, CpqProductSpecification, CpqOfferingComponent, CpqBundleSlot } from '../../data/entities'\nimport { cpqProductOfferingCreateSchema, cpqProductOfferingUpdateSchema } from '../../data/validators'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['cpq.offerings.view'] },\n POST: { requireAuth: true, requireFeatures: ['cpq.offerings.manage'] },\n PUT: { requireAuth: true, requireFeatures: ['cpq.offerings.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['cpq.offerings.manage'] },\n}\n\nexport async function GET(req: Request) {\n try {\n const ctx = await resolveCpqRouteContext(req)\n if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const url = new URL(req.url)\n const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId, deletedAt: null }\n\n // Detail view \u2014 load offering with spec and charges\n const id = url.searchParams.get('id')\n if (id) {\n const offering = await ctx.em.findOne(CpqProductOffering, { id, ...scope })\n if (!offering) return NextResponse.json({ error: 'Not found' }, { status: 404 })\n\n const [spec, charges] = await Promise.all([\n ctx.em.findOne(CpqProductSpecification, { id: offering.specId, ...scope }),\n ctx.em.find(CpqProductCharge, { offeringId: offering.id, ...scope }, { orderBy: { sortOrder: 'asc' } }),\n ])\n\n // Load components for bundle offerings\n let componentsData: Array<Record<string, unknown>> = []\n if (offering.offeringType === 'bundle') {\n const components = await ctx.em.find(CpqOfferingComponent, { offeringId: offering.id, ...scope }, { orderBy: { sortOrder: 'asc' } })\n if (components.length > 0) {\n const slotIds = [...new Set(components.map((c) => c.slotId))]\n const childOfferingIds = [...new Set(components.map((c) => c.childOfferingId))]\n const [slots, childOfferings] = await Promise.all([\n ctx.em.find(CpqBundleSlot, { id: { $in: slotIds }, ...scope }),\n ctx.em.find(CpqProductOffering, { id: { $in: childOfferingIds }, ...scope }),\n ])\n const slotMap = new Map(slots.map((s) => [s.id, s]))\n const offeringMap = new Map(childOfferings.map((o) => [o.id, o]))\n componentsData = components.map((c) => {\n const sl = slotMap.get(c.slotId)\n const ch = offeringMap.get(c.childOfferingId)\n return {\n id: c.id,\n slotId: c.slotId,\n slot: sl ? { id: sl.id, componentGroup: sl.componentGroup, name: sl.name, cardinalityMin: sl.cardinalityMin, cardinalityMax: sl.cardinalityMax } : null,\n childOfferingId: c.childOfferingId,\n childOffering: ch ? { id: ch.id, code: ch.code, name: ch.name, offeringType: ch.offeringType, specId: ch.specId } : null,\n isDefault: c.isDefault,\n sortOrder: c.sortOrder,\n isActive: c.isActive,\n }\n })\n }\n }\n\n return NextResponse.json({\n id: offering.id,\n specId: offering.specId,\n catalogOfferId: offering.catalogOfferId,\n code: offering.code,\n name: offering.name,\n description: offering.description,\n offeringType: offering.offeringType,\n designTimeValues: offering.designTimeValues,\n lifecycleStatus: offering.lifecycleStatus,\n effectiveFrom: offering.effectiveFrom,\n effectiveTo: offering.effectiveTo,\n metadata: offering.metadata,\n isActive: offering.isActive,\n createdAt: offering.createdAt,\n updatedAt: offering.updatedAt,\n specification: spec\n ? {\n id: spec.id,\n code: spec.code,\n name: spec.name,\n productId: spec.productId,\n specType: spec.specType,\n }\n : null,\n charges: charges.map((c) => ({\n id: c.id,\n code: c.code,\n name: c.name,\n description: c.description,\n chargeType: c.chargeType,\n chargeModel: c.chargeModel,\n pricingMethod: c.pricingMethod,\n pricingTableId: c.pricingTableId,\n priceColumnKey: c.priceColumnKey,\n fixedPrice: c.fixedPrice,\n currencyCode: c.currencyCode,\n quantityAttributeCode: c.quantityAttributeCode,\n applicabilityCondition: c.applicabilityCondition,\n sortOrder: c.sortOrder,\n isActive: c.isActive,\n })),\n ...(offering.offeringType === 'bundle' ? { components: componentsData } : {}),\n })\n }\n\n // List view\n const page = Math.max(1, Number(url.searchParams.get('page') ?? '1'))\n const pageSize = Math.min(100, Math.max(1, Number(url.searchParams.get('pageSize') ?? '50')))\n\n const filters: Record<string, unknown> = { ...scope }\n const specId = url.searchParams.get('specId')\n if (specId) filters.specId = specId\n const lifecycleStatus = url.searchParams.get('lifecycleStatus')\n if (lifecycleStatus) filters.lifecycleStatus = lifecycleStatus\n const code = url.searchParams.get('code')\n if (code) filters.code = code\n const offeringType = url.searchParams.get('offeringType')\n if (offeringType) filters.offeringType = offeringType\n const isActiveParam = url.searchParams.get('isActive')\n if (isActiveParam === 'true') filters.isActive = true\n else if (isActiveParam === 'false') filters.isActive = false\n\n // Free-text search across code, name, description (case-insensitive)\n const search = url.searchParams.get('search')?.trim()\n if (search) {\n filters.$or = [\n { code: { $ilike: `%${search}%` } },\n { name: { $ilike: `%${search}%` } },\n { description: { $ilike: `%${search}%` } },\n ]\n }\n\n // Configurable sort\n const ALLOWED_SORT_FIELDS = ['createdAt', 'updatedAt', 'code', 'name', 'lifecycleStatus', 'offeringType'] as const\n const sortFieldParam = url.searchParams.get('sortField') ?? ''\n const sortField = (ALLOWED_SORT_FIELDS as readonly string[]).includes(sortFieldParam)\n ? (sortFieldParam as (typeof ALLOWED_SORT_FIELDS)[number])\n : 'createdAt'\n const sortDir = url.searchParams.get('sortDir') === 'asc' ? 'asc' : 'desc'\n\n const [items, total] = await ctx.em.findAndCount(CpqProductOffering, filters, {\n limit: pageSize,\n offset: (page - 1) * pageSize,\n orderBy: { [sortField]: sortDir },\n })\n\n // Batch-load charges for all visible offerings in a single query\n // (replaces an N+1 pattern where the UI fetched each offering's detail separately).\n const offeringIds = items.map((item) => item.id)\n const chargesByOffering = new Map<string, Array<Record<string, unknown>>>()\n if (offeringIds.length > 0) {\n const allCharges = await ctx.em.find(\n CpqProductCharge,\n { offeringId: { $in: offeringIds }, ...scope },\n { orderBy: { sortOrder: 'asc' } },\n )\n for (const c of allCharges) {\n if (!c.offeringId) continue\n const list = chargesByOffering.get(c.offeringId) ?? []\n list.push({\n id: c.id,\n code: c.code,\n name: c.name,\n description: c.description,\n chargeType: c.chargeType,\n chargeModel: c.chargeModel,\n pricingMethod: c.pricingMethod,\n pricingTableId: c.pricingTableId,\n priceColumnKey: c.priceColumnKey,\n fixedPrice: c.fixedPrice,\n currencyCode: c.currencyCode,\n quantityAttributeCode: c.quantityAttributeCode,\n applicabilityCondition: c.applicabilityCondition,\n sortOrder: c.sortOrder,\n isActive: c.isActive,\n })\n chargesByOffering.set(c.offeringId, list)\n }\n }\n\n return NextResponse.json({\n items: items.map((item) => ({\n id: item.id,\n specId: item.specId,\n catalogOfferId: item.catalogOfferId,\n code: item.code,\n name: item.name,\n description: item.description,\n offeringType: item.offeringType,\n designTimeValues: item.designTimeValues,\n lifecycleStatus: item.lifecycleStatus,\n effectiveFrom: item.effectiveFrom,\n effectiveTo: item.effectiveTo,\n isActive: item.isActive,\n createdAt: item.createdAt,\n updatedAt: item.updatedAt,\n charges: chargesByOffering.get(item.id) ?? [],\n })),\n total,\n page,\n pageSize,\n totalPages: Math.ceil(total / pageSize),\n })\n } catch (err) {\n console.error('[cpq/product-offerings.GET]', err)\n return NextResponse.json({ error: 'Internal error' }, { status: 500 })\n }\n}\n\nexport async function POST(req: Request) {\n try {\n const ctx = await resolveCpqRouteContext(req)\n if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const body = cpqProductOfferingCreateSchema.parse(await req.json())\n const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId }\n\n // Validate that the spec exists\n const spec = await ctx.em.findOne(CpqProductSpecification, {\n id: body.specId,\n ...scope,\n deletedAt: null,\n })\n if (!spec) return NextResponse.json({ error: 'Specification not found' }, { status: 400 })\n\n // V-OFF-7: offeringType must match spec's specType\n const offeringType = body.offeringType ?? 'simple'\n if (offeringType !== spec.specType) {\n return NextResponse.json(\n { error: `V-OFF-7: offeringType '${offeringType}' must match specification's specType '${spec.specType}'` },\n { status: 400 },\n )\n }\n\n const entity = ctx.em.create(CpqProductOffering, { ...body, ...scope })\n await ctx.em.flush()\n\n return NextResponse.json(\n {\n id: entity.id,\n specId: entity.specId,\n catalogOfferId: entity.catalogOfferId,\n code: entity.code,\n name: entity.name,\n description: entity.description,\n offeringType: entity.offeringType,\n designTimeValues: entity.designTimeValues,\n lifecycleStatus: entity.lifecycleStatus,\n effectiveFrom: entity.effectiveFrom,\n effectiveTo: entity.effectiveTo,\n metadata: entity.metadata,\n isActive: entity.isActive,\n createdAt: entity.createdAt,\n updatedAt: entity.updatedAt,\n },\n { status: 201 },\n )\n } catch (err) {\n if (err instanceof z.ZodError) {\n return NextResponse.json({ error: 'Validation error', details: err.issues }, { status: 400 })\n }\n console.error('[cpq/product-offerings.POST]', err)\n return NextResponse.json({ error: 'Internal error' }, { status: 500 })\n }\n}\n\nexport async function PUT(req: Request) {\n try {\n const ctx = await resolveCpqRouteContext(req)\n if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const body = cpqProductOfferingUpdateSchema.parse(await req.json())\n const { id, ...updates } = body\n const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId, deletedAt: null }\n\n const entity = await ctx.em.findOne(CpqProductOffering, { id, ...scope })\n if (!entity) return NextResponse.json({ error: 'Not found' }, { status: 404 })\n\n ctx.em.assign(entity, updates)\n await ctx.em.flush()\n\n return NextResponse.json({\n id: entity.id,\n specId: entity.specId,\n catalogOfferId: entity.catalogOfferId,\n code: entity.code,\n name: entity.name,\n description: entity.description,\n offeringType: entity.offeringType,\n designTimeValues: entity.designTimeValues,\n lifecycleStatus: entity.lifecycleStatus,\n effectiveFrom: entity.effectiveFrom,\n effectiveTo: entity.effectiveTo,\n metadata: entity.metadata,\n isActive: entity.isActive,\n createdAt: entity.createdAt,\n updatedAt: entity.updatedAt,\n })\n } catch (err) {\n if (err instanceof z.ZodError) {\n return NextResponse.json({ error: 'Validation error', details: err.issues }, { status: 400 })\n }\n console.error('[cpq/product-offerings.PUT]', err)\n return NextResponse.json({ error: 'Internal error' }, { status: 500 })\n }\n}\n\nexport async function DELETE(req: Request) {\n try {\n const ctx = await resolveCpqRouteContext(req)\n if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const { id } = (await req.json()) as { id?: string }\n if (!id) return NextResponse.json({ error: 'Missing id' }, { status: 400 })\n\n const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId, deletedAt: null }\n const entity = await ctx.em.findOne(CpqProductOffering, { id, ...scope })\n if (!entity) return NextResponse.json({ error: 'Not found' }, { status: 404 })\n\n entity.deletedAt = new Date()\n await ctx.em.flush()\n\n return NextResponse.json({ success: true })\n } catch (err) {\n console.error('[cpq/product-offerings.DELETE]', err)\n return NextResponse.json({ error: 'Internal error' }, { status: 500 })\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }