Repository navigation
Workforce: field service (#1225, PR C) - #1252
Open
MikeAlhayek wants to merge 33 commits into
Open
MikeAlhayek wants to merge 33 commits into
MikeAlhayek wants to merge 33 commits into
Conversation
A new Geo module (Geo.Abstractions, Geo.Core and the module) gives every module a shared way to work with places: GeoPoint; geocoding, travel-time and route providers behind IGeoService, chosen in Settings > Maps and Geocoding, with travel times cached and the built-in haversine estimate (road factor and average speed) when a provider is missing or fails; service territories drawn on a map or listed by postal code (with prefixes), with a resolver that takes the smallest shape a point is in, then the exact postal code, then the longest prefix; and geocoding in the background, one request per record, retried with growing waits and written back through IGeocodeTarget. The territory list has an overview map and a finder, the editor draws with Leaflet-Geoman, and the settings show the geocoding status. CrestApps Resources vendors Leaflet, Leaflet.markercluster and Leaflet-Geoman with a crestapps-map wrapper that sends the site's origin as referrer for tiles. Address gains optional coordinates, and Workforce geocodes team locations and home bases when Geo is on, clearing a home base's coordinates when its address changes.
Three provider modules plug into Geo, each adding its section to Settings > Maps and Geocoding: Azure Maps (geocoding, the route matrix with traffic split to 100 cells a request, and directions), Google Maps (geocoding, the Distance Matrix split to its 25 by 25 and 100-element limits, directions with the encoded polyline decoded, and travel times never cached, as its terms require) and OSRM (travel times and routes from a self-hosted server, with its steps written as sentences). Keys are kept encrypted; refusals throw GeoProviderException, so travel times fall back to the built-in estimate and geocoding retries. Providers gain a cache cap and a display name, choosing a geocoding provider wakes the addresses that waited for one, and the settings show the latest geocoding problem. Every provider passes the same contract tests against responses written from its service's documented format.
…tore A new Assets module (Assets.Abstractions, Assets.Core and the module) keeps equipment, vehicles, tools and units installed at customers: asset types with a category, attributes and meters; numbered assets with an owner, a hierarchy that refuses cycles, an address, warranty, attribute values and documents; and append-only meter readings where a cumulative meter can't go back. The list searches by name, number or serial number and filters by type and state, and an asset's page shows its parts, warranty and readings. Its Fleet feature gives a schedulable asset its own Equipment calendar, which refuses double-booking, and a van that carries stock its own Inventory location owned by it. Inventory stock locations move from the settings to a StockLocation store, keeping their ids and the order orders take units from them (a deferred copy after the migration, with the settings still read until then); the settings page edits the store, and owned locations never fulfill orders. Stock movements gain a reference and the Consumed kind, with IInventoryService.ConsumeAsync. The Workforce, Assets and Territories menus get icons.
…ls, parts and invoices New CrestApps.OrchardCore.WorkOrders module (with WorkOrders.Abstractions and WorkOrders.Core): work order types, checklist templates, service level policies and sub-statuses, each with its own index table; work orders with the status rules, a service level clock that runs in weekly opening hours and stops while waiting, a once-only warning and breach from a background task, parts taken off an Inventory location with the work order as the reference, checklist answers, signatures, notes, completion checks, invoices through Transactions and follow-ups. Geo gives the service address its territory and geocodes it, the work order list filters by asset (linked from the asset page), and five workflow events can be limited to some types.
… orders once each New Service Agreements feature of the Work Orders module: agreements with a customer, place, assets, period, time zone, included or per-visit pricing and notice, and schedules whose RRULE (expanded with the Calendars recurrence engine) makes the work order of each visit ahead of time, once, for the visit day in the agreement's time zone. The agreed price replaces the type's billing, agreements expire after their end, and the Service Agreement Expiring workflow event starts once within the notice. Includes the list, editor and page with the next visits and the work orders made, and a run-now button; a work order made for a whole day shows its visit day.
… a live board A visit for each work order; the candidate finder checks skills at a level, certifications valid on the day, territory, published shifts less breaks and time off, and the trip from the visit before, and ranks by weighted parts. Assignment takes sorted locks per technician and checks again inside them, adds a Job event on the team calendar and schedules the work order. Field statuses move the visit along. The Dispatch Board shows a timeline per technician, the unscheduled queue and a map, with drag to assign and live updates over SignalR. Work order types edit skills and certifications, the editor places the job with a pin, territories list who serves them, and the work order page takes panels from other modules.
…es, tracking and visits at risk The built-in optimizer proposes a better day (two starts, cheapest insertion, relocate, swap and 2-opt; fixed visits stay) that the board applies move by move through the locked assignment. Crew sizes are enforced and required equipment is booked on Fleet Equipment calendars. Customers are told by SMS or email when a visit is scheduled, the day before, on the way (with an ETA and a signed tracking link that stops when the visit is over) and done. Visits running over, not started or arriving late are flagged at risk with a workflow event. Dispatch settings are grouped in cards.
…y Time and the hours report A new CrestApps.OrchardCore.TimeTracker module tracks working time for any user: stages that count toward any number of categories, clocking in, switching stage and clocking out without gaps, manager edits and removals with a reason kept on the entry and in the Audit Trail, and an hours-by-category report under Reports. It has recipe and deployment steps with their CrestApps Recipes schemas. AGENTS.md now says toolbar filters are bootstrap-select pickers as on Manage Content, how admin forms and menus are built, and that every stored record is portable through recipes and deployments.
Punches stay the clock's record. After every punch, correction and removal, the worker's Time Tracker entries around it are rebuilt from the punches, paired as timesheets pair them, in the stage of each activity code. Time Tracker gets SyncAsync for entries another clock keeps (ManagedBy), and IExternalTimeClock so it refuses to switch, change or remove that time and links My Time to My Work. An entry left open there ends where the clock's time starts. Every activity code gets a stage when the feature starts, and the editor has a Time Tracker card; a background task brings earlier punches in. The Time Clock feature depends on Time Tracker, and its pages and Predictability Pay move under the Time Tracker menu. The review records why punches stay: kiosk-only workers, the original punch as the payroll record, and the rounding, meal and overtime rules written against punches.
…team calendar Shift planning publishes to each team's Workforce calendar, so its pages sit under Calendars after Scheduling's, as Scheduling's do: Shift Schedules (renamed from Schedules), Open Shifts, Shift Trades, Rotations, Coverage and Shift Templates. A team's Workforce calendar has Shift schedules in its actions on Manage Calendars. The review records why the planner stays its own page: drafts must not reach the live calendar, and the grid has no room for coverage, weekly hours or auto-schedule.
… Content Every toolbar filter of the plan's modules (Workforce, Workforce Contact Center, Skills, Assets, Work Orders and Dispatch) is now its own btn-group filter with a selectpicker, a header and the bootstrap-select resources, instead of a plain form-select. Coverage's team filter, for one, now matches Orchard Core's Manage Content list.
…s from Teams The first provider to add a top-level menu item gives it its classes, and Skills adds Workforce before Workforce does, so the icon now sits there too. Get Started is a one-time setup page: it left the menu and opens from a button on the Teams list.
…k's work The new Contact Center Time Tracker feature keeps every agent's states in Time Tracker, workers or not. Each AgentStateChanged event rebuilds the last ten minutes of the agent state log into entries in the stage of each state, or of a break's reason first, set on Time Tracker > Contact Center Stages (a site setting with a deployment step, seeded for the usual states). Back-to-back states in one stage make one entry, and the state the agent is in stays open. Time Tracker's sync gained ReplacesRangeOnly, to rebuild only a range while keeping and continuing what came before, and Overrides, to cut another clock's entries around these. Agents' states thus replace a worker's clocked work while they are signed in, and the time clock's next sync goes around them.
… after A grant gives hours or days to use by a date, such as year-end bonus time. Time off comes out of grants first, the one that ends soonest first, then out of the rest of the balance, oldest first, and canceled time off goes back to the grant. Balances count what a grant will have lost by a day against what is available, and list what is left of each grant, on the admin page and in My Work. The accrual run expires what is left of a grant the day after its last day, once. The balance form moved to the ocat layout.
… its type Every time-off type gets a Time Tracker stage, in Time off and Paid when paid, with a Time Tracker card on the type. Each day of approved time off of a worker with a login is kept in it once the day comes: a full day from 9:00, a part day ending at its end, read-only in Time Tracker. A new ITimeOffRequestObserver, called after a request is saved, rebuilds the worker's days when time off is submitted, approved, declined, canceled or given a place from the waitlist, and an hourly task adds days as they come. Syncing from inside the approval change committed a cancel half-way, so the sync runs after the save; a test covers it. Time Tracker shows entry sources by name.
…user The user editor has a Workforce card for people with Manage workers: it makes the user a worker, or changes their worker's employee number, job title, employment, status, hire date, main team (joined from today, keeping the teams they are in) and contracted hours. WorkerAccounts does the work without saving on its own, so it runs inside the user's save. A linked worker takes the user's display name and email after each user update and on the worker page, where they are read-only. Time Tracker records people under their display name too, so a person shows under one name.
…enter setup, forms in cards Geo territories, asset types and assets, work order settings and agreements, adherence rule sets and forecasting setup travel by recipe and deployment step with their schemas, keeping their ids; Dispatch and Geo settings get site settings deployment steps. Time Tracker categories and stages keep their ids. The field service and Contact Center workforce editors use ocat rows in cards.
Technicians keep their visits on the phone and work offline: status, answers, parts, notes, photos and the customer's signature go into an outbox of keyed commands, applied once and in order when online. The server wins on assignment, answers merge by the device's time, and finishing completes the work order. Location is shared only with consent and while working, refreshes the ETA and shows on today's board. The same endpoints are under api/dispatch/v1/mobile, settings come from CrestApps:Dispatch:Mobile, and the My Work service worker keeps pages for offline use.
…g for dispatch A new OR-Tools module (Google.OrTools, Apache 2.0) with two features. OR-Tools Shift Scheduling auto-schedules shifts with a CP-SAT model under the built-in solver's hard limits; OR-Tools Dispatch optimizes a day with the routing solver, with open stretches, breaks, windows, allowed technicians, fixed visits and a penalty per visit left out. Both start from the built-in answer and keep the better one. The module isn't in the CMS targets because of its native libraries. The auto-schedule message and the board's proposal name the solver, and the auto-schedule shares no longer read 2,000%. Also pins DispatchHub in the hub convention test and describes address coordinates in the Customers schema.
…recipe and Get started The Field Service API feature maps work orders (api/work-orders/v1, created once per Idempotency-Key), visits, candidates, assignment and optimizing (api/dispatch/v1), geocoding and territories (api/geo/v1) and assets (api/assets/v1), each with the admin's permission. Seven AI tools create, follow and summarize work orders, find technicians, optimize a day (applying only the approved moves), reschedule visits, and tell customers where their technician is by their own phone number. Reports > Field Service > KPIs and a Field Service report data source with work orders and visits. The Field service sample recipe and a Get started checklist on the dispatch board.
…ispatch, more field service KPIs and API reads - Assets are owned by a customer, company or CRM contact picked by name (IAssetOwnerSource), with an Assets card on contact pages and an owner filter on the list; the work order editor picks its customer, company and contact the same way. - The Field Service data source gains Assets, Meter readings and Service history data sets. - Scheduling gains IAppointmentSlotFilter; Dispatch keeps a booking slot only while a technician can take the job, less the waiting bookings that overlap. - Fix: a booking's work order window ended at the booking's end, so a longer job could never be dispatched; the booked time is now the arrival. - KPIs add revenue per work order, repeat work on the same asset and parts by stock location; the API adds checklists, a day's board and service agreements.
…y, free parts by van, and asks for follow-ups - The pack carries each work order's assets (IFieldAssetSource) with the latest five other work orders on them, and the units each stock location has free of the job's parts (IFieldStockSource.AvailableAsync). - FieldCommandKind.FollowUp makes a follow-up work order once, with a note naming it. - Fix: the sync badge said "1 changes waiting"; locations without a part now show none free.
…eement schedules with their own assets - The checklist editor's Shown when column picks an earlier saved question and the answer; a question can't depend on itself or a later one. - Each service agreement schedule picks its own assets; none means the agreement's.
…le sets for named workers - IShiftService.RemoveAsync cancels a published shift, deletes its draft and logs the change. - The shift page's On the published schedule card changes the hours, gives the shift to someone else or takes it off now, with a reason and whether the worker asked. - Labor rule sets pick their own workers in the editor. - Fix: the card's time read 'A to 5:00 PM'.
…low events - Shifts approved time off took off the schedule are kept on the request with their drafts and put back when it is canceled, if still to come and the worker has no other shift then. - Shift Assigned, Shift Changed and Shift Removed events from every published schedule change, and Time Off Waitlist Promoted. - Fix: a time-off test compared worker ids sorted on one side only.
- ShiftReminderService reminds each worker of a shift as long before as they chose, once per shift and start, every five minutes. - During a worker's quiet hours only the inbox gets notifications. - Fix: saving My Work's notification settings turned off timesheet and attendance notifications.
…ises Shift Assigned
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
PR C of the Workforce Management plan (#1225): field service. It is stacked on #1249 (PR B, contact center workforce management), which is stacked on #1229 (PR A, the workforce core), so its base is PR B's branch; as each PR below it merges, this PR's base moves down.
The plan is at
docs/engineering/workforce/workforce-management-plan.md; its Progress section lists every change from the design, slice by slice.Slices (one commit each)
CrestApps.OrchardCore.Geo, withGeo.AbstractionsandGeo.Core) that references no vertical, so any module can use it:GeoPointand the geocoding, travel-time and route provider seams behindIGeoService, which picks providers from Settings > Maps and Geocoding, caches travel times, and falls back to the built-in haversine estimate (road factor and average speed) when a provider is missing or fails.021*prefixes), with a parent, time zone and travel speed, andITerritoryResolver: the smallest shape a point is in, else the exact postal code, else the longest prefix.IGeocodeQueue,IGeocodeTarget): one request per record, retried after 1, 5, 30 and 120 minutes, then given up; waiting while no provider is set up. Workforce geocodes team locations and home bases when Geo is on.Addressgains optional coordinates. CrestApps Resources vendors Leaflet, Leaflet.markercluster and Leaflet-Geoman (MIT) with a smallcrestapps-mapwrapper.StockLocationstore, keeping their ids and the order orders take units from them; owned locations never fulfill orders; stock movements gain a reference and theConsumedkind withConsumeAsync.CrestApps.OrchardCore.WorkOrders, withWorkOrders.AbstractionsandWorkOrders.Core):CrestApps.OrchardCore.Dispatch, withDispatch.AbstractionsandDispatch.Core):WorkOrderPanelsdisplay model).ITerritoryMemberSource).docs/engineering/workforce/time-tracker-people-teams-review.md:CrestApps.OrchardCore.TimeTracker) for any user, not only workers:ocat-*fields in cards, icons, reports under Reports) and that every stored record has recipe, deployment and schema support.ReplacesRangeOnly), and cut another clock's entries around these (Overrides).ITimeOffRequestObserver, called after the request is saved, keeps the days in step, and an hourly task adds days as they come.ocatrows in cards.IFieldSyncService, with receipts), so sending them again changes nothing.api/dispatch/v1/mobile, and the settings come fromCrestApps:Dispatch:Mobile, described in the module'sConfigurationSchema.json. The My Work service worker can now keep any portal page for offline use (MyWorkOfflineOptions). The work order's page shows its photos and signature images.CrestApps.OrchardCore.OrTools, Google OR-Tools 9.15, Apache 2.0) with two features.api/work-orders/v1: list, get, create once perIdempotency-Key, status, notes, answers, parts, signatures, attachments and completing;api/dispatch/v1: list, get, candidates, assign, unassign, status, optimize and apply, and technicians' positions;api/geo/v1, and assets with their meter readings atapi/assets/v1.IAssetOwnerSource), with an Assets card on contact pages and an owner filter on the asset list. The work order editor picks its customer, company and contact the same way.IAppointmentSlotFilter. Dispatch uses it to offer a booking time only while a technician can take the job, less the waiting bookings that overlap.checklists, a day'sboard, andagreements.IShiftService.RemoveAsync). Each change is checked, logged and paid like a republish.Testing
-warnaserror; the Geo, Workforce, architecture, PublicApi and address-using commerce suites;npm run check:manuals, eslint and vitest pass.ItemIdonly when it is a valid 26-character id. Readable ids in a hand-written recipe therefore broke the links between records and duplicated them on a second run. The sample uses valid ids, and the docs say so.DispatchHubwas missing from the hub convention test's list; and the Customers recipe schema didn't describe the address coordinates added in C1. These last two were found by the full suite, 13,413 tests.OnHold; and the location to take parts from silently defaulted to the first one.{z}broke the settings page.🤖 Generated with Claude Code