WFM Object Structure Introduced in 2GP

From 3B Knowledge
Jump to navigation Jump to search

Implementor guide to the objects introduced when the WFM scheduling app was decoupled from the 3B Onboarding package, and how they drive scheduling and compliance.

Compatibility. Existing clients on the 3B Onboarding objects may keep using them indefinitely. All new implementations must use the native objects documented here. The two models are wired in identically — only the loader objectName / filterRecordsBy and the field paths in context providers, loaders and compliance rules differ. Swapping models is a configuration change in Custom Metadata, not a code change.

All API names are shown without the b3s__ namespace prefix for readability; they all carry it in the org.

1. The objects

Object Role Key fields Parents
Cost_Center__c Cost grouping under an account Account__c (Master-Detail)
Job_Group__c Category / family of jobs (e.g. "Nursing", "Security") Name only
Job__c A role to be staffed Account__c, Job_Group__c
Site__c Physical workplace; carries the address, geocode and timezone Street/City/Province_State/Postal_Code/Country, Location__c (geo), Timezone__c, Site_Instructions__c Account__c
Site_Location__c Sub-area within a site (ward, gate, floor) Name only Site__c (Master-Detail)
Ticket_Type__c Kind of credential / competency (e.g. "First Aid", "SIA Licence") Name only
Ticket__c A credential held by a worker, with a validity window Valid_From__c, Valid_Until__c (Date) Contact__c (MD), Ticket_Type__c (MD)
Certificate_Requirement__c Declares which Ticket_Type is needed for work matching a set of criteria, and the scoring weight Priority__c (1–5), Max_Award__c, Max_Penalty__c, Ticket_Type__c criteria lookups: Account__c, Cost_Center__c, Job_Group__c, Job__c, Work_Site__c, Work_Location__c
Working_Pattern_Type__c Reusable shift-pattern template (e.g. "4-on-4-off", "Mon–Fri 9-5") criteria lookups: Work_Job__c, Work_Site__c, Work_Site_Location__c
Working_Pattern_Day__c One day-slot inside a template Week_Number__c, Day_Number__c, Start_Time__c, End_Time__c (Time) Working_Pattern_Type__c (MD)
Working_Pattern__c Assigns a pattern to a worker for a date range ("placement") Start__c, End__c (Date) Contact__c (MD), Job__c, Working_Pattern_Type__c
Shift_Task__c A sub-task within a shift Start__c, End__c (DateTime) Shift__c (MD), Work_Job__c, Work_Site__c

How they relate to a Shift

The Shift__c record is the spine. The new model adds these "Work_" links to it (replacing the old Onboarding lookups):

  • Work_Job__cJob__c (and through it, Job_Group__c and Account__c)
  • Work_Site__cSite__c (and through it, Timezone__c + geocode)
  • Work_Site_Location__cSite_Location__c
  • Contact__c → the assigned worker (null = open shift)
  • Shift_Demand__c + Required_Staff__c → demand parent + headcount

A shift therefore answers: who (Contact), what role (Work_JobJob_Group), where (Work_SiteSite_Location), and for whom (Account via the job). Those five dimensions — Account · Job Group · Job · Site · Site Location — are the universal matching key used by Certificate Requirements, Suitabilities and Working Pattern Types.

2. The two-axis design: requirements vs. holdings

The model deliberately separates what work needs from what a worker has, on both the credential and preference axes:

Axis "Requirement" side (attached to work criteria) "Holding" side (attached to a Contact)
Credentials Certificate_Requirement__cTicket_Type__c Ticket__cTicket_Type__c (+ validity dates)
Preference / skill Suitability__c (rating per criteria) — (Suitability is contact-scoped already)
Schedule shape Working_Pattern_Type__c + _Day__c (template) Working_Pattern__c (worker × date range)

Compliance matches the two sides at evaluation time by the shared Account/Job Group/Job/Site/Location key.

Criteria matching semantics (important)

