Reestruturação dos arquivos e pastas do código da extensão
This commit is contained in:
216
src/domain/ramp-forecast.js
Normal file
216
src/domain/ramp-forecast.js
Normal file
@@ -0,0 +1,216 @@
|
||||
(() => {
|
||||
function create(dependencies) {
|
||||
const {
|
||||
toNumber,
|
||||
normalizeString,
|
||||
parseDatePreservingDateOnly,
|
||||
formatWeekNumber,
|
||||
} = dependencies;
|
||||
|
||||
function buildRampForecastComparisonRows(workloads) {
|
||||
return (workloads || [])
|
||||
.flatMap((workload) => {
|
||||
const startDate = parseDatePreservingDateOnly(
|
||||
workload.consumptionStartDate
|
||||
);
|
||||
|
||||
if (Number.isNaN(startDate.getTime())) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const startMonth = startDate.getMonth() + 1;
|
||||
const startYear = startDate.getFullYear();
|
||||
const workloadLabel = `${workload.name || "Unnamed workload"}${
|
||||
workload.description ? ` | ${workload.description}` : ""
|
||||
}`;
|
||||
|
||||
return (workload.forecast || [])
|
||||
.map((forecastItem) => {
|
||||
const forecastMonth = parseForecastMonthNumber(forecastItem.month);
|
||||
const forecastYear = toNumber(forecastItem.year);
|
||||
|
||||
if (!forecastMonth || !forecastYear) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const monthIndex =
|
||||
(forecastYear - startYear) * 12 +
|
||||
(forecastMonth - startMonth) +
|
||||
1;
|
||||
|
||||
const currentValue = toNumber(
|
||||
forecastItem.adjustedConsumptionAmount
|
||||
);
|
||||
const calculatedValue =
|
||||
monthIndex >= 1 && monthIndex <= 12
|
||||
? calculateRampForecastAmount({
|
||||
adjustedACR: workload.adjustedACR,
|
||||
rampMonths: workload.rampMonths,
|
||||
monthIndex,
|
||||
consumptionStartDate: startDate,
|
||||
})
|
||||
: 0;
|
||||
|
||||
return {
|
||||
workloadLabel,
|
||||
periodLabel: formatForecastPeriodLabel(forecastYear, forecastMonth),
|
||||
currentValue,
|
||||
calculatedValue,
|
||||
delta: currentValue - calculatedValue,
|
||||
monthIndex,
|
||||
year: forecastYear,
|
||||
month: forecastMonth,
|
||||
sortKey: `${workloadLabel}|${String(forecastYear).padStart(
|
||||
4,
|
||||
"0"
|
||||
)}-${String(forecastMonth).padStart(2, "0")}`,
|
||||
};
|
||||
})
|
||||
.filter(Boolean);
|
||||
})
|
||||
.sort((left, right) => left.sortKey.localeCompare(right.sortKey));
|
||||
}
|
||||
|
||||
function hasRampForecastMismatch(workload) {
|
||||
return buildRampForecastComparisonRows([workload]).some((row) =>
|
||||
isNonZeroAmount(row.delta)
|
||||
);
|
||||
}
|
||||
|
||||
function calculateRampForecastAmount({
|
||||
adjustedACR,
|
||||
rampMonths,
|
||||
monthIndex,
|
||||
consumptionStartDate,
|
||||
}) {
|
||||
const annualValue = toNumber(adjustedACR);
|
||||
const ramp = Math.max(0, toNumber(rampMonths));
|
||||
const month = Math.max(1, toNumber(monthIndex));
|
||||
|
||||
if (!annualValue) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (ramp <= 0) {
|
||||
return Math.round(
|
||||
(annualValue / 12) *
|
||||
getStartMonthProrationFactor(month, consumptionStartDate)
|
||||
);
|
||||
}
|
||||
|
||||
const steadyStateMonthlyAmount = (2 * annualValue) / (24 - ramp);
|
||||
const rampStepCount = ramp + 1;
|
||||
const calculatedAmount =
|
||||
month <= rampStepCount
|
||||
? steadyStateMonthlyAmount * (month / rampStepCount)
|
||||
: steadyStateMonthlyAmount;
|
||||
|
||||
return Math.round(
|
||||
calculatedAmount * getStartMonthProrationFactor(month, consumptionStartDate)
|
||||
);
|
||||
}
|
||||
|
||||
function getStartMonthProrationFactor(monthIndex, consumptionStartDate) {
|
||||
if (toNumber(monthIndex) !== 1) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
const startDate = parseDatePreservingDateOnly(consumptionStartDate);
|
||||
|
||||
if (Number.isNaN(startDate.getTime())) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
const daysInMonth = new Date(
|
||||
startDate.getFullYear(),
|
||||
startDate.getMonth() + 1,
|
||||
0
|
||||
).getDate();
|
||||
const remainingDays = daysInMonth - startDate.getDate() + 1;
|
||||
|
||||
return Math.max(0, Math.min(1, remainingDays / daysInMonth));
|
||||
}
|
||||
|
||||
function isNonZeroAmount(value) {
|
||||
return Math.abs(toNumber(value)) > 0.000001;
|
||||
}
|
||||
|
||||
function parseForecastMonthNumber(value) {
|
||||
if (typeof value === "number") {
|
||||
return value >= 1 && value <= 12 ? value : 0;
|
||||
}
|
||||
|
||||
const normalized = normalizeString(value);
|
||||
const numericMonth = Number.parseInt(normalized, 10);
|
||||
|
||||
if (numericMonth >= 1 && numericMonth <= 12) {
|
||||
return numericMonth;
|
||||
}
|
||||
|
||||
const monthMap = {
|
||||
JAN: 1,
|
||||
JANUARY: 1,
|
||||
FEB: 2,
|
||||
FEBRUARY: 2,
|
||||
MAR: 3,
|
||||
MARCH: 3,
|
||||
APR: 4,
|
||||
APRIL: 4,
|
||||
MAY: 5,
|
||||
JUN: 6,
|
||||
JUNE: 6,
|
||||
JUL: 7,
|
||||
JULY: 7,
|
||||
AUG: 8,
|
||||
AUGUST: 8,
|
||||
SEP: 9,
|
||||
SEPT: 9,
|
||||
SEPTEMBER: 9,
|
||||
OCT: 10,
|
||||
OCTOBER: 10,
|
||||
NOV: 11,
|
||||
NOVEMBER: 11,
|
||||
DEC: 12,
|
||||
DECEMBER: 12,
|
||||
};
|
||||
|
||||
return monthMap[normalized] || 0;
|
||||
}
|
||||
|
||||
function formatForecastPeriodLabel(year, month) {
|
||||
const fiscalYear = month >= 6 ? year + 1 : year;
|
||||
const fiscalYearLabel = formatWeekNumber(fiscalYear % 100);
|
||||
const monthLabel = new Intl.DateTimeFormat("en-US", {
|
||||
month: "short",
|
||||
}).format(new Date(year, month - 1, 1));
|
||||
|
||||
return `FY${fiscalYearLabel} ${monthLabel}`;
|
||||
}
|
||||
|
||||
function createForecastUpdatePayload(rows, userEmail = "") {
|
||||
return (rows || [])
|
||||
.filter((row) => row.year && row.month)
|
||||
.map((row) => ({
|
||||
year: toNumber(row.year),
|
||||
month: toNumber(row.month),
|
||||
amount: toNumber(row.calculatedValue),
|
||||
updatedBy: userEmail,
|
||||
}));
|
||||
}
|
||||
|
||||
return {
|
||||
buildRampForecastComparisonRows,
|
||||
hasRampForecastMismatch,
|
||||
calculateRampForecastAmount,
|
||||
getStartMonthProrationFactor,
|
||||
isNonZeroAmount,
|
||||
parseForecastMonthNumber,
|
||||
formatForecastPeriodLabel,
|
||||
createForecastUpdatePayload,
|
||||
};
|
||||
}
|
||||
|
||||
globalThis.ArchPanelRampForecastDomain = Object.freeze({
|
||||
create,
|
||||
});
|
||||
})();
|
||||
Reference in New Issue
Block a user