Files
MixedAssets/README.md
2026-06-07 23:32:31 -05:00

37 KiB
Raw Permalink Blame History

DepreCore

DepreCore is a fixed asset management and depreciation platform for small and mid-sized organizations. This build includes an Express API, Vue/Vuetify frontend, SQLite persistence, role-aware login, asset register, templates, JSON tax-rule sets, depreciation reports, import/export, and admin configuration.

Each asset carries six independent books (GAAP, Federal, State, AMT, ACE, User-defined), each with its own cost, depreciation method, useful life, §179 amount, bonus percentage, and convention. The asset workbench also includes a File Cabinet (invoices, photos, manuals), warranty/service-agreement tracking, a per-asset task list, and a running notes timeline.

The Books screen manages the sets of books as first-class entities: create user-defined books, enable/disable each book, set its type (financial/tax/internal) and entry defaults, and assign which tax rule set drives its depreciation. Enabled depreciation books automatically apply to assets (new and existing), appear in the asset's Books tab, and are selectable in reports — the engine resolves rules per book rather than from a single global set. It also renders each book in a general-ledger table (asset cost, accumulated depreciation, and period expense rolled up to GL accounts with debit/credit and net book value), with PDF/Excel/CSV export.

Disposals support full and partial dispositions with automatic gain/loss against book net-book-value (with a live preview before committing), Form 4797 property typing, and impairment/write-down adjustments. Lease accounting captures each agreement and generates an ASC 842-style amortization schedule (present value, periodic interest/principal, lease-liability and right-of-use balances).

Barcode tracking lets you print Code 128 / Code 39 labels (single asset from the workbench, or a batch from the register) and includes a Scan screen: use a device camera to scan an asset's barcode (or type the code), then update its status, location, department, custodian, condition, and add a field note on the spot. Camera scanning requires HTTPS or localhost; manual code entry works everywhere.

