# Final billing and invoice — Phase 3C.2

## Boundary

Frozen starting point: `e8fba73`, 1,790 tests. `apps.commercial` owns the
invoice aggregate. No existing test, historical migration, inventory, repair,
quotation approval, authorization or handover invariant is changed.

**Actual Consumption → Commercial Allocation → Invoice**

Physical consumption proves what was used, but cannot always prove which approved
payer/price line applies. The invoice allocation layer connects these two durable
sources without rewriting either. An invoice establishes commercial value and
customer amount due, never payment. There is no PAID status, receipt, settlement,
refund, receivable ledger, claim settlement or Phase 3C.3 implementation.

## Models and fields

All evidence models inherit UUID id, created_at, updated_at, normal save/delete
guards, and protected foreign keys from commercial.Record.

| Model | Fields beyond common evidence fields |
| --- | --- |
| InvoiceSequence | ServiceCenter primary key; next_value |
| ServiceInvoice | one-to-one service_case; service_center; number; DRAFT/FINALIZED status; approved quotation; currency; generation; subtotal, discount_total, tax_total, grand_total, customer_pay_total, warranty_covered_total, company_covered_total; context_snapshot; note; created_by; finalized_by/at |
| InvoiceLine | invoice; quotation_line; generation; is_active; PART/LABOR/SERVICE kind; CUSTOMER/WARRANTY/COMPANY responsibility; description; part_code/name; quantity; unit_price, subtotal, discount, additional_discount, taxable_amount, tax_rate/label, tax, total; customer_pay, warranty_covered, company_covered; confirmed_by; confirmation_note |
| InvoiceAllocation | line; authoritative PartsDisposition consumption; quantity; AUTO/MANUAL mode; actor; reason; created_at is allocation decision time |

Invoice → case and invoice line → approved quotation line → revision retain
commercial provenance. Allocation → consumed disposition → immutable movement →
unit_links → SerializedStockUnit retains physical and serialized provenance.
No serial identifier is copied or reassigned. Several consumption records can
fund one invoice line; each remains individually traceable. One consumption row
can be split into whole-unit quantities across approved lines.

## Numbering and lifecycle

Numbers are ServiceCenter-scoped `INV-00000001` through an 18-digit sequence.
Creation locks the counter after the case, with transactional get-or-create.
Allocation rolls back with failed creation. Committed numbers are unique and
never recycled; there is no delete/void/reset API. One invoice exists per case,
including its draft. Thus duplicate final invoices are impossible by schema.

The only transition is DRAFT → FINALIZED. Draft rebuild/reconciliation increments
generation, retires old lines, and retains all old lines/allocations. Finalized
identity, currency, snapshots, amounts, allocations and actors/times are immutable.
There is no post-finalization edit, void, correction or regeneration. Explicit
credit/rebill mechanisms are deferred.

## Eligibility and quotation approval

Draft operations require an active consistent company/region/center and a case
in DIAGNOSED, REPAIRING, REPAIRED, QC_PENDING, QC_IN_PROGRESS, QC_PASSED or
READY_FOR_DELIVERY. All billing requires a **current APPROVED** quotation,
including covered-only billing. A replacement DRAFT/SUBMITTED/REJECTED quotation
blocks further reconciliation/finalization; an approved replacement requires a
draft rebuild and fresh review. Historical approvals never silently carry forward.

Finalization additionally requires QC_PASSED or READY_FOR_DELIVERY, with the frozen
handover technical-history validator: latest successful completed repair and QC,
matching completed diagnosis and assignment, and no open technical work. Finalizing
does not advance ServiceCase or end its assignment. Delivery winning the case lock
first prevents late finalization; finalization winning first permits normal delivery.
Existing invoice-free delivery/closure workflows remain unchanged.

Absence of a quotation is neither free work nor coverage. Unclassified work cannot
be invoiced by inventing responsibility. Missing/insufficient commercial approval
blocks billing. Frozen quotation services only permit changes during DIAGNOSED or
REPAIRING: obtain any required revised approval **before completing repair**.
This phase does not introduce retrospective pricing or reopen completed work to
resolve a commercial exception discovered after QC. Such cases remain blocked.

## Part reconciliation and ambiguity

The source is PartsDisposition(kind=CONSUMED) for the case, linked to its posted
CONSUME movement. Reservations, expected quantities, custody, availability,
transfers and unconsumed issued stock are never billed.

Phase 3B RETURNED means **unused stock returned from issue custody**. It is not a
consumption reversal. Supported APIs cannot return consumed quantity or restore
a consumed serialized unit. Consequently consumption is the sum of immutable
CONSUMED dispositions; subtracting RETURNED quantities would be incorrect.
No new stock arithmetic, ledger or reversal mechanism is introduced.

Automatic allocation requires exactly one applicable approved line for a source's
part and, if the quote line specifies it, exact performed repair action. All
proposed automatic allocations for that line must fit its approved quantity.
Even duplicate equivalent candidate lines are conservatively left for explicit
selection; no ordering, price, payer or creation-time priority is used.

