Add special-order product flow for sales opportunities
This commit is contained in:
@@ -14,6 +14,8 @@ import {
|
||||
CWForecastItemCreate,
|
||||
CWOpportunity,
|
||||
CWOpportunityNote,
|
||||
CWProcurementProduct,
|
||||
CWProcurementProductCreate,
|
||||
} from "../modules/cw-utils/opportunities/opportunity.types";
|
||||
import {
|
||||
resolveMember,
|
||||
@@ -547,15 +549,25 @@ export class OpportunityController {
|
||||
// Build a map of forecastDetailId → procurement product cancellation data
|
||||
const cancellationMap = new Map<number, Record<string, unknown>>();
|
||||
for (const pp of procProducts) {
|
||||
const forecastDetailId = pp.forecastDetailId as number | undefined;
|
||||
if (forecastDetailId) {
|
||||
const rawForecastDetailId = (pp as any)?.forecastDetailId;
|
||||
const forecastDetailId =
|
||||
typeof rawForecastDetailId === "number"
|
||||
? rawForecastDetailId
|
||||
: Number(rawForecastDetailId);
|
||||
|
||||
if (Number.isFinite(forecastDetailId) && forecastDetailId > 0) {
|
||||
cancellationMap.set(forecastDetailId, pp);
|
||||
}
|
||||
}
|
||||
|
||||
// Procurement-only view: only include forecast items that have a
|
||||
// matching procurement record (via forecastDetailId).
|
||||
const forecastItems = (forecast.forecastItems ?? []).filter((fi: any) =>
|
||||
cancellationMap.has(fi.id),
|
||||
);
|
||||
|
||||
// Apply local ordering if productSequence is set, otherwise fall back
|
||||
// to CW sequenceNumber.
|
||||
const forecastItems = forecast.forecastItems ?? [];
|
||||
let ordered: typeof forecastItems;
|
||||
|
||||
if (this.productSequence.length > 0) {
|
||||
@@ -816,6 +828,51 @@ export class OpportunityController {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add Procurement Products
|
||||
*
|
||||
* Creates one or more procurement products linked to this opportunity.
|
||||
* Use this when payloads include procurement-only fields such as customFields.
|
||||
*/
|
||||
public async addProcurementProducts(
|
||||
data: CWProcurementProductCreate | CWProcurementProductCreate[],
|
||||
): Promise<CWProcurementProduct[]> {
|
||||
try {
|
||||
const items = Array.isArray(data) ? data : [data];
|
||||
const normalized = items.map((item) => ({
|
||||
...item,
|
||||
opportunity: { id: this.cwOpportunityId },
|
||||
}));
|
||||
|
||||
const created = await opportunityCw.createProcurementProducts(normalized);
|
||||
await invalidateProductsCache(this.cwOpportunityId);
|
||||
return created;
|
||||
} catch (err: any) {
|
||||
console.error(
|
||||
`[addProcurementProducts] Failed to create procurement product(s) on opportunity ${this.cwOpportunityId}`,
|
||||
JSON.stringify(
|
||||
{
|
||||
data,
|
||||
status: err?.response?.status,
|
||||
statusText: err?.response?.statusText,
|
||||
responseData: err?.response?.data,
|
||||
message: err?.message,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
throw new GenericError({
|
||||
status: err?.response?.status ?? 500,
|
||||
name: "AddProcurementProductFailed",
|
||||
message:
|
||||
err?.response?.data?.message ??
|
||||
"Failed to add procurement product(s) to opportunity",
|
||||
cause: err?.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add Note
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user