You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Onderdeel van epic #145 (Facturatie-module). Fundament-eerst — datamodel + migratie vóór features. Geen afhankelijkheden; blokkeert Fact 02–07.
User Story
Als ontwikkelaar van de facturatie-module
wil ik een datamodel + migratie voor de facturatie-kern (Invoice + InvoiceLine)
zodat latere stories (factuurregels, voorvullen uit contract, PDF, betaalwijze, status & overzicht) op een stabiel, consistent fundament kunnen voortbouwen.
Context
Dit is de eerste story van de facturatie-epic (#145), stap 6 uit de bouwvolgorde in CLAUDE.md. De keten matching → contract → administratie → facturatie rond het
centrale paardprofiel moet sluitend worden. Facturatie haakt op bestaande modellen:
Stable — de uitgevende/afzendende partij (de stal die factureert).
User — de ontvanger (paardeigenaar of leaser als geregistreerd account).
Contract — de optionele bron van de factuur (stallingscontract met pensionprijs
en extra diensten, of leasecontract met leasevergoeding; lease-btw is 21%, zie src/features/lease/leaseKostenConfig.ts).
OwnerBusinessProfile — bevat de zakelijke/factuurgegevens van de te factureren
klant (bedrijfsnaam, adres, KvK, btw-nummer, afwijkend factuuradres). Wordt in deze
story niet gewijzigd; latere PDF-story (Fact 05) leest deze gegevens uit.
Dit is bewust een puur fundament-story: alleen het datamodel + de migratie. Geen
UI, geen actions, geen queries, geen factuurnummer-generatie of statusovergangslogica
— die volgen in Fact 02–07.
Scope
Binnen scope:
Prisma-model Invoice met:
stableId → relatie naar Stable (uitgevende stal), onDelete: Cascade.
recipientUserId → relatie naar User (ontvanger: eigenaar/leaser), nullable + onDelete: SetNull zodat een factuur de verwijdering van het account overleeft
(administratie/historie blijft behouden).
contractId → optionele relatie naar Contract, onDelete: SetNull (factuur
overleeft het verwijderen van zijn bron-contract).
invoiceNumber (String?, nullable) — concept-facturen hebben nog geen definitief
nummer; opvolgende nummering wordt pas in Fact 05 toegekend. Wel @unique zodat
dubbele nummers op DB-niveau onmogelijk zijn.
invoiceDate (DateTime?), dueDate (DateTime?) — nullable tot vastgesteld.
status (enum, zie hieronder), default CONCEPT.
Totalen als Decimal @db.Decimal(10, 2): subtotal (excl. btw), vatAmount
(totaal btw), total (incl. btw). Default 0.
notes (String?) — vrije opmerking op de factuur.
createdAt / updatedAt.
Indexen op stableId, recipientUserId, contractId.
Prisma-model InvoiceLine met:
invoiceId → relatie naar Invoice, onDelete: Cascade.
description (String) — omschrijving van de regel.
quantity (Decimal @db.Decimal(10, 2)) — aantal (Decimal i.p.v. Int, zodat
bijv. halve maanden / deelperiodes mogelijk zijn).
position (Int, default 0) — om de regelvolgorde stabiel te bewaren.
createdAt.
Index op invoiceId.
Enum InvoiceStatus: CONCEPT, VERZONDEN, BETAALD, VERVALLEN, GEANNULEERD (consistent met de Nederlandse enum-conventie en de bestaande ContractStatus-stijl).
Enum VatRate voor de toegestane btw-tarieven 0/9/21% — als enum met sprekende
waarden, bijv. NUL, LAAG, HOOG (resp. 0%, 9%, 21%). De numerieke percentages
worden in de app-laag aan de enum gekoppeld (latere story), niet als losse kolom.
Back-relations toevoegen op de bestaande modellen Stable, User en Contract
(invoices Invoice[]).
Eén Prisma-migratie die schoon draait op de bestaande database, zonder bestaande
data te raken (alle nieuwe tabellen/kolommen; geen wijziging aan bestaande modellen
behalve het toevoegen van de back-relation-velden).
Buiten scope (latere Fact-stories):
Autorisatie/inzage-regels op facturen (Fact 02).
Concept-factuur opstellen met handmatige regels, UI/actions (Fact 03).
Regels voorvullen uit het contract incl. btw-berekening (Fact 04).
Wijzigingen aan OwnerBusinessProfile (bestaat al; wordt later uitgelezen).
Acceptatiecriteria
Het Prisma-model Invoice bestaat met alle in scope genoemde velden, relaties
(Stable, User, Contract), onDelete-gedrag en indexen.
Het Prisma-model InvoiceLine bestaat met alle in scope genoemde velden, de Invoice-relatie (onDelete: Cascade) en de index op invoiceId.
De enums InvoiceStatus (CONCEPT/VERZONDEN/BETAALD/VERVALLEN/GEANNULEERD) en VatRate (0/9/21%) bestaan; Invoice.status heeft default CONCEPT.
Alle geldbedragen (subtotal, vatAmount, total, unitPrice, lineTotal, quantity) zijn Decimal @db.Decimal(10, 2) — geen Float/Int voor bedragen.
Het btw-tarief staat per InvoiceLine (vatRate), niet alleen op
factuurniveau.
Invoice.invoiceNumber is nullable én @unique.
Op Stable, User en Contract is een invoices Invoice[] back-relation
toegevoegd.
npx prisma migrate dev genereert één nieuwe migratie die schoon draait; npx prisma generate slaagt; bestaande data blijft ongemoeid (alleen nieuwe
tabellen/relatievelden).
Geen UI, actions, queries of bedrag-/nummerlogica toegevoegd in deze story.
Technische notities
Volg de bestaande schema-conventies: UUID-PK's (@default(uuid()) @db.Uuid),
enkelvoudige PascalCase-modelnamen, Nederlandse enum-waarden waar dat al de stijl is
(ContractStatus, LeaseStatus), @db.Decimal(10, 2) voor bedragen (zie LeaseListing.pricePerMonth).
Schemawijzigingen mogen zonder vooraf overleg (zie project memory); houd het strikt
binnen deze fundament-scope.
Prisma CLI draait via npx prisma in C:\Claude\velaro en leest .env.
vatAmount/total als opgeslagen kolommen (gedenormaliseerd) zijn bewust: zo legt
een factuur het bedrag op uitgiftemoment vast. De daadwerkelijke berekening/optelling
hoort in een latere story (app-laag), niet in deze migratie.
Open vragen
VatRate als enum vs. percentage-kolom. Voorgesteld is een enum (NUL/LAAG/ HOOG). Alternatief is een Int-kolom met het percentage (0/9/21), wat flexibeler is
als ooit een tarief wijzigt. De story is uitvoerbaar met de voorgestelde enum; de
bouwer mag bij sterke voorkeur de percentage-variant kiezen mits 0/9/21 wordt
afgedwongen (CHECK/validatie). Dit is een implementatiedetail, geen blokkade.
User Story
Als ontwikkelaar van de facturatie-module
wil ik een datamodel + migratie voor de facturatie-kern (
Invoice+InvoiceLine)zodat latere stories (factuurregels, voorvullen uit contract, PDF, betaalwijze, status & overzicht) op een stabiel, consistent fundament kunnen voortbouwen.
Context
Dit is de eerste story van de facturatie-epic (#145), stap 6 uit de bouwvolgorde in
CLAUDE.md. De keten matching → contract → administratie → facturatie rond hetcentrale paardprofiel moet sluitend worden. Facturatie haakt op bestaande modellen:
Stable— de uitgevende/afzendende partij (de stal die factureert).User— de ontvanger (paardeigenaar of leaser als geregistreerd account).Contract— de optionele bron van de factuur (stallingscontract met pensionprijsen extra diensten, of leasecontract met leasevergoeding; lease-btw is 21%, zie
src/features/lease/leaseKostenConfig.ts).OwnerBusinessProfile— bevat de zakelijke/factuurgegevens van de te facturerenklant (bedrijfsnaam, adres, KvK, btw-nummer, afwijkend factuuradres). Wordt in deze
story niet gewijzigd; latere PDF-story (Fact 05) leest deze gegevens uit.
Dit is bewust een puur fundament-story: alleen het datamodel + de migratie. Geen
UI, geen actions, geen queries, geen factuurnummer-generatie of statusovergangslogica
— die volgen in Fact 02–07.
Scope
Binnen scope:
Invoicemet:stableId→ relatie naarStable(uitgevende stal),onDelete: Cascade.recipientUserId→ relatie naarUser(ontvanger: eigenaar/leaser), nullable +onDelete: SetNullzodat een factuur de verwijdering van het account overleeft(administratie/historie blijft behouden).
contractId→ optionele relatie naarContract,onDelete: SetNull(factuuroverleeft het verwijderen van zijn bron-contract).
invoiceNumber(String?, nullable) — concept-facturen hebben nog geen definitiefnummer; opvolgende nummering wordt pas in Fact 05 toegekend. Wel
@uniquezodatdubbele nummers op DB-niveau onmogelijk zijn.
invoiceDate(DateTime?),dueDate(DateTime?) — nullable tot vastgesteld.status(enum, zie hieronder), defaultCONCEPT.Decimal @db.Decimal(10, 2):subtotal(excl. btw),vatAmount(totaal btw),
total(incl. btw). Default0.notes(String?) — vrije opmerking op de factuur.createdAt/updatedAt.stableId,recipientUserId,contractId.InvoiceLinemet:invoiceId→ relatie naarInvoice,onDelete: Cascade.description(String) — omschrijving van de regel.quantity(Decimal @db.Decimal(10, 2)) — aantal (Decimal i.p.v. Int, zodatbijv. halve maanden / deelperiodes mogelijk zijn).
unitPrice(Decimal @db.Decimal(10, 2)) — stuksprijs excl. btw.vatRate(enumVatRate, zie hieronder) — btw-tarief per regel.lineTotal(Decimal @db.Decimal(10, 2)) — regelbedrag excl. btw.position(Int, default 0) — om de regelvolgorde stabiel te bewaren.createdAt.invoiceId.InvoiceStatus:CONCEPT,VERZONDEN,BETAALD,VERVALLEN,GEANNULEERD(consistent met de Nederlandse enum-conventie en de bestaandeContractStatus-stijl).VatRatevoor de toegestane btw-tarieven 0/9/21% — als enum met sprekendewaarden, bijv.
NUL,LAAG,HOOG(resp. 0%, 9%, 21%). De numerieke percentagesworden in de app-laag aan de enum gekoppeld (latere story), niet als losse kolom.
Stable,UserenContract(
invoices Invoice[]).data te raken (alle nieuwe tabellen/kolommen; geen wijziging aan bestaande modellen
behalve het toevoegen van de back-relation-velden).
Buiten scope (latere Fact-stories):
OwnerBusinessProfile(bestaat al; wordt later uitgelezen).Acceptatiecriteria
Invoicebestaat met alle in scope genoemde velden, relaties(
Stable,User,Contract),onDelete-gedrag en indexen.InvoiceLinebestaat met alle in scope genoemde velden, deInvoice-relatie (onDelete: Cascade) en de index opinvoiceId.InvoiceStatus(CONCEPT/VERZONDEN/BETAALD/VERVALLEN/GEANNULEERD) enVatRate(0/9/21%) bestaan;Invoice.statusheeft defaultCONCEPT.subtotal,vatAmount,total,unitPrice,lineTotal,quantity) zijnDecimal @db.Decimal(10, 2)— geenFloat/Intvoor bedragen.InvoiceLine(vatRate), niet alleen opfactuurniveau.
Invoice.invoiceNumberis nullable én@unique.Stable,UserenContractis eeninvoices Invoice[]back-relationtoegevoegd.
npx prisma migrate devgenereert één nieuwe migratie die schoon draait;npx prisma generateslaagt; bestaande data blijft ongemoeid (alleen nieuwetabellen/relatievelden).
Technische notities
@default(uuid()) @db.Uuid),enkelvoudige PascalCase-modelnamen, Nederlandse enum-waarden waar dat al de stijl is
(
ContractStatus,LeaseStatus),@db.Decimal(10, 2)voor bedragen (zieLeaseListing.pricePerMonth).binnen deze fundament-scope.
npx prismainC:\Claude\velaroen leest.env.vatAmount/totalals opgeslagen kolommen (gedenormaliseerd) zijn bewust: zo legteen factuur het bedrag op uitgiftemoment vast. De daadwerkelijke berekening/optelling
hoort in een latere story (app-laag), niet in deze migratie.
Open vragen
VatRateals enum vs. percentage-kolom. Voorgesteld is een enum (NUL/LAAG/HOOG). Alternatief is eenInt-kolom met het percentage (0/9/21), wat flexibeler isals ooit een tarief wijzigt. De story is uitvoerbaar met de voorgestelde enum; de
bouwer mag bij sterke voorkeur de percentage-variant kiezen mits 0/9/21 wordt
afgedwongen (CHECK/validatie). Dit is een implementatiedetail, geen blokkade.