Example: approved Battery × 1 CUSTOMER at 2,000 and Battery × 1 WARRANTY at 2,000;
actual consumption Battery × 1. The automatic draft has no battery charge.
An authorized operator must select the approved funding line and record a reason.
Selecting WARRANTY retains 2,000 commercial value and zero customer liability.
Selecting CUSTOMER applies the approved 2,000 customer terms. The decision,
source, approved line, actor and timestamp survive finalization.

Every operation checks source and quote-line capacities. A source cannot be
allocated more than its consumed quantity; an approved line cannot authorize more
than its quantity. Repeated source/line entries must be combined. Cross-case,
returned, incompatible part/action and noncurrent quotation references are rejected.
Whole-number quantities preserve inventory semantics; fractional/bool quantities
are rejected. An approved quantity of three with actual two bills only two.

Drafts may be incomplete, but finalization requires **every consumed quantity**
to have approved allocation, with no unresolved ambiguity or omitted overrun.
Additional consumption after preparation is detected at finalization. No
unapproved extra quantity can disappear from reconciliation to hide liability.

## Labor/service confirmation

Quoted labor/service is never automatically charged. Invoice management requires
an explicit quantity and mandatory confirmation note for each performed approved
LABOR/SERVICE line. If the quotation links a repair action, it must have been
performed for this case. Unlinked services use accountable authorized confirmation
within the approved quantity and terms; successful repair/QC remains mandatory for
finalization. This is a documented operator assertion, not invented time tracking.
Description and responsibility are inherited from approval, not free-form new
charges. Omitted quoted labor does not become an invoice charge.

## Money, responsibility, discounts and tax

The existing money.calculate/decimal_value functions remain authoritative:
numeric(14,2), exact Decimal, bounded nonnegative inputs, no floats, tax rounded
per aggregated invoice line with ROUND_HALF_UP. Quantity is aggregated by selected
quotation line before calculating money, avoiding per-source rounding inflation.

Unit price, responsibility, description and tax rate/label come from the selected
approved line. No upward price or payer override is accepted. Approved fixed
discount is prorated to actual quantity and rounded **up** to a cent, preserving
the customer benefit on partial quantities. Optional additional discounts require
an explicit reason; they can only reduce liability and never exceed line value.
Negative discounts, tax overrides, hidden total overrides and additional scope
are rejected. Each billed line is capped at its approved line total; aggregate
customer due cannot exceed approved customer due.

Subtotal − discounts + tax = grand total. Customer + warranty + company = grand
total. Covered lines preserve their full commercial value. A zero-customer-due
invoice is valid and never creates a fake payment. Currency and final tax facts
are snapshots, with the same explicit two-decimal currency convention as quotation.

## Services and queries

Public commands in invoice_services:

- prepare_service_invoice(actor, service_case, invoice=None, expected_revision=None,
  note=""): create or rebuild; auto-allocate only unambiguous actual parts.
- reconcile_service_invoice(actor, invoice, expected_revision, allocations,
  confirmations=(), adjustments=(), note=""): replace the complete reviewed draft
  composition. Manual allocation dictionaries contain consumption, quotation_line,
  quantity, reason. Confirmation dictionaries contain quotation_line, quantity,
  reason. Adjustments contain quotation_line, additional_discount, reason.
- finalize_service_invoice(actor, invoice, expected_revision): revalidate current
  approved source, technical eligibility, complete quantities, provenance and
  financial snapshots under lock, then record server actor/time.

Commands are keyword-only. Revision preconditions are mandatory updated_at ISO
strings for every existing draft write. Internal consumption_sources is a trusted
case query used only after scope validation; it is not a public unscoped view.

Public scoped query APIs in invoice_queries require actor:
invoice_cases, service_invoices, current_service_invoice,
finalized_service_invoice, invoice_lines, invoice_allocations, invoice_detail,
service_invoice_summary, service_cases_ready_for_invoicing,
service_cases_with_draft_invoices, invoice_history_for_case.
Lines/allocations optionally include retained generations. Queues are discovery
queries; only the finalization command proves eligibility. Queries use deterministic
ordering and lazy SQL scope subqueries. Query budgets for an active superuser:
one query for list, lines, allocations, case history, either queue and summary;
four for detail including allocations and serialized links when no units exist,
five when fetching referenced unit objects. Related display fields are joined.
The Admin reconciliation display stays within 30 queries and does not increase
when two allocated lines replace an empty reconciliation.

## Authorization and Admin

Existing same-path business authorization is reused with ServiceCenter targets.
commercial.manage_serviceinvoice permits prepare/rebuild/reconcile and financial
allocation decisions. commercial.finalize_serviceinvoice separately permits final
issuance. commercial.view_serviceinvoice controls queries. An engineer's technical
assignment alone is insufficient. Staff, Groups and direct Django permissions
remain native Admin entry controls, not business scope. Active superuser behavior
is preserved; inactive hierarchy remains invalid even for superusers.