Certificate_Requirement__c and Suitability__c both use the same "populated criteria must all match" rule (see the compliance code in Scheduler_Tickets / Scheduler_Suitabilities):

  • Only the lookup fields that are populated on the requirement are tested.
  • Every populated field must equal the shift's corresponding value, or the requirement is skipped (return false).
  • At least one criterion must be populated and matched (evaluatedCriteriaCount > 0 && matches).

So a Certificate Requirement with only Job_Group__c set applies to all shifts in that job group; one with Job_Group__c + Work_Site__c applies only where both match. This is how you scope a requirement from broad (account-wide) to narrow (one site, one job).

3. How the data reaches the scheduler

Three Custom Metadata types wire these objects into the running app. They are unmanaged and live in force-app/unpackaged/customMetadata/.

3.1 Scheduling_Loader__mdt — fetch & shape the data

Each loader is a JSON spec telling the dataLoadingEngine what to query and how to index it. The Object_Name__c field scopes the loader to a context (Account, Contact, Global, or staged sets Load_1000 / Load_2000).

{
  "name": "certificates",            // referenced by compliance rules via loaders.find(l => l.name === ...)
  "objectName": "b3s__Ticket__c",
  "filterRecordsBy": ["b3s__Contact__c =:contextId"],
  "groupRecordsBy": "b3s__Contact__c",   // builds loader.grouped[key] = [records]
  "fieldToLoad": ["Name","b3s__Ticket_Type__c","b3s__Valid_From__c","b3s__Valid_Until__c", ...]
}

The loaders that feed the new model (named by their name key):

name Object Grouped by Consumed by
sites Site__c Id Distance rule (geocode), shift timezone
jobs Job__c Id Job/Job-Group resolution
certificates Ticket__c Contact__c Tickets rule (worker holdings)
certRequirements Certificate_Requirement__c (records) Tickets rule (requirements)
suitability Suitability__c Contact__c Suitabilities rule
workingPatterns Working_Pattern__c Id Placement rendering
contacts Contact Distance rule (MailingLatitude/Longitude), matching

Each loader exists in Account_*, Contact_*, Global_* and staged Load_1000_* / Load_2000_* variants. They differ only in filterRecordsBy (=:contextId against Account vs Contact vs unfiltered) — pick the set that matches your context provider's sharing scope.

3.2 Scheduling_Context_Provider__mdt — app definition

The big JSON (JSON__c) per object/sharing context. Defines the schedulables map — how each source object becomes a calendar entity:

Schedulable objectType Notes
shift Shift__c the assignable event; declares matchingLoaders, demand fields, Work_* field paths, timezone via Work_Site__r.Timezone__c
placement Working_Pattern__c renders a worker's assigned pattern as a background span
task Shift_Task__c sub-tasks on a shift
request Employee_Request__c availability (Available / Unavailable / Flexible)
break Break__c
invitation Invitation__c

The shift schedulable's matchingLoaders lists exactly the loaders the matching/compliance engine hydrates before scoring: ["contacts","certificates","certRequirements","suitability","sites"].

Group__c / Group_Assignment__c (Sharing Groups) decide which context provider(s) apply for a given record/user — see CLAUDE.md.

3.3 Scheduling_Filter__mdt — UI facets

Client-side filter definitions over the loaded events. The new-model filters key off the Work_* paths:

  • Shift_Job_Filterb3s__Work_Job__c
  • Shift_Job_Group_Filterb3s__Work_Job__r.b3s__Job_Group__c
  • Shift_Site_Filterb3s__Work_Site__c
  • Resource_Filter → resource (worker) id, plus an "Unassigned" bucket

4. Compliance & matching (Scheduling_Compliance_Rule__mdt)

Each rule is an async ({ event, events, contactId, loaders, matchingModifiers, EvaluationResult }) => EvaluationResult[] function. Rules read worker data out of loaders.find(l => l.name === ...).loader.grouped[contactId] and the shift's attributes out of event.extendedProps.record. Each returned EvaluationResult carries points (positive = award, negative = penalty), a category, a detail string, and optional isBlocking: true for hard stops. Scores are summed to rank candidates; blocking results prevent assignment.

