Fully implemented the Manage Time functionality

This commit is contained in:
2026-10-19 10:30:09 -03:00
parent d392e5ddd7
commit 9eef03e5f4
17 changed files with 1429 additions and 139 deletions

View File

@@ -34,7 +34,7 @@ function createTimeDates() {
}
// Source: src/services/fusion/time-entries.js
function createTimeEntries({ fetchJson, dates, getResourceUser }) {
function createTimeEntries({ fetchJson, dates, getResourceUser, saveHeaders }) {
const endpoint = "https://comcipapic-oalprod.integration.ocp.oraclecloud.com/ic/builder/rt/oalset_timeentrymobile/live;profile=PROD/services/auth/1.1/proxy/oke_seaas/uri/https/gxpap.oracle.com/oalcrm/service/set/seaas/mgmt/timeEntries/weekDetails";
async function requestWeekDetails(message) {
const debugRequests = [];
@@ -51,6 +51,61 @@ function createTimeEntries({ fetchJson, dates, getResourceUser }) {
throw error;
}
}
async function requestTimeEntries(message) {
const debugRequests = [];
try {
if (!/^\d+$/.test(String(message.weekId)) || Number(message.weekId) <= 0) throw new Error("Missing or invalid WeekId.");
const resource = await getResourceUser(message);
debugRequests.push(...resource.debugRequests);
const url = endpoint.replace(/\/weekDetails$/, "") + `?resourceId=${encodeURIComponent(resource.resource.resourcePartyId)}&sortBy=createdDate&sortOrder=ASC&weekId=${encodeURIComponent(message.weekId)}`;
const items = await fetchJson(url, { method: "GET", credentials: "include", headers: { Authorization: "Session", Accept: "application/json" } }, "requestTimeEntries", debugRequests);
if (!Array.isArray(items)) throw new Error("Invalid time entries response.");
return { ok: true, items, debugRequests };
} catch (error) { error.debugRequests = [...debugRequests, ...(error.debugRequests || [])]; throw error; }
}
async function requestTimeEntriesDelete(message) {
const debugRequests = [];
try {
if (!/^[1-9]\d*$/.test(String(message.id))) throw new Error("Missing or invalid time entry ID.");
const url = endpoint.replace(/\/weekDetails$/, "") + `/${message.id}`;
const headers = await saveHeaders(url);
const result = await fetchJson(url, { method: "DELETE", credentials: "include", headers },
"requestTimeEntriesDelete", debugRequests, { allowEmpty: true });
return { ok: true, result, debugRequests };
} catch (error) { error.debugRequests = debugRequests; throw error; }
}
async function requestTimeEntriesSave(message) {
const debugRequests = [];
try {
if (!["POST", "PUT"].includes(message.method)) throw new Error("Invalid time entry save method.");
if (!Array.isArray(message.items) || !message.items.length) throw new Error("No time entries to save.");
const hours = ["saturdayTaskHours", "sundayTaskHours", "mondayTaskHours", "tuesdayTaskHours", "wednesdayTaskHours", "thursdayTaskHours", "fridayTaskHours"];
for (const item of message.items) {
if (!item || !["SERVICE", "NON_SERVICE"].includes(item.tag) || !item.taskType
|| (item.tag === "SERVICE" && (!/^SR\d+$/.test(item.srNumber) || !item.activityCode))
|| !/^\d+$/.test(String(item.weekId)) || Number(item.weekId) <= 0
|| hours.some(key => typeof item[key] !== "number" || !Number.isFinite(item[key]) || item[key] < 0 || item[key] > 24)
|| !hours.some(key => item[key] > 0)
|| (message.method === "PUT" && (!/^\d+$/.test(String(item.id)) || Number(item.id) <= 0))) throw new Error("Invalid or incomplete time entry.");
}
const resource = await getResourceUser(message);
debugRequests.push(...resource.debugRequests);
const items = message.items.map(item => ({
...(message.method === "PUT" ? { id: item.id } : {}),
srNumber: item.tag === "NON_SERVICE" ? "non-service-sr" : item.srNumber,
activityCode: item.activityCode || "", taskType: item.taskType, deliveryType: item.deliveryType || "",
...Object.fromEntries(hours.map(key => [key, item[key]])), notes: String(item.notes || ""),
weekId: item.weekId, resourceId: String(resource.resource.resourcePartyId), tag: item.tag
}));
const url = endpoint.replace(/\/weekDetails$/, "");
const headers = await saveHeaders(url);
const result = await fetchJson(url, {
method: message.method, credentials: "include",
headers, body: JSON.stringify(items)
}, "requestTimeEntriesSave", debugRequests, { allowEmpty: true });
return { ok: true, result, debugRequests };
} catch (error) { error.debugRequests = [...debugRequests, ...(error.debugRequests || [])]; throw error; }
}
async function requestActiveServiceRequests(message) {
const debugRequests = [];
try {
@@ -95,7 +150,7 @@ function createTimeEntries({ fetchJson, dates, getResourceUser }) {
return { ok: true, items: result.items, debugRequests };
} catch (error) { error.debugRequests = debugRequests; throw error; }
}
return { requestWeekDetails, requestActiveServiceRequests, requestServiceRequestExpand, requestLookupTaskType };
return { requestWeekDetails, requestTimeEntries, requestTimeEntriesSave, requestTimeEntriesDelete, requestActiveServiceRequests, requestServiceRequestExpand, requestLookupTaskType };
}
// Source: src/shared/messages.js
@@ -103,6 +158,9 @@ function createTimeEntries({ fetchJson, dates, getResourceUser }) {
function createMessageTypes() {
return Object.freeze({
requestWeekDetails: "opportunitiesExtension.requestWeekDetails",
requestTimeEntries: "opportunitiesExtension.requestTimeEntries",
requestTimeEntriesSave: "opportunitiesExtension.requestTimeEntriesSave",
requestTimeEntriesDelete: "opportunitiesExtension.requestTimeEntriesDelete",
requestLookupTaskType: "opportunitiesExtension.requestLookupTaskType",
requestServiceRequestExpand: "opportunitiesExtension.requestServiceRequestExpand",
requestResourceUser: "opportunitiesExtension.requestResourceUser",
@@ -518,7 +576,7 @@ function createSrRequestHeaders({ runtimeApi }) {
function observeHeaders(rawUrl, headers) {
let url;
try { url = new URL(rawUrl); } catch { return; }
if (url.origin !== origin || !/^\/ic\/builder\/rt\/(?:SeaaS_initiation|SeaaS_SR_management)\/live(?:\/|;)/.test(url.pathname)) return;
if (url.origin !== origin || !/^\/ic\/builder\/rt\/(?:SeaaS_initiation|SeaaS_SR_management|oalset_timeentrymobile)\/live(?:\/|;)/.test(url.pathname)) return;
// Read only the client ID, not cookies, bodies or authorization.
const value = typeof headers?.get === "function" ? headers.get("x-appbuilder-client-id")
: headers?.find(item => item.name.toLowerCase() === "x-appbuilder-client-id")?.value;
@@ -531,7 +589,7 @@ function createSrRequestHeaders({ runtimeApi }) {
function start() {
if (started || !runtimeApi?.webRequest) return;
started = true;
const filter = { urls: [`${origin}${appPath}*`, `${origin}/ic/builder/rt/SeaaS_SR_management/*`], types: ["xmlhttprequest"] };
const filter = { urls: [`${origin}${appPath}*`, `${origin}/ic/builder/rt/SeaaS_SR_management/*`, `${origin}/ic/builder/rt/oalset_timeentrymobile/*`], types: ["xmlhttprequest"] };
runtimeApi.webRequest.onBeforeSendHeaders?.addListener(details => {
if (details.tabId >= 0 && !details.incognito) observeHeaders(details.url, details.requestHeaders);
}, filter, ["requestHeaders"]);
@@ -608,7 +666,29 @@ function createSrRequestHeaders({ runtimeApi }) {
});
return headers;
}
return { start, tokenHeaders, previewTokenHeaders, internalServiceRequestHeaders, observeResponseHeaders: observeHeaders };
async function timeEntriesHeaders(url) {
const target = new URL(url);
const path = "/ic/builder/rt/oalset_timeentrymobile/live;profile=PROD/services/auth/1.1/proxy/oke_seaas/uri/https/gxpap.oracle.com/oalcrm/service/set/seaas/mgmt/timeEntries";
const isDelete = target.pathname.startsWith(`${path}/`) && /^[1-9]\d*$/.test(target.pathname.slice(path.length + 1));
if (target.origin !== origin || (target.pathname !== path && !isDelete) || target.search || target.hash) {
throw new Error("Unexpected time entries URL.");
}
if (!runtimeApi?.declarativeNetRequest?.updateSessionRules || !runtimeApi.runtime?.getURL) {
throw new Error("Reload the extension to enable the time entry header permissions.");
}
const escapedUrl = target.href.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
await runtimeApi.declarativeNetRequest.updateSessionRules({
removeRuleIds: [isDelete ? 7205 : 7204],
addRules: [{
id: isDelete ? 7205 : 7204, priority: 1,
action: { type: "modifyHeaders", requestHeaders: [{ header: "Origin", operation: "set", value: target.origin }] },
condition: { regexFilter: `^${escapedUrl}$`, initiatorDomains: [new URL(runtimeApi.runtime.getURL("/")).hostname],
requestMethods: isDelete ? ["delete"] : ["post", "put"], resourceTypes: ["xmlhttprequest"] }
}]
});
return sessionHeaders(target.origin, "application/json");
}
return { start, tokenHeaders, previewTokenHeaders, internalServiceRequestHeaders, timeEntriesHeaders, observeResponseHeaders: observeHeaders };
}
// Source: src/services/spa/accounts.js
@@ -2425,6 +2505,9 @@ function startBackgroundApplication(dependencies) {
function registerBackgroundMessages({
messageTypes,
requestWeekDetails,
requestTimeEntries,
requestTimeEntriesSave,
requestTimeEntriesDelete,
requestLookupTaskType,
requestServiceRequestExpand,
requestResourceUser,
@@ -2468,8 +2551,18 @@ runtimeApi.runtime.onMessage.addListener((message, sender, sendResponse) => {
error: error.message, httpStatus: error.httpStatus, debugRequests: error.debugRequests || [] }));
return true;
}
if ([messageTypes.requestServiceRequestExpand, messageTypes.requestWeekDetails, messageTypes.requestResourceUser, messageTypes.requestActiveServiceRequests].includes(message.type)) {
const request = message.type === messageTypes.requestServiceRequestExpand ? requestServiceRequestExpand : message.type === messageTypes.requestWeekDetails ? requestWeekDetails : message.type === messageTypes.requestResourceUser ? requestResourceUser : requestActiveServiceRequests;
if (message.type === messageTypes.requestTimeEntriesSave) {
requestTimeEntriesSave(message).then(sendResponse).catch(error => sendResponse({ ok: false,
error: error.message, httpStatus: error.httpStatus, debugRequests: error.debugRequests || [] }));
return true;
}
if (message.type === messageTypes.requestTimeEntriesDelete) {
requestTimeEntriesDelete(message).then(sendResponse).catch(error => sendResponse({ ok: false,
error: error.message, httpStatus: error.httpStatus, debugRequests: error.debugRequests || [] }));
return true;
}
if ([messageTypes.requestTimeEntries, messageTypes.requestServiceRequestExpand, messageTypes.requestWeekDetails, messageTypes.requestResourceUser, messageTypes.requestActiveServiceRequests].includes(message.type)) {
const request = message.type === messageTypes.requestTimeEntries ? requestTimeEntries : message.type === messageTypes.requestServiceRequestExpand ? requestServiceRequestExpand : message.type === messageTypes.requestWeekDetails ? requestWeekDetails : message.type === messageTypes.requestResourceUser ? requestResourceUser : requestActiveServiceRequests;
request(message).then(sendResponse).catch(error => sendResponse({ ok: false,
error: error.message, httpStatus: error.httpStatus, debugRequests: error.debugRequests || [] }));
return true;
@@ -2675,7 +2768,7 @@ function createBackgroundApplication(overrides = {}) {
...overrides
};
const messageTypes = createMessageTypes();
const timeEntries = createTimeEntries({ dates: createTimeDates(), fetchJson: (...args) => httpBackgroundJson.fetchJson(...args), getResourceUser: message => fusionSrCreate.getResourceUser(message) });
const timeEntries = createTimeEntries({ dates: createTimeDates(), fetchJson: (...args) => httpBackgroundJson.fetchJson(...args), getResourceUser: message => fusionSrCreate.getResourceUser(message), saveHeaders: url => srRequestHeaders.timeEntriesHeaders(url) });
const spaCurrentUser = createSpaCurrentUser({ fetchJson: (...args) => httpBackgroundJson.fetchJson(...args) });
const requestCurrentUser = () => spaCurrentUser.requestCurrentUser();
const contracts = createBackgroundContracts();
@@ -2816,6 +2909,9 @@ function createBackgroundApplication(overrides = {}) {
startBackgroundApplication({
invalidateResourceUser: () => fusionSrCreate.invalidateResourceUser(),
requestWeekDetails: message => timeEntries.requestWeekDetails(message),
requestTimeEntries: message => timeEntries.requestTimeEntries(message),
requestTimeEntriesSave: message => timeEntries.requestTimeEntriesSave(message),
requestTimeEntriesDelete: message => timeEntries.requestTimeEntriesDelete(message),
requestLookupTaskType: () => timeEntries.requestLookupTaskType(),
requestServiceRequestExpand: message => timeEntries.requestServiceRequestExpand(message),
requestResourceUser: message => fusionSrCreate.getResourceUser(message),

File diff suppressed because one or more lines are too long