Esimerkki vierailutoimintojen vahvistuskomentosarjasta
Vierailutoimintojen vahvistuksen mukautetut komentosarjat auttavat varmistamaan, että käyttäjät noudattavat liiketoimintasääntöjä ennen vierailun allekirjoittamista tai lähettämistä. Nämä komentosarjat suoritetaan, kun käyttäjä valitsee Allekirjoita- tai Lähetä-toiminnon.
Vaaditut versiot
| Käytettävissä: Lightning Experiencessa |
| Käytettävissä: Enterprise Edition- ja Unlimited Edition -versioissa Life Sciences Cloudilla, Life Sciences Cloud for Customer Engagement -lisäosalisenssillä ja Life Sciences Customer Engagement -hallitulla paketilla. |
Jos haluat luoda mukautetun komentosarjan vierailutoimintojen vahvistukselle, luo Lightning näillä attribuuteilla:
- Syötä komentosarjan nimeksi visitSampleScript.
- Käytä komponentin nimeksi Lightning-komponentin nimeä, esimerkiksi visitSampleScript.
- Käytä Tyyppi-kenttään Visit-toiminnon vahvistus.
Lisää Lightning JavaScript-tiedostoon esimerkin mukautettu komentosarja myöhemmin ja ota Lightning käyttöön organisaatiossasi. Mukautettu komentosarja suorittaa vahvistuksen joka kerta, kun joku lähettää vierailutoiminnon ja kun se on allekirjoitettu.
Ota huomioon:
- Jos koskaan muutat Lightning ja tallennat sen, etsi myös tietue Mukautetut komentosarjat -osiosta ja napsauta Päivitä.
- Jos luot useamman kuin yhden komentosarjan, jonka tyyppi on Vierailutoiminto-vahvistus, vain komentosarjan ensimmäinen versio suoritetaan. Suoritettava komentosarja perustuu tunnukseen tai luontipäivään.
- Sama Vierailutoiminto-vahvistuskomentosarja suoritetaan sovelluksen mobiili- ja verkkoversioissa, joten muista testata molempia.
Esimerkkiskripti sisältää kuusi vahvistussääntöä:
| Sääntö | Käyttö | Käynnistimet | Virheviesti | Onnistui |
|---|---|---|---|---|
| atLeastOneSampleIsRequired | Varmistaa, että vierailu sisältää vähintään yhden näytteen (ProductDisbursement). | Suoritetaan aina | Vierailuun täytyy lisätä vähintään yksi näyte. | Näyttää vierailun näytteen määrän |
| atLeastOneDetailAndSampleAreRequired | Vaatii sekä näytteen että yksityiskohtaisen tuotteen (ProviderVisitProdDetailing). | Suoritetaan aina | Vierailuun täytyy lisätä vähintään yksi esimerkki ja yksityiskohtainen tuote. | Näyttää näytteiden ja yksityiskohtaisten tuotteiden määrät |
| atLeastOneMessageIsRequiredForEachVisitDetail | Jokaisella yksityiskohtaisella tuotteella täytyy olla vähintään yksi viesti. | Vain, kun käyttäjä on Field Sales Representative (tietty profiili) tai kun Vierailukanava-asetukseksi on määritetty Henkilökohtainen. | Jokaiselle yksityiskohtaiselle tuotteelle vaaditaan vähintään yksi viesti, kun kanava on 'In-Person' ja käyttäjällä on 'Field Sales Representative' -profiili | Ohittaa muut profiilit tai muut kuin henkilökohtaiset vierailut |
| specificSampleDependencyCheck | Jos tietty tuote on valittuna, sääntö tarkastaa, onko myös muita vaadittuja tuotteita valittu. | Kun esimerkkejä on olemassa | Jos vierailuun lisätään <product1>, myös <product2> täytyy lisätä. | N/A |
| isAtLeastOneHCP | HCO (terveydenhuollon organisaatio tai laitos) -kutsujen täytyy sisältää vähintään yksi HCP (terveydenhuollon tarjoaja) -tilin osallistuja. | Vain toimialueen tileille | Vähintään yhden HCP-palvelun (Healthcare Professional) täytyy liittyä, kun luot vierailua HCO:lle (Healthcare Organization). | Ohittaa, jos ensisijainen tili on henkilötili (HCP) |
| isMoreThanOneHCO | Rajoittaa vain yhteen HCO-organisaation (institution) osallistujaksi per puhelu. | Suoritetaan aina | Vain 1 HCO (Healthcare Organization) -osallistuja voidaan lisätä per vierailu. | Näyttää HCO-tilien määrän |
Esimerkkikomentosarja sisältää myös kolme uudelleenkäytettävää avustaja-metodia:
- getActionName(env)
- Hakee toiminnon nimen ympäristöstä
- Määrittää, tulisiko vahvistus suorittaa
- Suoritetaan vain Submit-, Sign- ja runCustomScriptValidations-objekteille
- parseContextData(record)
- Kerää asiayhteystiedot tietueobjektista turvallisesti
- Käsittelee JSON-merkkijonot ja objektit (mukaan lukien välityspalvelinobjektit)
- Palauttaa tyhjän objektin virheestä
- getFieldData(contextData, baseFieldName)
- Tämä on sovellusalustan tietoinen kenttien datan noutomenetelmä.
- Verkkosivulla se käyttää kentanNimi.VisitId -arvoa (esimerkiksi
ProductDisbursement.VisitId). - Mobiilisovelluksessa se käyttää yksinkertaista kentän nimeä (esimerkiksi
ProductDisbursement). - Metodi yrittää automaattisesti molempia ja palauttaa molempia.
Kun toteutat komentosarjaa, sinun tulisi ottaa huomioon ja käsitellä asianmukaisesti useita seikkoja:
- Kun Vierailutoimintojen vahvistus -komentosarja suoritetaan mobiililaitteilla ja verkossa, vierailun tiedot saattavat olla vain muistissa, joten sinun täytyy käyttää tämänhetkistä dataa käsittelemällä kentät contextData-kentässä. Voit käyttää db.query-kyselyä, mutta se tulisi varata tiedoille, joita ei muokata vierailussa, esimerkiksi tilin tietuetyypin tarkastamiseksi.
- Mukautettuun komentosarjaan välitetyssä kontekstidatassa saattaa olla eroavaisuuksia mobiililaitteiden ja verkkojen välillä. Esimerkki mukautetusta komentosarjasta käsittelee nämä eroavaisuudet käyttämällä yleistä avustajan menetelmää
getFieldData.
(() => {
// Note: Old data extraction functions removed - no longer needed
// since businessRuleValidator provides clean, structured parameters
// Platform detection flag - will be set in entry point
let hasWebField = false;
/**
* Gets the action name from environment
* @param {Object} env - JsEnv object for environment options
* @returns {string} The action name or empty string
*/
function getActionName(env) {
try {
if (env && typeof env.getOption === 'function') {
return env.getOption('actionName') || '';
}
return '';
} catch (error) {
return '';
}
}
// AccountDAO - Data Access Object for account-related operations
var accountDao = (function () {
var instance;
var currentRecord;
var isPersonAccount;
var isInstitution;
var childCallAccounts;
var accountCache = new Map(); // Simple cache for account data
// Helper functions for accountDao - moved inside closure to access currentRecord
async function checkForPersonAccount() {
let accountId = currentRecord.stringValue("AccountId");
// If not found, try extracting directly from context data
if (!accountId) {
try {
const contextData = parseContextData(currentRecord);
// Try different possible locations for AccountId
accountId = contextData.ProviderVisit?.AccountId ||
contextData.Visit?.AccountId ||
contextData.AccountId ||
contextData.Account?.Id ||
contextData.Account;
} catch (e) {
// Error accessing context data, continue with null accountId
accountId = null;
}
}
if (!accountId) {
return false; // Default to not person account if no account ID
}
try {
let account = await selectAccountById(accountId);
let result = account && account.length > 0 ? account[0].boolValue("IsPersonAccount") : false;
return result;
} catch (error) {
return false; // Default to false on error
}
}
async function checkForInstitution() {
let accountId = currentRecord.stringValue("AccountId");
// If not found, try extracting directly from context data
if (!accountId) {
try {
const contextData = parseContextData(currentRecord);
// Try different possible locations for AccountId
accountId = contextData.ProviderVisit?.AccountId ||
contextData.Visit?.AccountId||
contextData.AccountId ||
contextData.Account?.Id ||
contextData.Account;
} catch (e) {
// Error accessing context data, continue with null accountId
}
}
if (!accountId) {
return false; // Default to not institution if no account ID
}
try {
let account = await selectAccountById(accountId);
let isPersonAccount = account && account.length > 0 ? account[0].boolValue("IsPersonAccount") : false;
// If it's not a Person Account, then it's a Business Account (HCO)
let result = !isPersonAccount;
return result;
} catch (error) {
return false; // Default to false on error
}
}
async function selectChildCallAccountsById() {
// Extract attendee account IDs directly from the JSON data
// The attendee data is in the Visit.ParentVisitId array in the JSON
// Get the data from the current record context
let contextData;
try {
contextData = parseContextData(currentRecord);
} catch (error) {
return [];
}
// Extract attendee account IDs from Visit.ParentVisitId array
const attendeeVisits = contextData?.["Visit.ParentVisitId"] || contextData?.["ChildVisit"];
if (!Array.isArray(attendeeVisits) || attendeeVisits.length === 0) {
return [];
}
// Extract AccountIds from the attendee visits
const attendeeAccountIds = attendeeVisits
.map(visit => visit.AccountId || visit.accountid) // Try PascalCase first, then lowercase
.filter(accountId => accountId);
if (attendeeAccountIds.length === 0) {
return [];
}
// Query the Account records for these IDs
let result = await db.query(
"Account",
await new ConditionBuilder(
"Account",
new SetCondition("Id", "IN", attendeeAccountIds)
).build(),
["Id", "Name", "IsPersonAccount"]
);
return result || [];
}
function getRecordId(record) {
return record ? record.stringValue("Id") : null;
}
async function selectAccountById(accountId) {
// Check cache first
if (accountCache.has(accountId)) {
return accountCache.get(accountId);
}
// If not in cache, query database
let accounts = await db.query(
"Account",
await new ConditionBuilder(
"Account",
new FieldCondition("Id", "=", accountId)
).build(),
["Id", "Name", "IsPersonAccount"]
);
// Cache the result
accountCache.set(accountId, accounts);
return accounts;
}
var initialize = async function(record) {
currentRecord = record;
// Clear cache for new record context
accountCache.clear();
isPersonAccount = await checkForPersonAccount();
isInstitution = await checkForInstitution();
childCallAccounts = await selectChildCallAccountsById();
};
var getIsPersonAccount = function () {
return isPersonAccount;
};
var getIsInstitution = function () {
return isInstitution;
};
var getChildCallAccounts = function () {
return childCallAccounts;
};
var createInstance = function () {
return {
initialize: initialize,
getIsPersonAccount: getIsPersonAccount,
getChildCallAccounts: getChildCallAccounts,
getIsInstitution: getIsInstitution,
};
};
return {
getInstance: function () {
return instance || (instance = createInstance());
},
};
})();
// Main function that businessRuleValidator calls
async function validateVisit() {
try {
if (!record) {
return [{
title: "Error in validation",
status: "error",
error: "No record provided"
}];
}
// Use the properly provided business rule parameters from outer scope
const validationResults = await runValidation();
// Platform-specific handling: Web uses Promise.all(), Mobile doesn't
let resolvedResults;
if (hasWebField) {
// Web platform - use Promise.all() to resolve all promises
resolvedResults = await Promise.all(validationResults);
} else {
// Mobile platform - use validationResults directly
resolvedResults = validationResults;
}
// Ensure we always return an array
const finalResults = Array.isArray(resolvedResults) ? resolvedResults : [resolvedResults];
return finalResults;
} catch (error) {
return [{
title: "Error in validation",
status: "error",
error: error.message
}];
}
}
// Function to run the validation with data from outer scope
async function runValidation() {
// Initialize accountDao with the proper record object (JsDbObject)
await accountDao.getInstance().initialize(record);
// Always validate in this version
const isValidationRequired = true;
// Only run validations if needed
let validationResults = [];
if (isValidationRequired) {
// Array of validation functions to run
// Add new validation functions to this array
const validationFunctions = [
atLeastOneSampleIsRequired,
atLeastOneDetailAndSampleAreRequired,
atLeastOneMessageIsRequiredForEachVisitDetail,
specificSampleDependencyCheck,
isAtLeastOneHCP,
isMoreThanOneHCO,
// Add new validation functions here one at a time
// Example: validateSampleType,
// Example: validateComplianceAgreement,
];
// Run all validation functions (handling both sync and async)
validationResults = validationFunctions.map((validationFn, index) => {
try {
// Call validation functions - they access record, user, db, env from outer scope
const result = validationFn();
// If the result is a Promise, return it as is for Promise.all
if (result && typeof result.then === 'function') {
return result.then(asyncResult => {
return asyncResult;
}).catch(error => {
return {
title: `Error in ${validationFn.name}: ${error.message}`,
status: "error",
error: error.message
};
});
}
return result;
} catch (error) {
return {
title: `Error in ${validationFn.name}: ${error.message}`,
status: "error",
error: error.message
};
}
});
} else {
// Default return when validation is not required
validationResults = [{
title: "Validation not required",
status: "success"
}];
}
return validationResults;
}
// Helper function to get context data safely
function parseContextData(record) {
try {
if (!record || typeof record.getContextData !== 'function') {
return {};
}
const contextData = record.getContextData();
// Handle different return types from getContextData()
if (typeof contextData === 'string') {
// If it's a JSON string, parse it
return JSON.parse(contextData);
} else if (typeof contextData === 'object' && contextData !== null) {
// If it's already an object (including Proxy), use it directly
return contextData;
} else {
return {};
}
} catch (error) {
return {};
}
}
/**
* Helper function to get field data with web/mobile fallback
* Web uses nested field paths (e.g., "ObjectName.VisitId")
* Mobile uses simple field names (e.g., "ObjectName")
*
* @param {Object} contextData - The context data object
* @param {string} baseFieldName - The base field name (e.g., "ProductDisbursement")
* @returns {*} The field data from web or mobile field, or undefined
*/
function getFieldData(contextData, baseFieldName) {
const webField = `${baseFieldName}.VisitId`;
const mobileField = baseFieldName;
return contextData?.[webField] || contextData?.[mobileField];
}
// Validation rule: at least one sample is required
function atLeastOneSampleIsRequired() {
let hasSamples = false;
let sampleCount = 0;
try {
// Get context data from the record object (from outer scope)
const contextData = parseContextData(record);
// Use helper to get field data with web/mobile fallback
const sampleData = getFieldData(contextData, "ProductDisbursement") || null;
// Handle Proxy arrays properly
if (sampleData) {
try {
// Try to get length property (works for both arrays and Proxy arrays)
sampleCount = sampleData.length || 0;
hasSamples = sampleCount > 0;
} catch (lengthError) {
// Handle Proxy length access error
// Fallback: check if object has any enumerable properties
try {
const keys = Object.keys(sampleData);
hasSamples = keys.length > 0;
sampleCount = keys.length;
} catch (keysError) {
// Error getting keys - graceful fallback
hasSamples = false;
sampleCount = 0;
}
}
}
} catch (e) {
// Handle validation error gracefully
hasSamples = false;
sampleCount = 0;
}
return {
title: hasSamples ?
`Found ${sampleCount} sample(s)` :
"At least one sample must be added to the visit.",
status: hasSamples ? "success" : "error"
};
}
// Validation rule: at least one detail and sample are required
function atLeastOneDetailAndSampleAreRequired() {
try {
const contextData = parseContextData(record);
// Use helper to get field data with web/mobile fallback
const productDisbursementData = getFieldData(contextData, "ProductDisbursement");
const providerVisitProdDetailingData = getFieldData(contextData, "ProviderVisitProdDetailing");
// Handle Proxy arrays properly for both fields
let sampleCount = 0;
let detailCount = 0;
let hasProductDisbursement = false;
let hasProviderVisitProdDetailing = false;
// Check product disbursement
if (productDisbursementData) {
try {
sampleCount = productDisbursementData.length || 0;
hasProductDisbursement = sampleCount > 0;
} catch (e) {
// Error accessing length property
const keys = Object.keys(productDisbursementData || {});
sampleCount = keys.length;
hasProductDisbursement = sampleCount > 0;
}
}
// Check provider visit prod detailing
if (providerVisitProdDetailingData) {
try {
detailCount = providerVisitProdDetailingData.length || 0;
hasProviderVisitProdDetailing = detailCount > 0;
} catch (e) {
// Error accessing length property - try Object.keys fallback
const keys = Object.keys(providerVisitProdDetailingData);
detailCount = keys.length;
hasProviderVisitProdDetailing = detailCount > 0;
}
}
if (hasProductDisbursement && hasProviderVisitProdDetailing) {
return {
title: `Found ${sampleCount} sample(s) and ${detailCount} detailed product(s)`,
status: "success"
};
}
return {
title: "At least one sample and detailed product must be added to the visit.",
status: "error"
};
} catch (e) {
return {
title: "At least one sample and detailed product must be added to the visit.",
status: "error",
error: e.message
};
}
}
// Validation rule: at least one message is required for each visit detail
async function atLeastOneMessageIsRequiredForEachVisitDetail() {
try {
let userId;
// Try to get user Id from user (from outer scope)
if (user) {
try {
userId = user.stringValue('Id');
} catch (error) {
// Try alternative access methods
userId = user.Id || user["Id"];
if (!userId) {
// Error accessing userId, continue with fallback
}
}
}
if (!userId) {
return {
title: 'Profile validation skipped - no userId available',
status: "success",
};
}
// As rep user don't have access to User.ProfileId and Profile table
// we can only check by ProfileIdentifier in UserAdditionalInfo
//Please replace the Profile_Id with the id of the profile you want to apply this validation
let targetProfileId = 'Profile_Id';
let userAdditionalInfoResults;
let isFieldSalesRep = false;
try {
userAdditionalInfoResults = await db.query(
"UserAdditionalInfo",
await new ConditionBuilder(
"UserAdditionalInfo",
new FieldCondition("UserId", "=", userId)
).build(),
["Id", "ProfileIdentifier"]
);
if (userAdditionalInfoResults && userAdditionalInfoResults.length > 0) {
const profileId = userAdditionalInfoResults[0].stringValue('ProfileIdentifier');
isFieldSalesRep = profileId === targetProfileId;
} else {
return {
title: 'Profile validation skipped - profile not found',
status: "success",
};
}
} catch (error) {
return {
title: 'Profile validation skipped - unable to query profile',
status: "success",
};
}
if (!isFieldSalesRep) {
return {
title: `Profile validation skipped - user is not Field Sales Representative`,
status: "success",
};
}
// Get visit context data from the record object
const visitData = parseContextData(record);
// Check if channel is "In-Person"
const visitChannel = visitData?.Visit?.channel || visitData?.ProviderVisit?.Channel || '';
if (visitChannel !== "In-Person") {
return {
title: `Message validation skipped - visit channel is "${visitChannel}", not "In-Person"`,
status: "success",
};
}
// Check if we have visit details to validate
// Use helper to get field data with web/mobile fallback
const visitDetails = getFieldData(visitData, "ProviderVisitProdDetailing");
if (!Array.isArray(visitDetails) || visitDetails.length === 0) {
return {
title: 'Message validation passed - no visit details to validate',
status: "success"
};
}
// Validate each visit detail has at least one message
let detailsWithoutMessages = [];
visitDetails.forEach((detail, index) => {
// Use helper to get field data with web/mobile fallback
const messages = getFieldData(detail, "ProviderVisitDtlProductMsg");
const hasMessages = Array.isArray(messages) && messages.length > 0;
if (!hasMessages) {
const detailInfo = {
index: index + 1,
productId: detail?.productid || 'Unknown Product',
uid: detail?.uid || 'Unknown Detail'
};
detailsWithoutMessages.push(detailInfo);
}
});
// Set validation result
if (detailsWithoutMessages.length > 0) {
return {
title: "At least one message is required for each detailed product when the channel is 'In-Person' and the user has a 'Field Sales Representative' profile.",
status: "error"
};
} else {
return {
title: `All ${visitDetails.length} detailed products have messages - Field Sales Rep In-Person validation passed`,
status: "success"
};
}
} catch (error) {
return {
title: "At least one message is required for each detailed product when the channel is 'In-Person' and the user has a 'Field Sales Representative' profile.",
status: "error",
error: error.message
};
}
}
/**
* The rule 'specificSampleDependencyCheck' blocks the user from submitting a visit.
* Validation: If sample "Immunexis 10mg" is selected,
* then "ADRAVIL Sample Pack 5mg" must also be selected.
* @returns result { title: string, status: "success" | "error" };
*/
async function specificSampleDependencyCheck() {
try {
// Get visit context data from record (from outer scope)
let visitData = parseContextData(record);
// Check if we have samples to validate
// Use helper to get field data with web/mobile fallback
let samples = getFieldData(visitData, "ProductDisbursement");
// Handle Proxy arrays properly
let samplesCount = 0;
let isValidSamples = false;
if (samples) {
try {
samplesCount = samples.length || 0;
isValidSamples = samplesCount > 0;
} catch (e) {
// Error accessing samples length
const keys = Object.keys(samples || {});
samplesCount = keys.length;
isValidSamples = samplesCount > 0;
}
}
if (!isValidSamples) {
return {
title: 'Sample dependency validation passed - no samples to validate',
status: "success"
};
}
// Get all product item IDs from samples
let productItemIds = [];
try {
if (samples && typeof samples === 'object') {
// Handle both array and Proxy array
for (let i = 0; i < samplesCount; i++) {
try {
const sample = samples[i];
if (sample) {
const productItemId = sample.ProductItemId || sample.productitemid
productItemIds.push(productItemId);
}
} catch (sampleError) {
// Error accessing sample
}
}
}
} catch (mappingError) {
// Error mapping product item IDs
}
if (productItemIds.length === 0) {
return {
title: 'Sample dependency validation passed - no product item IDs found',
status: "success"
};
}
// Query ProductItem to get Product2Id
let productItems = await db.query(
"ProductItem",
await new ConditionBuilder(
"ProductItem",
new SetCondition("Id", "IN", productItemIds)
).build(),
["Id", "Product2Id"]
);
// Extract Product2Ids and create a map of productItemId to Product2Id
let product2Ids = [];
let productItemToProduct2Map = new Map();
if (productItems && Array.isArray(productItems)) {
productItems.forEach(item => {
const productItemId = item.stringValue("Id");
const product2Id = item.stringValue("Product2Id");
if (product2Id) {
product2Ids.push(product2Id);
productItemToProduct2Map.set(productItemId, product2Id);
}
});
}
// Query Product2 to get product names
let product2Items = await db.query(
"Product2",
await new ConditionBuilder(
"Product2",
new SetCondition("Id", "IN", product2Ids)
).build(),
["Id", "Name"]
);
// Create a map of Product2Id to Name
let product2NameMap = new Map();
if (product2Items && Array.isArray(product2Items)) {
product2Items.forEach(item => {
const id = item.stringValue("Id");
const name = item.stringValue("Name");
product2NameMap.set(id, name);
});
}
// Create a map of productItemId to ProductName (via Product2)
let itemToProductNameMap = new Map();
productItemToProduct2Map.forEach((product2Id, productItemId) => {
const productName = product2NameMap.get(product2Id);
if (productName) {
itemToProductNameMap.set(productItemId, productName);
}
});
// Get all sample names for the current visit
let sampleNames = [];
try {
if (samples && typeof samples === 'object') {
// Handle both array and Proxy array for sample names
for (let i = 0; i < samplesCount; i++) {
try {
const sample = samples[i];
if (sample) {
const productItemId = sample.ProductItemId || sample.productitemid;
const productName = itemToProductNameMap.get(productItemId) || '';
if (productName) {
sampleNames.push(productName);
}
}
} catch (sampleError) {
// Error accessing sample for name mapping
}
}
}
} catch (nameMappingError) {
// Error mapping sample names
}
// Check if Immunexis 10mg is present
const targetSample = "Immunexis 10mg";
const requiredSample = "ADRAVIL Sample Pack 5mg";
let hasImmunexis = sampleNames.includes(targetSample);
if (hasImmunexis) {
// If Immunexis is present, check if ADRAVIL is also present
let hasAdravil = sampleNames.includes(requiredSample);
if (!hasAdravil) {
return {
title: "If Immunexis 10mg is added to a visit, ADRAVIL Sample Pack 5mg must also be added. However, ADRAVIL Sample Pack 5mg can be added without Immunexis 10mg.",
status: "error"
};
} else {
return {
title: "Sample dependency validation passed - both Immunexis 10mg and ADRAVIL Sample Pack 5mg present",
status: "success"
};
}
} else {
return {
title: "Sample dependency validation passed - no Immunexis 10mg found",
status: "success"
};
}
} catch (error) {
// Error in specificSampleDependencyCheck
// In case of database error, we might want to pass validation or handle differently
// For now, we'll pass the validation to avoid blocking the user due to technical issues
return {
title: "Sample dependency validation passed - technical error occurred",
status: "success"
};
}
}
/**
* The rule 'isAtLeastOneHCP' blocks the user from submitting a call.
* Validation: Require at least one HCP (Person Account) for a HCO (Institution Account) call on Submit.
* @returns result { title: string, status: "success" | "error" };
*/
async function isAtLeastOneHCP() {
try {
// Log current account details from record (from outer scope)
const currentAccountId = record.stringValue("AccountId");
// Use accountDao to check if current account is a Person Account
let isPersonAccount = await accountDao.getInstance().getIsPersonAccount();
let isInstitution = await accountDao.getInstance().getIsInstitution();
if (isPersonAccount) {
// This is already a Person Account (HCP), so requirement is met
return {
title: "HCP validation passed - current account is a Person Account (HCP)",
status: "success"
};
}
// Only apply HCP validation to Institution accounts
if (!isInstitution) {
return {
title: "HCP validation skipped - account is not an Institution Account",
status: "success"
};
}
// This is an Institution Account, check for HCP attendees
let childCallAccounts = await accountDao.getInstance().getChildCallAccounts();
let isRequirementValid = false;
let hcpAttendees = [];
let nonHcpAttendees = [];
if (Array.isArray(childCallAccounts) && childCallAccounts.length > 0) {
// Check if any attendee is a Person Account (HCP)
for (let i = 0; i < childCallAccounts.length; i++) {
let attendee = childCallAccounts[i];
let attendeeIsPersonAccount = attendee.boolValue("IsPersonAccount");
let attendeeName = attendee.stringValue("Name") || attendee.stringValue("Id");
if (attendeeIsPersonAccount) {
isRequirementValid = true;
hcpAttendees.push(attendeeName);
} else {
nonHcpAttendees.push(attendeeName);
}
}
}
// Add more descriptive message based on the scenario
if (!isRequirementValid) {
return {
title: "At least one HCP (Healthcare Professional) must be associated when creating a visit for an HCO (Healthcare Organization).",
status: "error"
};
} else {
return {
title: `HCP validation passed - Institution Account with ${hcpAttendees.length} HCP attendee(s): ${hcpAttendees.join(', ')}`,
status: "success"
};
}
} catch (error) {
// Error in isAtLeastOneHCP
// In case of error, fail the validation to be safe
return {
title: "HCP validation failed - error occurred during validation",
status: "error",
error: error.message
};
}
}
/**
* The rule 'isMoreThanOneHCO' blocks user from submitting a call.
* Validation: Restrict to one HCO (Institution Account) attendee per Call.
* @returns result { title: string, status: "success" | "error" };
* Note: Expected only 1 HCO attendee per call)
*/
async function isMoreThanOneHCO() {
try {
let counter = 0;
let isPersonAccount = await accountDao.getInstance().getIsPersonAccount();
let accsRelatedToChildCall = await accountDao.getInstance().getChildCallAccounts();
let hcoAccounts = [];
let hcpAccounts = [];
if (isPersonAccount || accsRelatedToChildCall.length) {
for (let i = 0; i < accsRelatedToChildCall.length; i++) {
let relatedAcc = accsRelatedToChildCall[i];
let attendeeIsPersonAccount = relatedAcc.boolValue("IsPersonAccount");
let attendeeName = relatedAcc.stringValue("Name") || relatedAcc.stringValue("Id");
if (!attendeeIsPersonAccount) {
counter++;
hcoAccounts.push(attendeeName);
} else {
hcpAccounts.push(attendeeName);
}
}
} else {
counter++;
}
const isValid = counter <= 1;
if (!isValid) {
return {
title: "Only 1 HCO (Healthcare Organization) attendee can be added per visit.",
status: "error"
};
} else {
return {
title: `HCO count validation passed - found ${counter} HCO account(s)`,
status: "success"
};
}
} catch (error) {
// Error in isMoreThanOneHCO
return {
title: "HCO count validation failed - error occurred during validation",
status: "error",
error: error.message
};
}
}
// Entry point: Check if proper parameters are provided
if (record && user && env && db) {
// Check action name - only run validation for specific actions
const actionName = getActionName(env);
const allowedActions = ['Submit', 'Sign', 'runCustomScriptValidations'];
if (!allowedActions.includes(actionName)) {
// Skip validation for other actions
return [{
title: `Validation skipped - action is "${actionName}"`,
status: 'success'
}];
}
// Platform detection: Check which field structure exists to determine wrapping behavior
const contextData = parseContextData(record);
// Web uses nested field paths (e.g., "ProviderVisit")
// Mobile uses simple field names (e.g., "Visit")
hasWebField = contextData?.["ProviderVisit"] !== undefined;
// Wrap in array if using web platform, don't wrap for mobile
const wrapAsArray = hasWebField;
// Apply wrapping based on platform
if (wrapAsArray) {
// Web solution - wrap in array
return [validateVisit()];
} else {
// Mobile solution - no wrapping
return validateVisit();
}
}
// If no parameters provided yet, return validation function wrapped in array
return [validateVisit];
})();