The six shipped rules and the objects they use:

Rule Uses Logic summary
Scheduler_Tickets Certificate_Requirement__c (req) × Ticket__c (held) For each requirement matching the shift's criteria, check the worker holds a Ticket of that Ticket_Type whose Valid_From/Until covers the shift. Missing/expired → penalty scaled by Priority (1=100%…5=20%) and Max_Penalty; valid → award scaled by Max_Award. Null validity date = indefinite.
Scheduler_Suitabilities Suitability__c For suitabilities matching the shift criteria, award/penalise by Rating__c (Excellent/Good/Bad/Terrible) bounded by Max_Award/Max_Penalty.
Scheduler_Distance Site__c geocode + Contact mailing geocode Haversine distance worker↔site. Linear score +50 (≤5 km) → −50 (≥100 km); estimates travel time. Max Distance search modifier hard-blocks beyond N miles.
Scheduler_Breaks Shift__c (other shifts) Requires ≥8 h rest before/after each of the worker's other shifts; <8 h → blocking penalty, else +15 award.
Scheduler_DoubleBooking Shift__c (other shifts) Interval-overlap against the worker's other confirmed shifts → blocking conflict.
Scheduler_Availability Employee_Request__c Overlap with worker availability: Unavailable → blocking; Available → +25; Flexible → notice.

Only Scheduler_Tickets, Scheduler_Suitabilities and Scheduler_Distance depend on the new objects; the other three are object-agnostic (shift-vs-shift and request-vs-shift) and work unchanged in either model.

Demands are not compliance-checked. A demand creates child shifts on assignment (Shift_Demand__c + Required_Staff__c); only the resulting single-contact shifts run through the rules above. (CLAUDE.md, scheduler.)

5. Working patterns in detail

Templates are normalised so one shape serves many workers:

Working_Pattern_Type__c  ──<  Working_Pattern_Day__c   (Week_Number, Day_Number, Start_Time, End_Time)
        │  (Work_Job / Work_Site / Work_Site_Location defaults)
        └──<  Working_Pattern__c  (Contact + Job + Start..End date range)   → rendered as "placement"
  • A Type = the recurring shape (which week, which weekday, what hours), plus default Work Job/Site/Location.
  • A Working_Pattern instance binds that Type to one Contact + Job over a Start__cEnd__c date range, and shows on the scheduler as a placement background.

Weekday/DST note. Internal weekday indexing is Mon=0…Sun=6, and re-anchoring a pattern's hours onto calendar dates must be done midnight-up per target day (never +n*86400000) to stay DST-safe. See the "Weekday indexing" and "Re-anchoring" sections of CLAUDE.md before writing any pattern-expansion code.

6. Implementor checklist for a new (native-model) deployment

  1. Reference data: create Job_Group__cJob__c, Site__c (+Site_Location__c), Ticket_Type__c, optional Cost_Center__c.
  2. Workers' holdings: Ticket__c per worker/credential with validity dates; Suitability__c ratings; Working_Pattern__c assignments.
  3. Requirements: Certificate_Requirement__c records scoped via the Account/Job Group/Job/Site/Location lookups, with Priority, Max_Award, Max_Penalty.
  4. Shifts: populate Work_Job__c, Work_Site__c, Work_Site_Location__c (and Contact__c when assigned). The job's Account + Job_Group derive automatically.
  5. Config: use the native Scheduling_Loader__mdt set (*_Sites, *_Jobs, *_Certificates/*_CertificateRequirements, *_Suitabilities, *_Working_Patterns) and a context provider whose schedulables point at b3s__Shift__c / b3s__Working_Pattern__c etc. Attach via Group__c / Group_Assignment__c.
  6. Rules: enable the six Scheduling_Compliance_Rule__mdt records; they read the loaders above by name.

To migrate an Onboarding-model org, you only re-point the loaders' objectName/filterRecordsBy/fieldToLoad and the context-provider / compliance field paths at these objects — no app code changes.