dodil/erp-brick-fixed-assets

DODIL ERP brick: fixed assets

src/brick_fixed_assets/application/process.yaml

# The brick's process — module.yaml's workflows, copied (pattern.md §15). BRICK.declare_process serves
# it as BPMN 2.0 at GET /process/bpmn; it is identical to `erp-compose bpmn --from module.yaml`.
# A transition's key (or its derived <entity>_<from>_to_<to>) is frozen once signed: it is the BPMN id.
id: fixed-assets
name: Fixed assets & depreciation
version: 3
actors:
- id: asset_accountant
  name: the person who keeps the asset register (financial accountant, bookkeeper)
  does: creates assets and classes' defaults, posts manual acquisitions, settles assets under construction, runs and simulates depreciation, proposes retirements, transfers, revaluations and impairments, opens asset counts and acts on their differences
  sees: every asset of the companies they keep
  permissions:
  - fa:asset:write
  - fa:asset:read
  - fa:acquisition:write
  - fa:auc:settle
  - fa:depreciation:simulate
  - fa:depreciation:run
  - fa:transaction:write
  - fa:count:manage
  - fa:report:read
  - fa:capital_allowance:read
- id: finance_approver
  name: the financial controller (finance manager)
  does: approves depreciation postings, retirements, transfers between companies, revaluations and impairments; approves the capital allowances computation; closes the asset year; keeps day-to-day settings; explains migration differences
  sees: everything
  permissions:
  - fa:asset:read
  - fa:depreciation:approve
  - fa:transaction:approve
  - fa:capital_allowance:approve
  - fa:capital_allowance:read
  - fa:year:close
  - fa:report:read
  - fa:settings:write
  - fa:migration:read
- id: tax_preparer
  name: whoever prepares the corporation tax computation (in-house tax, or the external accountant)
  does: decides per addition which allowance is claimed (AIA, full expensing, first-year, pool), keeps the opening pools, prepares the capital allowances computation for each chargeable period
  sees: the tax area of every asset; not the book areas' journals
  permissions:
  - fa:asset:read
  - fa:capital_allowance:write
  - fa:capital_allowance:read
  - fa:report:read
- id: asset_custodian
  name: whoever looks after the assets on a site or in a department (site manager, office manager, IT)
  does: records units used for assets depreciated by use, counts the assets in their location or cost centre, reports one missing or damaged
  sees: the assets in their own location or cost centre
  permissions:
  - fa:asset:read_own
  - fa:usage:write
  - fa:count:write
- id: owner
  name: The owner (or whoever the owner names)
  does: holds the controls on the people who value assets (the depreciation areas and the ledger each posts to, the depreciation keys, the account determination per class, the capitalisation threshold, number formats, whether a depreciation posting needs a second person) and signs the migration reconciliation
  sees: everything
  permissions:
  - fa:asset:read
  - fa:report:read
  - fa:settings:controls
  - fa:migration:read
  - fa:migration:sign
  note: 'the controls are never held by whoever they control: an asset_accountant, finance_approver or tax_preparer holding fa:settings:controls is a configuration error the access test refuses'
- id: system
  name: The system
  does: stamps numbers, takes acquisitions from purchasing, computes planned depreciation, posts journals to gl per area, answers gl's reconciliation and close questions, suggests a class and life (suggest), loads and reconciles a migration
  sees: everything, through its own service account
  permissions:
  - fa:number_series:claim
  - fa:acquisition:intake
  - fa:journal:post
  - fa:depreciation:read
  - fa:asset:ai_suggest
  - fa:migration:load
  - fa:migration:reconcile
  note: 'each is an exact grant to one service client, never reached by a wildcard (permissions.machine): fa:acquisition:intake to purchasing''s client, fa:depreciation:read (and fa:asset:read for GET /assets/balances) to gl''s client, the rest to this module''s own'
