Skip to content

feat: Deposit & Final Invoices (Abschlagsrechnung / Schlussrechnung) - #349

Open
clstaudt wants to merge 3 commits into
mainfrom
feat/326-deposit-final-invoices
Open

feat: Deposit & Final Invoices (Abschlagsrechnung / Schlussrechnung)#349
clstaudt wants to merge 3 commits into
mainfrom
feat/326-deposit-final-invoices

Conversation

@clstaudt

@clstaudt clstaudt commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

Closes #326

Rebased onto current main (the branch was 171 commits behind, predating the ContractCharge system and the non-nullable InvoiceItem.VAT_category), then finished.

What this does

A fixed-price contract can carry a payment schedule — milestones by percentage or by amount. Each milestone is billed as a deposit invoice (Abschlagsrechnung); the last one produces a final invoice (Schlussrechnung) that states the full contract price, deducts every deposit already issued with the VAT contained in each, and shows the remaining balance.

  • Schema: PaymentMilestone table, Invoice.document_type extended with "deposit" / "final", deposit_for_id chain, milestone_id, Alembic revision 1100c34b90c6 (re-parented onto head 9cad5ae77a79).
  • Invoicing: generate_deposit_invoice() / generate_final_invoice(), create_deposit / create_final intents, deposits carry the contract's VAT_category, paid status propagates from a settlement to the deposits it settles.
  • Rendering: document-type banner, deposit context (which milestone of which contract), per-deposit deduction lines with "VAT included therein", and remaining balance — in all seven templates, driven by shared partials in templates/_shared/ so the settlement layout cannot drift between skins.
  • UI: payment-schedule editor on contracts, document-type picker in the create dialog (final enabled only once deposits exist), open-milestone picker, deposit chain grouping, milestone schedule badge.

Scope decisions

  • Contract charges are not billed on deposits. A deposit bills a milestone amount and nothing else; charges appear on the final invoice, where the full contract scope is settled.
  • Deposit and final invoices ship as PDF without embedded ZUGFeRD XML, with a logged warning. The builder emits type code 380 with full document totals, while EN16931 wants 386 for a prepayment and BT-113 prepaid amounts on the settlement — emitting 380 would state an amount the client does not owe. Correct prepayment support is follow-up scope.

Fixed since the draft

  • Stale milestone badge — the schedule progress is now derived from the invoice chain, which the view reloads, instead of from milestone.invoiced flags on a contract snapshot embedded in an older invoice. Ordinary invoices on a schedule contract no longer show the badge at all.
  • Final-invoice rendering failures — invoices are reloaded from the data source after the insert, so the deduction payload renders from a session-bound graph.
  • Deposits silently dropped from the deduction list — writing the milestone flags through save_invoice merged a contract graph loaded before the settlement existed, whose cascade wrote the old deposit_for_id back, leaving only the newest deposit deducted. Linking and flagging now go through targeted writes (link_deposits_to_final, mark_milestones_invoiced).

Test plan

  • uv run pytest — 580 passed, 1 skipped; ruff check / ruff format --check clean; npx tsc --noEmit clean.
  • tuttle_tests/test_deposit_invoices.py: VAT settlement math against the exact example in Deposit & Final Invoices (Abschlagsrechnung / Schlussrechnung) #326 (10,000 net / 1,900 VAT / 11,900 gross, less a 5,950 deposit containing 950 VAT, leaves 5,950), schedule validation (percentages sum to 100, amounts sum to the fixed price, no mixing, invoiced milestones cannot be repriced or removed), and the e-invoice guard.
  • test_rpc_dispatch.py::test_deposit_and_final_invoice_lifecycle: a 40/40/20 schedule end to end over real RPC — two deposits, the final deducting both, JSON-serialisable throughout (a deposit chain is what provokes DetachedInstanceError), paid status reaching the whole chain.
  • Deposit and final PDFs rendered in all seven templates, EN and DE, and read for layout and localisation.
  • Playwright Electron smoke run of the schedule → deposit → final flow (ui/scripts/smoke-deposit.ts); screenshots in the thread below.

Demo data

