WFM Changelog v4.X
Jump to navigation
Jump to search
V4.3
- Additional fixes for timezone related issues applied
- Shift Bulk Creator - fixed incorrect handling of times
- Sync Mechanism - added various enhancements to improve the Sync Mechanism in the scheduler
- Filters - the scheduler could be loaded with incorrect filters due to rendering the filters with all loaded events instead of all visible events in the active horizon dates
V4.2
- Applied various fixes for timezone related issues throughout the shift scheduler
V4.1
- Fixed erroneous handling of timezonePreference = "user"
- Changed the way shifts are displayed - we will now use the shift/request target timezone times when rendering shifts on the scheduler. Previously, we were using the user's local/sf timezone.
Fixes
- All Apps: Proper timezone handling and display throughout the applications. Read the article here for more details:
- Scheduler: Tested with 1000 candidates, 7 days, 1x shift per candidate per day. We advise against showing high 100s of candidates and to instead use Groups/parent object contexts to limit the data loaded as loading so much data becomes a limitation on the hardware - something beyound our control.
- Scheduler: Applying filters is much faster now
- Scheduler: Optimized the code responsible for rendering shift poppers (e.g. details, alerts, icon tooltips)
- All Apps: Select fields weren't opening on some mobile devices
- App: Fixed a bug where selecting on one shift and straight again on another shift could lose context
- App: Applied fixes to the maps component that relate to obtaining the user's location. We now continually update the location as the user moves and update their marker on the map. Previously their marker could become "stuck" in one location.
- App: hideIfNull could be ignored in invitations and shift job boards. That's fixed now
- All: Fixed a UI defect where "This is require" wasn't displayed on select fields
- All: Fixed a bug within the time range selector which could cause records to be created with incorrect times
- Client Scheduler & Scheduler: Shift Bulk Creator now is mobile friendly
- TS Submission: Improved weekly view UI
- Scheduler: applied improvements to the selection lasso
- Scheduler: fixed a bug with the lookBackExtendDays and lookForwardExtendDays where the defined integer wasn't applying correctly. Use case: cross week shifts (i.e. one that starts on Sunday and ends on Monday) should be visible and loaded. To do this, define lookBackExtendDays and lookForwardExtendDays limit to "1" to load one extra day before and after the currently loaded days
- All: Improved the UI where createAction is defined on a relationship field. Previously, we would show a popover over the shift. Now we display it in a sub-modal.
- Scheduler: Fixed various issues with the popover rendering. So, sometimes the context menu would be rendered in a location that's not where the user clicked or the shift details would be rendered out of screen
- Scheduler: DEV-732 - the "can" permissions (e.g. canEdit, canDelete, canCut, canCancel) weren't respected for bulk shift record edits. Now, if any 1 record in a bulk selection fails the "can" permissions, the option will be hidden from the context menu
- Scheduler: If an event failed ot insert (due to a trigger failing or a custom validation) previously the scheduler would not handle the server error correctly
- All: DEV-830 - drop downs on iOS devices weren't rendered
- Scheduler: Fixed a bug that selects all events behind a "more" link where we have eventMaxStack defined.
- Scheduler: Improved the UI responsible for rendering events hidden behind the "more" link when we have eventMaxStack defined. Now we display all events in a scrollable row.
- Scheduler: Fixed edge cases where command keys would become unresponsive (e.g. CTRL/CMD + C/V/X)
- All Apps: fixed issues relating to timezones where the week navigation component could render incorrect records lists
- DEV-642 - fixed a sticky header in Shifts/Availability mobile app views
- DEV-732 - context menu could have different options at times in the scheduler
- DEV-722 - scheduler could lose context
- DEV-776 - After delete trigger errors not handled correctly
- DEV-791 - issue with stacked shifts on the scheduler
- DEV-804 - geo tags could be created without latitude and longitude in the mobile app
- DEV-816 - a limit was hit when inserting/deleting over 200 records in the scheduler
- DEV-827 - overnight shifts not showing
- DEV-838 - bad mobile ui of shift invitations in the mobile app
- All: Time Range - clearing the date will clear the field even if only start / end is populated. Previously, if we only had one time, clearing the times/date would not clear the field state
- App: Fixed an issue where the global app flags canAddAvailability and canAddShifts were not applying correctly to block the user from adding ad-hock shifts or availability
- DEV-833 & DEV-820 - Pasting events in the scheduler with timezonePreference set to "client" could result in the event being pasted in the wrong day cell
Changes
- Scheduler: Major under the hood changes to support large schedule data sets. Note that loading 1000s of candidates & shifts can still be slow, however the bottleneck is the actual device and not the code. Try to limit group sizes.
- App: Added GPS accuracy wording in UI
- All Apps: Modals and popups UI improvement added
- App: added a label for the button "Claim Shift". You can change the label using the claimOpenShiftLabel property
- Client Scheduler: Improved mobile experience
- TS Submission: Improved mobile experience
- TS Approval: Improved mobile experience
- TS Submission: Split shifts from expenses view
- TS Approval: Split shifts from expenses view
- All: Caching of metadata is now set to 120 minutes. It was set to a week previously. This will clear metadata cache quicker so admin changes will reflect quicker too.
- DEV-737 - added "invite" button at the tom of the shift matching pop-up
- DEV-705 - added 12/24 hr time display
- Scheduler: Now, when a user changes the view or navigates between days/weeks, we will clear any applied filters before re-rendering the scheduler. This change is necessary because it is possible to apply a filter with options generated from shifts in the current view and then when a user navigates to a different week, the events might not generate the same filter options and thus we might end up with hidden events. Impact: filters need to be applied on week navigation, date jump or view change
- Scheduler: Deprecated access to the colorSchema variable in the filters API. You can no longer use the colorSchema to apply a color to the filter item checkbox. Remove any references in your custom defined filters
- App: Deprecated "Expense" quick add option. Use the new Expenses tab.
New
- All Apps: Added the ability to define if times are displayed on a 24hr clock or a 12hr clock using the new global boolean property is24Hour on the definition metadata
- All Apps: Added the ability to define a location based timezone assignment. Using a new property path
siteTimezoneFieldPathyou can now define the field responsible for applying a time offset to a given record. For shifts, when a Site record is linked and the Site has a timezone assignment, we will show two sets of times on the scheduler: the top one is the time in the current user location (or user preference, dependent on the timezonePreference setting) and the second line displays the time in the shift's site timezone. Similarly, for employee requests, we will use the Contact timezone to display the time off request in the current user's timezone as well as in the contact's assigned timezone.- "shift" schedulable use
"siteTimezoneFieldPath": "b3s__Site__r.b3s__Site_Timezone__c". - "invitation" schedulable use
"siteTimezoneFieldPath": "b3s__Shift__r.b3s__Site__r.b3s__Site_Timezone__c" - "request" schedulable use
"siteTimezoneFieldPath": "b3s__Contact__r.b3s__Timezone__c"
- "shift" schedulable use
- Scheduler Matching Engine: now, compliance rules can have an "isBlocking" boolean flag. When isBlocking is defined on the EvaluationResult, the scheduler will not allow the match result from being selected/invited/assigned. Likewise, if isBlocking is defined on an EvaluationResult on a rule designated for the mobile app, the match result won't even be shown altogether. Check out the Matching and Compliance Engine article for more details.
- Scheduler Matching Engine: We now have a new schedulable item "invitation" that needs to be included in Scheduling Context Provider. Check out the Matching and Compliance Engine article for more details.
- Scheduler Matching Engine: We now have support for filtering of matching results. Check out the Matching and Compliance Engine article for more details.
- Scheduler Matching Engine: We now have the ability to define minimum selection threshold.
- Scheduler Matching Engine: We now have the ability to define minimum assignment threshold.
- Scheduler Matching Engine: We now have the ability to cancel an invitation. The invitation status will be updated to "Revoked"
- Scheduler Matching Engine: We now have the ability to define additional fields from the contact (in candidates search) or the shift (in shifts search)
- Scheduler Matching Engine: We now have the ability to limit when a result can be: Selected, Assigned and when an invitation can be Selected, Assigned or Cancelled. This uses selectors to determine when that action is allowed.
- Scheduler Matching Engine: Improved UI/UX of the matching results display
- Scheduler: Added support for Tooltip Related Objects in the shift poppers
- All Apps: Added support for Related Objects within a form component
- TS Submission, TS Approval, Client Scheduler, Clock app: added support for a header bar which displays the user's name and allows them to select a theme. You can control whether you want the header bar displayed by changing the attributes hide-theme-switch="false" and hide-current-user="false"
- TS Submission, TS Approval, Client Scheduler, Clock app: added support for 9 new themes.
- TS Submission, TS Approval, Client Scheduler, Clock app: refreshed UI & UX
- All Apps: Added full support for multi-select fields
- App: Refreshed UI and UX with a new glassy design
- App: Added a new view that allows you to display submitted expenses. You can disable this using the new global option canSeeExpenses
- App: Added support for related lists display in: Invitations, Shift Search, Expenses view
- Client Scheduler: Added support for related lists entry and display on records
- TS Submission: Added support for related lists entry and display on records
- TS Approval: Added support for related lists entry and display on records
- Client Scheduler: Added support for defining shift filters
- TS Submission: Added support for defining shift and expense filters
- TS Approval: Added support for defining shift and expense filters
- App: Added filters to Invitations, Shifts Search and Expenses
- App: You can now disable Shift Job Boards (search tab) using the global option canSearchShifts
- App: You can now prevent users from starting ad-hoc shifts using the global option canStartAdhocShifts
- App: You can now prevent users from starting any shifts using the global option canClockInOut - this will hide the clock in/out button altogether
- All Apps: You can now define a new global option firstDay - this is an integer and allows you to set the first day of the week for all weekly headers. Set this to "0" for Sunda-Saturday week duration
- Client Scheduler & Scheduler: Added support for defining fields to be displayed in the Shift Bulk Creator using the array fields property shiftBulkViewTitle. You can now even use relationship fields, as long as these fields are loaded through the data loaders.
- Client Scheduler, TS Submission and TS Approval: added day, week and month navigation support
- App: Added day week and month expense navigation support
- All: Added a new "hidden" boolean property to form field definitions. Fields with this property will be hidden from the layout, but still updated/pre-populated. Use case: In TS Submission, when a candidate wants to submit an expense/shift, we would previously display the Contact field (usually disabled so the user wouldn't change it). Now, you can still default the value of that field and also hide it for better user experience.
- All: Added a new " prePopulate" boolean property to form field definitions. Fields with this property set to false will not pre-populate with the value of the contextual record. Use cases: in the mobile app, you can Accept/Reject shift invitations. If you define this property with "false" and then apply "hidden" to hide the acceptance field and set it in the background. This will provide better UI for candidates. Same applies for accepting/rejecting shifts in the TS Approval or for submitting week for approval in the TS Submission component. Quick example for the mobile app configuration:
"recordFormFieldsAcceptance": [
{
"field": "b3s__Status__c",
"props": {
"hideNoneOption": true
},
"type": "field-select",
"hidden": true,
"required": true,
"disabled": true,
"prePopulate": false,
"default": "Accepted",
"options": [
{
"label": "Accepted",
"value": "Accepted"
}
]
}
],
"recordFormFieldsRejection": [
{
"field": "b3s__Status__c",
"props": {
"hideNoneOption": true
},
"type": "field-select",
"hidden": true,
"required": true,
"disabled": true,
"prePopulate": false,
"default": "Rejected",
"options": [
{
"label": "Rejected",
"value": "Rejected"
}
]
}
]
- Scheduler/TS Submission/TS Approval/Client Scheduler: Added support for defining 1/2/3 columns layout for forms
- Scheduler: Added "hideAvatar" boolean property to resource definition - this allows you to hide the circle with the initials of the specific resource
- Scheduler: Whitelisted all default tailwind background and border colors. This allows admins to define shift background color schemas that support all the tailwind colors. So, you can now apply bg-red-300 or bg-cyan-900 just fine.
- Scheduler: Added double click to open event view
Breaking Changes and Post Install
- Accessing the shift/request's fields is no longer done through the field path
event.extendedProps.b3s__Job__r....rather, now you the fields are available on the record property in the extended props, e.g.event.extendedProps.record.b3s__Job__r....Please make sure to update all references where you access event fields in your configuration. This change applies to:- Compliance Rules (for the App or Scheduler) - update your Scheduling Compliance Rule metadata
- Scheduling Filters - update your Scheduling Filter metadata
- Include the new "invitation" schedulable in all instances of the Scheduling Context Provider
- Scheduler: Deprecated access to the colorSchema variable in the filters API. You can no longer use the colorSchema to apply a color to the filter item checkbox. Remove any references in your custom defined filters
- All: Ensure you add the field b3s__Site_Timezone__c to b3o__Site__c loaders and b3s__Timezone__c to Contact loaders to benefit from timezone handling.
- Add "Submitted" option to Shift -> Approval Status
Sample Metadata for this version
We have created a drive folder containing the sample configuration for the various applications for version 4.X. You can explore the configurations here.
Qualifications Check Compliance Rule
<?xml version="1.0" encoding="UTF-8"?>
<CustomMetadata xmlns="http://soap.sforce.com/2006/04/metadata" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<label>Qualifications Check (Final)</label>
<protected>false</protected>
<values>
<field>Javascript_Code__c</field>
<value xsi:type="xsd:string"><![CDATA[async ({ event, events, contactId, loaders, EvaluationResult }) => {
if (!contactId) {
console.warn('No contact id for evaluation');
return [];
}
const certificatesLoader = loaders.find((l) => l.name === 'certificates');
const contactCertificates = certificatesLoader?.loader?.grouped[contactId];
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 jobTypeCertRequirements = certRequirements.filter(
(req) => req.b3s__Job_Type__c != null && req.b3s__Job_Type__c === eventJobTypeId,
);
const siteCertRequirements = certRequirements.filter(
(req) => req.b3s__Site__c != null && req.b3s__Site__c === eventSiteId,
);
const getMissingCertificates = function (certificates = [], certRequirements = []) {
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 getValidCertificates = function (certificates = [], certRequirements = []) {
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({
event: event,
detail: `👎 Missing ${missingCert.b3s__Certificate_Type__r.Name}`,
category: 'Certificates',
points: -50,
isBlocking: true
}),
);
}
const validCerts = getValidCertificates(contactCertificates, [...accountCertRequirements, ...jobTypeCertRequirements, ...siteCertRequirements]);
for (const validCert of validCerts) {
results.push(
new EvaluationResult({
event: event,
detail: `👍 Has ${validCert.b3s__Certificate_Type__r.Name}`,
category: 'Certificates',
points: 25,
}),
);
}
return results;
}]]></value>
</values>
<values>
<field>Type__c</field>
<value xsi:type="xsd:string">Scheduling</value>
</values>
</CustomMetadata>
Filer on Resource
<?xml version="1.0" encoding="UTF-8"?>
<CustomMetadata xmlns="http://soap.sforce.com/2006/04/metadata" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<label>Resource Filter</label>
<protected>false</protected>
<values>
<field>Javascript_Code__c</field>
<value xsi:type="xsd:string"><![CDATA[{
name: "resource",
label: "Resource",
type: 'resource',
items: function () {
const workerOptions = [...new Map(resources.map(item => {
return [item.id, item];
})).values()].filter(resource => {
return !!resource.extendedProps?.record?.Id
})
.map(resource => {
return {
name: resource.extendedProps?.record?.Id,
label: resource.extendedProps?.record?.Name
}
}).sort(function (a, b) {
if (a.label < b.label) {
return -1;
} else if (a.label > b.label) {
return 1;
}
// a must be equal to b
return 0;
});
return [
{ label:'Open', name: '*' },
...workerOptions
]
},
matchesItems: function (resource, filterItems = []) {
return filterItems.some(filterItem => {
return (resource.extendedProps?.record?.Id ?? '*') === filterItem;
});
}
}]]></value>
</values>
<values>
<field>Order__c</field>
<value xsi:type="xsd:double">-1.0</value>
</values>
</CustomMetadata>
Filter on Event
<?xml version="1.0" encoding="UTF-8"?>
<CustomMetadata xmlns="http://soap.sforce.com/2006/04/metadata" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<label>Site</label>
<protected>false</protected>
<values>
<field>Javascript_Code__c</field>
<value xsi:type="xsd:string"><![CDATA[{
name: "site",
label: "Site",
type: 'shift',
isDefaultOpen: true,
items: function () {
const siteOptions = [...new Map(events.map(item => {
return [item.extendedProps?.record?.b3s__Site__c, item];
})).values()]
.filter(e => !!e.extendedProps?.record?.b3s__Site__c)
.map(event => {
return {
name: event.extendedProps?.record?.b3s__Site__c,
label: event.extendedProps?.record?.b3s__Site__r?.Name
}
});
return [
...siteOptions
]
},
matchesItems: function (event, filterItems = []) {
return filterItems.some(filterItem => {
return event.extendedProps?.record?.b3s__Site__c === filterItem;
});
}
}]]></value>
</values>
<values>
<field>Order__c</field>
<value xsi:nil="true"/>
</values>
</CustomMetadata>