workflows:
- entity: asset
  process: acquire
  states:
  - planned
  - active
  - retired
  - deactivated
  terminal:
  - deactivated
  transitions:
  - key: create_asset
    name: Create the asset
    from: none
    to: planned
    by: asset_accountant
    gate: a class in force today and a company
    effect: asset_no claimed from the class series; sub-number 0; defaults per area copied from the class; keyed on the request id
  - key: add_sub_asset
    name: Add a component or a later addition
    from: planned
    to: planned
    by: asset_accountant
    gate: the main asset exists in the same company and class
    effect: the next sub_no, its own key and life per area
  - key: ai_suggest_asset_class
    name: AI suggests the class, life and tax pool (suggest)
    from: planned
    to: planned
    by: system
    gate: the step is on
    effect: a suggestion per field with its evidence, in fa_ai_finding
  - key: accept_asset_suggestion
    name: Accept or correct each suggested field
    from: planned
    to: planned
    by: asset_accountant
    effect: each decision recorded; nothing applied without the accountant
  - key: take_acquisition
    name: Take the acquisition from purchasing
    from:
    - planned
    - active
    to: active
    by: system
    gate: the asset is of the bill's company and not retired; the period is open in every posting area's ledger
    effect: a transaction kind acquisition per area keyed acquisition_ref; journals per posting area (debit class cost, credit asset clearing); capitalised_on set by the first acquisition
  - key: post_manual_acquisition
    name: Post a manual acquisition
    from:
    - planned
    - active
    to: active
    by: asset_accountant
    gate: an offset account other than asset clearing; the value date today or earlier; above the capitalisation threshold or the class is LVA
    effect: a transaction kind acquisition (or grant, negative) per area; journals per posting area
  - key: change_asset_master
    name: Change location
    cost centre or life from a date: null
    from:
    - planned
    - active
    to:
    - planned
    - active
    by: asset_accountant
    gate: starts after today
    effect: a new dated row; depreciation recalculated prospectively from its date; posted periods never recomputed
  - key: record_usage
    name: Record units used this period
    from: active
    to: active
    by: asset_custodian
    gate: the asset's key is units_of_production; the period is not after today's; the units fit what remains
    effect: a usage row (asset, period, seq)
  - key: retire_fully
    name: The last value leaves the register
    from: active
    to: retired
    by: system
    gate: an approved retirement or transfer out leaves cost 0 in every area
    effect: status retired; derived from the transactions
  - key: deactivate_asset
    name: Deactivate a planned asset never capitalised
    from: planned
    to: deactivated
    by: asset_accountant
    requires: reason
    gate: no transaction in any area
    effect: kept with its number; never reused
  note: an asset's values per area are the SUM of its transactions; active and retired are derived from them, never set by hand
  readiness:
  - transition: create_asset
    gate: class_in_force
    reason: the class has no row in force today
    fix: 'Settings: asset classes (owner)'
  - transition: create_asset
    gate: number_series
    reason: the class has no number series for this year
    fix: 'Settings: number formats (owner)'
  - transition: take_acquisition
    gate: same_company
    reason: the asset belongs to another company than the bill
    fix: assign the order line to an asset of the bill's company
  - transition: take_acquisition
    gate: not_retired
    reason: the asset is fully retired
    fix: assign a new asset, or a new sub-number
  - transition: take_acquisition
    gate: period_open
    reason: a posting area's ledger period for the bill date is closed
    fix: ask the accountant to reopen the period in gl; the hand-over is retried
  - transition: post_manual_acquisition
    gate: offset_not_clearing
    reason: 'asset clearing is purchasing''s: a manual acquisition offsets another account'
    fix: pick the account the money or credit really came from
  - transition: post_manual_acquisition
    gate: not_future
    reason: an acquisition cannot be dated after today
    fix: post it on the day of the document
  - transition: post_manual_acquisition
    gate: above_threshold
    reason: the amount is below the capitalisation threshold for a class that is not LVA
    fix: expense it, or use class LVA
  - transition: change_asset_master
    gate: starts_after_today
    reason: a change starts tomorrow at the earliest
    fix: pick a later date
  - transition: record_usage
    gate: within_total
    reason: the units are more than the asset has left
    fix: check the reading, or extend the total units (a dated life change)
  - transition: deactivate_asset
    gate: never_capitalised
    reason: the asset has transactions
    fix: retire it instead (FA-16)
