@campfire-interactive/master-data-client 4.11.0 → 4.12.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.
@@ -0,0 +1,147 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.QuoteLineResource = void 0;
4
+ /**
5
+ * Quote lines — the finished goods on a quote.
6
+ *
7
+ * A quote is MULTI-PART: it is the document the OEM receives and it carries one
8
+ * line per finished good, each with its own volume, cost and price. See the
9
+ * nextgen ADR `2026-08-05-multi-part-quotes.md`.
10
+ *
11
+ * Two things to know before using this:
12
+ *
13
+ * 1. **`Quote.partId` is now a derived mirror** of these lines and is
14
+ * deprecated. It reflects the FIRST line only, so it is lossy the moment a
15
+ * quote has more than one. Read `lines` here instead. It keeps working for
16
+ * the whole deprecation window — including writes, which transparently
17
+ * maintain the quote's single line — and is dropped in a later major.
18
+ *
19
+ * 2. **Never sum lines across competing quotes.** When an OEM asks for several
20
+ * competing groupings of the same parts, those are alternatives, not
21
+ * additions. `rollUp` covers ONE quote; anything aggregating across a round
22
+ * must dedupe by part.
23
+ *
24
+ * Cost is DERIVED, never authored: it reaches a line only through
25
+ * {@link setEffectiveCost}, published by whichever engine resolved it. There is
26
+ * no authored-cost field, deliberately — the earlier design had one whose only
27
+ * writer was a human, so it could store a per-grouping cost but never produce
28
+ * one, and went stale unnoticed on the next re-cost.
29
+ */
30
+ class QuoteLineResource {
31
+ http;
32
+ constructor(http) {
33
+ this.http = http;
34
+ }
35
+ /**
36
+ * The quote's lines, each with resolved `terms` (volume / cost / price plus
37
+ * which tier each came from), and the quote-level `rollUp`.
38
+ */
39
+ async list(quoteId) {
40
+ return this.http.request({
41
+ method: 'GET',
42
+ path: `/v1/quotes/${encodeURIComponent(quoteId)}/lines`,
43
+ });
44
+ }
45
+ /**
46
+ * The resolved line sets for MANY quotes, in one round trip.
47
+ *
48
+ * Use this instead of looping {@link list} whenever you read volume off a set
49
+ * of quotes — a per-quote fan-out is slow and, against a hub holding one warm
50
+ * instance, manufactures cold starts.
51
+ *
52
+ * Chunk to 200 ids per call (the server rejects more). Ids that don't exist, or
53
+ * belong to another tenant, are simply absent from the result — so index the
54
+ * response by `quoteId` rather than assuming it lines up positionally with the
55
+ * ids you passed. A quote that exists but has no finished goods comes back with
56
+ * an empty `lines` array.
57
+ */
58
+ // Typed SingleResponse<T[]> rather than ListResponse<T>: this batch read is not
59
+ // paginated, so it carries no cursor/hasMore meta and claiming ListMeta would
60
+ // promise fields the server never sends.
61
+ async listForQuotes(quoteIds) {
62
+ return this.http.request({
63
+ method: 'GET',
64
+ path: '/v1/quote-lines',
65
+ query: { quoteIds: quoteIds.join(',') },
66
+ });
67
+ }
68
+ /**
69
+ * Add a finished good to a quote. Returns the full line set, so a caller never
70
+ * has to re-fetch to get the new roll-up.
71
+ *
72
+ * Omit `partId` to have it resolved from `partNumber` against the parts
73
+ * catalog; it is left null when nothing matches, which is legal — soft
74
+ * enforcement, so a part number that hasn't been reconciled into the catalog
75
+ * can still be quoted. Pass `partId: null` to pin it null explicitly.
76
+ */
77
+ async add(quoteId, input, idempotencyKey) {
78
+ return this.http.request({
79
+ method: 'POST',
80
+ path: `/v1/quotes/${encodeURIComponent(quoteId)}/lines`,
81
+ body: input,
82
+ idempotencyKey,
83
+ });
84
+ }
85
+ /**
86
+ * Update a line's AUTHORED terms. Derived cost/price are not settable here.
87
+ *
88
+ * A `note`-only patch is accepted while the quote is in review — flagging a bad
89
+ * part is something that happens DURING review. Anything touching the figures
90
+ * requires a pre-approval status (422 otherwise).
91
+ */
92
+ async update(quoteId, lineId, input) {
93
+ return this.http.request({
94
+ method: 'PATCH',
95
+ path: `/v1/quotes/${encodeURIComponent(quoteId)}/lines/${encodeURIComponent(lineId)}`,
96
+ body: input,
97
+ });
98
+ }
99
+ /** Remove a finished good. The last line may be removed. */
100
+ async remove(quoteId, lineId) {
101
+ return this.http.request({
102
+ method: 'DELETE',
103
+ path: `/v1/quotes/${encodeURIComponent(quoteId)}/lines/${encodeURIComponent(lineId)}`,
104
+ });
105
+ }
106
+ /**
107
+ * Set the document order. `lineIds` must list every line on the quote exactly
108
+ * once (422 otherwise). Order also decides which line supplies the deprecated
109
+ * `quote.partId` mirror.
110
+ */
111
+ async reorder(quoteId, lineIds) {
112
+ return this.http.request({
113
+ method: 'PUT',
114
+ path: `/v1/quotes/${encodeURIComponent(quoteId)}/lines/reorder`,
115
+ body: { lineIds },
116
+ });
117
+ }
118
+ /**
119
+ * Publish a DERIVED per-line cost — the line-scoped mirror of
120
+ * `quotes.setEffectiveCost`, and the only way a cost reaches a line.
121
+ *
122
+ * Deliberately NOT gated on quote status: a re-cost must always be able to
123
+ * land, or the published number silently diverges from the cost model that
124
+ * produced it. When the figure lands on a quote already in approval the
125
+ * response sets `invalidatesApproval` — the caller owns what to do about it
126
+ * (OMSF voids the collected approvals and re-routes).
127
+ */
128
+ async setEffectiveCost(quoteId, lineId, input, idempotencyKey) {
129
+ return this.http.request({
130
+ method: 'PATCH',
131
+ path: `/v1/quotes/${encodeURIComponent(quoteId)}/lines/${encodeURIComponent(lineId)}/effective-cost`,
132
+ body: input,
133
+ idempotencyKey,
134
+ });
135
+ }
136
+ /** Price-side sibling of {@link setEffectiveCost}. */
137
+ async setEffectivePrice(quoteId, lineId, input, idempotencyKey) {
138
+ return this.http.request({
139
+ method: 'PATCH',
140
+ path: `/v1/quotes/${encodeURIComponent(quoteId)}/lines/${encodeURIComponent(lineId)}/effective-price`,
141
+ body: input,
142
+ idempotencyKey,
143
+ });
144
+ }
145
+ }
146
+ exports.QuoteLineResource = QuoteLineResource;
147
+ //# sourceMappingURL=quoteLines.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quoteLines.js","sourceRoot":"","sources":["../src/quoteLines.ts"],"names":[],"mappings":"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"}
package/dist/quotes.d.ts CHANGED
@@ -19,13 +19,18 @@ export declare class QuoteResource {
19
19
  get(id: string, options?: GetQuoteOptions): Promise<SingleResponse<Quote>>;
20
20
  create(input: CreateQuoteInput, idempotencyKey?: string): Promise<CreateResponse<Quote>>;
21
21
  /**
22
- * Issue the next `RFQ-YYYY-NNNN` for the caller's tenant — the authoritative
22
+ * Issue the next `Q-YYYY-NNN` for the caller's tenant — the authoritative
23
23
  * quote-number mint (master-data owns the number and enforces `quoteNumber`
24
24
  * uniqueness). Atomic per (tenant, year); the first call of a year seeds the
25
- * counter above the existing numeric max. Returns `{ quoteNumber }`. CPQ's
26
- * createRfq calls this instead of scanning its own list and incrementing —
27
- * a scan that wedged at `RFQ-YYYY-10000` once a tenant's yearly sequence
28
- * passed four digits.
25
+ * counter above the existing numeric max, across both prefixes. Returns
26
+ * `{ quoteNumber }`. Both CPQ and OMSF call this instead of scanning the quote
27
+ * list and incrementing — scans that wedged at 10000 once a tenant's yearly
28
+ * sequence passed four digits (CPQ) and raced on concurrent creates (OMSF).
29
+ *
30
+ * **The prefix changed from `RFQ-` to `Q-` on 2026-08-06**, because `RFQ-` now
31
+ * names the customer's request that several competing quotes answer. Quotes
32
+ * numbered before the change keep their `RFQ-` numbers — treat a quote number
33
+ * as an opaque string and never parse a prefix out of it.
29
34
  */
30
35
  nextNumber(): Promise<SingleResponse<{
31
36
  quoteNumber: string;
@@ -1 +1 @@
1
- {"version":3,"file":"quotes.d.ts","sourceRoot":"","sources":["../src/quotes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AACvC,OAAO,KAAK,EACV,KAAK,EACL,aAAa,EACb,gBAAgB,EAChB,gBAAgB,EAChB,0BAA0B,EAC1B,qBAAqB,EACrB,sBAAsB,EACtB,iBAAiB,EACjB,eAAe,EACf,cAAc,EACd,YAAY,EACZ,cAAc,EACd,iBAAiB,EAClB,MAAM,SAAS,CAAC;AAEjB;;;;;;;;;;;GAWG;AACH,qBAAa,aAAa;IACZ,OAAO,CAAC,QAAQ,CAAC,IAAI;gBAAJ,IAAI,EAAE,aAAa;IAE1C,IAAI,CAAC,OAAO,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC;IAqB/D,GAAG,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;IAY1E,MAAM,CACV,KAAK,EAAE,gBAAgB,EACvB,cAAc,CAAC,EAAE,MAAM,GACtB,OAAO,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;IASjC;;;;;;;;OAQG;IACG,UAAU,IAAI,OAAO,CAAC,cAAc,CAAC;QAAE,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAOpE;;;;OAIG;IACG,MAAM,CACV,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,gBAAgB,EACvB,cAAc,CAAC,EAAE,MAAM,GACtB,OAAO,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;IASjC;;;;;;;;;;;;;;;;;OAiBG;IACG,gBAAgB,CACpB,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,0BAA0B,EACjC,cAAc,CAAC,EAAE,MAAM,GACtB,OAAO,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;IASjC;;;;;;;;;;;;;OAaG;IACG,OAAO,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQzD;;;;;;OAMG;IACG,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,cAAc,CAAC,iBAAiB,CAAC,CAAC;IAQvE,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,aAAa,CAAC,CAAC;IAOhE;;;;;;;;;;;;;;OAcG;IACG,gBAAgB,CACpB,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,qBAAqB,EAC5B,cAAc,CAAC,EAAE,MAAM,GACtB,OAAO,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;IASjC;;;;;;;;;;;;;;OAcG;IACG,iBAAiB,CACrB,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,sBAAsB,EAC7B,cAAc,CAAC,EAAE,MAAM,GACtB,OAAO,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;CAQlC"}
1
+ {"version":3,"file":"quotes.d.ts","sourceRoot":"","sources":["../src/quotes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AACvC,OAAO,KAAK,EACV,KAAK,EACL,aAAa,EACb,gBAAgB,EAChB,gBAAgB,EAChB,0BAA0B,EAC1B,qBAAqB,EACrB,sBAAsB,EACtB,iBAAiB,EACjB,eAAe,EACf,cAAc,EACd,YAAY,EACZ,cAAc,EACd,iBAAiB,EAClB,MAAM,SAAS,CAAC;AAEjB;;;;;;;;;;;GAWG;AACH,qBAAa,aAAa;IACZ,OAAO,CAAC,QAAQ,CAAC,IAAI;gBAAJ,IAAI,EAAE,aAAa;IAE1C,IAAI,CAAC,OAAO,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC;IAqB/D,GAAG,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;IAY1E,MAAM,CACV,KAAK,EAAE,gBAAgB,EACvB,cAAc,CAAC,EAAE,MAAM,GACtB,OAAO,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;IASjC;;;;;;;;;;;;;OAaG;IACG,UAAU,IAAI,OAAO,CAAC,cAAc,CAAC;QAAE,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAOpE;;;;OAIG;IACG,MAAM,CACV,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,gBAAgB,EACvB,cAAc,CAAC,EAAE,MAAM,GACtB,OAAO,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;IASjC;;;;;;;;;;;;;;;;;OAiBG;IACG,gBAAgB,CACpB,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,0BAA0B,EACjC,cAAc,CAAC,EAAE,MAAM,GACtB,OAAO,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;IASjC;;;;;;;;;;;;;OAaG;IACG,OAAO,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQzD;;;;;;OAMG;IACG,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,cAAc,CAAC,iBAAiB,CAAC,CAAC;IAQvE,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,aAAa,CAAC,CAAC;IAOhE;;;;;;;;;;;;;;OAcG;IACG,gBAAgB,CACpB,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,qBAAqB,EAC5B,cAAc,CAAC,EAAE,MAAM,GACtB,OAAO,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;IASjC;;;;;;;;;;;;;;OAcG;IACG,iBAAiB,CACrB,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,sBAAsB,EAC7B,cAAc,CAAC,EAAE,MAAM,GACtB,OAAO,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;CAQlC"}
package/dist/quotes.js CHANGED
@@ -68,13 +68,18 @@ class QuoteResource {
68
68
  });
69
69
  }
70
70
  /**
71
- * Issue the next `RFQ-YYYY-NNNN` for the caller's tenant — the authoritative
71
+ * Issue the next `Q-YYYY-NNN` for the caller's tenant — the authoritative
72
72
  * quote-number mint (master-data owns the number and enforces `quoteNumber`
73
73
  * uniqueness). Atomic per (tenant, year); the first call of a year seeds the
74
- * counter above the existing numeric max. Returns `{ quoteNumber }`. CPQ's
75
- * createRfq calls this instead of scanning its own list and incrementing —
76
- * a scan that wedged at `RFQ-YYYY-10000` once a tenant's yearly sequence
77
- * passed four digits.
74
+ * counter above the existing numeric max, across both prefixes. Returns
75
+ * `{ quoteNumber }`. Both CPQ and OMSF call this instead of scanning the quote
76
+ * list and incrementing — scans that wedged at 10000 once a tenant's yearly
77
+ * sequence passed four digits (CPQ) and raced on concurrent creates (OMSF).
78
+ *
79
+ * **The prefix changed from `RFQ-` to `Q-` on 2026-08-06**, because `RFQ-` now
80
+ * names the customer's request that several competing quotes answer. Quotes
81
+ * numbered before the change keep their `RFQ-` numbers — treat a quote number
82
+ * as an opaque string and never parse a prefix out of it.
78
83
  */
79
84
  async nextNumber() {
80
85
  return this.http.request({
@@ -1 +1 @@
1
- {"version":3,"file":"quotes.js","sourceRoot":"","sources":["../src/quotes.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"quotes.js","sourceRoot":"","sources":["../src/quotes.ts"],"names":[],"mappings":"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"}
package/dist/types.d.ts CHANGED
@@ -106,7 +106,7 @@ export interface ListProgramsOptions {
106
106
  export interface GetProgramOptions {
107
107
  include?: ('customerCompany' | 'oemCompany')[];
108
108
  }
109
- export type QuoteStatus = 'draft' | 'costing_requested' | 'costing_in_progress' | 'costing_complete' | 'pricing' | 'routed_for_approval' | 'in_review' | 'approved' | 'submitted' | 'negotiating' | 'awarded' | 'lost';
109
+ export type QuoteStatus = 'draft' | 'costing_requested' | 'costing_in_progress' | 'costing_complete' | 'pricing' | 'routed_for_approval' | 'in_review' | 'approved' | 'submitted' | 'negotiating' | 'awarded' | 'lost' | 'not_taken';
110
110
  /**
111
111
  * Who is driving the transition. Required on every status update so the
112
112
  * server can enforce role-gated transitions. `admin` is the escape hatch
@@ -137,6 +137,23 @@ export interface Quote {
137
137
  name: string | null;
138
138
  customerCompanyId: string;
139
139
  programId: string | null;
140
+ /**
141
+ * The OEM request this quote is one competing answer to — see {@link QuoteAsk}.
142
+ * Null means it competes with nothing and is awardable on its own, which is a
143
+ * legitimate state, not missing data.
144
+ */
145
+ askId: string | null;
146
+ /**
147
+ * @deprecated A DERIVED MIRROR of the quote's lines — read `quoteLines.list()`
148
+ * instead.
149
+ *
150
+ * Free text holding a part NUMBER (never a uuid), historically the quote's only
151
+ * part reference. A quote is multi-part now, and this reflects the FIRST line
152
+ * only, so it is lossy the moment a quote has more than one. Writes still work
153
+ * for the whole deprecation window (they transparently maintain the quote's
154
+ * single line), but new code should not read it as "the quote's part". Dropped
155
+ * in a later major. See the nextgen ADR `2026-08-05-multi-part-quotes.md`.
156
+ */
140
157
  partId: string | null;
141
158
  /**
142
159
  * The historical/parent quote this one was copied from. OMSF sets this when
@@ -215,6 +232,24 @@ export interface Quote {
215
232
  * column stays the canonical yearly curve.
216
233
  */
217
234
  effectiveVolumesByYear: EffectiveVolumeByYear[] | null;
235
+ /**
236
+ * The VEHICLE stream (added 2026-08-06) — how many vehicles the program
237
+ * builds, NOT how many parts we sell.
238
+ *
239
+ * This is what each {@link QuoteLine} bridges from: `vehicleAnnualVolume ×
240
+ * takeRate × partsPerVehicle` is that finished good's part volume. One quote
241
+ * covering four finished goods has ONE vehicle stream and four different part
242
+ * volumes, which is why the part columns above cannot serve as the input.
243
+ *
244
+ * **Null on every quote written before 2026-08-06**, and that is not a gap:
245
+ * those quotes' lines bridge from the part columns instead, at a 100/1 bridge
246
+ * that leaves the figure untouched. Read it as "does this quote calculate its
247
+ * line volumes, or carry them directly?" rather than as missing data.
248
+ */
249
+ vehicleAnnualVolume: number | null;
250
+ /** Per-year vehicle curve, same shape as {@link effectiveVolumesByYear}.
251
+ * Bridged year by year onto each line. */
252
+ vehicleVolumesByYear: EffectiveVolumeByYear[] | null;
218
253
  /**
219
254
  * Customer-committed PART volume on ARBITRARY periods — the general form of
220
255
  * {@link effectiveVolumesByYear}, for deals tracked at a granularity the year
@@ -306,6 +341,230 @@ export interface SetEffectivePriceInput {
306
341
  /** Which app is writing — gated to 'omsf' | 'cpq' | 'admin' server-side. */
307
342
  callerRole: 'omsf' | 'cpq' | 'admin';
308
343
  }
344
+ export type QuoteAskStatus = 'open' | 'resolved' | 'cancelled';
345
+ export interface QuoteAsk {
346
+ id: string;
347
+ tenantId: string;
348
+ /** `ASK-YYYY-NNN`, minted server-side from an atomic per-(tenant, year) counter. */
349
+ askNumber: string;
350
+ name: string | null;
351
+ customerCompanyId: string;
352
+ /** Reporting only — NEVER used to decide competition. An opportunity spans
353
+ * rounds; an ask is one round. */
354
+ opportunityId: string | null;
355
+ dueDate: string | null;
356
+ status: QuoteAskStatus;
357
+ /** Which quote won. A recorded fact — closing the siblings is a later phase. */
358
+ resolvedByQuoteId: string | null;
359
+ resolvedAt: string | null;
360
+ createdBy: string;
361
+ updatedBy: string;
362
+ createdAt: string;
363
+ updatedAt: string;
364
+ }
365
+ export interface QuoteAskSummary extends QuoteAsk {
366
+ /** How many quotes compete in this ask. Deliberately not a value aggregate. */
367
+ quoteCount: number;
368
+ }
369
+ /**
370
+ * A RANGE across the ask's quotes — deliberately not a total.
371
+ *
372
+ * The quotes are ALTERNATIVES: three of four lose on every multi-scenario RFQ by
373
+ * construction, so adding them multiply-counts the same business.
374
+ */
375
+ export interface QuoteAskRange {
376
+ quoteCount: number;
377
+ minValue: number | null;
378
+ maxValue: number | null;
379
+ minBlendedMarginPct: number | null;
380
+ maxBlendedMarginPct: number | null;
381
+ }
382
+ export interface QuoteAskCompetingQuote {
383
+ id: string;
384
+ quoteNumber: string;
385
+ name: string | null;
386
+ status: QuoteStatus;
387
+ lines: QuoteLine[];
388
+ rollUp: QuoteLineRollUp;
389
+ }
390
+ export interface QuoteAskDetail extends QuoteAsk {
391
+ quotes: QuoteAskCompetingQuote[];
392
+ range: QuoteAskRange;
393
+ }
394
+ export interface CreateQuoteAskInput {
395
+ customerCompanyId: string;
396
+ name?: string | null;
397
+ opportunityId?: string | null;
398
+ dueDate?: string | null;
399
+ }
400
+ export interface UpdateQuoteAskInput {
401
+ name?: string | null;
402
+ dueDate?: string | null;
403
+ opportunityId?: string | null;
404
+ status?: QuoteAskStatus;
405
+ /** Required when resolving; must be one of this ask's own quotes. */
406
+ resolvedByQuoteId?: string | null;
407
+ }
408
+ export interface ListQuoteAsksOptions {
409
+ status?: QuoteAskStatus;
410
+ customerCompanyId?: string;
411
+ opportunityId?: string;
412
+ }
413
+ /** A per-year volume point. Same shape the quote header uses. */
414
+ export interface QuoteLineVolumeByYear {
415
+ year: number;
416
+ effectiveVolume: number;
417
+ }
418
+ export interface QuoteLine {
419
+ id: string;
420
+ tenantId: string;
421
+ quoteId: string;
422
+ /** Document order. Also picks the line that supplies the deprecated `Quote.partId`. */
423
+ sortOrder: number;
424
+ /**
425
+ * The canonical master-data {@link Part}. NULL is legal — soft enforcement, so a
426
+ * part number not yet reconciled into the catalog can still be quoted. Denotes
427
+ * the BASE part per the part-identity ADR.
428
+ */
429
+ partId: string | null;
430
+ /** Required, denormalised. Carries the line on its own when `partId` is null. */
431
+ partNumber: string;
432
+ /** Engineering revision this line is quoted AT, when known. Never identity. */
433
+ revision: string | null;
434
+ /**
435
+ * Vehicle→part bridge, unique per line. At the defaults (100 / 1) the
436
+ * header→line volume projection is the IDENTITY.
437
+ */
438
+ takeRate: number | null;
439
+ partsPerVehicle: number | null;
440
+ /** Authored. Null inherits the header through the bridge. */
441
+ annualVolume: number | null;
442
+ /** Authored per-year series. Authoritative over `annualVolume` when present. */
443
+ effectiveVolumesByYear: QuoteLineVolumeByYear[] | null;
444
+ /** Authored offered price for this line. */
445
+ unitPrice: string | null;
446
+ /** Free text against an unhealthy line. Writable during review. */
447
+ note: string | null;
448
+ /** DERIVED — published only via `quoteLines.setEffectiveCost`. */
449
+ effectiveUnitCost: string | null;
450
+ effectiveCostSource: string | null;
451
+ effectiveCostBasis: string | null;
452
+ effectiveCostedAt: string | null;
453
+ effectiveCostedBy: string | null;
454
+ effectiveCostBreakdown: unknown | null;
455
+ /** DERIVED — published only via `quoteLines.setEffectivePrice`. */
456
+ effectiveUnitPrice: string | null;
457
+ effectivePriceSource: string | null;
458
+ effectivePriceBasis: string | null;
459
+ effectivePricedAt: string | null;
460
+ effectivePricedBy: string | null;
461
+ effectivePriceBreakdown: unknown | null;
462
+ createdBy: string;
463
+ updatedBy: string;
464
+ createdAt: string;
465
+ updatedAt: string;
466
+ /** Present when the line resolves to a catalog part. */
467
+ part?: {
468
+ id: string;
469
+ partNumber: string;
470
+ name: string;
471
+ partTypeId: string | null;
472
+ } | null;
473
+ /** Resolved figures — see {@link ResolvedQuoteLineTerms}. */
474
+ terms: ResolvedQuoteLineTerms;
475
+ }
476
+ /**
477
+ * A line's resolved terms. The `*Source` / `*FromLine` flags let a caller tell an
478
+ * authored figure from an inherited one without re-deriving the precedence.
479
+ */
480
+ export interface ResolvedQuoteLineTerms {
481
+ annualVolume: number;
482
+ volumesByYear: QuoteLineVolumeByYear[] | null;
483
+ unitCost: number | null;
484
+ unitPrice: number | null;
485
+ value: number;
486
+ marginPct: number | null;
487
+ /**
488
+ * Which tier the volume came from: the line's own series, the line's own annual
489
+ * figure, or the quote header bridged through take rate × parts per vehicle.
490
+ */
491
+ volumeSource: 'line_years' | 'line_annual' | 'header_bridged';
492
+ costFromLine: boolean;
493
+ priceFromLine: boolean;
494
+ }
495
+ /**
496
+ * Consolidated figures for ONE quote's lines, computed on read.
497
+ *
498
+ * `blendedMarginPct` is value-weighted over the lines whose margin is KNOWN, not
499
+ * over the whole quote value — a blend diluted by parts with no cost yet is a
500
+ * worse number that is equally false. `linesWithoutMargin` reports that gap.
501
+ */
502
+ export interface QuoteLineRollUp {
503
+ lineCount: number;
504
+ totalAnnualVolume: number;
505
+ totalValue: number;
506
+ blendedMarginPct: number | null;
507
+ linesWithoutMargin: number;
508
+ /** Lines whose part number isn't reconciled into the catalog yet. */
509
+ linesWithoutCatalogPart: number;
510
+ }
511
+ export interface QuoteLinesResponse {
512
+ quoteId: string;
513
+ quoteNumber: string;
514
+ lines: QuoteLine[];
515
+ rollUp: QuoteLineRollUp;
516
+ }
517
+ export interface QuoteLinesWithApprovalFlag extends QuoteLinesResponse {
518
+ /**
519
+ * True when the published figure landed on a quote already in approval — the
520
+ * approver signed off on a different number. Surfaced rather than silently
521
+ * absorbed; the caller decides whether to void and re-route.
522
+ */
523
+ invalidatesApproval: boolean;
524
+ }
525
+ export interface AddQuoteLineInput {
526
+ partNumber: string;
527
+ /** Omit to resolve from `partNumber`; pass `null` to pin it null. */
528
+ partId?: string | null;
529
+ revision?: string | null;
530
+ /** Defaults to the end of the list, so a new line can't take over the mirror. */
531
+ sortOrder?: number;
532
+ /** Percentage, 0–100. Defaults to 100. */
533
+ takeRate?: number;
534
+ /** Defaults to 1. */
535
+ partsPerVehicle?: number;
536
+ annualVolume?: number | null;
537
+ effectiveVolumesByYear?: QuoteLineVolumeByYear[] | null;
538
+ unitPrice?: number | null;
539
+ note?: string | null;
540
+ }
541
+ export interface UpdateQuoteLineInput {
542
+ partNumber?: string;
543
+ /** Omitted with a changed `partNumber` re-resolves the catalog link. */
544
+ partId?: string | null;
545
+ revision?: string | null;
546
+ sortOrder?: number;
547
+ takeRate?: number;
548
+ partsPerVehicle?: number;
549
+ annualVolume?: number | null;
550
+ effectiveVolumesByYear?: QuoteLineVolumeByYear[] | null;
551
+ unitPrice?: number | null;
552
+ note?: string | null;
553
+ }
554
+ export interface SetQuoteLineEffectiveCostInput {
555
+ unitCost: number;
556
+ /** Required — what the figure was costed against. */
557
+ costBasis: string;
558
+ /** Declared by the adapter. Defaults to 'cpq'. */
559
+ source?: string;
560
+ breakdown?: unknown;
561
+ }
562
+ export interface SetQuoteLineEffectivePriceInput {
563
+ unitPrice: number;
564
+ priceBasis: string;
565
+ source?: string;
566
+ breakdown?: unknown;
567
+ }
309
568
  export interface CreateQuoteInput {
310
569
  /**
311
570
  * MIGRATION ESCAPE HATCH — do not set this from production code.
@@ -323,6 +582,10 @@ export interface CreateQuoteInput {
323
582
  name?: string;
324
583
  customerCompanyId: string;
325
584
  programId?: string;
585
+ /** Join the quote to an OEM request ({@link QuoteAsk}). Optional — a quote with
586
+ * no ask competes with nothing and is awardable on its own. Validated to exist
587
+ * in-tenant AND to belong to the same customer as the quote. */
588
+ askId?: string | null;
326
589
  partId?: string;
327
590
  /** Historical/parent quote this one was copied from. Validated to exist
328
591
  * in-tenant on write; no DB FK. */
@@ -342,6 +605,12 @@ export interface CreateQuoteInput {
342
605
  eopPrecision?: SopEopPrecision;
343
606
  effectiveAnnualVolume?: number;
344
607
  effectiveVolumesByYear?: EffectiveVolumeByYear[];
608
+ /** The VEHICLE stream every line bridges from — vehicles the program builds,
609
+ * not parts sold. Each line turns it into its own part volume with its take
610
+ * rate and parts-per-vehicle. Omit it and the lines fall back to the part
611
+ * columns, which is what pre-2026-08-06 quotes do. (4.12.0) */
612
+ vehicleAnnualVolume?: number;
613
+ vehicleVolumesByYear?: EffectiveVolumeByYear[];
345
614
  /** Arbitrary-period committed volume. Non-empty makes the two fields above
346
615
  * derived — passing either alongside it is a 422. (4.8.0) */
347
616
  effectiveVolumesByPeriod?: EffectiveVolumeByPeriod[];
@@ -372,6 +641,9 @@ export interface UpdateQuoteInput {
372
641
  quoteNumber?: string;
373
642
  name?: string | null;
374
643
  programId?: string | null;
644
+ /** Join the quote to an OEM request ({@link QuoteAsk}), or `null` to detach it
645
+ * (it then competes with nothing). Validated to match the quote's customer. */
646
+ askId?: string | null;
375
647
  partId?: string | null;
376
648
  /** Set to a quote id to record provenance, or `null` to clear it. */
377
649
  referenceQuoteId?: string | null;
@@ -389,6 +661,11 @@ export interface UpdateQuoteInput {
389
661
  eopPrecision?: SopEopPrecision | null;
390
662
  effectiveAnnualVolume?: number | null;
391
663
  effectiveVolumesByYear?: EffectiveVolumeByYear[] | null;
664
+ /** The VEHICLE stream. Editing it moves EVERY line's calculated volume at
665
+ * once — one program build rate, many finished goods. `null` hands the lines
666
+ * back to the part columns. (4.12.0) */
667
+ vehicleAnnualVolume?: number | null;
668
+ vehicleVolumesByYear?: EffectiveVolumeByYear[] | null;
392
669
  /** Full replace. Non-empty makes the yearly fields derived (writing them is a
393
670
  * 422); `null` or `[]` clears the schedule and hands them back. The last
394
671
  * derived values are left in place — clearing stops maintaining them, it
@@ -1790,7 +2067,7 @@ export type SourceApp = 'master-data' | 'erfq' | 'cpq' | 'bom' | 'forecast' | 'o
1790
2067
  * "Empty" means SQL NULL only — a stored '', 0, false, or 0.0 counts as
1791
2068
  * populated and will not be overwritten by fill_empty. */
1792
2069
  export type MergePolicy = 'overwrite' | 'fill_empty';
1793
- export type ImportTargetField = 'name' | 'legalName' | 'code' | 'taxId' | 'dunsNumber' | 'website' | 'logoUrl' | 'roles' | 'email' | 'phone' | 'role' | 'type' | 'description' | 'street' | 'city' | 'stateProvince' | 'postalCode' | 'country' | 'category' | 'parent_company_ref' | 'companyCode' | 'locationCode' | 'region' | 'headcount' | 'annualCapacityHours' | 'currentUtilization' | 'riskLevel' | 'numShifts' | 'hoursPerShift' | 'workingDaysPerYear' | 'indirectHeadcountPct' | 'currency' | 'effectiveDate' | 'effectiveTo' | 'libraryCode' | 'kind' | 'plantCode' | 'skillTier' | 'materialCode' | 'regionCode' | 'lane' | 'rate' | 'unit' | 'sourcingMode' | 'indexRef' | 'notes' | 'partNumber' | 'manufacturingPartNumber' | 'customerPartNumber' | 'partType' | 'manufacturer' | 'partCategoryCode' | 'quality' | 'revision' | 'uom' | 'weightPerUnit' | 'recycledPct' | 'pcfPerUnit' | 'releaseStatus' | 'leadTime' | 'customerCompanyCode' | 'customerCompanyName' | 'sopDate' | 'eopDate' | 'displayOrder' | 'label' | 'sortOrder' | 'processFamily' | 'capacityHoursPerYear' | 'cavities' | 'availabilityPct' | 'performancePct' | 'defaultHeadcount' | 'defaultAutomationPct' | 'defaultMachinesPerOperator' | 'defaultSetupCrew' | 'perUsd' | 'effectiveFrom' | 'quoteNumber' | 'programCode' | 'partId' | 'regionName' | 'annualVolume' | 'apw' | 'mpw' | 'targetPrice' | 'dueDate' | 'status' | 'key' | 'dataType' | 'options' | 'group' | 'required' | 'min' | 'max' | 'regex' | 'precision' | 'basis' | 'bindings' | 'perBaseFactor' | 'needsDensity' | 'binding' | 'allowedUoms' | 'defaultUom' | 'escalationDefault' | 'density' | 'densityUnit';
2070
+ export type ImportTargetField = 'name' | 'legalName' | 'code' | 'taxId' | 'dunsNumber' | 'website' | 'logoUrl' | 'roles' | 'email' | 'phone' | 'role' | 'type' | 'description' | 'street' | 'city' | 'stateProvince' | 'postalCode' | 'country' | 'category' | 'parent_company_ref' | 'companyCode' | 'companyName' | 'locationCode' | 'region' | 'headcount' | 'annualCapacityHours' | 'currentUtilization' | 'riskLevel' | 'numShifts' | 'hoursPerShift' | 'workingDaysPerYear' | 'indirectHeadcountPct' | 'currency' | 'effectiveDate' | 'effectiveTo' | 'libraryCode' | 'kind' | 'plantCode' | 'skillTier' | 'materialCode' | 'regionCode' | 'lane' | 'rate' | 'unit' | 'sourcingMode' | 'indexRef' | 'notes' | 'partNumber' | 'manufacturingPartNumber' | 'customerPartNumber' | 'partType' | 'manufacturer' | 'partCategoryCode' | 'quality' | 'revision' | 'uom' | 'weightPerUnit' | 'recycledPct' | 'pcfPerUnit' | 'releaseStatus' | 'leadTime' | 'customerCompanyCode' | 'customerCompanyName' | 'sopDate' | 'eopDate' | 'displayOrder' | 'label' | 'sortOrder' | 'processFamily' | 'capacityHoursPerYear' | 'cavities' | 'availabilityPct' | 'performancePct' | 'defaultHeadcount' | 'defaultAutomationPct' | 'defaultMachinesPerOperator' | 'defaultSetupCrew' | 'perUsd' | 'effectiveFrom' | 'quoteNumber' | 'programCode' | 'partId' | 'regionName' | 'annualVolume' | 'apw' | 'mpw' | 'targetPrice' | 'dueDate' | 'status' | 'key' | 'dataType' | 'options' | 'group' | 'required' | 'min' | 'max' | 'regex' | 'precision' | 'basis' | 'bindings' | 'perBaseFactor' | 'needsDensity' | 'binding' | 'allowedUoms' | 'defaultUom' | 'escalationDefault' | 'density' | 'densityUnit';
1794
2071
  export interface ImportSession {
1795
2072
  id: string;
1796
2073
  tenantId: string;
@@ -2066,10 +2343,20 @@ export interface BulkArchiveResult {
2066
2343
  errors: number;
2067
2344
  };
2068
2345
  }
2346
+ /** Ownership verdict carried on an *enforced* entity's read/list response
2347
+ * `meta` (Master Data Ownership Phase 4). Present only for enforced elements
2348
+ * (Plant today). `readOnly` ⇒ the element is externally owned and Campfire
2349
+ * edits are rejected server-side; the UI renders read-only + `editHint`. */
2350
+ export interface OwnershipMeta {
2351
+ readOnly: boolean;
2352
+ ownerSystem: string;
2353
+ editHint: string | null;
2354
+ }
2069
2355
  export interface ApiMeta {
2070
2356
  requestId: string;
2071
2357
  timestamp: string;
2072
2358
  resolvedFrom?: string;
2359
+ ownership?: OwnershipMeta;
2073
2360
  }
2074
2361
  export interface ListMeta extends ApiMeta {
2075
2362
  nextCursor: string | null;
@@ -2083,6 +2370,29 @@ export interface DuplicateWarning {
2083
2370
  matchName: string;
2084
2371
  score: number;
2085
2372
  }
2373
+ export type OwnerType = 'campfire' | 'external';
2374
+ /** One catalog element merged with its (optional) stored ownership row. The
2375
+ * GET response fills catalog defaults where a tenant has no row. */
2376
+ export interface MasterDataElementOwnership {
2377
+ element: string;
2378
+ label: string;
2379
+ category: string;
2380
+ ownerType: OwnerType;
2381
+ /** Owning system display name; 'Campfire' when Campfire-owned. */
2382
+ systemName: string;
2383
+ syncMethod: string | null;
2384
+ /** Opaque integrations Source id (external only) — traceability, not a FK. */
2385
+ sourceRef: string | null;
2386
+ updatedAt: string | null;
2387
+ updatedBy: string | null;
2388
+ }
2389
+ export interface SetOwnershipInput {
2390
+ ownerType: OwnerType;
2391
+ /** Required when ownerType is 'external'. */
2392
+ systemName?: string;
2393
+ syncMethod?: string;
2394
+ sourceRef?: string;
2395
+ }
2086
2396
  export interface SingleResponse<T> {
2087
2397
  data: T;
2088
2398
  meta: ApiMeta;