Implemented the Organization Chart feature to create customer organizational

This commit is contained in:
2026-10-26 18:15:32 -03:00
parent bf48ed1dcb
commit 3229ce9c41
41 changed files with 4099 additions and 63 deletions

View File

@@ -0,0 +1,114 @@
const { test } = require("node:test");
const assert = require("node:assert/strict");
const fs = require("node:fs");
const vm = require("node:vm");
const factory = new vm.Script(fs.readFileSync("src/services/fusion/account-contact.js", "utf8") + "\ncreateFusionAccountContact").runInNewContext();
function setup(response) {
const calls=[], logs=[], failures=[], window={}; let tokens=0;
const service=factory({ environment:{window, async fetch(url,options) {
assert.equal(this,window,"Native fetch must use Window as its receiver");
calls.push({url,options}); return response instanceof Response ? response : Response.json(response);
}}, getToken:async()=>{tokens++;return "test-relay-token";},logRequestStart:entry=>{logs.push(entry);return entry;},
logRequestSuccess:()=>{}, logRequestFailure:(_,error)=>failures.push(error),readResponseBodyForDebug:response=>response.clone().text() });
return {service,calls,logs,failures,tokens:()=>tokens};
}
test("contact opportunities uses the person's PartyNumber, paging and contact-query headers",async()=>{
const h=setup({items:[{OptyNumber:"A56DLD"}],hasMore:true,totalResults:12});
const data=await h.service.requestOpportunitiesByContact("18725565",5);
assert.equal(data.items[0].OptyNumber,"A56DLD");
const {url,options}=h.calls[0], parsed=new URL(url);
assert.equal(parsed.searchParams.get("q"),"(OpportunityContact.ContactPartyNumber='18725565')");
assert.equal(parsed.searchParams.get("offset"),"5");assert.equal(parsed.searchParams.get("limit"),"5");
assert.equal(options.method,"GET");assert.equal(options.headers.Authorization,"Bearer test-relay-token");
assert.equal(options.headers.Preference,"transient");assert.equal(options.headers.Origin,"https://eeho.fa.us2.oraclecloud.com");
assert.equal(h.logs[0].name,"requestOpportunitiesByContact");
assert.equal(h.logs[0].url,url);
assert.ok(url.includes("q=%28OpportunityContact.ContactPartyNumber%3D%2718725565%27%29"));
await assert.rejects(h.service.requestOpportunitiesByContact(undefined),/PartyNumber/);
assert.equal(h.calls.length,1);
});
test("contact identity lookup uses exact PartyId without name keyword ranking",async()=>{
const h=setup({items:[{PartyId:"123",PartyName:"New Name",PartyNumber:"789"}]});
const contacts=await h.service.requestQueryAccountContact(456,"Old Name",()=>true,"123");
const body=JSON.parse(h.calls[0].options.body);
assert.equal(body.keywords,"");assert.deepEqual(body.q.criteria.at(-1),{attribute:"PartyId",op:"$eq",value:"123"});
assert.equal(contacts[0].PartyNumber,"789");
});
test("contact opportunity query preserves an alphanumeric PartyNumber",async()=>{
const h=setup({items:[],hasMore:false});
await h.service.requestOpportunitiesByContact("A4KCS2P");
assert.equal(new URL(h.calls[0].url).searchParams.get("q"),"(OpportunityContact.ContactPartyNumber='A4KCS2P')");
assert.equal(h.logs[0].metadata.partyNumber,"A4KCS2P");
});
test("opportunities resolution failures are logged before any HTTP request",async()=>{
const h=setup({items:[]});
await assert.rejects(h.service.requestOpportunitiesByContact(null,0,{contactPartyId:"123",contactName:"Alex",resolvePartyNumber:async()=>undefined}),/HTTP request was not sent/);
assert.equal(h.calls.length,0);assert.equal(h.tokens(),0);
assert.equal(h.logs[0].name,"resolveContactForOpportunities");assert.equal(h.logs[0].method,"LOCAL");assert.equal(h.logs[0].metadata.httpRequestSent,false);
assert.equal(h.logs[0].metadata.contactPartyId,"123");assert.equal(h.failures.length,1);
await assert.rejects(h.service.requestOpportunitiesByContact(null,0,{resolvePartyNumber:async()=>{throw new Error("Contact query failed");}}),/Contact query failed/);
assert.equal(h.failures.length,2);
});
test("contact search posts the specified payload with dynamic PartyId, keywords and Token Relay bearer token",async()=>{
const h=setup({items:[{PartyId:123,PartyName:"Same Name",JobTitle:"Architect"},{PartyId:456,PartyName:"Same Name",JobTitle:null}]});
const result=await h.service.requestQueryAccountContact("100000810210835",'Alex "Cloud"');
assert.equal(result.length,2); assert.equal(result[0].PartyId,"123");
const {url,options}=h.calls[0]; assert.ok(url.endsWith("/crmRestApi/searchResources/latest/custom-actions/queries"));
assert.equal(options.method,"POST");assert.equal(options.headers.Authorization,"Bearer test-relay-token");
assert.equal(options.headers["Content-Type"],"application/json");assert.equal(options.credentials,"include");
assert.equal(options.headers.Preference,"transient");
assert.equal(options.headers.Origin,"https://eeho.fa.us2.oraclecloud.com");
const fields=["PartyName","PrimaryEmail.EmailAddress","JobTitle","PrimaryAccount.PartyName","PartyId","PartyNumber"];
assert.deepEqual(JSON.parse(options.body),{aggregationResults:false,skipInValidFields:true,skipHiddenFromUIFields:true,language:"en",onlyData:true,
keywords:'Alex "Cloud"',entity:"Contact",fields,keywordsFields:fields,
q:{op:"$and",criteria:[{op:"$or",innerQuery:true,criteria:[{attribute:"PrimaryAccount.PartyId",op:"$eq",value:100000810210835}]}],innerQuery:true},
sort:[{attribute:"_score",direction:"descending"}],limit:15,offset:0});
assert.equal(h.logs[0].name,"requestQueryAccountContact");assert.equal(h.calls.length,1);
});
test("contact search skips blank or obsolete queries and never sends a query without an account",async()=>{
const h=setup({items:[]});
await h.service.requestQueryAccountContact(123," ");
await h.service.requestQueryAccountContact(123,"Alex",()=>false);
await assert.rejects(h.service.requestQueryAccountContact(undefined,"Alex"),/PartyId/);
assert.equal(h.calls.length,0);assert.equal(h.tokens(),0);
});
test("contact search reports HTTP and malformed-response errors",async()=>{
for(const response of [new Response("Denied",{status:401}),{items:null}]){
const h=setup(response);await assert.rejects(h.service.requestQueryAccountContact(123,"Alex"));
assert.equal(h.calls.length,1);assert.equal(h.failures.length,1);
}
});
test("contact creation maps account, phone and mutually exclusive contact preferences", async()=>{
for (const preference of ["none", "deny", "allow"]) {
const h=setup({PartyId:321});
await h.service.requestPersistContact({PartyId:123,PartyNumber:"456"},{firstName:" Alex ",lastName:" Test ",jobTitle:"Engineer",email:"alex@example.test",
emailConsent:preference,workConsent:preference,mobileConsent:preference,workPhone:"3131313131",workCountry:"55",workExtension:"3131",mobilePhone:"31999999999",mobileCountry:"55"});
const {url,options}=h.calls[0], payload=JSON.parse(options.body);
assert.ok(url.endsWith("/contacts?onlyData=true")); assert.equal(options.method,"POST");
assert.equal(options.headers.Authorization,"Bearer test-relay-token");assert.equal(options.headers.Origin,"https://eeho.fa.us2.oraclecloud.com");
assert.equal(payload.AccountPartyId,"123");assert.equal(payload.AccountPartyNumber,"456");assert.equal(payload.FirstName,"Alex");
assert.equal(payload.RawWorkPhoneNumber,"55-3131313131");assert.equal(payload.WorkPhoneExtension,"3131");assert.equal(payload.RawMobileNumber,"55-31999999999");
for(const key of ["DoCallWorkPhoneFlag","DoCallMobilePhoneFlag","DoContactEmailFlag"]) assert.equal(payload[key],preference==="none"?undefined:preference==="allow");
for(const key of ["DoNotCallWorkPhoneFlag","DoNotCallMobileFlag","DoNotContactEmailFlag"]) assert.equal(payload[key],preference==="none"?undefined:preference==="deny");
assert.equal(h.logs[0].name,"requestPersistContact");
}
});
test("contact creation reports failed POST without automatic retries",async()=>{
const h=setup(new Response("Denied",{status:403}));
await assert.rejects(h.service.requestPersistContact({PartyId:123,PartyNumber:"456"},{firstName:"Alex",lastName:"Test",jobTitle:"",email:"alex@example.test",workPhone:"",workCountry:"",workExtension:"",mobilePhone:"",mobileCountry:""}),/403/);
assert.equal(h.calls.length,1);assert.equal(h.failures.length,1);
});
test("contact creation sends directly and omits empty optional fields", async()=>{
const values={firstName:"Alex",lastName:"Test",jobTitle:"",email:"alex@example.test",workPhone:"",workCountry:"",workExtension:"",mobilePhone:"",mobileCountry:""};
const h=setup({PartyId:321});
await h.service.requestPersistContact({PartyId:123,PartyNumber:"456"},values);
assert.equal(h.calls.length,1);assert.equal(h.tokens(),1);
assert.deepEqual(JSON.parse(h.calls[0].options.body),{FirstName:"Alex",LastName:"Test",AccountPartyId:"123",AccountPartyNumber:"456",Type:"ZCA_CONTACT",EmailAddress:"alex@example.test"});
});