# Settlements — Cross-project billing

> Issue settlements, freeze tickets, and track project budgets

The Settlements module lets you combine tickets from multiple projects into a single settlement, drive it through a status workflow (draft → sent → paid), and manage attachments (invoices, reports, acceptance protocols). Once a settlement is sent, linked tickets are frozen — editing is locked, but status changes on the Kanban board still work.

## Features

- **Cross-project M2M**: A single settlement can span multiple projects. Link tickets from any of the related projects — perfect for freelancers and agencies.
- **Bidirectional status workflow**: Draft → Sent → Paid with automatic timestamps (sent_at, paid_at). Reverse transitions allowed — admins can unfreeze tickets when needed.
- **Ticket freezing**: After sending a settlement, linked tickets are frozen — full editing (title, description, priority) is locked. Status changes via Kanban drag-drop still work.
- **Categorized attachments**: Upload invoices, reports, acceptance protocols to MinIO with categories (invoice, report, acceptance_protocol, other) and state (draft/signed). 200MB limit, 10 files per settlement.
- **Granular RBAC**: rozliczenia:read/write/delete permissions per project. Users without permission don't see the section on tickets (hidden in HTML and MCP) — not just disabled.
- **Global cross-project view**: The /dashboard/rozliczenia page shows settlements across all projects you have access to. Filter by projects, status, and date.

## How it works

1. **Create a settlement**: New settlement (ROZ-N) with period, name, and selected projects. Requires rozliczenia:write in each project.
2. **Link tickets**: HTMX autocomplete search — find by number (MNX-12) or title. Only tickets from settlement's projects are shown.
3. **Attach files**: Upload invoices, reports, or acceptance protocols. Choose category and state (draft/signed). Preview/download/delete.
4. **Send and freeze**: Change status to 'Sent' — tickets become frozen (editing returns 403), but Kanban status updates still work.
5. **Mark as paid**: After payment — click 'Mark as paid'. paid_at is set, sent_at preserved. Tickets stay frozen.
6. **Revert if needed**: Admins can revert status (paid → sent → draft). Tickets automatically unfreeze when settlement returns to draft.

## AI and MCP

All settlement operations are exposed via MCP — the AI agent can create settlements, link tickets, upload attachments (base64), and change statuses. RBAC is enforced by each tool.

- `list_settlements`: List project settlements with filters (status, period, page).
- `get_settlement`: Settlement details: projects, tickets, attachments, status, timestamps.
- `create_settlement`: Create a cross-project settlement (write permission validated in all projects).
- `update_settlement`: Edit a draft settlement (name, period, projects, notes).
- `delete_settlement`: Soft delete a draft settlement.
- `change_settlement_status`: Draft ↔ sent ↔ paid workflow with transition validation and timestamps.
- `link_ticket_to_settlement`: Link a ticket (draft only + cross-project validation).
- `unlink_ticket_from_settlement`: Unlink a ticket from a settlement.
- `list_settlement_tickets`: List linked tickets (keys + titles + statuses).
- `add_settlement_attachment`: Upload a file as base64 → MinIO. Categories: invoice/report/acceptance_protocol/other.
- `get_settlement_attachment`: Download attachment as base64 with mime_type and filename.
- `list_settlement_attachments`: List attachment metadata (no bytes).
- `delete_settlement_attachment`: Delete attachment (draft only).

## Technical details

- **Data model**: Settlement + M2M SettlementProject + M2M SettlementTicket + 1:N SettlementAttachment. Soft delete (is_active=False).
- **Workflow**: ALLOWED_SETTLEMENT_TRANSITIONS: dict[str, frozenset[str]] — state machine with transition validation and timestamps (sent_at, paid_at).
- **Storage**: MinIO for attachments, PostgreSQL for metadata. Upload and download run in ThreadPoolExecutor (async-friendly).
- **Freeze logic**: is_ticket_frozen(ticket) — dynamic function checks if a ticket is linked to a settlement in sent/paid status. Automatic unfreeze when settlement returns to draft.
- **RBAC**: New 'rozliczenia' key in PERMISSION_MODULES (read/write/delete). Per-project validation — cross-project settlements require write permission in all projects.
- **MON-62 regression**: Freezing protects the integrity of sent settlements. Status-only updates are allowed on frozen tickets (Kanban drag-drop) — full editing is blocked.
