all the haul

This commit is contained in:
2026-04-07 23:56:31 +00:00
parent 87cce83030
commit 24f303355b
244 changed files with 33743 additions and 11249 deletions
+36 -138
View File
@@ -1,6 +1,4 @@
import { prisma, redis } from "../../constants";
import { getCachedOppCwData, getCachedProducts } from "./opportunityCache";
import { OpportunityStatus } from "../../workflows/wf.opportunity";
import { events } from "../globalEvents";
import { opportunities } from "../../managers/opportunities";
import { normalizeProbabilityRatio } from "../sales-utils/normalizeProbability";
@@ -101,13 +99,16 @@ interface CachedOpportunityRevenue {
}
interface OpportunityRow {
id: string;
cwOpportunityId: number;
id: number;
uid: string;
name: string;
primarySalesRepIdentifier: string | null;
secondarySalesRepIdentifier: string | null;
statusCwId: number | null;
statusName: string | null;
primarySalesRepId: string | null;
secondarySalesRepId: string | null;
status: {
wonFlag: boolean;
lostFlag: boolean;
closeFlag: boolean;
} | null;
closedFlag: boolean;
dateBecameLead: Date | null;
closedDate: Date | null;
@@ -137,107 +138,23 @@ const toFinite = (value: unknown): number => {
return n;
};
const isWon = (opp: {
statusCwId: number | null;
statusName: string | null;
closedFlag: boolean;
}) => {
if (opp.statusCwId === OpportunityStatus.Won) return true;
if (opp.statusName?.toLowerCase().includes("won")) return true;
if (opp.closedFlag && opp.statusName?.toLowerCase().includes("won"))
return true;
return false;
};
const isWon = (opp: { status: { wonFlag: boolean } | null }) =>
Boolean(opp.status?.wonFlag);
const isLost = (opp: {
statusCwId: number | null;
statusName: string | null;
closedFlag: boolean;
}) => {
if (opp.statusCwId === OpportunityStatus.Lost) return true;
if (opp.statusName?.toLowerCase().includes("lost")) return true;
if (opp.closedFlag && opp.statusName?.toLowerCase().includes("lost"))
return true;
return false;
};
const isLost = (opp: { status: { lostFlag: boolean } | null }) =>
Boolean(opp.status?.lostFlag);
const isClosedOpportunity = (opp: {
statusCwId: number | null;
statusName: string | null;
status: { wonFlag: boolean; lostFlag: boolean; closeFlag: boolean } | null;
closedFlag: boolean;
}) => {
if (opp.closedFlag) return true;
if (opp.status?.closeFlag) return true;
if (isWon(opp)) return true;
if (isLost(opp)) return true;
return false;
};
const buildCancellationMap = (procProducts: any[]) => {
const map = new Map<number, any>();
for (const pp of procProducts) {
const rawForecastDetailId = pp?.forecastDetailId;
const forecastDetailId =
typeof rawForecastDetailId === "number"
? rawForecastDetailId
: Number(rawForecastDetailId);
if (Number.isFinite(forecastDetailId) && forecastDetailId > 0) {
map.set(forecastDetailId, pp);
}
}
return map;
};
const computeRevenueFromProductsBlob = (
blob: any,
): Omit<OpportunityRevenue, "cacheHit"> => {
const forecastItems = Array.isArray(blob?.forecast?.forecastItems)
? blob.forecast.forecastItems
: [];
const procProducts = Array.isArray(blob?.procProducts)
? blob.procProducts
: [];
const cancellationMap = buildCancellationMap(procProducts);
let totalRevenue = 0;
let taxableRevenue = 0;
for (const item of forecastItems) {
if (!cancellationMap.has(item?.id)) continue;
if (!item?.includeFlag) continue;
const quantity = Math.max(0, toFinite(item?.quantity));
const revenue = toFinite(item?.revenue);
const cancellation = cancellationMap.get(item.id);
const cancelledFlag = Boolean(cancellation?.cancelledFlag);
const quantityCancelled = Math.max(
0,
toFinite(cancellation?.quantityCancelled),
);
if (cancelledFlag && quantity > 0 && quantityCancelled >= quantity)
continue;
const ratio =
quantity > 0 ? Math.max(0, (quantity - quantityCancelled) / quantity) : 1;
const effectiveRevenue = revenue * ratio;
totalRevenue += effectiveRevenue;
if (item?.taxableFlag) taxableRevenue += effectiveRevenue;
}
const nonTaxableRevenue = totalRevenue - taxableRevenue;
return {
totalRevenue: roundCurrency(totalRevenue),
taxableRevenue: roundCurrency(taxableRevenue),
nonTaxableRevenue: roundCurrency(nonTaxableRevenue),
};
};
const computeRevenueFromControllers = (
products: Array<{
@@ -298,20 +215,8 @@ const writeCachedOpportunityRevenue = async (
);
};
const resolveProbabilityRatio = async (opp: {
cwOpportunityId: number;
probability: number;
}): Promise<number> => {
const fromDb = normalizeProbabilityRatio(opp.probability);
if (fromDb > 0) return fromDb;
const cachedCwOpp = await getCachedOppCwData(opp.cwOpportunityId);
if (!cachedCwOpp) return 0;
const rawProbability =
cachedCwOpp?.probability?.name ?? cachedCwOpp?.probability ?? 0;
return normalizeProbabilityRatio(rawProbability);
};
const resolveProbabilityRatio = (opp: { probability: number }): number =>
normalizeProbabilityRatio(opp.probability);
const getOpportunityRevenueCacheFirst = async (
cwOpportunityId: number,
@@ -327,18 +232,6 @@ const getOpportunityRevenueCacheFirst = async (
}
}
if (!opts?.forceColdLoad) {
const cachedProducts = await getCachedProducts(cwOpportunityId);
if (cachedProducts) {
const computed = computeRevenueFromProductsBlob(cachedProducts);
await writeCachedOpportunityRevenue(cwOpportunityId, computed);
return {
...computed,
cacheHit: true,
};
}
}
try {
const opportunity = await opportunities.fetchRecord(cwOpportunityId);
const products = await opportunity.fetchProducts({
@@ -489,8 +382,8 @@ export async function refreshSalesOpportunityMetricsCache(
AND: [
{
OR: [
{ primarySalesRepIdentifier: { in: memberIdentifiers } },
{ secondarySalesRepIdentifier: { in: memberIdentifiers } },
{ primarySalesRepId: { in: memberIdentifiers } },
{ secondarySalesRepId: { in: memberIdentifiers } },
],
},
{ dateBecameLead: { gte: yearStart } },
@@ -501,12 +394,17 @@ export async function refreshSalesOpportunityMetricsCache(
},
select: {
id: true,
cwOpportunityId: true,
uid: true,
name: true,
primarySalesRepIdentifier: true,
secondarySalesRepIdentifier: true,
statusCwId: true,
statusName: true,
primarySalesRepId: true,
secondarySalesRepId: true,
status: {
select: {
wonFlag: true,
lostFlag: true,
closeFlag: true,
},
},
closedFlag: true,
dateBecameLead: true,
closedDate: true,
@@ -565,7 +463,7 @@ export async function refreshSalesOpportunityMetricsCache(
async (opp) => {
const [revenue, probabilityRatio] = await Promise.all([
withTimeout(
getOpportunityRevenueCacheFirst(opp.cwOpportunityId, {
getOpportunityRevenueCacheFirst(opp.id, {
forceColdLoad,
}),
PRODUCT_LOOKUP_TIMEOUT_MS,
@@ -619,10 +517,10 @@ export async function refreshSalesOpportunityMetricsCache(
for (const opp of opportunityRows) {
const assigned = new Set<string>();
if (opp.primarySalesRepIdentifier)
assigned.add(opp.primarySalesRepIdentifier);
if (opp.secondarySalesRepIdentifier)
assigned.add(opp.secondarySalesRepIdentifier);
if (opp.primarySalesRepId)
assigned.add(opp.primarySalesRepId);
if (opp.secondarySalesRepId)
assigned.add(opp.secondarySalesRepId);
for (const identifier of assigned) {
const bucket = opportunitiesByMember.get(identifier);
@@ -665,8 +563,8 @@ export async function refreshSalesOpportunityMetricsCache(
);
const breakdownEntry: OpportunityBreakdownEntry = {
id: opp.id,
cwId: opp.cwOpportunityId,
id: opp.uid,
cwId: opp.id,
name: opp.name,
revenue: revenue.totalRevenue,
taxableRevenue: revenue.taxableRevenue,