dodil/erp-brick-time-off

DODIL ERP brick: time off

src/brick_time_off/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: time-off
name: Time off & absence
version: 1
actors:
- id: employee
  name: Anyone who works here
- id: manager
  name: Line manager (derived from hr)
- id: hr_admin
  name: HR / people operations
- id: owner
  name: The owner (or whoever the owner names)
- id: system
  name: The system
workflows:
- entity: leave_request
  states:
  - requested
  - approved
  - rejected
  - withdrawn
  - cancelled
  terminal:
  - rejected
  - withdrawn
  - cancelled
  transitions:
  - key: request_leave
    name: Ask for time off
    from: none
    to: requested
    by: employee
    gate: employed on every day (hr); every half day free (one claim row each); the cost within the balance and allow_negative_days;
      at most max_days_ahead ahead
    effect: request_id claimed from the company's yearly series; the day claims written; the approver derived from hr on the
      first day and fixed
  - key: approve_leave
    name: Approve
    from: requested
    to: approved
    by:
    - manager
    - hr_admin
    approval: true
    gate: the approver is the request's approver or a delegate and not the requester (email AND sub); the overlap rule answered
      (warn needs an acknowledgement, block refuses)
    effect: ledger entry take keyed req:<request_id>:r<rev>; visible to payroll and timesheets after commit
  - key: reject_leave
    name: Reject
    from: requested
    to: rejected
    by:
    - manager
    - hr_admin
    requires: reason
    effect: day claims released
  - key: withdraw_leave
    name: Withdraw before a decision
    from: requested
    to: withdrawn
    by: employee
    effect: day claims released
  - key: ask_leave_cancellation
    name: Ask to cancel approved leave
    from: approved
    to: approved
    by: employee
    gate: no day of it in the past (a past day is corrected by HR, TOF-12); no open cancellation ask
    effect: a cancellation ask recorded on the request (asked_at, by); the leave stands until it is decided
  - key: cancel_leave
    name: Cancel approved leave
    from: approved
    to: cancelled
    by:
    - manager
    - hr_admin
    - system
    requires: reason
    gate: the canceller is not the requester (email AND sub), except the system for a leaver (TOF-21)
    effect: reversal entry req:<request_id>:r<rev>:cancel; day claims released
  - key: refuse_leave_cancellation
    name: Refuse the cancellation ask
    from: approved
    to: approved
    by:
    - manager
    - hr_admin
    requires: reason
    gate: an open cancellation ask; the decider is not the requester
    effect: the ask closed with its reason; no ledger entry
  readiness:
  - transition: request_leave
    gate: employed
    reason: not employed on {date} (hr)
    fix: HR checks the start or last day
  - transition: request_leave
    gate: day_free
    reason: '{date} {part} is already in request {request_id}'
    fix: change the dates, or withdraw the other request
  - transition: request_leave
    gate: balance
    reason: '{cost} asked, {available} available'
    fix: ask for fewer days, or for unpaid leave
  - transition: approve_leave
    gate: second_person
    reason: you asked for this time off
    fix: it goes to your manager
  - transition: approve_leave
    gate: overlap
    reason: '{n} of {team} off on {date}'
    fix: acknowledge the warning, or reject
  note: A cancellation ask is a recorded fact on an approved request, not a state, so no edge ever re-enters an earlier state.
    "Taken" is derived from the dates (an approved day in the past), never a state.
- entity: sickness
  states:
  - open
  - closed
  - voided
  terminal:
  - voided
  transitions:
  - key: record_sickness
    name: Record sickness
    from: none
    to: open
    by:
    - employee
    - manager
    - hr_admin
    gate: employed on the first day; no other absence on the same days (sickness during approved leave is recorded by HR,
      TOF-12)
    effect: absence_id claimed; day claims of the days so far; pay treatment sick
  - key: extend_sickness
    name: Extend the expected return
    from: open
    to: open
    by:
    - employee
    - manager
    - hr_admin
    effect: revision +1; payroll reads changed_since
  - key: add_fit_note
    name: Add a fit note
    from: open
    to: open
    by:
    - employee
    - hr_admin
    effect: evidence object; evidence_ok when it covers every day past fit_note_after_days
  - key: add_late_fit_note
    name: Add a fit note after the return
    from: closed
    to: closed
    by:
    - employee
    - hr_admin
    effect: 'evidence only: the days never change; payroll reads changed_since'
  - key: confirm_return
    name: Confirm the return to work
    from: open
    to: closed
    by:
    - manager
    - hr_admin
    requires: last_day_off
    gate: the confirmer is not the sick person
    effect: the days fixed; claims after last_day_off released
  - key: void_sickness
    name: Void a mistaken record
    from: open
    to: voided
    by: hr_admin
    requires: reason
    gate: no day of it already in a calculated pay run, else HR closes it and corrects with an adjustment
- entity: tof_migration_run
  states:
  - loaded
  - reconciled
  - signed
  - discarded
  terminal:
  - signed
  - discarded
  transitions:
  - key: load_tof_from_odoo
    name: Load Odoo's time off
    from: none
    to: loaded
    by: system
    gate: hr's migration signed
  - key: reconcile_tof_migration
    name: Reconcile against Odoo
    from: loaded
    to: reconciled
    by: system
  - key: sign_tof_migration
    name: Sign the reconciliation
    from: reconciled
    to: signed
    by: owner
    approval: true
    gate: every check equal or explained
  - key: discard_tof_migration
    name: Discard the run
    from:
    - loaded
    - reconciled
    to: discarded
    by: owner
    requires: reason