Skip to content

Budget — events

Per-module slice of docs/events-catalog.md (auto-generated). Update both files when adding or removing events.

Published

EventWhenPayload
budget.sentBudget marked as sent (email or manual delivery).budget_id, clinic_id, patient_id, budget_number, plan_id (nullable), delivery metadata.
budget.acceptedPatient (public link) or staff (in-clinic) accepts and signs.Snapshot incl. accepted_via, total, plan_id (nullable). Subscribers: treatment_plan (pending → active; closed-as-rejected → active), patient_timeline, notifications.
budget.rejectedPatient or staff rejects.Snapshot incl. rejection_reason, plan_id (nullable). Subscriber: treatment_plan (pending → closed).
budget.expiredDaily cron, valid_until < today while draft/sent.Snapshot incl. days_overdue, plan_id.
budget.renegotiatedPOST /budgets/{id}/renegotiate cancels a sent budget for renegotiation.budget_id, plan_id (nullable), patient_id, version, cancelled_at, cancelled_by. Subscriber: treatment_plan (pending → draft).
budget.cancelledPOST /budgets/{id}/cancel — staff cancels directly (issue #162). Not published when the cancel is initiated by treatment_plan.reopen() (publish_event=False) — the plan module owns that transition and an echo would deadlock.clinic_id, budget_id, patient_id, budget_number, plan_id (nullable), reason, cancelled_by, occurred_at. Subscriber: treatment_plan (pending → draft).
budget.supersededPOST /budgets/{id}/resend clones a terminal (rejected/expired/cancelled) budget to a new draft version (issue #162). Published after the request transaction commits — sole deviation from the pre-commit pattern; the subscriber points an FK at the new row, which is invisible pre-commit.clinic_id, budget_id (old), new_budget_id, patient_id, plan_id, version (new), resent_by, occurred_at. Subscriber: treatment_plan (repoints budget_id).
budget.viewedPatient opens the public link (first time, idempotent).budget_id, plan_id, patient_id, viewed_at, ip_hash.
budget.reminder_sentAutomatic reminder milestone (7d / 14d).budget_id, plan_id, patient_id, milestone_days, sent_at.

plan_id is resolved by reverse raw-SQL lookup (BudgetWorkflowService._lookup_plan) — never by importing treatment_plan models (ADR 0003). It is null for standalone budgets.

Subscribed

EventHandlerEffect
odontogram.treatment.performed__init__.pyBudgetService.on_treatment_performedMark matching line items done.
treatment_plan.budget_sync_requested__init__.py::_on_sync_requestedRebuild draft-budget lines from the snapshot payload.
treatment_plan.treatment_added__init__.py::_on_treatment_added_to_planAdd matching line to the linked draft budget (no-op on non-draft).
treatment_plan.treatment_removed__init__.py::_on_treatment_removed_from_planRemove matching line from the linked draft budget.

Adding a new event

  1. Add the constant to backend/app/core/events/types.py (EventType).
  2. Publish from a service method, after the DB commit succeeds.
  3. Add the row to the table(s) above.
  4. Run python backend/scripts/generate_catalogs.py to refresh the global catalog.