Asset Maintenance & Repair Workflow
A design reference for preventive maintenance scheduling and repair-with-consumed-items on fixed assets
1. Requirements
1.1 Functional requirements
- Assign each maintainable asset to a maintenance team (a manager plus a roster of members, each with a role) and attach one or more recurring maintenance tasks to the asset — each task naming its own periodicity (daily up to every three years), an assignee, and whether a certificate is required on completion.
- Automatically re-assign a maintenance task to its assignee whenever the task or its due date changes, using a to-do/assignment mechanism rather than email alone.
- Materialize each task’s next due occurrence as its own maintenance log record, distinct from the recurring task definition itself, so a history of past occurrences accumulates instead of a single task row being overwritten in place.
- Automatically flag a task or a log as overdue once its due date has passed without completion, both when the parent record is saved and on a recurring daily sweep, without requiring a person to notice.
- On completion of a log, automatically compute the task’s next due date from its periodicity and the completion date, and reset the task back to a normal (non-overdue) state.
- Record repairs against an asset independently of the preventive-maintenance mechanism above: a failure date, an optional completion date with automatically computed downtime, a free-text description of actions performed, and a repair status.
- Allow a repair to consume stock items (spare parts, drawn from a warehouse, with serial/batch tracking where the item requires it) and to allocate repair cost from one or more already-submitted purchase invoices, each allocation capped by how much of that invoice’s non-stock expense amount has not already been claimed by another repair.
- Optionally capitalize a repair’s total cost (consumed-item value plus allocated invoice cost) onto the asset — increasing its book value, optionally extending its useful life by a number of months, and causing its depreciation schedule to be regenerated — rather than expensing the repair outright.
- Automatically place an asset out of order while a repair against it is pending, and automatically flip it back to its normal status once every pending repair is resolved.
1.2 Non-functional requirements
- No silent double-booking of purchase invoice cost: two repairs must not be able to claim overlapping amounts from the same purchase invoice and expense account.
- Traceability from consumed spare parts to the ledger: every stock item drawn into a repair must produce a normal stock movement and, if the repair is capitalized, a normal ledger posting — not an off-book adjustment.
- Reversibility: cancelling a capitalized repair must symmetrically undo the asset value increase, the life extension, and the ledger postings it made, and must release any serial/batch allocation it consumed.
- Consistency with the asset lifecycle: repair and maintenance state changes must route through the same asset-status and asset-activity mechanisms the rest of the asset lifecycle uses, not a parallel notion of asset state.
1.3 Constraints
- The maintenance side of this workflow is intentionally not submittable at the task-and-schedule level — only the generated occurrence log is a submittable record; the recurring plan itself is a plain, always-editable document.
- Repair cost allocation only reads from an invoice’s already-posted ledger figures; it never re-derives or re-validates the invoice’s own accounting.
asset_repair_purchase_invoiceis not a separate document type — it is a child table on the repair, one row per (purchase invoice, expense account) pair being drawn from.
2. High-Level Design
2.1 Component diagram
2.2 Walkthrough — from a plan to a completed occurrence
- A team is defined once (manager, member roster) and referenced by any number of assets.
- A maintenance plan is created per asset, naming the team and one or more tasks; saving it re-assigns every task to its assignee and, for every task, creates or refreshes an open log for its current due date. A task already overdue at save time is marked so immediately.
- A daily sweep independently flips any still-
Plannedlog whose due date has passed toOverdue, so a log left untouched between saves does not silently stayPlannedforever. - The assignee completes the work and submits the log as
Completed(with a completion date, and a certificate attachment if the task requires one) or asCancelled. Submission is blocked if the log is left in any other status. - Completing a log pushes state back onto the task: if the completion date is new, the task’s
last_completion_dateandnext_due_date(recomputed from periodicity) advance, and the task returns toPlannedfor its next occurrence. ACancelledlog instead cancels the task itself. - A separate daily job looks at every asset flagged as requiring maintenance: if it has a pending repair, it is set
Out of Order; else if any of its tasks is due today, it is setIn Maintenance; otherwise its status is recomputed normally.
2.3 Data flow — a capitalized repair
3. Deep Dive
3.1 Data model
Asset Maintenance Team / Maintenance Team Member A named team (unique name, one company) with a manager and a child table of members, each carrying a maintenance role. Purely a roster; it has no schedule of its own.
Asset Maintenance
Exactly one per asset (the asset link field is unique), naming the team responsible and holding the table of tasks below. It is a plain document with no submit lifecycle — every change takes effect immediately on save, including task re-assignment and log synchronization. Deleting it, if the asset was In Maintenance at the time, causes the asset’s status to be recomputed.
Maintenance Task (child row of Asset Maintenance)
A recurring definition: a name, a type (Preventive Maintenance or Calibration), a periodicity (Daily through 3-Yearly), a start date and optional end date, whether a certificate is required, an assignee, and mutable state — next_due_date, last_completion_date, and a status of Planned, Overdue, or Cancelled. The end date, once reached, stops future occurrences from being generated.
Asset Maintenance Log
A submittable record, one per due occurrence of a task, carrying most of the task’s fields fetched read-only (item, description, periodicity, assignee) plus its own due date, completion date, status (Planned, Completed, Cancelled, or Overdue), and an optional certificate attachment gated on the task requiring one. Synchronization is idempotent by design: saving the parent plan again looks for an existing open log (Planned or Overdue) for the same task before creating a new one, so re-saving does not spawn duplicate occurrences; only tasks that have disappeared from the plan get their leftover open logs force-cancelled.
Asset Repair
A submittable record against exactly one asset, independent of the maintenance-plan mechanism above (a repair can exist with no maintenance plan at all, and vice versa). Carries a failure date, an optional completion date, a downtime figure (hours between the two, computed on request rather than stored authoritatively), a repair status of Pending, Completed, or Cancelled, and two optional capitalization fields: capitalize_repair_cost and increase_in_asset_life (months). Both capitalization fields are force-cleared if the asset already reads as fully depreciated at validation time. Submission is blocked while the status is still Pending — a repair must be resolved one way or the other before it can be finalized.
Asset Repair Consumed Item (child table, not a document) One row per spare part drawn from stock: item, warehouse, valuation rate, quantity consumed, and — if the item is serialized or batch-tracked — a serial/batch allocation. Each row’s value (rate × quantity) rolls up into the repair’s consumed-items cost. On submission every row becomes one line of a Material Issue stock movement created for the repair; on cancellation, any serial/batch allocation it made is released.
Asset Repair Purchase Invoice (child table, not a document — per the plan’s scope note, this is one line item on the repair, not a distinct record type) One row per (already-submitted purchase invoice, non-stock expense account) pair being drawn from, with a repair cost for that pair. Two guards keep this honest: the same (invoice, account) pair cannot appear twice on one repair, and a row’s repair cost cannot exceed that invoice/account’s total expense amount minus whatever every other submitted repair has already claimed from the same pair — computed directly from ledger figures on the invoice, not from a separately maintained running total.
3.2 Status lifecycle
A repair cannot be submitted while still Pending — the status must move to Completed or Cancelled first. Cancelling a submitted (capitalized) repair reverses the value increase, the life extension, and the ledger postings symmetrically, and releases any serial/batch allocation the consumed items had made — the reversal is a mirror of on_submit, not a separate code path.
3.3 Algorithm — repair cost and capitalization
- Consumed-item cost is the straight sum of
valuation_rate × consumed_quantityacross every stock row. - Repair cost is the straight sum of the
repair_costcolumn across every allocated-invoice row. - Total repair cost = consumed-item cost + repair cost. This is the figure that, if
capitalize_repair_costis set, gets added to the asset’s total and additional cost, and to every finance book’s running book value — proportionally to the whole asset, not split by finance book. - Useful life extension, if declared, is added directly to (or, on cancellation, subtracted from) each finance book’s own life-extension counter — the same field the disposal and value-adjustment mechanisms in the lifecycle document read when regenerating a schedule.
- Schedule regeneration is triggered exactly once per submit or cancel, using the same shared routine described in the lifecycle document, with a note recording that the change came from this repair.
3.4 Error handling
- Invoice cost double-claiming: the available amount for an (invoice, expense account) pair is recomputed live from ledger figures minus every other submitted repair’s claim against the same pair, so two repairs racing to claim the same invoice cannot both succeed for more than the true remaining amount.
- Ineligible invoices: only submitted purchase invoices are offered, and only their non-stock, non-fixed-asset expense accounts are valid — a stock item’s warehouse account or an asset’s own capitalization account can never be selected.
- Missing serial/batch allocation: a consumed item flagged as serialized in its item master cannot be submitted without one.
- Repair against a disposed asset: creating or validating a repair against an asset already
SoldorScrappedis rejected outright. - Unfinished draft blocking submission: a repair left
Pendingcannot itself be submitted, forcing every capitalization decision to be made before the record becomes immutable.
4. Scale and Reliability
- Load pattern: almost entirely human-triggered (creating repairs, completing logs) plus one lightweight daily sweep per mechanism — there is no bursty or high-volume path here.
- Idempotent synchronization: re-saving a maintenance plan is safe to repeat because occurrence creation always checks for an existing open log first; this is the main correctness property this subsystem leans on instead of locking.
- Blast radius of a repair failure: a repair’s stock movement, ledger posting, and schedule regeneration all happen inside the same submit transaction, so a failure partway through does not leave the asset half-updated.
- Growth pressure point: the daily asset-status job and the daily overdue sweep both scan every maintainable asset unconditionally; at fleet scale these sweeps, not the human-triggered paths, would need a more targeted, due-date-indexed query.
5. Trade-off Analysis
| Decision | Trade-off |
|---|---|
| Maintenance plan is not submittable; only the generated log is | Keeps the recurring definition easy to tweak (add a task, change an assignee) without an amendment workflow — at the cost of the plan itself having no audit trail of its own; only occurrence history is durable. |
| Occurrence sync runs on every save rather than on a schedule | Immediate feedback (an overdue task shows up the moment you look at the plan) — at the cost of relying on someone opening and saving the record; the daily sweep exists specifically to cover the gap between saves. |
| Repair cost allocation reads live from ledger figures instead of a maintained running balance | No separate total to keep in sync with the invoice — at the cost of a live query on every validation, which is fine at repair volumes but would not scale to a high-frequency claiming pattern. |
| Consumed items always produce a real stock movement, even when the repair is not capitalized | Spare-part consumption is always inventory-accurate regardless of the accounting treatment chosen — at the cost of a stock entry existing for repairs that never touch the asset’s book value, which can look inconsistent to someone expecting “no capitalization” to mean “no other side effect.” |
| Asset status forced to Out of Order by any pending repair, with no severity distinction | Simple, unambiguous signal that something is wrong with the asset — at the cost of a trivial repair and a fleet-stopping one looking identical from the asset’s status field alone. |
6. What to Revisit as the System Grows
- Severity or priority on a repair. Today every pending repair forces the same
Out of Orderstatus regardless of how serious the fault is; a severity field feeding a less binary asset-availability signal would help fleets with a high repair volume. - A due-date index for the two daily sweeps. Both the overdue-log flip and the asset-status recomputation scan the whole maintainable fleet unconditionally; narrowing each to rows actually due would matter once the fleet is large.
- Partial/multi-step repairs. The model assumes one failure-to-completion span per repair record; a fleet with long-running, multi-visit repairs would benefit from a record that can represent several work sessions against one failure rather than one record per attempt.
- Certificate handling beyond a single attachment. Calibration-heavy fleets often need a full certificate history with expiry tracking, not just one file on the most recent log.