- entity: auc_settlement
  process: acquire
  states:
  - draft
  - settled
  - reversed
  terminal:
  - reversed
  transitions:
  - key: draft_auc_settlement
    name: Draft the settlement rule
    from: none
    to: draft
    by: asset_accountant
    gate: an AuC asset with unsettled line items
    effect: per line item, receivers (asset, percent or amount), keyed (auc asset, settlement date, seq)
  - key: settle_auc
    name: Settle into the finished assets
    from: draft
    to: settled
    by: asset_accountant
    gate: each line item distributed exactly 100% or by amounts not above what is unsettled; every receiver is an asset of the same company; the period is open
    effect: a claim row per (line item, receiver); credit the AuC and debit each receiver per area; the receivers' capitalisation date = the settlement date
  - key: reverse_auc_settlement
    name: Reverse a settlement
    from: settled
    to: reversed
    by: asset_accountant
    requires: reason
    gate: no receiver has been depreciated, retired or transferred since
    effect: reversing transactions dated today; the line items are unsettled again
  readiness:
  - transition: settle_auc
    gate: fully_distributed
    reason: a line item's rule does not add up to 100% (or exceeds what is unsettled)
    fix: complete the rule
  - transition: settle_auc
    gate: not_settled_twice
    reason: a line item is already settled (another settlement claimed it)
    fix: reverse that settlement first
  - transition: reverse_auc_settlement
    gate: receivers_untouched
    reason: a receiver has been depreciated or changed since
    fix: retire or transfer from the receiver instead
- entity: depreciation_run
  process: asset_accounting
  states:
  - simulated
  - approved
  - posted
  - reversed
  terminal:
  - reversed
  transitions:
  - key: simulate_depreciation
    name: Simulate the period
    from: none
    to: simulated
    by: asset_accountant
    gate: a period of an open asset year, not after today's period
    effect: planned-to-date minus posted per asset, area and period; keyed (company_code, period_id, run_no); nothing posted
  - key: resimulate_depreciation
    name: Simulate again after a change
    from: simulated
    to: simulated
    by: asset_accountant
    effect: the figures recomputed from rows
  - key: approve_depreciation_run
    name: Approve the run
    from: simulated
    to: approved
    by: finance_approver
    approval: true
    gate: the approver did not start the run (email AND sub); no gate fails (usage_missing, account_determination)
    effect: who, when (today in the business time zone)
  - key: post_depreciation_run
    name: Post the run per area to its ledger
    from: approved
    to: posted
    by: system
    gate: each posting area's ledger period is open
    effect: one journal per area, keyed derived_id(run_id, area); posted rows per asset, area and period
  - key: post_unapproved_run
    name: Post without a second person (owner switched it off)
    from: simulated
    to: posted
    by: system
    gate: the owner's control depreciation_needs_approval is false
    effect: as post_depreciation_run; the starter recorded as poster
  - key: reverse_depreciation_run
    name: Reverse the run
    from: posted
    to: reversed
    by: finance_approver
    approval: true
    requires: reason
    gate: the year is not closed; the period of today is open
    effect: reversing journals per area dated today; reversing posted rows; the next run plans the period again
  readiness:
  - transition: simulate_depreciation
    gate: not_in_advance
    reason: the period is after the current period
    fix: run it in its own month
  - transition: approve_depreciation_run
    gate: second_person
    reason: you started this run (same email or same sign-in)
    fix: another approver
  - transition: approve_depreciation_run
    gate: usage_missing
    reason: a units-of-production asset has no reading for the period
    fix: the custodian records the units (zero is a reading)
  - transition: approve_depreciation_run
    gate: account_determination
    reason: a class has no account in force for an area
    fix: 'Settings: account determination (owner)'
  - transition: post_depreciation_run
    gate: period_open
    reason: a posting area's ledger period is closed
    fix: reopen the period in gl, or post in the next run
  - transition: reverse_depreciation_run
    gate: year_open
    reason: the asset year is closed
    fix: correct it in the next year's run
