Loading
目录
选择筛选器

          没有结果
          没有结果
          以下是一些搜索提示

          检查关键字的拼写。
          使用更普遍的搜索词。
          选择更少的筛选器,并扩大搜索范围。

          搜索所有 Salesforce 帮助
          走访操作验证脚本示例

          走访操作验证脚本示例

          走访操作验证自定义脚本有助于确保用户在签署或提交走访之前遵循业务规则。这些脚本将在用户选择签名或提交操作时运行。

          所需的 Edition

          适用于:Lightning Experience
          适用于:具有Life Sciences Cloud、Life Sciences Cloud for Customer Engagement加载项许可证和Life Sciences Customer Engagement受管软件包的EnterpriseUnlimited Edition。

          要为访问操作验证创建自定义脚本,请创建具有以下属性的 Lightning 组件:

          • 对于脚本名称,使用 visitSampleScript
          • 对于组件名称,使用 Lightning 组件名称,例如 visitSampleScript
          • 对于类型,使用访问操作验证

          在 Lightning 组件的 JavaScript 文件中,稍后从示例添加自定义脚本,并将 Lightning 组件部署到贵组织。每当有人提交访问操作和签名时,自定义脚本都会执行验证。

          请注意:

          • 如果您曾经更改过 Lightning 组件并保存它,还要在自定义脚本中找到该记录并单击刷新。
          • 如果您创建多个将访问操作验证作为类型的脚本,则仅运行第一个版本的脚本。运行哪个脚本取决于 ID 或创建日期。
          • 相同的访问操作验证脚本在应用程序的移动和 Web 版本中运行,因此请确保在这两个版本中进行测试。

          示例脚本包含六个验证规则:

          验证规则
          规则 使用 触发器 错误消息 成功时
          atLeastOneSampleIsRequired 确保访问包含至少一个样本 (ProductDisbursement)。 始终运行 必须将至少一个样本添加到走访。 显示走访的样本数量
          atLeastOneDetailAndSampleAreRequired 需要示例和详细产品 (ProviderVisitProdDetailing)。 始终运行 走访中必须添加至少一个示例和详细产品。 显示样品和详细产品的计数
          atLeastOneMessageIsRequiredForEachVisitDetail 每个详细产品必须至少有一条消息。 仅在用户是现场销售代表(特定简档)或走访渠道设置为亲自走访时 在渠道为“亲自”且用户拥有“现场销售代表”简档时,每个详细产品至少需要一条消息 跳过其他简档或非亲自走访
          specificSampleDependencyCheck 如果选择了特定产品,规则会检查是否也选择了其他必需的产品。 当样本存在时 如果 <product1> 添加到走访,也必须添加 <product2> 不适用
          isAtLeastOneHCP HCO(医疗保健组织或机构)呼叫必须有至少一个 HCP(医疗保健提供商)客户出席者。 仅适用于机构客户 在为 HCO(医疗保健组织)创建走访时,必须关联至少一个 HCP(医疗保健专业人员)。 如果主要客户是个人客户 (HCP),则跳过
          isMoreThanOneHCO 限制为每个呼叫只有一个 HCO(机构)出席者。 始终运行 每次访问只能添加 1 个 HCO(医疗保健组织)出席者。 显示 HCO 客户计数

          示例脚本还包含三个可重复使用的帮助程序方法:

          • getActionName(env)
            • 从环境中获取操作名称
            • 确定是否运行验证
            • 仅对提交、签名和 runCustomScriptValidations 运行
          • parseContextData(record)
            • 安全地从记录对象中提取上下文数据
            • 处理 JSON 字符串和对象(包括代理对象)
            • 出错时返回空对象
          • getFieldData(contextData, baseFieldName)
            • 这是平台感知的字段数据检索方法。
            • 对于 Web,它使用 FieldName.VisitId (例如,ProductDisbursement.VisitId。)
            • 对于移动设备,它使用简单的字段名称(例如 ProductDisbursement。)
            • 该方法会自动尝试这两种方法,并返回任何存在的方法。

          在实施脚本时,需要注意并正确处理几个方面:

          • 在移动设备和 Web 上,当访问操作验证脚本运行时,访问的数据可能只在内存中,因此您需要通过处理 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];
           })();
           
          正在加载
          Salesforce Help | Article