訪問アクション検証スクリプトの例
訪問アクション検証カスタムスクリプトは、ユーザーが訪問に署名または送信する前にビジネスルールに準拠するのに役立ちます。これらのスクリプトは、ユーザーが [署名] または [送信] アクションを選択したときに実行されます。
必要なエディション
| 使用可能なインターフェース: Lightning Experience |
| 使用可能なエディション: Life Sciences Cloud、Life Sciences Cloud for Customer Engagementアドオン ライセンス、Life Sciences Customer Engagement管理パッケージが付属するEnterprise EditionおよびUnlimited Edition。 |
Visit Action Validation のカスタムスクリプトを作成するには、次の属性を使用して Lightning コンポーネントを作成します。
- [スクリプト名] に visitSampleScript を使用します。
- コンポーネント名には、Lightning コンポーネント名を使用します (例: visitSampleScript)。
- [種別] に [Visit Action Validation (訪問アクション検証)] を使用します。
Lightning コンポーネントの JavaScript ファイルで、後で例のカスタムスクリプトを追加して、Lightning コンポーネントを組織にリリースします。カスタムスクリプトは、訪問アクションが送信されたときと署名されたときに検証を実行します。
次の点に注意してください。
- Lightning コンポーネントを変更して保存する場合は、[カスタムスクリプト] でもレコードを見つけて [更新] をクリックします。
- 訪問アクション検証を種別として複数のスクリプトを作成した場合、スクリプトの最初のバージョンのみが実行されます。ID または作成日に基づいて実行されるスクリプト。
- 同じ訪問アクション検証スクリプトがモバイルバージョンと Web バージョンのアプリケーションで実行されるため、必ず両方でテストしてください。
このサンプルスクリプトには、6 つの入力規則が含まれています。
| ルール | 使途 | トリガー | Error Message | 成功時 |
|---|---|---|---|---|
| atLeastOneSampleIsRequired | 訪問に少なくとも 1 つのサンプルが含まれていることを確認します (ProductDisbursement)。 | 常に実行 | 訪問に少なくとも 1 つのサンプルを追加する必要があります。 | 訪問のサンプル数を表示します。 |
| atLeastOneDetailAndSampleAreRequired | サンプル商品と詳細商品 (ProviderVisitProdDetailing) の両方が必要です。 | 常に実行 | 訪問には少なくとも 1 つのサンプルと詳細な商品を追加する必要があります。 | サンプルと詳細な商品の両方の数を表示します。 |
| atLeastOneMessageIsRequiredForEachVisitDetail | 各詳細商品には少なくとも 1 つのメッセージが必要です。 | ユーザーがフィールド営業担当者 (特定のプロファイル) であるか、訪問チャネルが対面に設定されている場合のみ | チャネルが「対面」で、ユーザーに「フィールド営業担当者」プロファイルがある場合、詳細な商品ごとに少なくとも 1 つのメッセージが必要です。 | 他のプロファイルまたは対面以外の訪問をスキップする |
| specificSampleDependencyCheck | 特定の商品が選択されている場合、ルールは他の必須商品も選択されているかどうかを確認します。 | サンプルが存在する場合 | <product1> が訪問に追加されている場合、<product2> も追加する必要があります。 | なし |
| isAtLeastOneHCP | HCO (ヘルスケア組織または機関) コールには、少なくとも 1 人の HCP (ヘルスケア提供者) 取引先出席者が必要です。 | 機関取引先のみ | HCO (ヘルスケア組織) の訪問を作成する場合、少なくとも 1 人の HCP (ヘルスケア従事者) を関連付ける必要があります。 | 主取引先が個人取引先 (HCP) の場合はスキップ |
| isMoreThanOneHCO | 通話ごとに 1 人の HCO (機関) 出席者のみに制限されます。 | 常に実行 | 訪問ごとに 1 人の HCO (医療機関) 被招集者のみを追加できます。 | HCO 取引先の数を表示します。 |
このサンプルスクリプトには、次の 3 つの再利用可能なヘルパーメソッドも含まれます。
- getActionName(env)
- 環境からアクション名を取得する
- 検証を実行するかどうかを決定する
- Submit、Sign、および runCustomScriptValidations でのみ実行されます。
- parseContextData(record)
- レコードオブジェクトからコンテキストデータを安全に抽出する
- JSON 文字列とオブジェクト (プロキシオブジェクトを含む) の両方を処理する
- エラー時に空のオブジェクトを返します。
- getFieldData(contextData, baseFieldName)
- これは、プラットフォーム対応の項目データ取得方法です。
- Web の場合、FieldName.VisitId (
ProductDisbursement.VisitIdなど) を使用します。 - モバイルの場合は、単純なフィールド名を使用します (
ProductDisbursementなど)。 - このメソッドは自動的に両方を試して、存在する方を返します。
スクリプトを実装するときには、次の点に留意して適切に処理する必要があります。
- モバイルおよび Web では、Visit Action Validation スクリプトを実行すると Visit のデータがメモリ内にのみ存在する可能性があるため、contextData の項目を処理することで現在のデータにアクセスする必要があります。db.query を使用できますが、これは取引先のレコードタイプの確認など、訪問で変更されていないデータ用に予約する必要があります。
- カスタムスクリプトに渡されるコンテキストデータは、モバイルと Web で異なる場合があります。カスタムスクリプトの例では、一般的なヘルパーメソッド
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];
})();
この記事で問題は解決されましたか?
ご意見をお待ちしております。

