Loading
範例:儲存、簽署和提交時驗證造訪資料

範例:儲存、簽署和提交時驗證造訪資料

此範例指令檔會在使用者儲存、簽署或提交造訪時驗證造訪資料。它會檢查必要的樣品、產品詳細資料、訊息、範例相依性和出席者帳戶類型。

必要版本

提供版本:Lightning Experience
提供版本:具有 Life Sciences Cloud、Life Sciences Cloud for Customer Engagement 附加元件授權和 Life Sciences Customer Engagement 受管理封裝的 EnterpriseUnlimited Edition。

驗證規則

範例指令檔包含六個驗證規則。

規則 使用 觸發 錯誤訊息 成功
atLeastOneSampleIsRequired 確保造訪包含至少一個範例 (ProductDisbursement)。 一律執行 至少一個範例必須新增至造訪。 顯示造訪的樣本數
atLeastOneDetailAndSampleAreRequired 需要範例與詳細產品 (ProviderVisitProdDetailing)。 一律執行 必須將至少一個範例和詳細產品新增至造訪。 顯示樣品與詳細產品的計數
atLeastOneMessageIsRequiredForEachVisitDetail 每個詳細產品必須至少有一則訊息。 僅當使用者擁有「現場銷售代表」設定檔,或當造訪管道設定為「面對面」時 當管道為「面對面」,且使用者有「現場銷售代表」設定檔時,每個詳細產品都需要至少一個訊息。 略過其他設定檔或非面對面造訪
specificSampleDependencyCheck 如果已選取特定產品,規則會檢查是否已選取其他必要產品。 當存在範例時 如果將 <product1> 新增至造訪,則必須也新增 <product2>
isAtLeastOneHCP HCO (醫療照護組織) 造訪必須有至少一個 HCP (醫療照護專業人員) 帳戶出席者。 僅限機構帳戶 建立 HCO (醫療照護組織) 的造訪時,必須與至少一個 HCP (醫療照護專家) 相關聯。 如果主要帳戶是個人帳戶 (HCP),則會略過
isMoreThanOneHCO 每通通電話僅限一個 HCO (機構) 出席者。 一律執行 每個造訪只能新增 1 個 HCO (醫療照護組織) 出席者。 顯示 HCO 帳戶計數

協助方法

範例指令檔也包含三個可重複使用的協助程式方法。

  • getActionName(env)
    • 從環境取得動作名稱
    • 決定是否執行驗證
    • 僅針對「提交」、「簽署」和「儲存」執行
  • parseContextData(record)
    • 安全地從記錄物件中提取內容資料
    • 處理 JSON 字串與物件 (包括 Proxy 物件)
    • 錯誤時傳回空白物件
  • getFieldData(contextData, baseFieldName)
    • 使用認知平台的回復式回復功能來取用欄位資料
    • 針對 Web,請使用 FieldName.VisitId (例如,ProductDisbursement.VisitId)
    • 針對行動裝置,請使用簡單的欄位名稱 (例如,ProductDisbursement)
    • 自動同時嘗試並傳回任何存在的項目

考量事項

實作造訪動作驗證指令檔時,請記住這些考量事項。

  • 在行動裝置和 Web 上,當執行造訪動作驗證指令檔時,造訪的資料只能在記憶體中。若要存取目前的資料,請在 contextData 中處理欄位。為造訪中未修改的資料保留 db.query,例如檢查帳戶的記錄類型。
  • 在行動裝置與 Web 之間傳送至自訂指令檔的內容資料可能不同。此範例指令檔使用常見的協助程式方法 getFieldData 來處理這些差異。

範例指令檔

(() => {

   // 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