Problem
Tuttle has no concept of linked partial invoices. A deposit invoice and its final invoice are currently two independent, unrelated invoices. The final invoice cannot automatically deduct previously paid deposits, and there is no chain showing they belong together.
Payment schedule at the contract level
The payment structure (e.g. "50% on commissioning, 50% on delivery") is defined in the contract/offer, not invented at invoice time. New table:
class PaymentMilestone(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
contract_id: int = Field(foreign_key="contract.id", ondelete="CASCADE")
title: str # e.g. "Upon commissioning"
percentage: Optional[Decimal] # e.g. 50 — alternative to amount
amount: Optional[Decimal] # absolute amount — alternative to percentage
position: int # ordering
invoiced: bool = Field(default=False)
contract: "Contract" = Relationship(
back_populates="payment_milestones",
sa_relationship_kwargs={"lazy": "subquery"},
)
When creating invoices, Tuttle pulls the milestone schedule as a template, ensuring consistency between offer and invoices.
Proposed changes
Extend Invoice.document_type (currently "invoice" | "reminder") with two new values:
"deposit" — deposit / advance payment invoice
"final" — final settlement invoice (deducts prior deposits)
Self-referential chain (analogous to the existing reminder chain):
deposit_for_id: Optional[int] = Field(
default=None, foreign_key="invoice.id",
)
deposits: List["Invoice"] = Relationship(...) # on final invoice
final_invoice: Optional["Invoice"] = Relationship(...) # on deposit invoice
Final invoice rendering (tax-law requirement)
The final invoice must show the full contract amount, not just the remainder:
Total fee 10,000.00
plus 19% VAT 1,900.00
Gross 11,900.00
less deposit per invoice no. 2025-01 -5,950.00
(VAT included therein: 950.00)
Remaining balance 5,950.00
Correct VAT handling across the chain
- Deposit invoices: VAT on the partial amount
- Final invoice: VAT on the full amount, minus VAT already invoiced on deposits
- The rendering logic must settle VAT amounts, not just net amounts
UI behavior
- "Create final invoice" auto-marks linked deposits as paid (or warns if still unpaid)
- Clear project/client attribution in invoice list, independent of numbering
- Deposit chain visualization on invoice detail view
- Overview of upcoming deposit/final invoices derived from contract milestones
Scope
- Schema:
PaymentMilestone table, new document_type values, deposit chain FK, Alembic migration
- Invoicing logic: final invoice with deduction lines + correct VAT settlement
- Rendering: legally compliant PDF layout for deposit and final invoices
- UI: milestone editor on contract, deposit chain, "create final invoice" action, open-items consistency
Problem
Tuttle has no concept of linked partial invoices. A deposit invoice and its final invoice are currently two independent, unrelated invoices. The final invoice cannot automatically deduct previously paid deposits, and there is no chain showing they belong together.
Payment schedule at the contract level
The payment structure (e.g. "50% on commissioning, 50% on delivery") is defined in the contract/offer, not invented at invoice time. New table:
When creating invoices, Tuttle pulls the milestone schedule as a template, ensuring consistency between offer and invoices.
Proposed changes
Extend
Invoice.document_type(currently"invoice"|"reminder") with two new values:"deposit"— deposit / advance payment invoice"final"— final settlement invoice (deducts prior deposits)Self-referential chain (analogous to the existing reminder chain):
Final invoice rendering (tax-law requirement)
The final invoice must show the full contract amount, not just the remainder:
Correct VAT handling across the chain
UI behavior
Scope
PaymentMilestonetable, newdocument_typevalues, deposit chain FK, Alembic migration