feat: Deposit & Final Invoices (Abschlagsrechnung / Schlussrechnung) - #349
Open
clstaudt wants to merge 3 commits into
Open
feat: Deposit & Final Invoices (Abschlagsrechnung / Schlussrechnung)#349clstaudt wants to merge 3 commits into
clstaudt wants to merge 3 commits into
Conversation
…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
force-pushed
the
feat/326-deposit-final-invoices
branch
from
August 8, 2026 11:51
fb7f6db to
2d8b53c
Compare
… 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>
aaronspring
reviewed
Aug 10, 2026
aaronspring
left a comment
Contributor
There was a problem hiding this comment.
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
> collapsedhiding the milestones only and when user wants to add should click on> collapsed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #326
Rebased onto current
main(the branch was 171 commits behind, predating theContractChargesystem and the non-nullableInvoiceItem.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.
PaymentMilestonetable,Invoice.document_typeextended with"deposit"/"final",deposit_for_idchain,milestone_id, Alembic revision1100c34b90c6(re-parented onto head9cad5ae77a79).generate_deposit_invoice()/generate_final_invoice(),create_deposit/create_finalintents, deposits carry the contract'sVAT_category, paid status propagates from a settlement to the deposits it settles.templates/_shared/so the settlement layout cannot drift between skins.Scope decisions
Fixed since the draft
milestone.invoicedflags on a contract snapshot embedded in an older invoice. Ordinary invoices on a schedule contract no longer show the badge at all.save_invoicemerged a contract graph loaded before the settlement existed, whose cascade wrote the olddeposit_for_idback, 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 --checkclean;npx tsc --noEmitclean.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 provokesDetachedInstanceError), paid status reaching the whole chain.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