Admin creates through the service and otherwise displays readonly model fields.
The custom workflow shows current financial lines, actual consumption, approved
source lines, allocation decisions and retired line history. Mutations require
POST, CSRF, native change permission, business permission and a signed token bound
to invoice id, revision and user id. Submitted totals, actors, status, prices and
responsibility are not accepted domain inputs. Model choices are scoped; services
independently reject malicious references. No raw child admin or deletion is exposed.
Finalized workflows omit edit forms. Formset choices are cached across forms.

## Locking and concurrency

Commands reuse the existing handover authorization context: actor SHARE, Company
and authorization dependency SHARE locks, hierarchy/scope validation, then
ServiceCase FOR UPDATE. Invoice numbering locks its per-center counter only on
creation. Finalization invokes the existing technical-history locks (assignment,
diagnosis, repair, QC), then invoice FOR UPDATE and its children. Other invoice
writes lock invoice after case. No inventory position, unit, part or location lock
is acquired after case; invoice code does not mutate or lock stock.

Frozen quotation, repair, consumption and unused-return writers all serialize on
that same case. Approval/technical changes read fresh state after waiting. Completed
consumption evidence cannot change, and unused returns cannot invalidate it.
Permission revocation is coordinated by the existing role/path locks. Presentation
queries acquire no row locks. Database child guards lock their parent invoice.

PostgreSQL race tests assert actual blocking and final persisted state for numbering,
first draft, duplicate finalization, draft edits/rebuilds, competing source/payer
allocations, approval-line capacity, quotation replacement, immutable approval,
consumption, unused returns, technical edits, permission revocation, handover,
rollback and stale signed Admin submission. Invalid late quotation/repair/consumption
operations remain rejected by frozen lifecycle rules rather than being enabled
for a test. Unresolved allocation cannot finalize under contention.

## Database and immutable history

0003 adds the four invoice models and local constraints: identity/number uniqueness,
one invoice per case, valid status/finalization metadata, valid currency/generation,
positive quantities, kind/responsibility, nonnegative amounts, line and aggregate
arithmetic, exact payer allocation, unique active quotation line and source pair.

0004 adds history guards: no deletion, immutable final roots/children and immutable
allocation evidence; draft lines may only retire. Approved-source matching,
cross-case allocation linkage and exact rounded tax receive additional database
checks. Deferred triggers reconcile active line totals and part allocation sums.
Cross-domain capacity, complete consumption coverage, lifecycle, current approval
and business authorization are transactional service invariants, not a second
inventory constraint system. Privileged raw SQL remains an acknowledged boundary.

## Verification and exact changed-file inventory

Final verification on PostgreSQL 18.6:

- One complete `python manage.py test --noinput` run after the final code/test
  change: **1,872 tests, 0 failures, 0 errors, 0 skips**, in **2,963.973 seconds**.
- Frozen baseline: 1,790 unchanged tests. Added: 82 invoice tests, including
  **27 real PostgreSQL concurrency tests**, all passing in that complete run.
- Fresh test-database migrations succeeded. Development commercial migrations
  0003 and 0004 are applied; historical migrations are unchanged.
- Django checks and migration-drift checks pass. Query budgets above pass,
  including serialized detail and the Admin reconciliation growth check.
- Scope isolation, cross-case references, totals/payer injection, separate
  finalization authority, stale tokens, CSRF, deletion and immutable history
  protections pass. The bounded changed-file key-pattern scan found no matches;
  `.env` is ignored and untracked.
- Full-run output is retained in the system temporary directory as
  `c-care-phase3c2-full-suite.log`.

Git status: three modified tracked files and eleven untracked new files, all
unstaged. Standard `git diff --stat` reports three files changed, 11 insertions
and one deletion; that command excludes the eleven untracked files listed below.
`git diff --check` passes. No baseline test or historical migration changed;
nothing was staged, committed or pushed.

Created:

- apps/commercial/invoice_models.py
- apps/commercial/invoice_services.py
- apps/commercial/invoice_queries.py
- apps/commercial/invoice_admin.py
- apps/commercial/templates/admin/commercial/invoice_workflow.html
- apps/commercial/migrations/0003_invoicesequence_serviceinvoice_invoiceline_and_more.py
- apps/commercial/migrations/0004_invoice_history_integrity.py
- apps/commercial/test_invoice.py
- apps/commercial/test_invoice_security.py
- apps/commercial/test_invoice_concurrency.py
- docs/SERVICE_INVOICE.md

Modified narrowly:

- apps/commercial/models.py — register invoice models
- apps/commercial/admin.py — register invoice Admin
- docs/ARCHITECTURE.md — invoice ownership and boundary

No historical migration or frozen test is modified. No staging, commit or push.


## Phase 6D operational presentation

Scoped invoice queues/details reuse stored invoice/payer totals and the existing
reconciliation/finalization forms. Technical consumption quantities are distinguished
from monetary payment allocations. Settlement is shown only with separate payment
read scope and is supplied by payment_queries. Invoice value != revenue. No
invoice lifecycle or historical allocation semantics change; see OPERATIONAL_UI.md.
