Skip to content

Add TCO (total cost of ownership) reporting#82

Merged
JamesReate merged 16 commits into
mainfrom
worktree-tco-reporting
Jul 10, 2026
Merged

Add TCO (total cost of ownership) reporting#82
JamesReate merged 16 commits into
mainfrom
worktree-tco-reporting

Conversation

@jamesliupenn

Copy link
Copy Markdown
Member

Summary

Adds a fleet-wide and per-vehicle Total Cost of Ownership report, built on top of the existing Glovebox document pipeline.

  • Backend: new vehicle_tco_settings table (optional per-vehicle acquisition price/date/useful-life), TCOService (cost-eligibility filtering, amount extraction, straight-line depreciation, CSV export), and TCOController exposing /tco/* routes under the existing tenant-scoped JWT auth.
  • Frontend: a new "Fuel" document category, an optional Amount field on the Glovebox upload-confirm modal, and a new TCO nav tab with a fleet-wide table + per-vehicle drilldown (category breakdown, line items, acquisition-settings form, CSV export).
  • Correctness fixes found during review: tombstoned (deleted) documents were being double-counted in TCO totals — fixed by sharing the same tombstone-exclusion logic Glovebox's document list already uses. Vehicles the dev license lacks DIMO permissions for now show a clear "No access" badge instead of erroring.
  • Follow-up feature work: TCO fleet-table caching (mirrors the existing FleetCache pattern, invalidated on upload/delete/settings-save/backfill), a way to backfill cost amounts onto documents that were uploaded without one (via a non-mutating "cost-amendment" CloudEvent, following the same pattern dimo.tombstone uses for delete), respecting hidden vehicles from the Fleet List view, and pagination (10 rows/page, fleet total always summed across all vehicles).

Test plan

  • go build ./..., go vet ./..., go test ./... all pass (unit tests cover cost-eligibility, amount extraction, depreciation math, CSV export, tombstone exclusion, and cost-amendment parsing)
  • tsc --noEmit and npm run build pass with no new lint errors
  • Manually verified locally: TCO nav tab loads, /tco/* routes are correctly gated behind JWT auth (same as /documents/*), fleet table + drilldown render against a live tenant, styling matches the rest of the app
  • Not yet exercised in a live browser session by a human reviewer — recommend a manual pass on the deployed preview before merging (upload a document with an amount, confirm it appears in TCO, try acquisition settings + backfill + CSV export)

🤖 Generated with Claude Code

@jamesliupenn jamesliupenn requested a review from JamesReate July 9, 2026 04:52
@jamesliupenn jamesliupenn force-pushed the worktree-tco-reporting branch from 0da1efa to 04f36f2 Compare July 9, 2026 17:38
jamesliupenn and others added 16 commits July 10, 2026 10:42
Plans a fleet-wide/per-vehicle total-cost-of-ownership tab built on top of
the existing Glovebox document pipeline, with straight-line depreciation
and CSV export.
Corrected against the actual SQLBoiler-generated types (VehicleTcoSetting,
types.NullDecimal) rather than the plan's assumed VehicleTCOSetting/null.Float64.
Exposes the TCOService (settings CRUD, vehicle/fleet summaries, CSV
export) built in Tasks 1-4 over HTTP, following the documents.go
controller pattern for tenant/vehicle authorization.
Implement frontend types and service layer for TCO reporting feature.
- TCOSettings, LineItem, VehicleTCOSummary, FleetTotals, FleetTCOSummary types
- TCOService singleton with methods for settings, summary, vehicle detail, and CSV export
- Follows existing document-service pattern with ApiService and TenantService integration

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds 'dimo.document.vehicle.fuel' to CE_TYPE_TO_LABEL and UPLOAD_CATEGORIES.
Introduces COST_ELIGIBLE_CATEGORIES Set to track which document types can
carry cost amounts and count toward TCO operating costs. Mirrors the backend's
CostEligibleCETypes from tco_service.go.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds tco-view.ts rendering the fleet TCO summary table (via TCOService
from Task 6), with a CSV export button, and wires it into the side nav
and app-root router as a new /:tenantId/tco route.
…r-vehicle export

Replaces Task 9's openVehicle stub with a full drilldown view: category
cost breakdown, line-item table, an acquisition settings form
(purchase price/date/useful life), and a per-vehicle CSV export.
…settings error on vehicle switch

Deleted Glovebox documents (tombstone CEs) kept contributing to TCO cost
totals and CSV exports forever because VehicleSummary read the same
fetch-api entries as ListDocuments but never filtered tombstoned ids.
Extracted the tombstone-id-parsing logic into a shared gateway.TombstonedIDs
helper so both call sites stay in sync going forward.

Also fixes tco-view's openVehicle leaking a stale settingsError from a
previous vehicle's failed save into the next vehicle's drilldown.
…issing permissions and hidden vehicles

Restyled tco-view.ts to use the app's shared header.top-bar/tenant-switcher/
table conventions (previously it had ad-hoc styling that didn't match the
rest of the app).

The fleet table now loads the vehicle list first (fast) and fills in each
vehicle's cost figures in the background (mirroring Glovebox's doc-count
prefetch pattern), instead of blocking the whole view on the slowest
vehicle's fetch-api round trip.

Vehicles the dev license lacks SACD permissions for previously returned a
raw 500 and left their row stuck in perpetual "loading" once the table
started rendering progressively. TCOService.VehicleSummary now detects this
case the same way DocumentsController.ListDocuments already does, and the
row shows a "No access" badge instead.

Vehicles hidden via the Fleet List view are now hidden here too by default,
with the same "Show hidden vehicles" toggle used there.
… paginate

Caching: TCOCache (mirrors FleetCache) holds the last-loaded vehicle list
and per-vehicle cost figures, keyed by tenant. Revisiting the TCO tab now
paints instantly from cache instead of re-fetching the whole fleet; a
manual refresh button forces a full reload. Cache is invalidated whenever
something could make it stale — a Glovebox upload, a delete, a settings
save, or a backfilled amount.

Backfill: documents uploaded without an amount (including everything from
before this feature existed) can now get one after the fact. Since
CloudEvents are immutable, backfilling emits a new "cost-amendment" CE that
references the original document by id rather than mutating it — the same
pattern dimo.tombstone already uses for delete. VehicleTCOSummary now
returns a MissingAmounts list; the drilldown offers an inline amount input
per missing document.

Pagination: the fleet table shows 10 vehicles per page. The "Fleet total"
footer row is always summed across every visible vehicle regardless of
which page is showing, not just the current page.
…zation

main merged per-member fleet-group access control (VehicleService.GetVehicle/
ListVehicles gained an allowedGroupIDs param) since this branch was cut.
TCOService.VehicleSummary/FleetSummary and TCOController now thread
GetAllowedGroups(c) through the same way documents.go/telemetry.go already
do, so limited members are correctly scoped to their accessible groups
instead of failing to compile.

Also regenerates xliff/es.xlf and src/generated/locales/es.ts for the new
TCO strings — CI's localize:check requires these committed in sync with
source.
@JamesReate JamesReate force-pushed the worktree-tco-reporting branch from 04f36f2 to 44a60cf Compare July 10, 2026 16:00
@JamesReate JamesReate merged commit 3fcade6 into main Jul 10, 2026
3 checks passed
@JamesReate JamesReate deleted the worktree-tco-reporting branch July 10, 2026 16:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants