5.5.2026 update

This commit is contained in:
2026-05-05 15:23:26 -03:00
parent 5ea33e353e
commit 8e2cd5dee9
5 changed files with 555 additions and 659 deletions

View File

@@ -16,6 +16,28 @@ import {
resolveStateReadPath,
} from "./lib/runtime-paths.mjs";
import { parseArgs, splitCsv } from "./lib/cli-shared.mjs";
import { quoteLiteral, toSqlList } from "./lib/sql-literals.mjs";
import {
SA_ATTACH_DEFAULT_ALL_STATUSES,
SA_ATTACH_DEFAULT_CLUSTER,
SA_ATTACH_DEFAULT_MIN_OPPORTUNITY_PROBABILITY,
SA_ATTACH_DEFAULT_OPEN_STATUSES,
SA_ATTACH_DEFAULT_PRODUCT_LOBS,
SA_ATTACH_DEFAULT_REVENUE_TYPE_GROUPS,
SA_ATTACH_DEFAULT_SALES_CREDIT_TYPE,
SA_ATTACH_DEFAULT_SOLUTIONS,
SA_ATTACH_DEFAULT_WON_STATUSES,
applyCloseWindowIntentDefaults,
applyCurrentWorkloadIntentDefaults,
resolveCommercialClusterFilter,
resolveCoverageLimit,
resolveManagerEmailFilter,
resolveManagerEmailListFilter,
resolveOptionalClusterFilter,
resolveOwnerEmailFilter,
resolveResourceFilter,
resolveSaAttachFilters,
} from "./lib/sa-attach-filters.mjs";
import {
cookieHeaderFor,
extractClientBin,
@@ -38,8 +60,13 @@ import {
buildWarnings,
emitJson,
mapRows,
parseNumericValue,
} from "./lib/output-contract.mjs";
import {
buildSrCoverageKey,
hasUsableOpportunityId,
summarizeCurrentTeamOpportunityCoverage,
summarizeOrgWideRevenueLineCoverage,
} from "./lib/coverage-summary.mjs";
const DEFAULT_BASE_URL = "https://salesintelligence-dv.oracle.com";
const DEFAULT_WORKBOOK_URL =
@@ -104,20 +131,6 @@ const DATASET_REGISTRY = {
allowedScopes: [SCOPE_MY_TEAM],
},
};
const SA_ATTACH_DEFAULT_CLUSTER = "Brazil";
const SA_ATTACH_DEFAULT_SOLUTIONS = [
"Cloud Infrastructure - Workloads",
"Hardware - Bookings",
"On-Premise Technology - Bookings",
];
const SA_ATTACH_DEFAULT_PRODUCT_LOBS = ["Cloud Infrastructure", "Hardware", "License"];
const SA_ATTACH_DEFAULT_REVENUE_TYPE_GROUPS = ["NEW", "EXPANSION", "WORKLOAD"];
const SA_ATTACH_DEFAULT_OPEN_STATUSES = ["Open"];
const SA_ATTACH_DEFAULT_WON_STATUSES = ["Won", "Won Pending"];
const SA_ATTACH_DEFAULT_ALL_STATUSES = [
...SA_ATTACH_DEFAULT_OPEN_STATUSES,
...SA_ATTACH_DEFAULT_WON_STATUSES,
];
const VIEW_SCOPE_REGISTRY = {
"current-fiscal-year": { sourceName: SOURCE_DV_SE_TEAM, requiresMyTeam: true },
"data-quality-session-context": { sourceName: SOURCE_DV_SE_TEAM, requiresMyTeam: true },
@@ -136,21 +149,6 @@ const VIEW_SCOPE_REGISTRY = {
"srs-by-resource-email": { sourceName: SOURCE_DV_SE_TEAM, requiresMyTeam: true },
"won-and-open-by-se-opty": { sourceName: SOURCE_DV_SE_TEAM, requiresMyTeam: true },
};
const SA_ATTACH_DEFAULT_SALES_CREDIT_TYPE = "QUOTA";
const SA_ATTACH_DEFAULT_MIN_OPPORTUNITY_PROBABILITY = 0;
const SA_ATTACH_PRODUCT_LOB_ALIASES = {
"cloud applications": ["Cloud Applications"],
"cloud infrastructure": ["Cloud Infrastructure"],
hardware: ["Hardware"],
license: ["License"],
"license and hardware": ["License", "Hardware"],
};
const SA_ATTACH_REVENUE_TYPE_GROUP_ALIASES = {
new: "NEW",
expansion: "EXPANSION",
workload: "WORKLOAD",
workloads: "WORKLOAD",
};
const OPPORTUNITY_DETAILS_COLUMNS = [
{ name: "opportunityId", expr: '"Opportunity"."Opportunity ID"', kind: "text" },
{ name: "opportunityHours", expr: '"Effort"."# Opportunity Hours"', kind: "scalar" },
@@ -367,7 +365,6 @@ const WORKLOAD_MANAGER_RESOURCE_CLOSE_WINDOW_COLUMNS = [
"resourceDetails",
];
const TEAM_EMAIL_COLUMN_NAMES = Array.from({ length: 8 }, (_, index) => `seTeamLevel${index + 1}EmailAddress`);
const SQL_NULL_TEXT = `CAST(NULL AS VARCHAR(${VARCHAR_SIZE}))`;
const SQL_NULL_SCALAR = "CAST(NULL AS DOUBLE)";
@@ -2126,51 +2123,7 @@ const INTENT_CATALOG = {
"List commercial opportunities/workloads in a close-date window and enrich them with current-team presales/SR coverage for the executing user when present. Org-wide opportunity rows are preserved even when no current-team resource matches.",
view: "opportunities-close-window",
parameters: [
{
name: "fromDate",
cli: "--from-date",
required: false,
default: "today",
description: "Start of the close-date window. Accepts YYYY-MM-DD, today, +7d, -3d, or today+7d.",
},
{
name: "toDate",
cli: "--to-date",
required: false,
default: "+7d",
description: "End of the close-date window. Accepts YYYY-MM-DD, today, +7d, -3d, or today+7d.",
},
{
name: "date",
cli: "--date",
required: false,
description: "Exact close date. Shortcut for a one-day window.",
},
{
name: "month",
cli: "--month",
required: false,
description: "Calendar month in YYYY-MM format. Shortcut for a full-month window.",
},
{
name: "quarter",
cli: "--quarter",
required: false,
description: "Fiscal quarter in FY26-Q4 or Q4 FY26 format. Shortcut for a full fiscal-quarter window.",
},
{
name: "week",
cli: "--week",
required: false,
description: "Annual week shortcut anchored on June 1. Accepts 1..53, w1..w53, or week 1..week 53.",
},
{
name: "weekYear",
cli: "--week-year",
required: false,
default: "current June-cycle start year",
description: "Cycle start year used with --week. For example, 2025 means the 2025-06-01 through 2026-05-31 cycle.",
},
...closeWindowDateParameters(),
{
name: "optyStatus",
cli: "--opty-status",
@@ -2205,51 +2158,7 @@ const INTENT_CATALOG = {
required: true,
description: "Oracle user identifier. Accepts resource.member or resource.member@oracle.com.",
},
{
name: "fromDate",
cli: "--from-date",
required: false,
default: "today",
description: "Start of the close-date window. Accepts YYYY-MM-DD, today, +7d, -3d, or today+7d.",
},
{
name: "toDate",
cli: "--to-date",
required: false,
default: "+7d",
description: "End of the close-date window. Accepts YYYY-MM-DD, today, +7d, -3d, or today+7d.",
},
{
name: "date",
cli: "--date",
required: false,
description: "Exact close date. Shortcut for a one-day window.",
},
{
name: "month",
cli: "--month",
required: false,
description: "Calendar month in YYYY-MM format. Shortcut for a full-month window.",
},
{
name: "quarter",
cli: "--quarter",
required: false,
description: "Fiscal quarter in FY26-Q4 or Q4 FY26 format. Shortcut for a full fiscal-quarter window.",
},
{
name: "week",
cli: "--week",
required: false,
description: "Annual week shortcut anchored on June 1. Accepts 1..53, w1..w53, or week 1..week 53.",
},
{
name: "weekYear",
cli: "--week-year",
required: false,
default: "current June-cycle start year",
description: "Cycle start year used with --week. For example, 2025 means the 2025-06-01 through 2026-05-31 cycle.",
},
...closeWindowDateParameters(),
{
name: "optyStatus",
cli: "--opty-status",
@@ -2283,51 +2192,7 @@ const INTENT_CATALOG = {
required: true,
description: "Manager email to match in the coverage hierarchy. Accepts resource.manager or resource.manager@oracle.com.",
},
{
name: "fromDate",
cli: "--from-date",
required: false,
default: "today",
description: "Start of the close-date window. Accepts YYYY-MM-DD, today, +7d, -3d, or today+7d.",
},
{
name: "toDate",
cli: "--to-date",
required: false,
default: "+7d",
description: "End of the close-date window. Accepts YYYY-MM-DD, today, +7d, -3d, or today+7d.",
},
{
name: "date",
cli: "--date",
required: false,
description: "Exact close date. Shortcut for a one-day window.",
},
{
name: "month",
cli: "--month",
required: false,
description: "Calendar month in YYYY-MM format. Shortcut for a full-month window.",
},
{
name: "quarter",
cli: "--quarter",
required: false,
description: "Fiscal quarter in FY26-Q4 or Q4 FY26 format. Shortcut for a full fiscal-quarter window.",
},
{
name: "week",
cli: "--week",
required: false,
description: "Annual week shortcut anchored on June 1. Accepts 1..53, w1..w53, or week 1..week 53.",
},
{
name: "weekYear",
cli: "--week-year",
required: false,
default: "current June-cycle start year",
description: "Cycle start year used with --week. For example, 2025 means the 2025-06-01 through 2026-05-31 cycle.",
},
...closeWindowDateParameters(),
{
name: "optyStatus",
cli: "--opty-status",
@@ -3199,28 +3064,6 @@ custom SQL may use placeholders like {{field:closeDate}} and {{macro:securityEnt
`;
}
function normalizeSaAttachProductLobs(values) {
return Array.from(
new Set(
values.flatMap((value) => {
const normalized = String(value || "").trim().toLowerCase();
return SA_ATTACH_PRODUCT_LOB_ALIASES[normalized] || [String(value).trim()];
})
)
);
}
function normalizeSaAttachRevenueTypeGroups(values) {
return Array.from(
new Set(
values.map((value) => {
const normalized = String(value || "").trim().toLowerCase();
return SA_ATTACH_REVENUE_TYPE_GROUP_ALIASES[normalized] || String(value).trim().toUpperCase();
})
)
);
}
async function withConsoleLogRoutedToStderr(callback) {
const originalLog = console.log;
console.log = (...args) => console.error(...args);
@@ -3307,14 +3150,6 @@ function hasFreshSessionContextCache(meta) {
return Date.now() - capturedAtMs <= SESSION_CONTEXT_CACHE_MAX_AGE_MS;
}
function quoteLiteral(value) {
return `'${String(value).replace(/'/g, "''")}'`;
}
function toSqlList(values) {
return values.map(quoteLiteral).join(", ");
}
function normalizeScopeInput(value = SCOPE_AUTO) {
const normalized = String(value || SCOPE_AUTO).trim().toLowerCase();
if (!normalized || normalized === SCOPE_AUTO) return SCOPE_AUTO;
@@ -4292,176 +4127,6 @@ function buildQueryModeSql(args) {
};
}
function normalizeMatchRole(value) {
const normalized = String(value || "team").trim().toLowerCase();
if (["team", "member", "resource", "participant"].includes(normalized)) {
return "team";
}
if (["lead", "leader", "owner"].includes(normalized)) {
return "lead";
}
throw new Error(`Unsupported match role "${value}". Use team or lead.`);
}
function normalizeOracleEmailInput(value, missingMessage) {
const raw = String(value || "").trim().toLowerCase();
const email = raw && raw.includes("@") ? raw : raw ? `${raw}@oracle.com` : "";
if (!email) {
throw new Error(missingMessage);
}
return {
rawInput: raw,
email,
emailSql: quoteLiteral(email),
};
}
function resolveResourceFilter(args) {
const { rawInput, email, emailSql } = normalizeOracleEmailInput(
args["resource-email"] || args.resource || "",
"Pass --resource-email or --resource when using a resource-scoped query."
);
const matchRole = normalizeMatchRole(args["match-role"] || "team");
return {
resourceInput: rawInput,
resourceEmail: email,
resourceEmailSql: emailSql,
matchRole,
srStatuses: splitCsv(args["sr-status"], []),
};
}
function resolveManagerEmailFilter(args) {
const { rawInput, email, emailSql } = normalizeOracleEmailInput(
args["manager-email"] || "",
"Pass --manager-email when using a manager-scoped opportunity query."
);
return {
managerInput: rawInput,
managerEmail: email,
managerEmailSql: emailSql,
};
}
function resolveManagerEmailListFilter(args) {
const tokens = [
...splitCsv(args["manager-email-list"], []),
...splitCsv(args["manager-email"], []),
];
const normalizedEmails = Array.from(
new Set(
tokens
.map((token) =>
normalizeOracleEmailInput(
token,
"Pass --manager-email-list or --manager-email when using a manager/resource workload query."
).email
)
.filter(Boolean)
)
).sort();
if (normalizedEmails.length === 0) {
throw new Error("Pass --manager-email-list or --manager-email when using a manager/resource workload query.");
}
return {
managerEmails: normalizedEmails,
managerEmailsSqlList: toSqlList(normalizedEmails),
};
}
function resolveOwnerEmailFilter(args, flagName = "owner-email", missingMessage = "Pass --owner-email when using an owner-scoped opportunity query.") {
const { rawInput, email, emailSql } = normalizeOracleEmailInput(
args[flagName] || "",
missingMessage
);
return {
ownerInput: rawInput,
ownerEmail: email,
ownerEmailSql: emailSql,
};
}
function applyCurrentWorkloadIntentDefaults(args) {
const nextArgs = { ...args };
if (!hasAnyDateWindowArgs(nextArgs)) {
nextArgs.quarter = "current-quarter";
}
if (!nextArgs.solution) {
nextArgs.solution = "Cloud Infrastructure - Workloads";
}
if (!nextArgs["revenue-type-group"]) {
nextArgs["revenue-type-group"] = "WORKLOAD";
}
return nextArgs;
}
function applyCloseWindowIntentDefaults(args) {
const nextArgs = { ...args };
if (!hasAnyDateWindowArgs(nextArgs)) {
nextArgs["from-date"] = "today";
nextArgs["to-date"] = "+7d";
}
return nextArgs;
}
function resolveSaAttachFilters(args, definition) {
const statuses = splitCsv(args["revenue-line-status"], definition.defaultStatuses || SA_ATTACH_DEFAULT_ALL_STATUSES);
const cluster = String(args.cluster || SA_ATTACH_DEFAULT_CLUSTER).trim();
const solutions = splitCsv(args.solution, SA_ATTACH_DEFAULT_SOLUTIONS);
const productLobs = normalizeSaAttachProductLobs(
splitCsv(args["executive-product-lob"], SA_ATTACH_DEFAULT_PRODUCT_LOBS)
);
const revenueTypeGroups = normalizeSaAttachRevenueTypeGroups(
splitCsv(args["revenue-type-group"], SA_ATTACH_DEFAULT_REVENUE_TYPE_GROUPS)
);
const salesCreditType = String(args["sales-credit-type"] || SA_ATTACH_DEFAULT_SALES_CREDIT_TYPE).trim();
const minOpportunityProbability = Number(
args["min-opportunity-probability"] || SA_ATTACH_DEFAULT_MIN_OPPORTUNITY_PROBABILITY
);
const ownerEmail = args["owner-email"]
? normalizeOracleEmailInput(args["owner-email"], "Pass --owner-email with a valid Oracle email.").email
: null;
const ownerManagerEmail = args["owner-manager-email"]
? normalizeOracleEmailInput(args["owner-manager-email"], "Pass --owner-manager-email with a valid Oracle email.").email
: null;
const forecastTypes = splitCsv(args["forecast-type"], []);
if (!cluster) {
throw new Error("Cluster cannot be empty for SA Attach views.");
}
if (solutions.length === 0) {
throw new Error("Pass at least one --solution value for SA Attach views.");
}
if (statuses.length === 0) {
throw new Error("Pass at least one --revenue-line-status value for SA Attach views.");
}
if (productLobs.length === 0) {
throw new Error("Pass at least one --executive-product-lob value for SA Attach views.");
}
if (revenueTypeGroups.length === 0) {
throw new Error("Pass at least one --revenue-type-group value for SA Attach views.");
}
if (!salesCreditType) {
throw new Error("Sales credit type cannot be empty for SA Attach views.");
}
if (!Number.isFinite(minOpportunityProbability)) {
throw new Error("Pass a numeric --min-opportunity-probability value for SA Attach views.");
}
return {
cluster,
solutions,
statuses,
productLobs,
revenueTypeGroups,
salesCreditType,
minOpportunityProbability,
ownerEmail,
ownerManagerEmail,
forecastTypes,
};
}
function buildSrResourcePredicate(resourceEmailSql, matchRole) {
if (matchRole === "lead") {
return `"Service Request"."Lead Email" = ${resourceEmailSql}`;
@@ -4604,19 +4269,6 @@ function buildManagerResourceCoverageSqlForOpportunityIds(opportunityIds, manage
});
}
function resolveOptionalClusterFilter(args) {
const cluster = typeof args.cluster === "string" ? args.cluster.trim() : "";
return cluster || null;
}
function resolveCommercialClusterFilter(args, fallback = SA_ATTACH_DEFAULT_CLUSTER) {
return resolveOptionalClusterFilter(args) || fallback;
}
function resolveCoverageLimit(limit) {
return Math.min(10000, Math.max(5000, Number(limit) * 8));
}
function buildWorkloadCloseWindowSql({
fromDateSql,
toDateSql,
@@ -5325,286 +4977,6 @@ function buildIntentScopeDescriptor(name, definition) {
return buildViewScopeDescriptor(definition.view, VIEW_DEFS[definition.view] || {});
}
function hasUsableOpportunityId(value) {
const normalized = String(value || "").trim();
return Boolean(normalized && normalized.toLowerCase() !== "unspecified");
}
function buildCustomerMatchKey(value) {
return String(value || "")
.normalize("NFKD")
.replace(/[\u0300-\u036f]/g, "")
.replace(/\s+/g, " ")
.trim()
.toLowerCase();
}
function normalizeEmailAddress(value) {
return String(value || "").trim().toLowerCase();
}
function toSortedUniqueStrings(values) {
return Array.from(
new Set(
(values || [])
.map((value) => String(value || "").trim())
.filter(Boolean)
)
).sort((left, right) => left.localeCompare(right));
}
function sortCandidateInternalSrDetails(details) {
return Array.from(
new Map(
(details || [])
.filter((detail) => detail?.srNumber)
.map((detail) => [String(detail.srNumber || "").trim(), detail])
).values()
).sort((left, right) => String(left.srNumber || "").localeCompare(String(right.srNumber || "")));
}
function buildSrLinkageMode(directSrNumbers, candidateInternalSrDetails) {
const directCount = toSortedUniqueStrings(directSrNumbers).length;
const candidateCount = sortCandidateInternalSrDetails(candidateInternalSrDetails).length;
if (directCount > 0 && candidateCount > 0) return "direct-plus-candidate-internal";
if (directCount > 0) return "direct-only";
if (candidateCount > 0) return "candidate-internal-only";
return "none";
}
function buildResolvedSrNumbers(directSrNumbers, candidateInternalSrDetails) {
return toSortedUniqueStrings([
...toSortedUniqueStrings(directSrNumbers),
...sortCandidateInternalSrDetails(candidateInternalSrDetails).map((detail) => detail.srNumber),
]);
}
function buildInternalSrAwareResourceDetails({
opportunityId,
customerName,
resourceEmails,
srNumbersByResourceOpportunity = new Map(),
candidateInternalSrDetailsByResource = new Map(),
}) {
const customerMatchKey = buildCustomerMatchKey(customerName);
const normalizedResourceEmails = toSortedUniqueStrings(
(resourceEmails || []).map((email) => normalizeEmailAddress(email))
);
return normalizedResourceEmails.map((resourceEmail) => {
const directSrNumbers = toSortedUniqueStrings(
Array.from(srNumbersByResourceOpportunity.get(buildSrCoverageKey(opportunityId, resourceEmail)) || [])
);
const candidateInternalSrDetails = sortCandidateInternalSrDetails(
Array.from(candidateInternalSrDetailsByResource.get(resourceEmail)?.values() || []).filter(
(candidate) => buildCustomerMatchKey(candidate.customerName) === customerMatchKey
)
);
return {
resourceEmail,
srNumbers: buildResolvedSrNumbers(directSrNumbers, candidateInternalSrDetails),
directSrNumbers,
candidateInternalSrNumbers: candidateInternalSrDetails.map((candidate) => candidate.srNumber),
candidateInternalSrDetails,
srLinkageMode: buildSrLinkageMode(directSrNumbers, candidateInternalSrDetails),
companionEmails: normalizedResourceEmails.filter((email) => email !== resourceEmail),
};
});
}
function summarizeCurrentTeamOpportunityCoverage(opportunities, coverageRows) {
const opportunityMap = new Map(
opportunities
.filter((record) => record?.opportunityId)
.map((record) => [
record.opportunityId,
{
...record,
bookingValue: parseNumericValue(record.bookingValue),
workloadAmount: parseNumericValue(record.workloadAmount),
},
])
);
const coverageByOpportunity = new Map();
for (const row of coverageRows) {
const opportunityId = row?.opportunityId;
if (!opportunityId || !opportunityMap.has(opportunityId)) continue;
const entry = coverageByOpportunity.get(opportunityId) || {
opportunityId,
teamMemberEmails: new Set(),
currentTeamOpportunityHours: 0,
coverageRows: 0,
};
for (const fieldName of TEAM_EMAIL_COLUMN_NAMES) {
const email = row?.[fieldName];
if (email) entry.teamMemberEmails.add(email);
}
entry.currentTeamOpportunityHours += parseNumericValue(
row?.opportunityHoursAttribute ?? row?.opportunityHours
);
entry.coverageRows += 1;
coverageByOpportunity.set(opportunityId, entry);
}
const totalWindowOpportunities = opportunityMap.size;
const records = Array.from(opportunityMap.values())
.map((opportunity) => {
const coverage = coverageByOpportunity.get(opportunity.opportunityId) || {
opportunityId: opportunity.opportunityId,
teamMemberEmails: new Set(),
currentTeamOpportunityHours: 0,
coverageRows: 0,
};
return {
opportunityId: opportunity.opportunityId,
opportunityName: opportunity.opportunityName,
customerName: opportunity.customerName,
opportunityStatus: opportunity.opportunityStatus,
closeDate: opportunity.closeDate,
salesStage: opportunity.salesStage,
bookingValue: opportunity.bookingValue,
workloadAmount: opportunity.workloadAmount,
teamMemberEmails: Array.from(coverage.teamMemberEmails).sort(),
currentTeamOpportunityHours: coverage.currentTeamOpportunityHours,
coverageRows: coverage.coverageRows,
};
})
.sort((left, right) => {
const closeDateCompare = String(left.closeDate || "").localeCompare(String(right.closeDate || ""));
if (closeDateCompare !== 0) return closeDateCompare;
return String(left.opportunityId || "").localeCompare(String(right.opportunityId || ""));
});
const summary = records.reduce(
(totals, record) => {
const matched = parseNumericValue(record.coverageRows) > 0;
return {
totalWindowOpportunities,
matchedOpportunities: totals.matchedOpportunities + (matched ? 1 : 0),
unmatchedOpportunities: totals.unmatchedOpportunities + (matched ? 0 : 1),
totalBookingValue: totals.totalBookingValue + parseNumericValue(record.bookingValue),
totalWorkloadAmount: totals.totalWorkloadAmount + parseNumericValue(record.workloadAmount),
matchedBookingValue:
totals.matchedBookingValue + (matched ? parseNumericValue(record.bookingValue) : 0),
matchedWorkloadAmount:
totals.matchedWorkloadAmount + (matched ? parseNumericValue(record.workloadAmount) : 0),
totalCurrentTeamOpportunityHours:
totals.totalCurrentTeamOpportunityHours + parseNumericValue(record.currentTeamOpportunityHours),
};
},
{
totalWindowOpportunities,
matchedOpportunities: 0,
unmatchedOpportunities: 0,
totalBookingValue: 0,
totalWorkloadAmount: 0,
matchedBookingValue: 0,
matchedWorkloadAmount: 0,
totalCurrentTeamOpportunityHours: 0,
}
);
return { records, summary };
}
function summarizeOrgWideRevenueLineCoverage(
workloadRows,
coverageRows,
srNumbersByResourceOpportunity = new Map(),
candidateInternalSrDetailsByResource = new Map()
) {
const includeSrDetails =
srNumbersByResourceOpportunity.size > 0 || candidateInternalSrDetailsByResource.size > 0;
const workloadRecords = workloadRows
.filter((record) => record?.revenueLineId && record?.opportunityId)
.map((record) => ({
closeDate: record.closeDate,
revenueLineId: record.revenueLineId,
opportunityId: record.opportunityId,
opportunityName: record.opportunityName,
customerName: record.customerName,
workloadAmount: parseNumericValue(record.workloadAmount),
}));
const matchedOpportunities = new Map();
for (const row of coverageRows) {
const opportunityId = row?.opportunityId;
const managerEmail = row?.seTeamLevel4EmailAddress;
const resourceEmail = row?.seTeamLevel5EmailAddress;
if (!opportunityId || !managerEmail || !resourceEmail) continue;
const entry = matchedOpportunities.get(opportunityId) || {
managerEmails: new Set(),
resourceEmails: new Set(),
};
entry.managerEmails.add(normalizeEmailAddress(managerEmail));
entry.resourceEmails.add(normalizeEmailAddress(resourceEmail));
matchedOpportunities.set(opportunityId, entry);
}
const matchedRecords = workloadRecords
.filter((record) => matchedOpportunities.has(record.opportunityId))
.map((record) => {
const coverage = matchedOpportunities.get(record.opportunityId);
const resourceEmails = toSortedUniqueStrings(Array.from(coverage.resourceEmails));
const resourceDetails = includeSrDetails
? buildInternalSrAwareResourceDetails({
opportunityId: record.opportunityId,
customerName: record.customerName,
resourceEmails,
srNumbersByResourceOpportunity,
candidateInternalSrDetailsByResource,
})
: resourceEmails.map((resourceEmail) => ({
resourceEmail,
companionEmails: resourceEmails.filter((email) => email !== resourceEmail),
}));
return {
...record,
managerEmails: toSortedUniqueStrings(Array.from(coverage.managerEmails)),
resourceEmails,
resourceDetails,
};
})
.sort((left, right) => {
const closeDateCompare = String(left.closeDate || "").localeCompare(String(right.closeDate || ""));
if (closeDateCompare !== 0) return closeDateCompare;
const opportunityCompare = String(left.opportunityId || "").localeCompare(String(right.opportunityId || ""));
if (opportunityCompare !== 0) return opportunityCompare;
return String(left.revenueLineId || "").localeCompare(String(right.revenueLineId || ""));
});
const distinctMatchedOpportunities = new Set(matchedRecords.map((record) => record.opportunityId));
const distinctMatchedRevenueLines = new Set(matchedRecords.map((record) => record.revenueLineId));
const summary = matchedRecords.reduce(
(totals, record) => ({
totalWindowRevenueLines: totals.totalWindowRevenueLines,
matchedRevenueLines: totals.matchedRevenueLines + 1,
matchedOpportunities: totals.matchedOpportunities,
totalWorkloadAmount: totals.totalWorkloadAmount + parseNumericValue(record.workloadAmount),
managersRequested: totals.managersRequested,
resourcesMatched: totals.resourcesMatched,
}),
{
totalWindowRevenueLines: workloadRecords.length,
matchedRevenueLines: 0,
matchedOpportunities: distinctMatchedOpportunities.size,
totalWorkloadAmount: 0,
managersRequested: 0,
resourcesMatched: Array.from(
new Set(
matchedRecords.flatMap((record) => record.resourceEmails || [])
)
).length,
}
);
summary.matchedRevenueLines = distinctMatchedRevenueLines.size;
return { records: matchedRecords, summary };
}
function buildSrCoverageKey(opportunityId, resourceEmail) {
return `${String(opportunityId || "").trim()}::${String(resourceEmail || "").trim().toLowerCase()}`;
}
async function fetchResourceOpportunitySrCoverage({
resourceEmails,
opportunityIds,