dodil/erp-brick-timesheets

DODIL ERP brick: timesheets

src/brick_timesheets/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: timesheets
name: Timesheets
version: 1
actors:
- id: employee
  name: Anyone who records time
- id: manager
  name: Line manager (derived from hr)
- id: project_lead
  name: Project lead (from projects)
- id: finance
  name: Finance / the biller
- id: owner
  name: The owner (or whoever the owner names)
- id: system
  name: The system
workflows:
- entity: timesheet
  states:
  - open
  - submitted
  - approved
  - returned
  - superseded
  terminal:
  - returned
  - superseded
  transitions:
  - key: open_timesheet
    name: Open the week
    from: none
    to: open
    by:
    - employee
    - system
    gate: employed on a day of the week (hr)
    effect: sheet_id <person_id>:<iso week>, revision 1; expected minutes from hr; approved absences as read-only lines
  - key: submit_timesheet
    name: Submit the week
    from: open
    to: submitted
    by: employee
    gate: hours_covered; lines bookable on their days; cost centres open; not_stale
    effect: lines frozen; the route fixed (approval_route in force on the week's Monday); the approver derived from hr on
      the Monday
  - key: approve_project_lines
    name: Approve the project lines
    from: submitted
    to: submitted
    by: project_lead
    approval: true
    gate: approval_route project_lead_then_manager; the lead leads the line's project and is not the sheet's person (email
      AND sub)
    effect: the line approvals recorded
  - key: approve_timesheet
    name: Approve the week
    from: submitted
    to: approved
    by:
    - manager
    - finance
    approval: true
    gate: the approver is the sheet's approver or a delegate and not its person (email AND sub); every project line approved
      when the route asks; not_stale
    effect: 'after commit, from pending rows: the labour journal (TS-11), deliveries (TS-12), billing claims (TS-13); payroll
      and time-off can read the hours'
  - key: return_timesheet
    name: Return with a question
    from: submitted
    to: returned
    by:
    - manager
    - project_lead
    - finance
    requires: reason
    effect: revision n+1 opens with the lines copied; rev n approvals void
  - key: reopen_timesheet
    name: Reopen an approved week for a correction
    from: approved
    to: superseded
    by: finance
    requires: reason
    gate: the reopener is neither the sheet's person nor its approver (email AND sub)
    effect: revision n+1 opens with the lines copied; on its approval revision n's journal is reversed and every downstream
      effect moves by the difference
  readiness:
  - transition: submit_timesheet
    gate: hours_covered
    reason: '{date}: {booked} of {expected} hours'
    fix: book the missing hours, or ask for the absence in time off
  - transition: submit_timesheet
    gate: bookable
    reason: '{project} is not bookable on {date}: {why}'
    fix: book to another project or item
  - transition: submit_timesheet
    gate: cost_centre_open
    reason: cost centre {cost_centre} is closed (gl)
    fix: book to an open cost centre
  - transition: approve_timesheet
    gate: second_person
    reason: this is your own week
    fix: it goes to your manager
  - transition: approve_timesheet
    gate: project_lines_approved
    reason: lines on {project} wait for {lead}
    fix: the lead approves them, or returns the week
  - transition: approve_timesheet
    gate: not_stale
    reason: 'lines changed under the week: named'
    fix: return the week for a correction
  note: returned and superseded are terminal for their revision; the week continues as revision n+1, so no state is ever re-entered
- entity: billing_run
  states:
  - draft
  - released
  - held
  - cancelled
  terminal:
  - released
  - cancelled
  transitions:
  - key: create_time_billing_run
    name: Gather the month's billable hours
    from: none
    to: draft
    by: system
    gate: the project's terms bill via ar; approved billable lines not claimed by another run
    effect: run_id tsbill:<project_id>:<period_id>; a ts_billing_claim row per line (plain INSERT)
  - key: release_time_billing_run
    name: Release to invoicing
    from:
    - draft
    - held
    to: released
    by: finance
    gate: the total not negative; the releaser is not a person whose hours are in it (email AND sub)
    effect: ar POST /invoices/raise from a pending row after commit
  - key: hold_time_billing_run
    name: Hold the run
    from: draft
    to: held
    by: finance
    requires: reason
  - key: cancel_time_billing_run
    name: Cancel the run
    from:
    - draft
    - held
    to: cancelled
    by: finance
    requires: reason
    effect: the claims released, so the lines fall into the next run
- entity: ts_migration_run
  states:
  - loaded
  - reconciled
  - signed
  - discarded
  terminal:
  - signed
  - discarded
  transitions:
  - key: load_ts_from_odoo
    name: Load Odoo's timesheets
    from: none
    to: loaded
    by: system
    gate: hr's and projects' migrations signed
  - key: reconcile_ts_migration
    name: Reconcile against Odoo
    from: loaded
    to: reconciled
    by: system
  - key: sign_ts_migration
    name: Sign the reconciliation
    from: reconciled
    to: signed
    by: owner
    approval: true
    gate: every check equal or explained
  - key: discard_ts_migration
    name: Discard the run
    from:
    - loaded
    - reconciled
    to: discarded
    by: owner
    requires: reason