Matching and Compliance Engine: Difference between revisions

Jump to navigation Jump to search
No edit summary
Line 44: Line 44:


== Compliance Rule Loaders ==
== Compliance Rule Loaders ==
You can create custom loaders and pass the loader data to compliance rules by specifying the loader name in the matchingLoaders array property
You can create custom loaders and pass the loader data to compliance rules by specifying the loader name in the matchingLoaders array property on the shift schedulable definition (both for the Scheduler and the Mobile App definition).


Example loader that loads certificates<syntaxhighlight lang="javascript">
Example loader that loads certificates.<syntaxhighlight lang="javascript">
{
{
     name: "certificates",
     name: "certificates",
Line 64: Line 64:




</syntaxhighlight>Note that for shift search in the mobile app, we recommend that you define a "candidateShifts" loader where you can pull the candidate's other shifts, so you can evaluate worktime regulations. E.g.:<syntaxhighlight lang="javascript">
{
    name: "candidateShifts",
    objectName: "b3s__Shift__c",
    filterRecordsBy: function () {
        return "b3s__Contact__c = {1}"
    },
    groupRecordsBy: "Id",
    fieldToLoad: ['b3s__Scheduled_Start_Time__c', 'b3s__Scheduled_End_Time__c', 'b3s__Contact__c'],
    filteringItems: function () {
        return [
            `'${contextRecordId}'`,
            `'${contactUserId}'`
        ]
    }
}
</syntaxhighlight>
</syntaxhighlight>


Line 89: Line 105:
== Complete Examples ==
== Complete Examples ==
<syntaxhighlight lang="javascript">
<syntaxhighlight lang="javascript">
//Example 1 - check for shift/availablility overlap
async ({ event, events, contactId, loaders, EvaluationResult }) => {
async ({ event, events, contactId, loaders, EvaluationResult }) => {
     if (!contactId) {
     if (!contactId) {
Line 95: Line 110:
         return [];
         return [];
     }
     }
     const unavailableRequests = events.filter(
 
        (e) =>
     const certificatesLoader = loaders.find((l) => l.name === 'certificates');
            e.extendedProps.b3s__Contact__c === contactId &&
    const contactCertificates = certificatesLoader?.loader?.grouped[contactId];
            e.title === 'request' &&
 
            e.extendedProps.b3s__Type__c === 'Unavailable',
    const certRequirementsLoader = loaders.find((l) => l.name === 'certRequirements');
    const certRequirements = certRequirementsLoader?.loader.records;
 
    const eventAccountId = event.extendedProps.record?.b3s__Job__r?.b3o__Client_Account__c;
    const eventJobTypeId = event.extendedProps.record?.b3s__Job__r?.b3o__Job_Type__c;
    const eventSiteId = event.extendedProps.record?.b3s__Site__c;
 
    const accountCertRequirements = certRequirements.filter(
        (req) => req.b3s__Account__c != null && req.b3s__Account__c === eventAccountId,
     );
     );
     const availableRequests = events.filter(
     const jobTypeCertRequirements = certRequirements.filter(
         (e) =>
         (req) => req.b3s__Job_Type__c != null && req.b3s__Job_Type__c === eventJobTypeId,
            e.extendedProps.b3s__Contact__c === contactId &&
    );
            e.title === 'request' &&
    const siteCertRequirements = certRequirements.filter(
            e.extendedProps.b3s__Type__c === 'Available',
        (req) => req.b3s__Site__c != null && req.b3s__Site__c === eventSiteId,
     );
     );


    const checkOverlap = (shift, requests) => {
        return requests.some((request) => {
            const shiftStart = new Date(shift.extendedProps.b3s__Scheduled_Start_Time__c).getTime();
            const shiftEnd = new Date(shift.extendedProps.b3s__Scheduled_End_Time__c).getTime();
            const requestStart = new Date(request.extendedProps.b3s__Start__c).getTime();
            const requestEnd = new Date(request.extendedProps.b3s__End__c).getTime();


            return (
    const getMissingCertificates = function (certificates = [], certRequirements = []) {
                //Request starts within shift
        const contactCertificateTypes = new Set(certificates.map((item) => item.b3o__Certificate_Type__c));
                (requestStart >= shiftStart && requestStart <= shiftEnd) ||
        const missingCerts = certRequirements.filter(
                //Request ends within shift
            (certReq) => !contactCertificateTypes.has(certReq.b3s__Certificate_Type__c),
                (requestEnd >= shiftStart && requestEnd <= shiftEnd) ||
        );
                //Request within shift times
 
                (requestStart >= shiftStart && requestEnd <= shiftEnd) ||
         return missingCerts;
                //Shift within request times
                (shiftStart >= requestStart && shiftEnd <= requestEnd)  
            )
         });
     };
     };


     if (checkOverlap(event, unavailableRequests)) {
     const getValidCertificates = function (certificates = [], certRequirements = []) {
         return [
        const contactCertificateTypes = new Set(certificates.map((item) => item.b3o__Certificate_Type__c));
        const missingCerts = certRequirements.filter((certReq) =>
            contactCertificateTypes.has(certReq.b3s__Certificate_Type__c),
        );
 
        return missingCerts;
    };
 
    const missingCerts = getMissingCertificates(contactCertificates, [...accountCertRequirements, ...jobTypeCertRequirements, ...siteCertRequirements]);
    let results = [];
    for (const missingCert of missingCerts) {
         results.push(
             new EvaluationResult({
             new EvaluationResult({
                 event: event,
                 event: event,
                 detail: '🏝️ Not available',
                 detail: `👎 Missing ${missingCert.b3s__Certificate_Type__r.Name}`,
                 category: 'Availability',
                 category: 'Certificates',
                 points: -50,
                 points: -50,
                isBlocking: true
             }),
             }),
         ];
         );
     } else if (checkOverlap(event, availableRequests)) {
     }
        return [
 
            new EvaluationResult({
    const validCerts = getValidCertificates(contactCertificates, [...accountCertRequirements, ...jobTypeCertRequirements, ...siteCertRequirements]);
                event: event,
     for (const validCert of validCerts) {
                detail: '💪 Asked for shifts',
         results.push(
                category: 'Availability',
                points: 10,
            }),
        ];
     } else {
         return [
             new EvaluationResult({
             new EvaluationResult({
                 event: event,
                 event: event,
                 detail: '😎 Available',
                 detail: `👍 Has ${validCert.b3s__Certificate_Type__r.Name}`,
                 category: 'Availability',
                 category: 'Certificates',
                 points: 10,
                 points: 25,
             }),
             }),
         ];
         );
     }
     }
}
    return results;
 
}]
 
</syntaxhighlight>
</syntaxhighlight>


Navigation menu