- entity: asset_transaction
  process: asset_accounting
  states:
  - proposed
  - approved
  - posted
  - rejected
  - reversed
  terminal:
  - rejected
  - reversed
  transitions:
  - key: propose_retirement
    name: Propose a sale or a scrapping
    from: none
    to: proposed
    by: asset_accountant
    gate: the retirement date today or earlier; by amount, percent or quantity, not above what remains
    effect: depreciation to the retirement date by the key's convention; gain or loss per area computed
  - key: propose_asset_transfer
    name: Propose a transfer to another asset or company
    from: none
    to: proposed
    by: asset_accountant
    gate: the receiving asset, or company and class; a transfer variant (gross | net); the transfer date falls in a period whose depreciation is posted (transfer_period_posted)
    effect: the values leaving and arriving per area
  - key: propose_revaluation
    name: Propose a revaluation
    from: none
    to: proposed
    by: asset_accountant
    requires: evidence
    gate: the area's policy is the revaluation model
    effect: the change per area; reserve and profit or loss split
  - key: propose_impairment
    name: Propose an impairment or its reversal
    from: none
    to: proposed
    by: asset_accountant
    requires: evidence
    gate: not above the NBV; a reversal not above what the carrying amount would have been
    effect: the loss or reversal per area
  - key: approve_asset_transaction
    name: Approve the transaction
    from: proposed
    to: approved
    by: finance_approver
    approval: true
    gate: the approver is not the proposer (email AND sub); the asset's version unchanged since the proposal
    effect: who and when recorded
  - key: reject_asset_transaction
    name: Reject with a reason
    from: proposed
    to: rejected
    by: finance_approver
    requires: reason
  - key: post_asset_transaction
    name: Post per area to its ledger
    from: approved
    to: posted
    by: system
    gate: every company's and area's ledger period open
    effect: journals per company and posting area keyed derived_id(transaction_id, area); the tax area recorded, never posted
  - key: reverse_asset_transaction
    name: Reverse a posted transaction
    from: posted
    to: reversed
    by: finance_approver
    approval: true
    requires: reason
    gate: the year is open; nothing later on the asset depends on it
    effect: a reversing transaction dated today
  note: a transfer between companies is ONE transaction with two legs (retire in the sender, acquire in the receiver); both post or neither
  readiness:
  - transition: propose_retirement
    gate: within_remaining
    reason: more than what remains of the asset (another retirement claimed it)
    fix: lower the amount or percent
  - transition: propose_retirement
    gate: not_future
    reason: a retirement cannot be dated after today
    fix: record it on the day it left
  - transition: propose_impairment
    gate: not_above_nbv
    reason: the impairment is more than the net book value
    fix: lower it to the NBV at most
  - transition: approve_asset_transaction
    gate: second_person
    reason: you proposed this (same email or same sign-in)
    fix: another approver
  - transition: approve_asset_transaction
    gate: asset_unchanged
    reason: the asset changed since the proposal
    fix: propose again from the current values
  - transition: post_asset_transaction
    gate: period_open
    reason: a company's or area's ledger period is closed
    fix: reopen it in gl
