Billing — permissions
Returned by BillingModule.get_permissions() (relative names — the registry namespaces them as billing.<name>).
| Permission | Allows | Required by |
|---|---|---|
billing.read | Listing and viewing invoices, invoice payments, history, PDF invoices, previewing PDFs, viewing billing settings, and fetching patient billing summaries. | GET /invoices, GET /invoices/{invoice_id}, GET /invoices/{invoice_id}/payments, GET /invoices/{invoice_id}/history, GET /invoices/{invoice_id}/pdf, GET /invoices/{invoice_id}/pdf/preview, GET /settings, GET /patients/{patient_id}/summary. |
billing.write | Creating and modifying draft invoices, creating invoices from budgets, patching billing party details, deleting draft invoices, managing line items, issuing invoices, emailing invoices, issuing credit notes, and applying invoice payments. | POST /invoices, POST /invoices/from-budget/{budget_id}, PUT /invoices/{invoice_id}, PATCH /invoices/{invoice_id}/billing-party, DELETE /invoices/{invoice_id}, POST /invoices/{invoice_id}/items, PUT /invoices/{invoice_id}/items/{item_id}, DELETE /invoices/{invoice_id}/items/{item_id}, POST /invoices/{invoice_id}/issue, POST /invoices/{invoice_id}/send-email, POST /invoices/{invoice_id}/credit-note, POST /invoices/{invoice_id}/payments. |
billing.admin | Managing invoice numbering series (list, create, update, reset), voiding draft invoices, and updating clinic billing settings. | GET /series, POST /series, PUT /series/{series_id}, POST /series/{series_id}/reset, POST /invoices/{invoice_id}/void, PUT /settings. |
Role assignment
Declared in the module manifest (backend/app/modules/billing/__init__.py):
| Role | Permissions | Notes |
|---|---|---|
| admin | * | Full control, including managing series, settings, and voiding invoices. |
| dentist | * | Full clinical and billing management. |
| hygienist | read | View invoices and patient billing summaries. |
| assistant | read, write | Create draft invoices, apply payments, and issue invoices chairside. |
| receptionist | read, write | Front-desk billing, creating and issuing invoices, payment collection. |
See backend/app/core/auth/permissions.py for the canonical role table.
Why dentist holds billing.admin
dentist is granted *, which the permission matcher (backend/app/core/auth/permissions.py) expands to billing.read, billing.write and billing.admin — the same admin-tier reach admin has. This is intended and is deliberately not narrowed (maintainer confirmation on issue #542):
- The irreversible, AEAT-chained actions are not admin-tier. Issuing an invoice and issuing a credit note (how an already-issued invoice is reversed) are
billing.write, held byreceptionistandassistanttoo. Compliance chaining into Veri*Factu runs through aBillingHookRegistryhook —VerifactuHook.on_invoice_issued(verifactu/hook.py, registered inon_activateand resolved inline in the issuing path) — independently of who triggered the issue. voidis draft-only (InvoiceWorkflowService.can_void,workflow.py), so it never touches an issued or fiscally-recorded invoice.- The remaining admin actions a dentist can reach — numbering-series configuration and the clinic billing settings (default payment term, invoice footer, bank details) — are clinic fiscal configuration, not patient data. LOPD is unaffected: the grant exposes no personal data beyond what
billing.readalready returns.
Narrowing dentist to read/write was considered and rejected: it would block a chairside void-and-recreate of a draft and the correction of a misconfigured series, forcing a round trip to reception for routine corrections, with no compliance benefit because the fiscally-sensitive paths are already billing.write.
Adding a new permission
- Add the relative name to
get_permissions()inbackend/app/modules/billing/__init__.py. - Add it to the role mapping in
manifest.role_permissions(same file) so the appropriate roles can use it on a fresh install. - Wire the gate on the endpoint via
Depends(require_permission("billing.<name>")). - Mirror in
frontend/app/config/permissions.tsunderPERMISSIONS.billing.*so the UI can callusePermissions().can(PERMISSIONS.billing.<name>). - Add a row to this file with the matching endpoints.
- Re-run
python backend/scripts/generate_catalogs.py.