The Maintenance screen builds reusable preventative-maintenance (PM) plans — each with a frequency (every N days/weeks/months/quarters/bi-yearly/years), an ordered step checklist with per-step time estimates (the plan's estimated minutes auto-totals from the steps), and a components/parts list (part #, description, supplier, cost). Plans are attached to assets from the asset's PM tab with a per-asset interval (default set in Admin) and an end date pre-filled to the asset's depreciation end. Completing a service opens a structured form — performed-by (the logged-in user), a required signature, 15★ condition ratings (Safety/Is it safe?, Physical condition, Operating condition), dynamic completion notes, and photos — then checks off steps, captures parts cost, records history, and rolls the next-due date forward; due/overdue PM raises alerts. Completed forms (with signature, ratings, notes, and photos) are viewable from the Maintenance screen and from each asset's PM tab. Maintenance spend is tracked per asset in a dedicated PM book (Books screen → general ledger) that aggregates completion costs.

The Parts inventory screen (a sub-menu under Maintenance) is a standalone inventory of maintenance parts & supplies, kept separate from the depreciation register. Look up a part to see whether it's in stock, which aisle/bin to get it from, and how much is reserved vs available; add identification details like photos, measurements, manufacturer part #, and barcode. Stock can live in one or more Work Location contacts, and each part's supplier is chosen from the Contacts directory (vendors, service providers, or contractors). Stock movements (receive, issue, adjust, reserve, unreserve) are recorded with full history, and parts at/below their reorder point are flagged as low stock.

The Contacts screen is a directory of people and organizations — Employees, Vendors, Service Providers, Contractors, Work Locations, and Other — each with name, international-friendly address and phone, email, and notes, plus type-specific fields (employee ID/department/manager/start date; contractor tax ID or business license; vendor/service-provider star rating and credit term) and a per-contact notes log. It works fully without Workday; when Workday is connected, employee records are synced into Contacts with manager and start-date enrichment, and the sync can be scheduled automatically from the Admin → Workday panel.

The Alerts screen surfaces reminders for assets needing maintenance, overdue tasks, preventative-maintenance due dates, and warranties/leases about to expire. A background job (and an on-demand scan) computes alerts with a configurable lead time and severity, and — when SMTP is configured and alerts are enabled — emails a digest of new items via Nodemailer. SMTP/email server settings live in a dedicated panel in the Admin pane (the password is write-only and never returned by the API).

The Reports workbench is driven by a server report catalog covering depreciation (annual, monthly, quarterly, YTD, net book value, lifetime, projection), activity (rollforward, acquisitions, disposals & gain/loss, adjustments, construction-in-progress), journal entries (depreciation, disposals), tax (Form 4562, Form 4797, AMT, ACE, personal property tax, UBIA for §199A), a fixed-asset card, and a custom Report Builder with selectable columns. Every report can be saved with its options for reuse and exported to PDF, Excel, or CSV.

Run

npm install
npm run dev

Open http://localhost:5173.

Default seeded login:

admin@deprecore.local
ChangeMe123!

Scripts

npm run dev      # API + Vite SPA
npm run build    # production SPA build
npm start        # Express API and built SPA
npm test         # API smoke test

Configuration

Environment variables are shown in .env.example. The app also stores editable runtime settings in SQLite through the Admin screen, including server FQDN, allowed CORS domains, and database metadata.

Variable Default Purpose
DEPRECORE_PORT (or PORT) 3000 API/server port
DEPRECORE_DATA_DIR ./data Data directory
DEPRECORE_DB_PATH ./data/deprecore.sqlite SQLite database file
DEPRECORE_SERVER_FQDN localhost Server FQDN (also editable in Admin)
DEPRECORE_CORS_ORIGINS http://localhost:5173,... Allowed CORS origins (comma-separated)
DEPRECORE_JWT_SECRET dev fallback JWT signing secret — set a long random value in production
DEPRECORE_ADMIN_PASSWORD ChangeMe123! Password for the seeded admin user

API Reference

The API is served under /api. All payloads are JSON unless noted (file upload and export endpoints differ).

Authentication

Authenticate with POST /api/auth/login to receive a JWT, then send it on every other request:

Authorization: Bearer <token>

Tokens expire after 12 hours. The only routes that do not require a token are GET /api/health, GET /api/public/bootstrap, and POST /api/auth/login.

# Login
curl -s http://localhost:3000/api/auth/login \
  -H 'Content-Type: application/json' \
  -d '{"email":"admin@deprecore.local","password":"ChangeMe123!"}'
# => { "token": "<jwt>", "user": { "id":1, "name":"...", "role":"admin", ... } }

Roles

Four roles gate write access: admin, finance, operations, viewer. The capability matrix is returned by GET /api/access-control. Endpoints below list the roles allowed to call them; read endpoints are available to any authenticated user.

Errors & status codes

Errors return { "error": "message" } with an appropriate status:

Status Meaning
400 Validation error (bad body, invalid JSON, missing required field)
401 Missing/expired token or invalid credentials
403 Authenticated but role not permitted
404 Resource not found
500 Unhandled server error

Endpoints

Auth & profile

Method Path Roles Description
GET /api/health public Liveness check
GET /api/public/bootstrap public App name, setup status, DB driver
POST /api/auth/login public Exchange credentials for a JWT
GET /api/profile any Current user + preferences
PUT /api/profile any Update preferences (e.g. theme)

Dashboard & reference data

Method Path Roles Description
GET /api/dashboard any Portfolio stats, charts, recent audit trail
GET /api/entities any List entities/companies
POST /api/entities admin, finance Create an entity
GET /api/references any Reference values (locations, departments, categories)

Assets

Method Path Roles Description
GET /api/assets any List/filter assets (?search=&status=&entity_id=&category=)
POST /api/assets admin, finance, operations Create asset (optionally with books[])
GET /api/assets/lookup?code= any Resolve by barcode value, asset ID, or serial number
GET /api/assets/:id any Hydrated asset (books, files, warranties, tasks, notes, disposals, adjustments, leases)
PUT /api/assets/:id admin, finance, operations Update asset (and books[] when provided)
DELETE /api/assets/:id admin, finance Delete asset
POST /api/assets/mass-update admin, finance Bulk field update { ids:[], changes:{} }
GET /api/assets/:id/depreciation any Multi-year schedule across active books (?startYear=&endYear=)
POST /api/assets/:id/files admin, finance, operations Upload a file (multipart file)
GET /api/assets/:id/files/:fileId any Download a stored file
DELETE /api/assets/:id/files/:fileId admin, finance, operations Delete a file
POST /api/assets/:id/photos admin, finance, operations Add an identification photo { data, name, mime, caption } (base64 data URL)
PUT /api/assets/:id/photos/:photoId admin, finance, operations Update a photo caption
DELETE /api/assets/:id/photos/:photoId admin, finance, operations Delete a photo
POST /api/assets/:id/warranties admin, finance, operations Add warranty/service agreement
DELETE /api/assets/:id/warranties/:warrantyId admin, finance, operations Delete warranty
POST /api/assets/:id/tasks admin, finance, operations Add task
PUT /api/assets/:id/tasks/:taskId admin, finance, operations Update task (e.g. mark complete)
DELETE /api/assets/:id/tasks/:taskId admin, finance, operations Delete task
POST /api/assets/:id/notes admin, finance, operations Add timestamped note
DELETE /api/assets/:id/notes/:noteId admin, finance, operations Delete note

Disposals & adjustments

Method Path Roles Description
POST /api/assets/:id/disposal/preview any Compute gain/loss without committing
POST /api/assets/:id/disposals admin, finance Record full/partial disposal
DELETE /api/assets/:id/disposals/:disposalId admin, finance Reverse a disposal
POST /api/assets/:id/adjustments admin, finance Record impairment/write-down/write-up
DELETE /api/assets/:id/adjustments/:adjustmentId admin, finance Delete an adjustment

Disposal body: { book_type, disposal_date, disposal_price, disposal_expense, method, property_type, partial, disposed_cost, notes }. Adjustment body: { type, amount, book_type, adjustment_date, reason } where type is impairment | write_down | write_up | revaluation.

Leases

Method Path Roles Description
GET /api/leases any List leases (?asset_id=)
POST /api/leases admin, finance Create lease
PUT /api/leases/:id admin, finance Update lease
DELETE /api/leases/:id admin, finance Delete lease
GET /api/leases/:id/schedule any ASC 842-style amortization schedule

Lease body: { asset_id, lessor, contract_value, start_date, end_date, discount_rate, payment_frequency } where payment_frequency is monthly | quarterly | semiannual | annual.

Books & general ledger

Method Path Roles Description
GET /api/books any Book definitions + available rule sets
POST /api/books admin, finance Create a user-defined book { code, name, book_type, ... }
PUT /api/books/:id admin, finance Update book (name, type, enabled, tax_rule_set_id, defaults)
DELETE /api/books/:id admin, finance Delete a book (blocked for primary/PM books or books in use)
GET /api/books/:code/ledger?year= any GL rollup + per-asset detail for a book
POST /api/books/:code/ledger/export any Export the GL ({ year, format }, format `pdf

Assignments & employees

Method Path Roles Description
GET /api/employees any List employees (?search=&status=)
POST /api/employees admin, finance, operations Create/upsert employee
GET /api/assignments any List assignments + summary (?asset_id=&employee_id=&active=true)
POST /api/assignments admin, finance, operations Assign asset to employee/department/location
PUT /api/assignments/:id/release admin, finance, operations Release an assignment

Contacts

Method Path Roles Description
GET /api/contacts any List contacts (?type=&search=&status=)
GET /api/contacts/:id any Single contact with its notes log
POST /api/contacts admin, finance, operations Create a contact
PUT /api/contacts/:id admin, finance, operations Update a contact
DELETE /api/contacts/:id admin, finance Delete a contact
POST /api/contacts/:id/notes admin, finance, operations Add a note
DELETE /api/contacts/:id/notes/:noteId admin, finance, operations Delete a note

Contact type is employee | vendor | service_provider | contractor | work_location | other. Vendor/provider credit_term is na | net_30 | net_60 | net_90 | net_180; rating is 15. The Workday connector (/api/workday/*) upserts employee contacts; PUT /api/workday/connection accepts sync_enabled and sync_interval_hours for the automatic scheduled sync.

Templates (data-entry forms)

Method Path Roles Description
GET /api/templates any List asset entry templates
POST /api/templates admin, finance Create a template (defaults + custom fields)

Tax rule sets ("tax templates")

Method Path Roles Description
GET /api/tax-rules any List rule sets (parsed JSON)
POST /api/tax-rules admin, finance Create a rule set
PUT /api/tax-rules/:id admin, finance Update a rule set
DELETE /api/tax-rules/:id admin, finance Delete a rule set
POST /api/tax-rules/:id/activate admin, finance Activate (deactivates others in the same jurisdiction)
POST /api/tax-rules/expand admin, finance Merge the generated 68-method catalog into a draft

See Tax Rule Sets below for the full JSON schema.

Reports

Method Path Roles Description
GET /api/reports/catalog any Report types + parameter specs
POST /api/reports/run any Run a report { type, options }{ report }
POST /api/reports/export any Export { type, options, format } (`pdf
GET /api/saved-reports any List saved reports
POST /api/saved-reports admin, finance, operations Save a report { name, report_type, options }
DELETE /api/saved-reports/:id admin, finance, operations Delete a saved report
GET /api/reports/depreciation any Legacy annual depreciation (?year=&book=)
GET /api/reports/monthly-depreciation any Legacy monthly depreciation
GET /api/reports/depreciation.pdf any Legacy PDF

Report types include depreciation_expense, monthly_depreciation, quarterly_depreciation, ytd_depreciation, net_book_value, lifetime_depreciation, projection, rollforward, acquisitions, disposals, adjustments, cip, journal_depreciation, journal_disposals, form_4562, form_4797, amt, ace, personal_property_tax, ubia, fixed_asset_card, custom, and the maintenance set: pm_due (PM due before a selectable date), pm_uncompleted (assets with overdue PM), pm_completed (PM completed since a selectable date), pm_plan (printable individual PM plan via selector), and alerts_since (alerts raised since a selectable date). Parameter types include select, multiselect, number, and date. A report result has the shape { title, columns:[{key,label,format}], rows:[], totals:{}, chart?, meta? }.

Import / export

Method Path Roles Description
GET /api/export/assets?format= any Export assets (`json
POST /api/import/assets admin, finance Import assets (multipart file: JSON/CSV/XLSX/XML)

Preventative maintenance

Method Path Roles Description
GET /api/pm-plans any List PM plans (steps/components; photo photo_count only, no blobs)
GET /api/pm-plans/:id any One plan with full guidance-photo image data
POST /api/pm-plans admin, finance, operations Create a plan { name, frequency_value, frequency_unit, steps[], components[], photos[] } (steps carry est_minutes; estimated minutes auto-total)
PUT /api/pm-plans/:id admin, finance, operations Update a plan (photos: resend existing as { id, caption }, new as { data, name, mime, caption })
DELETE /api/pm-plans/:id admin, finance Delete a plan
GET /api/pm-settings any PM defaults (frequency + alert lead days)
PUT /api/pm-settings admin Save PM defaults
GET /api/assets/:id/pm any Asset's attached PM schedules + completions
POST /api/assets/:id/pm admin, finance, operations Attach a plan { plan_id, frequency_value, frequency_unit, start_date, end_date }
PUT /api/assets/:id/pm/:apId admin, finance, operations Update an asset's PM schedule
DELETE /api/assets/:id/pm/:apId admin, finance, operations Detach a schedule
POST /api/assets/:id/pm/:apId/complete admin, finance, operations Record a service { steps[], components[], cost, notes_list[], ratings{safety,physical,operating}, signature, photos[] } (signature required) and advance next-due
GET /api/pm-completions any List completed PM forms (?asset_id=)
GET /api/pm-completions/:id any Full completion form (signature, ratings, notes, photos)

frequency_unit is days | weeks | months | quarters | semiannual | years. PM schedules feed the alerts engine (pm_due / pm_overdue).

Photos. Assets carry identification photos (Photos tab in the asset workbench) — what it looks like, where it is, nameplate/serial labels — each with an optional caption. PM plans carry guidance photos (diagrams, part locations, before/after) that appear to the technician on the completion form. On a phone, the "Add photos" buttons open the camera (capture). Photos are stored as base64 data URLs; plan guidance photos are also surfaced on each asset's PM schedule (/api/assets/:id/pm).

Parts inventory

A standalone inventory of maintenance parts & supplies (sub-menu under Maintenance). It is not part of the depreciation register — parts are not assets and do not depreciate. Each part tracks stock per location with aisle/bin, on-hand vs reserved quantities, a reorder point for low-stock flagging, identification details (measurements, manufacturer part #, barcode, photos), a supplier chosen from Contacts (vendors/service providers/contractors), and stock stored at one or more Work Location contacts.

Method Path Roles Description
GET /api/parts any List parts with aggregated stock (?search=&category=&status=&low_stock=true)
GET /api/parts/:id any Single part with stock locations, photos, and movement history
POST /api/parts admin, finance, operations Create a part { part_number, name, category, unit_of_measure, unit_cost, reorder_point, reorder_quantity, measurements, manufacturer, barcode, supplier_contact_id, status }
PUT /api/parts/:id admin, finance, operations Update a part
DELETE /api/parts/:id admin, finance Delete a part
POST /api/parts/:id/stock admin, finance, operations Create/update a stock location { id?, location_contact_id, aisle, bin, quantity, reserved }
DELETE /api/parts/:id/stock/:stockId admin, finance, operations Remove a stock location
POST /api/parts/:id/transactions admin, finance, operations Record a movement { stock_id, type, quantity, asset_id?, notes? }
POST /api/parts/:id/photos admin, finance, operations Attach a photo { name, mime, data } (base64 data URL)
DELETE /api/parts/:id/photos/:photoId admin, finance, operations Delete a photo

Movement type is receive (add on-hand), issue (consume), adjust (set on-hand to an absolute count), reserve, or unreserve. low_stock is flagged when on-hand ≤ a non-zero reorder point. available = on-hand reserved. All stock/movement/photo endpoints return the fully hydrated part.

Alerts & notifications

Method Path Roles Description
GET /api/alerts any List alerts (?status=&type=) + summary
POST /api/alerts/scan admin, finance, operations Recompute alerts from due/expiring items
POST /api/alerts/run admin Scan and notify (email digest and/or webhook) on new alerts
PUT /api/alerts/:id/acknowledge admin, finance, operations Acknowledge an alert
PUT /api/alerts/:id/dismiss admin, finance, operations Dismiss an alert (won't reappear)
POST /api/alerts/:id/ticket admin, finance, operations Submit the alert as a ServiceNow incident (returns the existing one if already raised)
GET /api/notifications/settings admin SMTP + alert + webhook settings (password/secret masked)
PUT /api/notifications/settings admin Save settings (send smtp_password/webhook_secret only to change them; webhook_secret_clear: true removes the secret)
POST /api/notifications/test admin Send a test email ({ to } optional)
POST /api/notifications/webhook-test admin POST a test event to the configured webhook URL

Alert types: maintenance_overdue, maintenance_due, warranty_expired, warranty_expiring, lease_expiring, pm_due, pm_overdue. Severity is critical (overdue/expired or within 7 days) or warning. The server also runs the scan-and-notify cycle ~twice daily; it's a no-op unless something is configured.

Webhook delivery. When webhook_enabled is true and webhook_url is set, each newly raised alert is delivered as its own JSON POST (one request per alert) on the alert cycle — independently of, or alongside, email. An alert is marked notified once any channel handles it, so it is delivered at most once. If webhook_secret is set, the raw request body is signed with HMAC-SHA256 and sent in the X-DepreCore-Signature: sha256=<hex> header so the receiver can verify authenticity. Per-alert payload:

{
  "event": "alert",
  "id": 142, "type": "pm_overdue", "severity": "critical",
  "title": "PM overdue: Lift quarterly service",
  "message": "Lift quarterly service on FA-1042 was due 2026-05-20.",
  "due_date": "2026-05-20", "status": "open",
  "reference_type": "asset_pm", "reference_id": 88,
  "asset_id": 1042, "asset_code": "FA-1042",
  "created_at": "2026-06-04T12:00:00.000Z", "sent_at": "2026-06-04T12:05:00.000Z"
}

Admin & integrations

Method Path Roles Description
GET /api/settings admin App settings
PUT /api/settings admin Update settings { settings:{} }
GET /api/users admin List users
POST /api/users admin Create user
PUT /api/users/:id admin Update user (role/status/team/password)
GET /api/teams admin List teams
POST /api/teams admin Create team
GET /api/access-control admin Roles + capability matrix
GET /api/workday/connection admin Workday connector config
PUT /api/workday/connection admin Save Workday connector
POST /api/workday/sync-workers admin Pull workers from Workday
POST /api/workday/import-workers admin Import a worker payload
GET /api/servicenow/settings admin ServiceNow connection config (password masked)
PUT /api/servicenow/settings admin Save ServiceNow config (send servicenow_password only to change it; servicenow_password_clear: true removes it)
POST /api/servicenow/test admin Verify the connection (GETs the incident table)
POST /api/servicenow/cmdb-sync admin, finance Pull configuration items from the CMDB into the asset register

ServiceNow integration

Configured in Admin → ServiceNow integration using basic auth over HTTPS against a ServiceNow instance (https://<instance>.service-now.com).

Incident push. Alerts can be submitted as ServiceNow incidents via the Table API (POST /api/now/table/{incident_table}). From the Alerts screen, the ServiceNow action on any open alert creates an incident and links it back (number + deep link shown in the Ticket column). Severity maps to impact/urgency (critical → 1/1, warning → 2/2, else 3/3); optional assignment_group and caller_id are applied when set. With auto-create enabled, the alert cycle raises an incident for each new critical alert automatically. Alerts store the linked external_ticket_number / external_ticket_url and won't duplicate.

CMDB pull. The asset register can be populated from the CMDB via the Table API (GET /api/now/table/{ci_table}, default cmdb_ci_hardware), requested with display values. A JSON field map (asset field → CI column, with sensible hardware defaults) controls population; an optional sysparm_query filters which CIs are pulled. Existing assets are matched by ServiceNow sys_id, then serial number, then asset tag, so re-syncing updates in place rather than duplicating. The CI's sys_id is stored on the asset (servicenow_sys_id).


Tax Rule Sets ("tax templates")

A tax rule set is the JSON template that tells the depreciation engine which methods exist and how to compute them, plus the statutory limits for a jurisdiction (e.g. US-FED, US-CA). It satisfies the requirement for a JSON-driven system for federal/state/local depreciation-law updates.

Rule sets are stored in the tax_rule_sets table and edited in the Tax rules screen (Monaco JSON editor) or via the API. The starter federal template lives at data/tax-rules/federal-2026.json and is seeded on first run.

How a rule set is selected

Each book (GAAP, Federal, …) has a tax_rule_set_id. When the engine depreciates an asset for a given book, it uses that book's assigned rule set; if a book has no assignment, it falls back to the globally active rule set. Assign rule sets to books on the Books screen.

Because the engine looks up each asset book's depreciation_method by code inside the rule set's methods[] array, the assigned rule set must contain the method codes your assets use. Use Regenerate method catalog (UI) or POST /api/tax-rules/expand to inject the standard 68 methods, then add or edit any custom methods.

Top-level fields

{
  "schema": "deprecore.taxRuleSet.v1",   // optional label
  "jurisdiction": "US-FED",                 // unique key with version
  "name": "Federal 68-method depreciation catalog",
  "version": "2026.2",                      // unique per jurisdiction
  "effectiveDate": "2026-01-01",            // ISO date
  "sourceNote": "Validate against current IRS guidance before filing.",
  "books": ["GAAP", "FEDERAL", "STATE", "AMT", "ACE", "USER"],
  "limits": { /* see below */ },
  "conventions": ["half_year","mid_quarter","mid_month","full_month","none"],
  "assetLives": [ { "code": "5Y", "label": "5-year property", "months": 60 } ],
  "methods": [ /* see below  the part the engine computes from */ ],
  "catalogNotes": ["free-form notes"]
}
Field Type Notes
jurisdiction string Paired with version as a unique key; assign to a book by id
name, version, effectiveDate string Metadata; version must be unique within a jurisdiction
limits object Statutory thresholds (reference/advisory — see below)
conventions string[] Conventions offered in the UI
assetLives object[] { code, label, months } reference table of class lives
methods object[] Required for calculation — the method catalog (see below)

limits object

These values are stored for compliance reference and surfaced in the UI. The engine applies §179 and bonus per asset-book (from each book's section_179_amount and bonus_percent); the limits document the statutory caps your data entry should respect.

"limits": {
  "deMinimisSafeHarbor": {
    "defaultThreshold": 2500,
    "auditedFinancialStatementThreshold": 5000
  },
  "section179": {
    "deductionLimit": 2500000,
    "phaseOutBegins": 4000000,
    "cannotCreateLoss": true
  },
  "bonusDepreciation": {
    "qualifiedPropertyPercent": 100,
    "effectivePlacedInServiceAfter": "2025-01-19"
  }
}

methods[] — the calculation catalog

Each method is referenced by an asset book's depreciation_method (its code). Shape:

Field Type Used by Notes
code string all Unique identifier referenced by asset books
family string UI Grouping label: MACRS, ACRS, GAAP, Manual, Lease
label string UI Human-readable name
formula string engine One of the formulas below — drives the math
lifeMonths number engine Optional; overrides the book/asset useful life
defaultConvention string engine Convention applied for first/last partial years
rateMultiplier number engine Declining-balance rate (e.g. 2 = 200%, 1.5 = 150%)
switchToStraightLine boolean engine MACRS-style switch to SL when it yields more
rates number[] engine For rate_table: per-year fraction of basis
fallbackFormula string engine Used when rates is empty (e.g. straight_line)
system, legacy, sourceNote, requiresMonthTable, requiresListedPropertyTable mixed reference Informational only

Supported formula values

formula Computation Honors fields
straight_line basis / years, prorated by convention lifeMonths, defaultConvention
sum_of_years_digits SYD acceleration lifeMonths
declining_balance Declining balance at rateMultiplier; optional SL switch rateMultiplier, switchToStraightLine, defaultConvention, lifeMonths
macrs_declining_balance Same as declining_balance (MACRS GDS/ADS) same as above
rate_table basis × rates[year]; falls back to fallbackFormula if rates empty rates, fallbackFormula, lifeMonths
acrs_alternate_straight_line Straight-line forced to half-year convention (legacy ACRS) lifeMonths
manual Reads per-year amounts from the asset book's manual_depreciation map
(unknown) Falls back to straight_line

Depreciable basis & conventions

The engine computes, for each asset book:

depreciable basis = max(0, (cost  land  salvage) × businessUse%)   §179  bonus
§179  = min(basis, book.section_179_amount)
bonus = (basis  §179) × book.bonus_percent / 100      (taken in year 1)

First-year proration by convention (subsequent years run full, with the remainder pushed into the final year):

Convention First-year fraction
half_year 0.5
mid_quarter Q1 0.875, Q2 0.625, Q3 0.375, Q4 0.125
mid_month (12 month + 0.5) / 12
full_month (13 month) / 12
none 1 (full year)

The effective life is ceil(lifeMonths / 12) where lifeMonths is the method's lifeMonths, else the book's useful_life_months, else the asset's, else 60.

Worked examples

A minimal rule set with one custom straight-line method and one rate-table method:

{
  "jurisdiction": "US-CA",
  "name": "California starter",
  "version": "2026.1",
  "effectiveDate": "2026-01-01",
  "limits": { "section179": { "deductionLimit": 25000, "phaseOutBegins": 200000 } },
  "conventions": ["half_year", "mid_month", "none"],
  "assetLives": [{ "code": "5Y", "label": "5-year", "months": 60 }],
  "methods": [
    {
      "code": "ca_sl_5",
      "family": "GAAP",
      "label": "CA straight-line 5-year",
      "formula": "straight_line",
      "lifeMonths": 60,
      "defaultConvention": "half_year"
    },
    {
      "code": "ca_macrs_5",
      "family": "MACRS",
      "label": "CA MACRS 5-year (table)",
      "formula": "rate_table",
      "lifeMonths": 60,
      "defaultConvention": "half_year",
      "rates": [0.20, 0.32, 0.192, 0.1152, 0.1152, 0.0576]
    }
  ]
}

A 200% declining-balance MACRS method with the straight-line switch:

{
  "code": "macrs_gds_200db_5",
  "family": "MACRS",
  "label": "MACRS GDS 200% DB 5-year",
  "formula": "macrs_declining_balance",
  "lifeMonths": 60,
  "rateMultiplier": 2,
  "switchToStraightLine": true,
  "defaultConvention": "half_year"
}

Creating a tax template

In the UI (Tax rules screen, admin/finance):

  1. Click New (blank draft) or Upload JSON to load a file into the editor.
  2. Fill in metadata (name, jurisdiction, version, effective date) and edit the JSON body in the Monaco editor — invalid JSON disables Save and shows the parse error.
  3. Optionally click Regenerate method catalog to inject the standard 68 methods, then add/edit custom methods.
  4. Save, then Activate to make it the active set for its jurisdiction. Export downloads the JSON; assign it to books on the Books screen.

Via the API:

TOKEN=... # from /api/auth/login

# (optional) expand a draft to include the 68-method catalog
curl -s http://localhost:3000/api/tax-rules/expand \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"rules_json":{"jurisdiction":"US-CA","version":"2026.1"}}'

# create
curl -s http://localhost:3000/api/tax-rules \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{
        "jurisdiction":"US-CA","name":"California starter","version":"2026.1",
        "effective_date":"2026-01-01","active":false,
        "rules_json":{ "jurisdiction":"US-CA","version":"2026.1","methods":[ /* ... */ ] }
      }'

# activate (deactivates other US-CA sets)
curl -s -X POST http://localhost:3000/api/tax-rules/<id>/activate \
  -H "Authorization: Bearer $TOKEN"

Then assign the rule set to a book:

curl -s -X PUT http://localhost:3000/api/books/<bookId> \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"tax_rule_set_id": <ruleSetId>}'

Asset entry templates

Separate from tax rule sets, asset entry templates (/api/templates) speed up data entry by pre-filling defaults and adding custom fields. A template has:

  • defaults — an object of asset fields applied on selection (e.g. category, useful_life_months, depreciation_method, GL accounts).
  • custom_fields — an array of field definitions: { key, label, type, required, defaultValue } where type is text | numeric | date | bool.
{
  "name": "Laptop or workstation",
  "description": "Standard computer equipment, 5-year life",
  "defaults": { "category": "Computer Equipment", "useful_life_months": 60, "depreciation_method": "macrs_gds_200db_5" },
  "custom_fields": [
    { "key": "assigned_employee_id", "label": "Employee ID", "type": "text", "required": false },
    { "key": "calibration_due", "label": "Calibration due", "type": "date", "required": false }
  ]
}

Compliance Note

The depreciation engine is built to support rule updates, projections, books, conventions, and method expansion. The included tax template is an engineering starter and should be validated against current IRS, state, and local guidance before production tax filing.