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