Two schedules, per the AGENTS.md demo-data rule: Sam Lowry's Heating Repair is settled (deposit + final, 50/50), and Central Services' Heating Modernisation is mid-flight — one deposit paid, two milestones open — so the workflow can be walked in the app.

Made with Cursor

clstaudt and others added 2 commits August 8, 2026 13:05
…srechnung)

Implements #326 — linked partial invoice chains with milestone-based
payment schedules.

Schema:
- Add PaymentMilestone table for contract payment schedules
- Extend Invoice.document_type with "deposit" and "final"
- Add deposit chain FK (deposit_for_id) and milestone FK
- Alembic migration for all new columns/tables

Invoicing logic:
- generate_deposit_invoice() for milestone-based deposits
- generate_final_invoice() showing full amount with deposit deductions
- Last-milestone shortcut auto-creates final invoice
- toggle_paid propagates across deposit chains

Rendering:
- Legally compliant PDF layout for deposit and final invoices
- Deposit deduction lines with VAT breakdown on final invoices
- i18n labels (EN/DE/ES) for all new document types

UI:
- Milestone editor on contract detail view
- Document type picker (Invoice / Milestone) in create dialog
- Deposit chain visualization in invoice list
- MilestoneScheduleBadge showing progress
- Deposit/final invoice detail views

Known issues:
- Milestone paid count badge may show stale data
- PDF rendering for final invoices needs session hydration fix

Co-authored-by: Cursor <cursoragent@cursor.com>
…voicing

Completes the deposit / final invoice workflow so a Schlussrechnung is
correct wherever it is rendered and cannot be mis-stated to a client:

- Shared Jinja partials and stylesheet under templates/_shared/ carry the
  document-type banner, deposit context and settlement lines, so all seven
  invoice skins deduct deposits identically instead of each restating the
  legally relevant layout.
- Deposits are linked to their settlement, and milestones flagged, through
  targeted writes. Merging whole invoice graphs let a stale contract
  snapshot write back the old deposit_for_id, which dropped every deposit
  but the newest from the deduction list.
- ZUGFeRD XML is skipped with a logged warning for document types the
  builder cannot express: it emits type code 380 with full totals, while
  EN16931 wants 386 for a prepayment and BT-113 prepaid amounts on the
  settlement.
- Demo data grows a schedule mid-flight (one deposit issued, two open) so
  the workflow can be walked in the app, plus a Playwright smoke script.

Co-authored-by: Cursor <cursoragent@cursor.com>
@clstaudt
clstaudt force-pushed the feat/326-deposit-final-invoices branch from fb7f6db to 2d8b53c Compare August 8, 2026 11:51
… own idiom

The coloured banner, boxed context and all-caps number label announced the
document type at full volume in every template, fighting the design of
skins that are deliberately quiet. An invoice already says what it is in
its heading and number — the type only needs to register, not to shout.

- Banners now appear for reminders only. Deposit and final carry the type
  as a "document type" meta-table row (modern, minimal, bold), a qualifier
  in the heading (grayshades, anvil, base) or their existing title block
  (classic).
- The deposit context loses its grey box and accent-coloured micro-labels;
  it reads as a short key/value list between two hairlines.
- The remaining banner keeps a single left rule in the template's accent
  colour instead of a filled, type-coded strip.

Co-authored-by: Cursor <cursoragent@cursor.com>
@clstaudt
clstaudt marked this pull request as ready for review August 8, 2026 12:02
@clstaudt
clstaudt requested a review from aaronspring August 8, 2026 12:03
@clstaudt clstaudt added this to the 4.3 milestone Aug 9, 2026

@aaronspring aaronspring left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

never worked with Abschlagsrechnung but seems a useful feature.

Comments:

  • Invoice Details view:
    • same for "Final" and "Half upfront": what should the details view show? currently the different deposits and the remaining balance.
  • only for fixed-price contracts. do you see deposit invoices used for time-based?
  • in contracts -> fixed-price, if I add milestones and then click on collapse, payment schedule disappears and only reappears if I click on "add milestone". should rather be > collapsed hiding the milestones only and when user wants to add should click on > collapsed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Deposit & Final Invoices (Abschlagsrechnung / Schlussrechnung)

2 participants