Fixed the date ranges for the Reference options

This commit is contained in:
2026-10-19 18:10:16 -03:00
parent cc025e488d
commit 71d550dfb1
10 changed files with 135 additions and 372 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -89,8 +89,7 @@ function createContentContracts() {
"Next Quarter",
"Previous Quarter",
"Current Fiscal Year",
"4 Rolling Quarters (CQ + 3)",
"RENEWALS (Current + Past Due)"
"4 Rolling Quarters (CQ + 3)"
];
const ALLOWED_PATHS = [
"/hcmUI/faces/FuseWelcome",

View File

@@ -212,75 +212,6 @@ function createCrmSrForm({ MODAL_ID, allowsCrmActivities, isMissing, getOpportun
active = view;
const current = () => active === view && allowsCrmActivities();
function updateSave() { save.disabled = loadingFields.size > 0 || availableFields.size < 4 || Boolean(view.saving); }
function confirmPayload(payload, requestHeaders = {}) {
return new Promise(resolve => {
const overlay = node("section", "opportunities-extension-action-form-modal opportunities-extension-sr-confirmation");
overlay.setAttribute("role", "dialog");
overlay.setAttribute("aria-modal", "true");
overlay.setAttribute("aria-labelledby", "opportunities-extension-sr-confirm-title");
const panel = node("div", "opportunities-extension-action-form-dialog opportunities-extension-sr-confirm-dialog");
const heading = node("header", "opportunities-extension-account-plans-header");
const title = node("h2", "", "Confirm Service Request");
title.id = "opportunities-extension-sr-confirm-title";
heading.append(title);
const body = node("div", "opportunities-extension-sr-confirm-body");
const label = node("p", "", "requestPostServiceRequest");
label.id = "opportunities-extension-sr-confirm-payload-label";
const preview = node("pre", "opportunities-extension-sr-confirm-payload", JSON.stringify(payload, null, 2));
preview.tabIndex = 0;
preview.setAttribute("aria-labelledby", label.id);
body.append(label, preview);
const headerPreviews = [];
for (const name of ["requestSrTokenCatalog", "requestPostServiceRequest"]) {
if (!requestHeaders[name]) continue;
const headerLabel = node("p", "", `${name} - Headers`);
headerLabel.id = `opportunities-extension-sr-confirm-${name}-headers`;
const headerPreview = node("pre", "opportunities-extension-sr-confirm-payload", JSON.stringify(requestHeaders[name], null, 2));
headerPreview.tabIndex = 0;
headerPreview.setAttribute("aria-labelledby", headerLabel.id);
headerPreviews.push(headerPreview);
body.append(headerLabel, headerPreview);
if (name === "requestSrTokenCatalog" && !requestHeaders[name]["x-appbuilder-client-id"]) {
body.append(node("p", "opportunities-extension-action-form-status", "x-appbuilder-client-id: not yet observed in SeaaS requests."));
}
}
const footer = node("footer", "opportunities-extension-action-form-footer");
const cancel = node("button", "", "Cancel");
const confirm = node("button", "", "Confirm Save");
cancel.type = confirm.type = "button";
footer.append(cancel, confirm);
panel.append(heading, body, footer);
overlay.append(panel);
dialog.inert = true;
dialog.setAttribute("aria-hidden", "true");
modal.setAttribute("aria-modal", "false");
modal.append(overlay);
let settled = false;
function finish(confirmed) {
if (settled) return;
settled = true;
overlay.remove();
dialog.inert = false;
dialog.setAttribute("aria-hidden", "false");
modal.setAttribute("aria-modal", "true");
resolve(confirmed);
}
cancel.addEventListener("click", () => finish(false));
confirm.addEventListener("click", () => finish(true));
overlay.addEventListener("keydown", event => {
event.stopPropagation();
if (event.key === "Escape") { event.preventDefault(); finish(false); }
if (event.key === "Tab") {
const controls = [preview, ...headerPreviews, cancel, confirm];
const index = controls.indexOf(document.activeElement);
if (index < 0 || (event.shiftKey ? index === 0 : index === controls.length - 1)) {
event.preventDefault(); controls[event.shiftKey ? controls.length - 1 : 0].focus();
}
}
});
cancel.focus();
});
}
form.addEventListener("submit", async event => {
event.preventDefault();
if (!current() || view.saving || save.disabled) return;
@@ -331,7 +262,7 @@ function createCrmSrForm({ MODAL_ID, allowsCrmActivities, isMissing, getOpportun
status.hidden = true;
try {
const prepared = await requestSave({ phase: "prepare", payload });
if (!current() || host.hidden || !await confirmPayload(prepared.payload, prepared.headers)) return;
if (!current() || host.hidden) return;
save.textContent = "Saving...";
await requestSave({ phase: "confirm", payload: prepared.payload });
view.saving = false;

View File

@@ -1,15 +1,15 @@
function createOpportunitiesFiscalPeriods() {
// Oracle fiscal years start on June 1; each range ends at the next period's start.
const FISCAL_START_MONTH = 5;
function getFiscalPeriodRange(period, currentDate) {
const currentYear = currentDate.getFullYear();
const currentMonth = currentDate.getMonth();
const fiscalStartYear = currentMonth >= 5 ? currentYear : currentYear - 1;
const currentQuarterIndex = Math.floor(((currentMonth - 5 + 12) % 12) / 3);
const fiscalStartYear = currentMonth >= FISCAL_START_MONTH ? currentYear : currentYear - 1;
const currentQuarterIndex = Math.floor(((currentMonth - FISCAL_START_MONTH + 12) % 12) / 3);
if (period === "Current Fiscal Year") {
const isBeforeFiscalYearStart = currentMonth < 5;
const startYear = isBeforeFiscalYearStart ? currentYear - 1 : currentYear;
const endYear = isBeforeFiscalYearStart ? currentYear : currentYear + 1;
return createDateRange(startYear, 5, endYear, 4);
return createDateRange(fiscalStartYear, FISCAL_START_MONTH, fiscalStartYear + 1, FISCAL_START_MONTH);
}
let quarterOffset;
@@ -28,19 +28,14 @@ function createOpportunitiesFiscalPeriods() {
return null;
}
const start = new Date(fiscalStartYear, 5 + (quarterOffset * 3), 1);
const end = new Date(start.getFullYear(), start.getMonth() + (numberOfQuarters * 3), 0);
return {
startCloseDate: formatOracleDate(start),
endCloseDate: formatOracleDate(end)
};
const startMonth = FISCAL_START_MONTH + (quarterOffset * 3);
return createDateRange(fiscalStartYear, startMonth, fiscalStartYear, startMonth + (numberOfQuarters * 3));
}
function createDateRange(startYear, startMonth, endYear, endMonth) {
return {
startCloseDate: formatOracleDate(new Date(startYear, startMonth, 1)),
endCloseDate: formatOracleDate(new Date(endYear, endMonth + 1, 0))
endCloseDate: formatOracleDate(new Date(endYear, endMonth, 1))
};
}

View File

@@ -310,17 +310,6 @@
.opportunities-extension-action-form-field select { height: 38px; }
.opportunities-extension-action-form-field select[hidden] { display: none; }
.opportunities-extension-sr-form-dialog { width: min(900px, 100%); }
.opportunities-extension-sr-confirmation { z-index: 6; }
.opportunities-extension-sr-confirm-dialog { width: min(800px, 100%); display: flex; flex-direction: column; overflow: hidden; }
.opportunities-extension-sr-confirm-dialog > header,
.opportunities-extension-sr-confirm-dialog > footer { flex-shrink: 0; }
.opportunities-extension-sr-confirm-dialog > footer { padding: 16px 20px; }
.opportunities-extension-sr-confirm-body { min-height: 0; overflow: auto; padding: 0 20px; }
.opportunities-extension-sr-confirm-body > p { font-size: 13px; font-weight: 600; }
.opportunities-extension-sr-confirm-payload { margin: 0; padding: 16px; border: 1px solid #b9b4ae; border-radius: 4px; background: #f6f5f4; color: #312d2a; font: 13px/1.6 Consolas, monospace; white-space: pre-wrap; overflow-wrap: anywhere; }
.opportunities-extension-sr-confirmation :focus-visible { outline: 2px solid #00758f; outline-offset: 2px; }
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-sr-confirm-payload { background: #211f1d; color: #eeeae6; border-color: #756f68; }
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-sr-confirmation :focus-visible { outline-color: #80d6e8; }
.opportunities-extension-sr-form-dialog > .opportunities-extension-account-plans-header { padding: 20px 24px; }
.opportunities-extension-sr-form-dialog > .opportunities-extension-action-form { gap: 20px 24px; padding: 24px; align-items: start; }
.opportunities-extension-sr-form-dialog .opportunities-extension-action-form-field { gap: 8px; }

View File

@@ -102,19 +102,21 @@ function createFusionOpportunities({
op: "$and",
criteria: [
{
op: "$and",
criteria: [{
op: "$or",
criteria: Array.from(getOpportunityTypeValues(), (value) => ({
op: "$eq",
attribute: "RecordSet",
value
}))
}]
},
{
op: "$wi",
attribute: "EffectiveDate",
value1: dateRange.startCloseDate,
value2: dateRange.endCloseDate,
dynamicDate: false
value1: new Date(dateRange.startCloseDate).toISOString().replace(/Z$/, "+0000"),
value2: new Date(dateRange.endCloseDate).toISOString().replace(/Z$/, "+0000")
}
]
},
@@ -154,7 +156,7 @@ function createFusionOpportunities({
"CustomerAccount.Region_c",
"CustomerAccount.PrimaryAddress.CountryCode"
],
sort: [],
sort: null,
language: "en",
skipInValidFields: true,
skipHiddenFromUIFields: true,

View File

@@ -48,7 +48,7 @@ test("Redwood cascade is byte-identical to the characterized original", () => {
test("fiscal module can run alone without DOM, browser APIs or network", () => {
const factory = new vm.Script(`${read("src/features/opportunities/fiscal-periods.js")}\ncreateOpportunitiesFiscalPeriods;`).runInNewContext();
assert.deepEqual(json(factory().getFiscalPeriodRange("Current Fiscal Year", new Date(2027, 0, 15))), {
startCloseDate: "2026-06-01T00:00:00.000Z", endCloseDate: "2027-05-31T00:00:00.000Z"
startCloseDate: "2026-06-01T00:00:00.000Z", endCloseDate: "2027-06-01T00:00:00.000Z"
});
});

View File

@@ -11,7 +11,7 @@ const FILTER_KEY = "opportunities-extension-filter-preferences";
const TYPE_KEY = "opportunities-extension-opportunity-type-preferences";
const CACHE_KEY = "opportunitiesExtension.opptyProductsCache.v1";
const DAY = 86400000;
const range = { startCloseDate: "2026-06-01T00:00:00.000Z", endCloseDate: "2026-08-31T00:00:00.000Z" };
const range = { startCloseDate: "2026-06-01T00:00:00.000Z", endCloseDate: "2026-09-01T00:00:00.000Z" };
const drain = () => new Promise(resolve => setImmediate(resolve));
const productRequest = (requestId, optyNumbers = ["SYNTH-1"]) => ({
type: PREFIX + "requestOpptyProducts", requestId, optyNumbers, accessToken: "synthetic-products-token"
@@ -63,21 +63,47 @@ test("distribution contracts: manifests, execution boundaries and deterministic
test("fiscal dates preserve quarter/year boundaries, leap year and unsupported renewals", () => {
const h = loadContent();
const cases = [
["Current Quarter", [2026, 5, 1], "2026-06-01", "2026-08-31"],
["Current Quarter", [2026, 7, 31], "2026-06-01", "2026-08-31"],
["Current Quarter", [2026, 8, 1], "2026-09-01", "2026-11-30"],
["Current Quarter", [2028, 1, 29], "2027-12-01", "2028-02-29"],
["Next Quarter", [2026, 10, 30], "2026-12-01", "2027-02-28"],
["Previous Quarter", [2026, 7, 24], "2026-03-01", "2026-05-31"],
["Current Fiscal Year", [2026, 4, 31], "2025-06-01", "2026-05-31"],
["Current Fiscal Year", [2026, 5, 1], "2026-06-01", "2027-05-31"],
["Current Fiscal Year", [2027, 0, 1], "2026-06-01", "2027-05-31"],
["4 Rolling Quarters (CQ + 3)", [2026, 8, 16], "2026-09-01", "2027-08-31"]
["Current Quarter", [2026, 0, 1], "2025-12-01", "2026-03-01"],
["Current Quarter", [2026, 1, 28], "2025-12-01", "2026-03-01"],
["Current Quarter", [2026, 2, 1], "2026-03-01", "2026-06-01"],
["Current Quarter", [2026, 4, 31], "2026-03-01", "2026-06-01"],
["Current Quarter", [2026, 5, 1], "2026-06-01", "2026-09-01"],
["Current Quarter", [2026, 7, 31], "2026-06-01", "2026-09-01"],
["Current Quarter", [2026, 8, 1], "2026-09-01", "2026-12-01"],
["Current Quarter", [2026, 9, 19], "2026-09-01", "2026-12-01"],
["Current Quarter", [2026, 10, 30], "2026-09-01", "2026-12-01"],
["Current Quarter", [2026, 11, 1], "2026-12-01", "2027-03-01"],
["Current Quarter", [2026, 11, 31], "2026-12-01", "2027-03-01"],
["Current Quarter", [2028, 1, 29], "2027-12-01", "2028-03-01"],
["Next Quarter", [2026, 0, 1], "2026-03-01", "2026-06-01"],
["Next Quarter", [2026, 4, 31], "2026-06-01", "2026-09-01"],
["Next Quarter", [2026, 5, 1], "2026-09-01", "2026-12-01"],
["Next Quarter", [2026, 10, 30], "2026-12-01", "2027-03-01"],
["Next Quarter", [2026, 11, 1], "2027-03-01", "2027-06-01"],
["Previous Quarter", [2026, 0, 1], "2025-09-01", "2025-12-01"],
["Previous Quarter", [2026, 2, 1], "2025-12-01", "2026-03-01"],
["Previous Quarter", [2026, 5, 1], "2026-03-01", "2026-06-01"],
["Previous Quarter", [2026, 8, 1], "2026-06-01", "2026-09-01"],
["Previous Quarter", [2026, 11, 1], "2026-09-01", "2026-12-01"],
["Current Fiscal Year", [2026, 0, 1], "2025-06-01", "2026-06-01"],
["Current Fiscal Year", [2026, 4, 31], "2025-06-01", "2026-06-01"],
["Current Fiscal Year", [2026, 5, 1], "2026-06-01", "2027-06-01"],
["Current Fiscal Year", [2027, 0, 1], "2026-06-01", "2027-06-01"],
["4 Rolling Quarters (CQ + 3)", [2026, 0, 1], "2025-12-01", "2026-12-01"],
["4 Rolling Quarters (CQ + 3)", [2026, 2, 1], "2026-03-01", "2027-03-01"],
["4 Rolling Quarters (CQ + 3)", [2026, 5, 1], "2026-06-01", "2027-06-01"],
["4 Rolling Quarters (CQ + 3)", [2026, 8, 16], "2026-09-01", "2027-09-01"],
["4 Rolling Quarters (CQ + 3)", [2026, 11, 1], "2026-12-01", "2027-12-01"]
];
for (const [period, date, start, end] of cases) {
assert.deepEqual(plain(h.getFiscalPeriodRange(period, new Date(...date))), {
startCloseDate: start + "T00:00:00.000Z", endCloseDate: end + "T00:00:00.000Z"
});
const dateRange = h.getFiscalPeriodRange(period, new Date(...date));
const criterion = h.createOpportunitiesQueryPayload(dateRange, 0).q.criteria[1];
assert.equal(criterion.value1, start + "T00:00:00.000+0000");
assert.equal(criterion.value2, end + "T00:00:00.000+0000");
assert.equal(dateRange.endCloseDate, end + "T00:00:00.000Z", "payload must not mutate the shared date range");
}
assert.equal(h.getFiscalPeriodRange("RENEWALS (Current + Past Due)", new Date()), null);
});
@@ -90,11 +116,11 @@ test("opportunity query preserves fields, record-set OR, date AND, and paginatio
assert.deepEqual(plain(h.createOpportunitiesQueryPayload(range, 15)), {
aggregationResults: false, applications: ["ORACLE-ISS-APP"], onlyData: true, entity: "Opportunity",
q: { op: "$and", criteria: [
{ op: "$or", criteria: types.map(value => ({ op: "$eq", attribute: "RecordSet", value })) },
{ op: "$wi", attribute: "EffectiveDate", value1: range.startCloseDate, value2: range.endCloseDate, dynamicDate: false }
{ op: "$and", criteria: [{ op: "$or", criteria: types.map(value => ({ op: "$eq", attribute: "RecordSet", value })) }] },
{ op: "$wi", attribute: "EffectiveDate", value1: "2026-06-01T00:00:00.000+0000", value2: "2026-09-01T00:00:00.000+0000" }
] }, keywords: null, keywordsFields: fields,
fields: [...fields, "CustomerAccount.PartyUniqueName", "CustomerAccount.PartyId", "CustomerAccount.PartyNumber", "CustomerAccount.Region_c", "CustomerAccount.PrimaryAddress.CountryCode"],
sort: [], language: "en", skipInValidFields: true, skipHiddenFromUIFields: true,
sort: null, language: "en", skipInValidFields: true, skipHiddenFromUIFields: true,
copiedFrom: "queries/d5dbcd01-9ff3-40be-9b5b-64744cbf7162", limit: 15, offset: 15
});
});
@@ -163,6 +189,15 @@ test("filter storage round-trip, WON_PENDING membership, search, sorting and ref
assert.equal(reopened.filters().ownerSearch, "");
});
test("removed renewals reference falls back to Current Quarter without losing other filters", () => {
const storage = new Map([[FILTER_KEY, JSON.stringify({ period: "RENEWALS (Current + Past Due)", tableSearch: "cloud" })]]);
const h = loadContent({ storage });
const select = { value: "Current Quarter" };
h.restoreFilterPreferences(select);
assert.equal(select.value, "Current Quarter");
assert.equal(h.filters().tableSearch, "cloud");
});
test("malformed preferences fall back, while theme/type/cache keys retain their schema", () => {
const storage = new Map([[FILTER_KEY, "not JSON"], ["opportunities-extension-theme", "dark"],
[TYPE_KEY, JSON.stringify({ opportunityTypeValues: ["ORA_MYOPTIES", "UNKNOWN"], opptyProductsCacheHours: 24 })]]);

View File

@@ -174,70 +174,28 @@ test("SR Save validates form, maps dynamic values, locks duplicate clicks and pr
assert.equal(posts[0].message.phase, "prepare");
const preparedPayload = { ...payload, ReportedByPartyId: 303, SvcQuestionsCollection_c: [],
SourceCd: "SVC_OPPORTUNITY_SCREEN", SeverityCd: "ORA_SVC_SEV2", ChannelTypeCd: "ORA_SVC_WEB" };
const preparedHeaders = {
requestSrTokenCatalog: { Authorization: "Session", "Content-Type": "application/vnd.oracle.adf.action+json", Origin: "https://example.invalid" },
requestPostServiceRequest: { Authorization: "Session", "Content-Type": "application/vnd.oracle.adf.action+json", Origin: "https://example.invalid", "vb-proxy-header-x-oracle-ectoken": "(generated after confirmation)" }
};
posts[0].resolve({ ok: true, payload: preparedPayload, headers: preparedHeaders });
posts[0].resolve({ ok: true, payload: preparedPayload });
await flush();
const confirmation = () => h.nodes().find(node => node.className?.includes("opportunities-extension-sr-confirmation"));
const confirm = () => h.nodes().find(node => node.textContent === "Confirm Save");
assert.ok(confirmation());
const preview = h.nodes().find(node => node.tag === "pre");
assert.equal(preview.textContent, JSON.stringify(preparedPayload, null, 2));
const previews = h.nodes().filter(node => node.tag === "pre");
assert.equal(previews.length, 3);
assert.equal(previews[1].textContent, JSON.stringify(preparedHeaders.requestSrTokenCatalog, null, 2));
assert.equal(previews[2].textContent, JSON.stringify(preparedHeaders.requestPostServiceRequest, null, 2));
assert.ok(h.nodes().some(node => node.textContent === "x-appbuilder-client-id: not yet observed in SeaaS requests."));
assert.equal(h.form().parent.inert, true);
assert.equal(h.document.activeElement.textContent, "Cancel");
assert.equal(h.nodes().some(node => node.className?.includes("opportunities-extension-sr-confirmation")), false);
assert.equal(h.nodes().some(node => node.textContent === "Confirm Save"), false);
assert.equal(posts.length, 2, "Save proceeds directly from preparation to creation");
assert.equal(posts[1].message.phase, "confirm");
assert.deepEqual(posts[1].message.payload, preparedPayload);
assert.equal(save().textContent, "Saving...");
await submit();
assert.equal(posts.length, 1, "awaiting confirmation must never send a creation request");
confirm().focus();
confirmation().listeners.keydown({ key: "Tab", preventDefault() {}, stopPropagation() {} });
assert.equal(h.document.activeElement, preview);
h.nodes().filter(node => node.tag === "button" && node.textContent === "Cancel").at(-1).click();
assert.equal(posts.length, 2, "duplicate clicks cannot create another SR");
posts[1].resolve({ ok: false, error: "Synthetic failure" });
await saving;
assert.equal(posts.length, 1, "Cancel never creates SR");
assert.equal(confirmation(), undefined);
assert.equal(h.form().parent.inert, false);
assert.equal(notices.at(-1)[1], true);
assert.equal(save().disabled, false);
assert.equal(h.nodes().find(node => node.name === "description").value, "Dynamic description");
assert.equal(h.document.activeElement, save());
assert.equal(notices.length, 0);
const escaped = submit();
posts[1].resolve({ ok: true, payload: preparedPayload });
await flush();
confirmation().listeners.keydown({ key: "Escape", preventDefault() {}, stopPropagation() {} });
await escaped;
assert.equal(posts.length, 2);
assert.ok(h.form(), "Escape closes only the confirmation");
const failed = submit();
posts[2].resolve({ ok: true, payload: preparedPayload });
await flush();
const confirmButton = confirm();
confirmButton.click();
confirmButton.click();
await flush();
assert.equal(posts.length, 4, "double confirm sends only one creation request");
assert.equal(posts[3].message.phase, "confirm");
assert.deepEqual(posts[3].message.payload, preparedPayload);
assert.equal(posts[3].message.payload.AccountPartyId, 100000000000101);
assert.equal(save().textContent, "Saving...");
posts[3].resolve({ ok: false, error: "Synthetic failure" });
await failed;
assert.equal(notices.at(-1)[1], true);
delete h.app.opportunitiesTableState.opportunitiesTableState.items[1].CustomerAccount.CountryCode;
const retry = submit();
assert.equal(posts[4].message.payload.CountryText_c, "BR", "uses PrimaryAddress.CountryCode even without CustomerAccount.CountryCode");
posts[4].resolve({ ok: true, payload: preparedPayload });
assert.equal(posts[2].message.payload.CountryText_c, "BR", "uses PrimaryAddress.CountryCode even without CustomerAccount.CountryCode");
posts[2].resolve({ ok: true, payload: preparedPayload });
await flush();
confirm().click();
await flush();
posts[5].resolve({ ok: true, result: { SrId: 505, SrNumber: "SR505" } });
assert.equal(posts.length, 4);
posts[3].resolve({ ok: true, result: { SrId: 505, SrNumber: "SR505" } });
await retry;
assert.equal(h.form(), undefined);
assert.equal(notices.at(-1)[0], "Service Request saved successfully.");