- entity: capital_allowance_computation
  process: asset_accounting
  states:
  - draft
  - prepared
  - approved
  - filed
  terminal:
  - filed
  transitions:
  - key: draft_ca_computation
    name: Open the computation for a chargeable period
    from: none
    to: draft
    by: tax_preparer
    gate: the previous period filed, or opening pools entered
    effect: keyed (company_code, period_start, period_end); pools, additions and disposals derived from the tax area
  - key: choose_allowance
    name: Choose the allowance for an addition
    from: draft
    to: draft
    by: tax_preparer
    gate: the claim fits the asset (AIA not on a car; full expensing only for a company's new and unused main-pool plant)
    effect: a claim row per asset (aia | full_expensing | fya_50 | pool | single_asset | sba | none) and amount
  - key: prepare_ca_computation
    name: Prepare the computation
    from: draft
    to: prepared
    by: tax_preparer
    gate: every addition has a claim; the AIA claimed within the company's allocated limit for the period
    effect: WDA, balancing allowances and charges, closing pools computed and kept
  - key: approve_ca_computation
    name: Approve the computation
    from: prepared
    to: approved
    by: finance_approver
    approval: true
    gate: the approver is not the preparer (email AND sub)
  - key: return_ca_computation
    name: Send back with a reason
    from:
    - prepared
    - approved
    to: draft
    by: finance_approver
    requires: reason
  - key: file_ca_computation
    name: Mark it filed with the return
    from: approved
    to: filed
    by: finance_approver
    requires: return_reference
    effect: frozen; later changes land in the next period as adjustments
  readiness:
  - transition: choose_allowance
    gate: claim_allowed
    reason: this allowance is not available for this asset (a car cannot take AIA)
    fix: pick the pool the asset belongs to
  - transition: prepare_ca_computation
    gate: aia_within_limit
    reason: AIA claimed is above the company's share of the limit for this period
    fix: move the excess to the pool, or change the group allocation (owner)
  - transition: approve_ca_computation
    gate: second_person
    reason: you prepared this computation (same email or same sign-in)
    fix: another approver
- entity: asset_count
  process: acquire
  states:
  - open
  - counted
  - closed
  terminal:
  - closed
  transitions:
  - key: open_asset_count
    name: Open a count for a location
    from: none
    to: open
    by: asset_accountant
    gate: a location or cost centre and a custodian
    effect: keyed (location, count_date); the expected list fixed
  - key: count_asset
    name: Record what is found
    from: open
    to: open
    by: asset_custodian
    gate: the count date today or earlier; the custodian's own location
    effect: a result per line (found | not_found | damaged | found_unlisted) with who counted
  - key: finish_asset_count
    name: Hand the count back
    from: open
    to: counted
    by: asset_custodian
    gate: every expected line has a result
  - key: decide_count_difference
    name: Decide each difference
    from: counted
    to: counted
    by: asset_accountant
    requires: decision
    effect: a proposal (retirement, transfer, new asset) or a note per difference
  - key: close_asset_count
    name: Close the count
    from: counted
    to: closed
    by: asset_accountant
    gate: every difference has a decision
    effect: last-counted date set on every found asset
  readiness:
  - transition: finish_asset_count
    gate: all_counted
    reason: 'lines without a result: named'
    fix: count them, or record not_found
  - transition: close_asset_count
    gate: differences_decided
    reason: a difference has no decision
    fix: decide it (retire, transfer, add, or note)
- entity: fa_migration_run
  process: asset_accounting
  states:
  - loaded
  - reconciled
  - signed
  - discarded
  terminal:
  - signed
  - discarded
  transitions:
  - key: load_assets_from_odoo
    name: Load the Odoo asset register
    from: none
    to: loaded
    by: system
    gate: the doors are decided; gl has mapped every account and the fiscal year; purchasing has mapped the bills used
    effect: classes, assets and takeover values written read-only, keyed odoo:<db>:<model>:<id>; a re-run changes nothing (FA-30)
  - key: reconcile_assets_migration
    name: Reconcile against Odoo
    from: loaded
    to: reconciled
    by: system
    effect: every migration.odoo.reconcile check computed on both sides, with the difference
  - key: explain_assets_difference
    name: Explain each difference
    from: reconciled
    to: reconciled
    by: finance_approver
    requires: reason
    gate: a check is not equal and may be explained
  - key: reload_assets_migration
    name: Load again after a fix
    from:
    - loaded
    - reconciled
    to: loaded
    by: system
    gate: not signed (a load after the signature is refused 409 not_signed)
  - key: sign_assets_migration
    name: Sign the reconciliation
    from: reconciled
    to: signed
    by: owner
    approval: true
    gate: every check equal, or explained where explained is allowed
    effect: who (email and sub), when and the run's checksums recorded; depreciation runs here from the next period
  - key: discard_assets_migration
    name: Discard the run
    from:
    - loaded
    - reconciled
    to: discarded
    by: owner
    requires: reason
  readiness:
  - transition: load_assets_from_odoo
    gate: doors_decided
    reason: a migration door is undecided
    fix: 'Migration: answer it'
  - transition: load_assets_from_odoo
    gate: accounts_mapped
    reason: Odoo asset, depreciation or expense accounts used have no gl account_id
    fix: run the gl migration first
  - transition: load_assets_from_odoo
    gate: asset_not_taken_over
    reason: this asset was taken over by a signed run (409 not_signed)
    fix: adjust it here; a signed run's assets are never reloaded
  - transition: load_assets_from_odoo
    gate: not_signed
    reason: 'this run is signed: signed history is never rewritten'
    fix: start a new run for what changed after cutover
  - transition: sign_assets_migration
    gate: zero_unexplained
    reason: 'checks neither equal nor explained: named'
    fix: explain each, or fix and reload
  - transition: sign_assets_migration
    gate: must_equal
    reason: a check that must be equal differs
    fix: fix the mapping and reload