-
Notifications
You must be signed in to change notification settings - Fork 15
Service Layer Progress
Rollout status of the service-layer refactor: collapsing each module's duplicated UI + REST API write logic into one shared service, module by module, behind a before/after parity harness.
Strategy: prove the pattern on the smallest, lowest-risk module first; do tickets last (biggest, most integrations). Each module is its own revertable commit; nothing ships unless the harness proves the API is byte-identical.
| Module | Status | Adapter lines removed | Parity | Notes |
|---|---|---|---|---|
| Service Status | β Done (#718) | 474 removed / 75 kept (9 files) | β byte-identical | Pilot. Established the primitives + pattern. Display-UTC fix #719. |
| Morning Checks | β Done (#720) | 424 removed / 94 kept (10 files) | β byte-identical | Unified documented UI/API drift. Added ActorContext.actorName + shared apiFailFromService(). |
| Software | β Done (#722) | 175 removed / 40 kept (3 files) | β byte-identical | Only licences overlap UI+API; inventory is read-only and widgets/apikeys are UI-only (no twin), so left untouched. |
| Calendar | β Done (#723) | 302 removed / 70 kept (5 files) | β byte-identical | Events overlap UI+API; UI-only categories folded in. NaΓ―ve-local datetime contract preserved (not converted to UTC). |
| Forms | β Done (#724) | 722 removed / 55 kept (6 files) | β byte-identical | Largest so far. Positional field sync + version chains + the form.submitted workflow dispatch all in one write path. |
| Contracts | β Done (#725) | 462 removed / 56 kept (6 files) | β byte-identical | Contracts + suppliers + term upsert. Lookup settings (UI-only) and supplier contacts (UI-flat vs API-nested mismatch) left on their own code. |
| Knowledge | β¬ Planned | β | β | |
| Tasks | β¬ Planned | β | β | |
| CMDB | β¬ Planned | β | β | |
| Network Mapper | β¬ Planned | β | β | |
| Assets | β¬ Planned | β | β | |
| Changes | β¬ Planned | β | β | |
| Problems | β¬ Planned | β | β | |
| Service Status settings | β folded into pilot | β | n/a | Incident-statuses + impact-levels are UI-only; moved into the service. |
| Tickets | β¬ Last | β | β | Biggest payoff, most integrations β deliberately last. |
The first module is roughly line-neutral: it pays a one-time cost (the shared
ActorContext + ServiceError primitives, ~105 lines) and its service includes UI-only logic
that was never duplicated. The thousands of lines of net removal arrive as the larger,
heavily-mirrored modules migrate β tickets above all.
| Lines | |
|---|---|
One-time primitives (includes/service_context.php + apiFailFromService) |
+122 (paid once, for all modules) |
| Service Status: adapter logic removed | β474 |
| Service Status: adapters kept (thin) | +75 |
| Service Status: service (incl. UI-only ops) | +390 |
| Morning Checks: adapter logic removed | β424 |
| Morning Checks: adapters kept (thin) | +94 |
| Morning Checks: service (incl. UI-only ops) | +310 |
| Software: adapter logic removed | β175 |
| Software: adapters kept (thin) | +40 |
| Software: service (licences only) | +168 |
| Calendar: adapter logic removed | β302 |
| Calendar: adapters kept (thin) | +70 |
| Calendar: service (events + UI-only categories) | +251 |
| Forms: adapter logic removed | β722 |
| Forms: adapters kept (thin) | +55 |
| Forms: service (forms + versions + submissions + dispatch) | +445 |
| Contracts: adapter logic removed | β462 |
| Contracts: adapters kept (thin) | +56 |
| Contracts: service (contracts + suppliers + terms) | +341 |
Where a module's UI and API already behaved differently, we converge to the API's behaviour (the API is the external contract; it stays identical, the UI shifts to match). Record each convergence here so nothing is a silent surprise.
Service Status (#718):
- Create timestamps: server-local β UTC (SLA-correct; same bug class as Forms #701).
- Empty
description:""βnull. - Affected-service validation: silently-skip unknowns β strict 422.
- Incident status: name-only β name or id.
- UI deletes: idempotent β 404 on missing (matches the API).
Morning Checks (#720):
- Result date: malformed silently-swapped-for-today β strict 422.
- Unknown check id on a result: raw FK 500 β 422 with a clear message.
- Result
CreatedBy: left NULL by the UI β the recording analyst's name (attribution; the API already did this). - Check validation messages align with the API wording; updating/deleting a missing check β 404 (was a silent no-op).
Software (#722):
- Licence app id: no check (raw FK error on a bad id) β the app must exist, 422 with a clear message.
- Quantity / cost / dates: silently coerced β validated (422) β a negative quantity, a non-numeric cost or a malformed date is rejected.
- Empty optional fields (
licence_key,portal_url,vendor_contact,notes): stored as""βnull; missingcurrency/statusfall back to GBP / Active. - Saving or deleting a missing licence: silent success β "Licence not found." (matches the API's 404).
Calendar (#723):
- Event category id: no check (raw FK error on a bad id) β the category must exist, 422.
- Event times: no check β
end_atbeforestart_atis rejected (422); naive-datetime format is validated (Z/offset rejected) β the naive-local contract is kept, not converted to UTC. -
Generated events (
source = 'asset_warranty'etc.): the UI let you edit/delete one, only for it to reappear on the next sync β now refused (409) on both edit and delete, matching the API. - Saving or deleting a missing event: silent success β "Event not found." (matches the API's 404).
-
Categories are a UI-only operation (no API twin) β moved into the service with behaviour preserved verbatim (name required,
#RRGGBBcolour check, in-use delete guard).
Forms (#724):
- Field validation: a blank label was silently dropped, an unrecognised field type blindly stored β both now 422.
- Submission with an unknown field id: raw FK error β 422 with a clear message.
- Editing a frozen (non-leaf) version β 409; deleting a form that still has newer versions β 409 (was a raw FK error). Whole-chain delete stays an API-only option (
?chain=true). - Submitting to an inactive form β 409 (the UI merged not-found + inactive into one message; now split into 404 / 409).
- Saving/deleting a missing form or submission β 404 (was a silent success).
- The
form.submittedworkflow dispatch now fires from the shared write path, so UI and API submissions trigger workflows identically β the first webhook-relevant side effect to land on a service (see the eventing note above).
Contracts (#725):
- A contract now needs a
contract_number+title; its supplier / status / schedule / owner id must exist (were raw FK errors); dates are validated; the value must be numeric and the currency a 3-letter code. - A duplicate
contract_numberis refused (409) on create and update β the raw UI allowed duplicates. - Deleting a supplier cleanly unlinks every referrer (assets, contacts, contracts, RFP invites/scores) before the delete, and created timestamps are stamped UTC (the UI relied on the server-local column default).
-
Scope: only the cleanly-duplicated overlaps (contracts, suppliers, term values) were migrated. The lookup settings (contract/supplier statuses, supplier types, payment schedules, term tabs) are UI-only (the API exposes them read-only) and stay on their own endpoints; supplier contacts are modelled flat by the UI (a
supplier_idin the body, movable) but nested under a supplier by the API β a structural mismatch not worth forcing into one shape β so they stay separate too.
-
Display-layer UTC parsing β storage is now consistently UTC, but module JS parses
"Y-m-d H:i:s"as local (new Date(...)), so UTC data can render an hour off. Fix is a small per-moduleparseUtc()helper (mark the naive string UTC beforetoLocale*). Done for service-status (#719); still to do on calendar, morning-checks and other timestamp-rendering modules. See Watch out for #2. - Webhook events from the write path β once a module is on its service, emitting lifecycle events (for outbound webhooks) is one line from the single write path, firing identically for UI, API and automation. This is the webhooks "wider event catalogue" phase.
- Read the complete surface β every UI endpoint and the API resource for the module.
- Enumerate the UI-vs-API divergences; agree the convergence.
- Build/adapt the parity harness; capture golden-before on untouched code.
- Extract
<Module>Service; repoint the API write handlers + the UI endpoints at it. - Run the harness after; require the API byte-identical, UI converged as designed.
- Ship as one commit. Update this page.
See Service Layer β architecture for the conventions and the full "Watch out forβ¦" list.
FreeITSM β an open-source IT Service Management platform Β· github.com/edmozley/freeitsm Β· MIT licence
- Installation
- β° Scheduled tasks (cron jobs)
- Architecture
- AI Providers
- Internationalisation (i18n)
- Timezones & Time Handling
- Theming & Dark Mode
- β¨οΈ Command palette (βK)
- π Searching inside tickets
- π Attached documents
- MobileβFriendly
-
Security
- Layer 1 β which modules you can enter
- β³ π§© Module Access Control
- β³ π οΈ Module Access β Developer Guide
- Layer 2 β what you can administer
- β³ π Roles & Permissions
- β³ π οΈ Roles β Developer Guide
- β³ π€ Why capabilities are constants
- Layer 3 β the System module
- β³ π Admin Access Control
- Hardening
- β³ π Security review response 2026-08
- β³ π‘οΈ Security hardening 2026-08
- β³ π οΈ Security hardening 2026-08 β Developer Guide
- β³ π‘οΈ Round three β plain English
- β³ π οΈ Round three β Developer Guide
- Single Sign-On (SSO)
- ποΈ LDAP & Active Directory
- Browser Extension
- API Reference
-
π REST API β how it works
- β³ π« REST API: Tickets
- β³ π» REST API: Assets
- β³ π΄ REST API: Problems
- β³ π REST API: Changes
- β³ π REST API: Knowledge
- β³ β REST API: Tasks
- β³ ποΈ REST API: CMDB
- β³ π REST API: Contracts
- β³ ποΈ REST API: Calendar
- β³ πΏ REST API: Software
- β³ π¦ REST API: Service Status
- β³ βοΈ REST API: Morning Checks
- β³ π REST API: Forms
- β³ βοΈ REST API: Workflow
- β³ πΊοΈ REST API: Network Mapper
- β³ π§ Using the API docs page
- β³ π OpenAPI specification
- β³ β OpenAPI: kept correct
- β³ π οΈ Maintaining the catalogue
- Watchtower
-
Tickets
- β³ Mailbox Authentication
- β³ π€ Email send log
- β³ Basic IMAP mailboxes
- β³ Email rendering & images
- β³ SLA Management
- β³ WhatsApp channel
- β³ π¬ Web chat channel
- β³ π£ Slack channel
- β³ π Linking tickets
- β³ ποΈ Canned responses
- β³ βοΈ Limiting replies to particular senders
- β³ βοΈ Email signatures
- β³ π The public web address
- β³ π Raising a ticket for someone else
- β³ π Merging tickets
- β³ β Splitting tickets
- β³ β Selecting several tickets
- β³ π οΈ Snoozing tickets β Developer Guide
- β³ π₯ Collision detection
- β³ β±οΈ Time tracking
- Problem Management
- Tasks
- Assets
- Knowledge
- Change Management
- Calendar
- Morning Checks
- Reporting
- Software
- Forms
- Contracts
- Service Status
- π Notifications
- π¨ War Room
- Self-Service Portal
- LMS
- Process Mapper
- CMDB
- Network Mapper
- Workflows
- Issue trackers (Jira, Azure DevOps)
- System
-
Overview
- β³ π Progress tracker
- β³ Concepts & vocabulary
- β³ Email routing & mailboxes
- β³ Settings: global vs per-company
- β³ Users & self-service
- β³ Staff cross-company access
- β³ Worked examples
- β³ Pitfalls & gotchas
- β³ Scope: what it's for
- β³ π οΈ Developer Guide (make a module multi-company)
- β³ ποΈ Case study: CMDB (a linked graph)
- β³ π§ͺ Test harness (prove it's isolated)