@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.
- package/CHANGELOG.md +62 -0
- package/MIGRATION.md +34 -1
- package/README.md +17 -12
- package/dist/modules/cpq/api/product-attributes/route.js +28 -53
- package/dist/modules/cpq/api/product-attributes/route.js.map +2 -2
- package/dist/modules/cpq/api/product-charges/route.js +12 -2
- package/dist/modules/cpq/api/product-charges/route.js.map +2 -2
- package/dist/modules/cpq/api/product-offerings/route.js +2 -0
- package/dist/modules/cpq/api/product-offerings/route.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/offerings/[id]/page.js +128 -59
- package/dist/modules/cpq/backend/cpq/offerings/[id]/page.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/pricing/[id]/EntriesEditor.js +11 -11
- package/dist/modules/cpq/backend/cpq/pricing/[id]/EntriesEditor.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/specifications/[id]/page.js +55 -1
- package/dist/modules/cpq/backend/cpq/specifications/[id]/page.js.map +2 -2
- package/dist/modules/cpq/cli.js +58 -4
- package/dist/modules/cpq/cli.js.map +3 -3
- package/dist/modules/cpq/data/charge-pricing.js +12 -0
- package/dist/modules/cpq/data/charge-pricing.js.map +7 -0
- package/dist/modules/cpq/data/entities.js +3 -0
- package/dist/modules/cpq/data/entities.js.map +2 -2
- package/dist/modules/cpq/data/validators.js +36 -15
- package/dist/modules/cpq/data/validators.js.map +2 -2
- package/dist/modules/cpq/events.js +7 -0
- package/dist/modules/cpq/events.js.map +2 -2
- package/dist/modules/cpq/lib/seeds/demo.js +103 -0
- package/dist/modules/cpq/lib/seeds/demo.js.map +7 -0
- package/dist/modules/cpq/migrations/Migration20260602000000_cpq_charge_model_split.js +28 -0
- package/dist/modules/cpq/migrations/Migration20260602000000_cpq_charge_model_split.js.map +7 -0
- package/dist/modules/cpq/services/cpqOrderService.js +130 -76
- package/dist/modules/cpq/services/cpqOrderService.js.map +2 -2
- package/dist/modules/cpq/services/cpqPricingService.js +53 -29
- package/dist/modules/cpq/services/cpqPricingService.js.map +3 -3
- package/dist/modules/cpq/services/types.js.map +2 -2
- package/package.json +11 -9
- package/src/modules/cpq/__integration__/TC-CPQ-012.spec.ts +34 -4
- package/src/modules/cpq/__tests__/package-isolation.test.ts +8 -3
- package/src/modules/cpq/api/product-attributes/__tests__/serializeAttribute.test.ts +62 -0
- package/src/modules/cpq/api/product-attributes/route.ts +34 -52
- package/src/modules/cpq/api/product-charges/route.ts +23 -2
- package/src/modules/cpq/api/product-offerings/route.ts +2 -0
- package/src/modules/cpq/backend/cpq/offerings/[id]/page.tsx +151 -62
- package/src/modules/cpq/backend/cpq/pricing/[id]/EntriesEditor.tsx +24 -22
- package/src/modules/cpq/backend/cpq/specifications/[id]/page.tsx +73 -1
- package/src/modules/cpq/cli.ts +83 -4
- package/src/modules/cpq/data/__tests__/charge-pricing.test.ts +49 -0
- package/src/modules/cpq/data/__tests__/validators.test.ts +60 -5
- package/src/modules/cpq/data/charge-pricing.ts +48 -0
- package/src/modules/cpq/data/entities.ts +10 -0
- package/src/modules/cpq/data/validators.ts +56 -26
- package/src/modules/cpq/events.ts +11 -3
- package/src/modules/cpq/lib/seeds/__tests__/demo.test.ts +78 -0
- package/src/modules/cpq/lib/seeds/demo.ts +145 -0
- package/src/modules/cpq/migrations/.snapshot-open-mercato.json +16 -0
- package/src/modules/cpq/migrations/Migration20260602000000_cpq_charge_model_split.ts +57 -0
- package/src/modules/cpq/services/__tests__/cpqPricingService.test.ts +172 -1
- package/src/modules/cpq/services/cpqOrderService.ts +214 -103
- package/src/modules/cpq/services/cpqPricingService.ts +82 -34
- 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
|
|
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
|
-
|
|
11
|
-
|
|
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
|
-
|
|
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 **
|
|
59
|
-
|
|
60
|
-
- **L3** — `dainamite-core` is the first L3 customer app
|
|
61
|
-
|
|
62
|
-
|
|
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
|
|
104
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAClB,SAAS,8BAA8B;AACvC,SAAS,qBAAqB,+BAA+B;AAC7D,SAAS,iCAAiC,uCAAuC;AAE1E,MAAM,WAAW;AAAA,EACtB,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,mBAAmB,EAAE;AAAA,EACjE,MAAM,EAAE,aAAa,MAAM,iBAAiB,CAAC,qBAAqB,EAAE;AAAA,EACpE,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,qBAAqB,EAAE;AAAA,EACnE,QAAQ,EAAE,aAAa,MAAM,iBAAiB,CAAC,qBAAqB,EAAE;AACxE;AAEA,eAAsB,IAAI,KAAc;AACtC,MAAI;AACF,UAAM,MAAM,MAAM,uBAAuB,GAAG;AAC5C,QAAI,CAAC,IAAI,KAAM,QAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAElF,UAAM,MAAM,IAAI,IAAI,IAAI,GAAG;AAC3B,UAAM,OAAO,KAAK,IAAI,GAAG,OAAO,IAAI,aAAa,IAAI,MAAM,KAAK,GAAG,CAAC;AACpE,UAAM,WAAW,KAAK,IAAI,KAAK,KAAK,IAAI,GAAG,OAAO,IAAI,aAAa,IAAI,UAAU,KAAK,IAAI,CAAC,CAAC;AAC5F,UAAM,QAAQ,EAAE,gBAAgB,IAAI,gBAAgB,UAAU,IAAI,UAAU,WAAW,KAAK;AAE5F,UAAM,UAAmC,EAAE,GAAG,MAAM;AAEpD,UAAM,YAAY,IAAI,aAAa,IAAI,WAAW;AAClD,QAAI,UAAW,SAAQ,YAAY;AACnC,UAAM,SAAS,IAAI,aAAa,IAAI,QAAQ;AAC5C,QAAI,OAAQ,SAAQ,SAAS;AAE7B,UAAM,CAAC,OAAO,KAAK,IAAI,MAAM,IAAI,GAAG,aAAa,qBAAqB,SAAS;AAAA,MAC7E,OAAO;AAAA,MACP,SAAS,OAAO,KAAK;AAAA,MACrB,SAAS,EAAE,WAAW,MAAM;AAAA,IAC9B,CAAC;AAED,WAAO,aAAa,KAAK;AAAA,MACvB,OAAO,MAAM,IAAI,
|
|
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": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAClB,SAAS,8BAA8B;AACvC,SAAS,qBAAqB,+BAA+B;AAC7D,SAAS,iCAAiC,uCAAuC;AAE1E,MAAM,WAAW;AAAA,EACtB,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,mBAAmB,EAAE;AAAA,EACjE,MAAM,EAAE,aAAa,MAAM,iBAAiB,CAAC,qBAAqB,EAAE;AAAA,EACpE,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,qBAAqB,EAAE;AAAA,EACnE,QAAQ,EAAE,aAAa,MAAM,iBAAiB,CAAC,qBAAqB,EAAE;AACxE;AASO,SAAS,mBAAmB,MAA2B;AAC5D,SAAO;AAAA,IACL,IAAI,KAAK;AAAA,IACT,WAAW,KAAK;AAAA,IAChB,QAAQ,KAAK;AAAA,IACb,MAAM,KAAK;AAAA,IACX,MAAM,KAAK;AAAA,IACX,eAAe,KAAK;AAAA,IACpB,gBAAgB,KAAK;AAAA,IACrB,SAAS,KAAK;AAAA,IACd,aAAa,KAAK;AAAA,IAClB,iBAAiB,KAAK;AAAA,IACtB,iBAAiB,KAAK;AAAA,IACtB,WAAW,KAAK;AAAA,IAChB,cAAc,KAAK;AAAA,IACnB,UAAU,KAAK;AAAA,IACf,WAAW,KAAK;AAAA,IAChB,YAAY,KAAK;AAAA,IACjB,UAAU,KAAK;AAAA,IACf,WAAW,KAAK;AAAA,IAChB,WAAW,KAAK;AAAA,EAClB;AACF;AAEA,eAAsB,IAAI,KAAc;AACtC,MAAI;AACF,UAAM,MAAM,MAAM,uBAAuB,GAAG;AAC5C,QAAI,CAAC,IAAI,KAAM,QAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAElF,UAAM,MAAM,IAAI,IAAI,IAAI,GAAG;AAC3B,UAAM,OAAO,KAAK,IAAI,GAAG,OAAO,IAAI,aAAa,IAAI,MAAM,KAAK,GAAG,CAAC;AACpE,UAAM,WAAW,KAAK,IAAI,KAAK,KAAK,IAAI,GAAG,OAAO,IAAI,aAAa,IAAI,UAAU,KAAK,IAAI,CAAC,CAAC;AAC5F,UAAM,QAAQ,EAAE,gBAAgB,IAAI,gBAAgB,UAAU,IAAI,UAAU,WAAW,KAAK;AAE5F,UAAM,UAAmC,EAAE,GAAG,MAAM;AAEpD,UAAM,YAAY,IAAI,aAAa,IAAI,WAAW;AAClD,QAAI,UAAW,SAAQ,YAAY;AACnC,UAAM,SAAS,IAAI,aAAa,IAAI,QAAQ;AAC5C,QAAI,OAAQ,SAAQ,SAAS;AAE7B,UAAM,CAAC,OAAO,KAAK,IAAI,MAAM,IAAI,GAAG,aAAa,qBAAqB,SAAS;AAAA,MAC7E,OAAO;AAAA,MACP,SAAS,OAAO,KAAK;AAAA,MACrB,SAAS,EAAE,WAAW,MAAM;AAAA,IAC9B,CAAC;AAED,WAAO,aAAa,KAAK;AAAA,MACvB,OAAO,MAAM,IAAI,kBAAkB;AAAA,MACnC;AAAA,MACA;AAAA,MACA;AAAA,MACA,YAAY,KAAK,KAAK,QAAQ,QAAQ;AAAA,IACxC,CAAC;AAAA,EACH,SAAS,KAAK;AACZ,YAAQ,MAAM,gCAAgC,GAAG;AACjD,WAAO,aAAa,KAAK,EAAE,OAAO,iBAAiB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACvE;AACF;AAEA,eAAsB,KAAK,KAAc;AACvC,MAAI;AACF,UAAM,MAAM,MAAM,uBAAuB,GAAG;AAC5C,QAAI,CAAC,IAAI,KAAM,QAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAElF,UAAM,OAAO,gCAAgC,MAAM,MAAM,IAAI,KAAK,CAAC;AACnE,UAAM,QAAQ,EAAE,gBAAgB,IAAI,gBAAgB,UAAU,IAAI,SAAS;AAG3E,QAAI,YAAY,KAAK;AACrB,QAAI,CAAC,aAAa,KAAK,QAAQ;AAC7B,YAAM,OAAO,MAAM,IAAI,GAAG,QAAQ,yBAAyB,EAAE,IAAI,KAAK,QAAQ,GAAG,OAAO,WAAW,KAAK,CAAC;AACzG,UAAI,KAAM,aAAY,KAAK;AAAA,IAC7B;AACA,QAAI,CAAC,WAAW;AACd,aAAO,aAAa,KAAK,EAAE,OAAO,wDAAwD,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IAC9G;AAEA,UAAM,SAAS,IAAI,GAAG,OAAO,qBAAqB,EAAE,GAAG,MAAM,WAAW,GAAG,MAAM,CAAC;AAClF,UAAM,IAAI,GAAG,MAAM;AAEnB,WAAO,aAAa,KAAK,mBAAmB,MAAM,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACtE,SAAS,KAAK;AACZ,QAAI,eAAe,EAAE,UAAU;AAC7B,aAAO,aAAa,KAAK,EAAE,OAAO,oBAAoB,SAAS,IAAI,OAAO,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IAC9F;AACA,YAAQ,MAAM,iCAAiC,GAAG;AAClD,WAAO,aAAa,KAAK,EAAE,OAAO,iBAAiB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACvE;AACF;AAEA,eAAsB,IAAI,KAAc;AACtC,MAAI;AACF,UAAM,MAAM,MAAM,uBAAuB,GAAG;AAC5C,QAAI,CAAC,IAAI,KAAM,QAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAElF,UAAM,OAAO,gCAAgC,MAAM,MAAM,IAAI,KAAK,CAAC;AACnE,UAAM,EAAE,IAAI,GAAG,QAAQ,IAAI;AAC3B,UAAM,QAAQ,EAAE,gBAAgB,IAAI,gBAAgB,UAAU,IAAI,UAAU,WAAW,KAAK;AAE5F,UAAM,SAAS,MAAM,IAAI,GAAG,QAAQ,qBAAqB,EAAE,IAAI,GAAG,MAAM,CAAC;AACzE,QAAI,CAAC,OAAQ,QAAO,aAAa,KAAK,EAAE,OAAO,YAAY,GAAG,EAAE,QAAQ,IAAI,CAAC;AAE7E,QAAI,GAAG,OAAO,QAAQ,OAAO;AAC7B,UAAM,IAAI,GAAG,MAAM;AAEnB,WAAO,aAAa,KAAK,mBAAmB,MAAM,CAAC;AAAA,EACrD,SAAS,KAAK;AACZ,QAAI,eAAe,EAAE,UAAU;AAC7B,aAAO,aAAa,KAAK,EAAE,OAAO,oBAAoB,SAAS,IAAI,OAAO,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IAC9F;AACA,YAAQ,MAAM,gCAAgC,GAAG;AACjD,WAAO,aAAa,KAAK,EAAE,OAAO,iBAAiB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACvE;AACF;AAEA,eAAsB,OAAO,KAAc;AACzC,MAAI;AACF,UAAM,MAAM,MAAM,uBAAuB,GAAG;AAC5C,QAAI,CAAC,IAAI,KAAM,QAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAElF,UAAM,EAAE,GAAG,IAAK,MAAM,IAAI,KAAK;AAC/B,QAAI,CAAC,GAAI,QAAO,aAAa,KAAK,EAAE,OAAO,aAAa,GAAG,EAAE,QAAQ,IAAI,CAAC;AAE1E,UAAM,QAAQ,EAAE,gBAAgB,IAAI,gBAAgB,UAAU,IAAI,UAAU,WAAW,KAAK;AAC5F,UAAM,SAAS,MAAM,IAAI,GAAG,QAAQ,qBAAqB,EAAE,IAAI,GAAG,MAAM,CAAC;AACzE,QAAI,CAAC,OAAQ,QAAO,aAAa,KAAK,EAAE,OAAO,YAAY,GAAG,EAAE,QAAQ,IAAI,CAAC;AAE7E,WAAO,YAAY,oBAAI,KAAK;AAC5B,UAAM,IAAI,GAAG,MAAM;AAEnB,WAAO,aAAa,KAAK,EAAE,SAAS,KAAK,CAAC;AAAA,EAC5C,SAAS,KAAK;AACZ,YAAQ,MAAM,mCAAmC,GAAG;AACpD,WAAO,aAAa,KAAK,EAAE,OAAO,iBAAiB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACvE;AACF;",
|
|
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
|
|
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
|
-
|
|
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,
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAClB,SAAS,8BAA8B;AACvC,SAAS,kBAAkB,oBAAoB,+BAA+B;AAC9E,SAAS,8BAA8B,oCAAoC;
|
|
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
|
}
|