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). Afsluitende story van de epic.
Bouwt voort op Fact 01 (#146: Invoice/InvoiceLine, enum InvoiceStatus
CONCEPT/VERZONDEN/BETAALD/VERVALLEN/GEANNULEERD, VatRate), Fact 02 (#147:
autorisatie/inzage in src/features/facturen/ -- getInvoicesForActiveStable,
getInvoicesForStable, getInvoicesForAllStables, getInvoicesForRecipient,
assertCanManageInvoice, getSignedUrlVoorLaatsteFactuurDocument), Fact 03/04
(#148/#149: concept opstellen + voorvullen), Fact 05 (#150: PDF + nummering,
maakFactuurDefinitief zet de status al op VERZONDEN en genereert de PDF) en
Fact 06 (#151: betaalwijze/SEPA + betaalblok op de PDF). Het in #148-#150 bewust
uitgestelde "Facturen"-sidebar-item en het facturatie-overzicht horen bij deze story.
User Story
Als staleigenaar of stalmedewerker (OWNER/STAFF)
wil ik een overzicht van alle facturen van mijn stal met hun status en filters, de
betaalstatus van een factuur kunnen bijwerken (verzonden / betaald / geannuleerd), zien
welke facturen vervallen zijn en daar een herinnering voor kunnen markeren
zodat ik de volledige factuur-levenscyclus na het uitreiken kan opvolgen -- wie heeft
betaald, wat staat open, wat is te laat -- vanuit een centraal scherm dat via de sidebar
bereikbaar is, terwijl de paardeigenaar/leaser zijn eigen verzonden facturen blijft inzien.
Context
De facturatie-keten is opgebouwd van fundament naar feature: datamodel + autorisatie
(Fact 01/02), concept opstellen + voorvullen (Fact 03/04), definitief maken met PDF en
opvolgende nummering (Fact 05) en betaalwijze/SEPA op de PDF (Fact 06). Wat ontbreekt is
het sluiten van de levenscyclus na uitreiken en een centrale ingang:
Er is nog geen facturen-overzicht en geen "Facturen"-sidebar-item -- dat is in Fact 03/05
bewust naar deze story doorgeschoven (gecontroleerd in src/components/SidebarClient.tsx:
Dashboard, Mijn stallen, Paarden, Lease, Contracten, Team, Accounts, Taken, Instellingen
-- geen Facturen). Facturen zijn nu alleen via een directe URL bereikbaar
(/stal/facturen/nieuw, /stal/facturen/[id]/bewerken).
De betaalstatus is na het definitief maken vastgepind op VERZONDEN en kan niet verder
bewogen worden naar BETAALD, VERVALLEN of GEANNULEERD. Er is geen statusbeheer en geen
automatisch vervallen.
Een vervallen factuur (vervaldatum verstreken, nog niet betaald) wordt nergens
gesignaleerd; er is geen herinnering.
"Verzenden" -- afbakening. Fact 05 koos er bewust voor om bij het definitief maken de
status meteen op VERZONDEN te zetten en de afgeschermde PDF (signed URL) beschikbaar te
maken. Daadwerkelijke e-mailverzending valt onder "externe integraties" die de MVP volgens
CLAUDE.md niet bouwt tenzij expliciet, en er is geen mail-infrastructuur in het project.
Deze story houdt "verzenden" daarom op het bestaande minimum: een factuur is "verzonden"
zodra ze definitief is (VERZONDEN + opvraagbare PDF via signed URL), en de ontvanger ziet
haar in zijn eigenaar-weergave. We bouwen geen e-mailkanaal. Wel maakt deze story het
versturen zichtbaar/beheerbaar vanuit het overzicht (status, PDF openen). Zie open punten.
Herinneringen -- afbakening. Zonder e-mailkanaal is een herinnering in deze epic-fase
een administratieve markering: het overzicht toont de vervallen facturen apart, en de stal
kan een factuur als "herinnerd" markeren (met datum) zodat ze bijhoudt dat er een
herinnering is uitgegaan (telefonisch/persoonlijk). Geen geautomatiseerde mailings of
herinneringsbatches.
Conform CLAUDE.md: Nederlandstalige UI, design-tokens uit src/styles/globals.css,
sidebar-navigatie via SidebarClient.tsx, autorisatie als server-side kernlogica, mutaties
via server actions. Het overzicht spiegelt stal/contracten/page.tsx (incl. de
ALLE_STALLEN-modus en de lazy-overgangsverwerking) en de auto-VERVALLEN-mechaniek spiegelt
verwerkTijdgebondenOvergangen uit de contract-module.
Bestaande bouwstenen om te hergebruiken (niet dupliceren)
Overzicht-blueprint: src/app/(app)/stal/contracten/page.tsx (auth-guards,
ALLE_STALLEN-modus per stal, panel/badge-UI) en ContractOverzicht.
Lazy-overgang-blueprint: verwerkTijdgebondenOvergangen / verwerkTijdgebondenOvergang in
src/features/contracten/actions.ts.
Eigenaar-inzage: src/app/(app)/eigenaar/page.tsx (waar de eigenaar zijn contracten ziet;
deze story voegt een facturen-paneel toe met getInvoicesForRecipient).
Duplicatie en user journey (actief gecontroleerd)
Geen bestaand facturen-scherm of -navigatie-item (Glob over src/app/(app)/stal/** en
gelezen SidebarClient.tsx): de routes /stal/facturen/nieuw en /stal/facturen/[id]/bewerken
bestaan, maar er is geen overzichtspagina (/stal/facturen/page.tsx) en geen sidebar-item.
Deze story is de enige, juiste plek voor beide. Geen concurrerend ingangspunt.
Eigenaar-journey: de eigenaar/leaser heeft al de read-only query getInvoicesForRecipient;
deze story toont die op het bestaande eigenaar-dashboard (/eigenaar) als extra paneel --
naast "Mijn contracten" -- zodat er geen tweede eigenaar-scherm ontstaat.
Status vs. contract-status: de factuurstatus (InvoiceStatus) staat los van de
contractstatus; geen overlap.
Nieuwe overzichtspagina, gespiegeld op stal/contracten/page.tsx:
Auth-guards: niet ingelogd -> /login; platform-admin -> /admin; gebruiker zonder
stalrol -> /eigenaar.
ALLE_STALLEN-modus: een paneel per stal (memberships), via getInvoicesForAllStables /
per stal getInvoicesForStable. Specifieke stal: via getInvoicesForActiveStable /
getInvoicesForStable.
Per factuur tonen: factuurnummer (of "Concept" als er nog geen nummer is), ontvanger
(naam/e-mail), factuurdatum, vervaldatum, totaal incl. btw (formatEuro), en de status
als badge (CONCEPT/VERZONDEN/BETAALD/VERVALLEN/GEANNULEERD met passende badge-variant).
Nieuwste eerst (queries leveren dat al).
Filteren op status (minimaal: alle / openstaand / betaald / vervallen / concept /
geannuleerd). Filter mag server-side (querystring) of client-side; bron van waarheid
blijft de server-query.
Samenvattende cijfers per (actieve) stal: aantal/bedrag openstaand (VERZONDEN +
VERVALLEN), betaald, en totaal-omzet (som van niet-CONCEPT, niet-GEANNULEERD totalen).
Eenvoudige optelling met Prisma.Decimal/formatEuro; geen grafieken.
Een rij linkt naar de bestaande bewerk-/detailpagina (/stal/facturen/[id]/bewerken); een
knop "Nieuwe factuur" naar /stal/facturen/nieuw.
Lazy auto-VERVALLEN bij paginabezoek: bij het laden worden facturen met status VERZONDEN
waarvan de dueDate is verstreken naar VERVALLEN gezet (idempotent), daarna opnieuw
ophalen zodat het overzicht klopt -- exact het patroon van verwerkTijdgebondenOvergangen.
Voeg in buildMainLinks() (stalleden-tak) een navigatie-item voor /stal/facturen toe
(label "Facturen", exact: false), op een logische plek (na "Contracten"). Een passend
SVG-icoon mag worden toegevoegd aan NavIcon (in de bestaande stijl, geen externe
iconenlib). Het item is zichtbaar voor stalleden (OWNER/STAFF), niet voor de
eigenaar-/admin-navigatie.
Nieuwe server actions, elk via assertCanManageInvoice(userId, invoiceId) (Fact 02), met
een centrale statusovergang-validatie die ongeldige overgangen weigert (nette NL-melding):
Markeren als betaald: VERZONDEN/VERVALLEN -> BETAALD.
Annuleren: CONCEPT/VERZONDEN/VERVALLEN -> GEANNULEERD. Een BETAALD-factuur kan niet
geannuleerd worden (creditfacturen vallen buiten scope).
Auto-VERVALLEN: VERZONDEN -> VERVALLEN wanneer dueDate < vandaag -- als herbruikbare
helper (lazy bij overzicht-/eigenaar-bezoek), niet als handmatige knop.
De toegestane overgangen staan op een centrale plek (bv. een STATUS_OVERGANGEN-map of
een assertGeldigeStatusovergang-helper) zodat ze consistent worden afgedwongen en
herbruikbaar zijn; geen verspreide ad-hoc checks.
revalidatePath op het overzicht en de detail-/bewerkpagina na elke mutatie.
4. Herinnering bij vervallen facturen
Het overzicht toont de vervallen facturen herkenbaar (badge/sectie/filter).
Een server action "markeer als herinnerd" die op een vervallen (of verzonden, zie open
punt) factuur een herinnerings-momentopname vastlegt: minimaal een reminderSentAt
DateTime? op Invoice (zodat de stal ziet of/wanneer er een herinnering is uitgegaan).
Geen e-mail/notificatie; puur administratieve markering.
In het overzicht zichtbaar of een factuur al herinnerd is (datum/badge).
(Schemawijziging -- een nullable veld -- mag zonder vooraf overleg, project memory.)
Voeg een paneel "Mijn facturen" toe dat getInvoicesForRecipient(user.id) toont: per
factuur het nummer, datum, vervaldatum, totaal, status-badge en -- bij een beschikbare
PDF -- een "PDF openen"-knop via getFactuurPdfUrl/de signed-URL-helper (autorisatie wordt
door Fact 02 afgedwongen). Concept-facturen verschijnen hier nooit (query sluit ze uit).
Read-only: de eigenaar beheert geen status.
Geen apart eigenaar-scherm; uitsluitend een paneel op het bestaande dashboard.
6. Statusbeheer-UI op de detail-/bewerkpagina
Op /stal/facturen/[id]/bewerken (de niet-concept tak, waar al FactuurDefinitiefActie
staat) knoppen voor de toegestane statusovergangen ("Markeren als betaald", "Annuleren",
"Markeren als herinnerd"), die alleen verschijnen wanneer de overgang geldig is. Voor een
concept blijven de bestaande Fact 03-acties + "Factuur definitief maken" ongewijzigd.
Buiten scope (blijft buiten MVP / latere keuze):
Daadwerkelijke e-mail-/notificatieverzending van facturen of herinneringen, en
geautomatiseerde herinneringsbatches. Geen mail-infrastructuur in de MVP (CLAUDE.md);
"verzenden" = VERZONDEN + opvraagbare PDF.
Creditfacturen, herfacturatie, factuur-PDF-versiebeheer of het wijzigen van een reeds
definitieve factuur (een definitieve factuur blijft read-only; statusbeheer wijzigt alleen
de status/herinnering, niet de regels/bedragen).
Platform-admin factuurbeheer (Fact 02-afbakening; admins hebben geen factuurbeheer op
platform-niveau).
Wijzigingen aan de Fact 01-06-lagen behalve: het toevoegen van de statusovergang-actions,
het ene reminderSentAt-veld, de overzichtspagina, het sidebar-item en het eigenaar-paneel.
De PDF-, nummering- en betaalwijze-stack worden hergebruikt, niet gewijzigd.
Acceptatiecriteria
Als een OWNER/STAFF naar /stal/facturen gaat, dan ziet hij een overzicht van
de facturen van zijn (actieve) stal met factuurnummer/Concept, ontvanger, factuur- en
vervaldatum, totaal incl. btw en de status als badge, nieuwste eerst; in de
ALLE_STALLEN-modus een overzicht per stal waar hij lid van is.
Als een gebruiker zonder OWNER/STAFF-rol (of een platform-admin/eigenaar) het
overzicht of een statusactie aanroept, dan wordt dit server-side geweigerd
(redirect resp. "Geen toegang") -- niet alleen in de UI.
In de sidebar is voor stalleden een "Facturen"-navigatie-item zichtbaar dat naar
/stal/facturen linkt; het verschijnt niet in de eigenaar- of admin-navigatie.
Als het overzicht wordt geladen, dan worden VERZONDEN-facturen met een
verstreken dueDate automatisch op VERVALLEN gezet (idempotent, lazy) en toont het
overzicht de bijgewerkte status.
De facturen zijn te filteren op status (minimaal: openstaand / betaald / vervallen /
concept / geannuleerd), en het overzicht toont samenvattende cijfers (openstaand
bedrag, betaald, omzet) voor de actieve stal.
Als een OWNER/STAFF een VERZONDEN- of VERVALLEN-factuur als betaald markeert, dan wordt de status BETAALD; als hij een ongeldige overgang probeert (bv.
BETAALD -> VERZONDEN, of een BETAALD-factuur annuleren), dan wordt dit geweigerd
met een nette Nederlandse melding.
Als een OWNER/STAFF een factuur annuleert, dan krijgt ze status GEANNULEERD
(toegestaan vanuit CONCEPT/VERZONDEN/VERVALLEN, niet vanuit BETAALD) en verdwijnt ze
uit de openstaand-/omzetcijfers.
De toegestane statusovergangen worden centraal gedefinieerd en consistent afgedwongen;
een ongeldige overgang is server-side onmogelijk.
Als een factuur vervallen is, dan is ze in het overzicht herkenbaar
(badge/filter) en kan de OWNER/STAFF haar als herinnerd markeren, waarna het overzicht
toont dat (en wanneer) er een herinnering is genoteerd (reminderSentAt). Er wordt geen
e-mail/notificatie verstuurd.
Als een paardeigenaar/leaser zijn dashboard (/eigenaar) opent, dan ziet hij
een paneel "Mijn facturen" met uitsluitend zijn eigen, niet-CONCEPT facturen (status,
datums, totaal) en kan hij de PDF openen via een signed URL; hij kan geen status
wijzigen.
UI is Nederlandstalig en gebruikt de bestaande design-tokens/CSS-klassen
(page-container, panel, badge, btn-primary/btn-ghost, etc.); er is geen
tailwind.config.ts of nieuw token/kleur geintroduceerd.
npx prisma migrate dev draait schoon (alleen het nieuwe nullable reminderSentAt-veld;
bestaande data ongemoeid); npx prisma generate en npx tsc --noEmit slagen;
statusovergang- en auto-VERVALLEN-logica zit in herbruikbare helpers in
src/features/facturen/.
Technische notities
Routes/UI:
src/app/(app)/stal/facturen/page.tsx -- nieuw overzicht; spiegel
stal/contracten/page.tsx (auth-guards, ALLE_STALLEN per stal, panel/badge-UI). Overweeg
een FactuurOverzicht-component analoog aan ContractOverzicht.
Statusbeheer-/herinnering-UI op de bestaande
src/app/(app)/stal/facturen/[id]/bewerken/page.tsx (niet-concept tak; daar staat al
FactuurDefinitiefActie). Een client-component voor de statusknoppen mag, maar de
overgang wordt server-side gevalideerd.
Eigenaar-paneel in src/app/(app)/eigenaar/page.tsx via getInvoicesForRecipient +
getFactuurPdfUrl.
Queries hergebruiken (Fact 02): getInvoicesForActiveStable/...ForStable/...ForAllStables
voor het stal-overzicht; getInvoicesForRecipient voor de eigenaar. Voeg eventueel een
dunne query voor de samenvattende cijfers toe in queries.ts (som per status), of bereken
ze uit de al opgehaalde lijst.
Statusovergangen centraal: definieer de toegestane overgangen een keer (bv. een
STATUS_OVERGANGEN-map Record<InvoiceStatus, InvoiceStatus[]> of een
assertGeldigeStatusovergang(huidig, nieuw)-helper) in src/features/facturen/. De server
actions (markeerBetaald, annuleerFactuur, markeerHerinnerd) lopen via
assertCanManageInvoice en deze validatie. Spiegel de stijl van de contract-acties (Error
bij overtreding, daarna revalidatePath).
Auto-VERVALLEN, lazy: een helper verwerkVervallenFacturen(invoiceIds of stableId) die
VERZONDEN-facturen met dueDate < vandaag naar VERVALLEN zet, idempotent, fouttolerant per
factuur (een falende overgang mag het laden niet breken) -- exact het patroon van
verwerkTijdgebondenOvergangen in src/features/contracten/actions.ts. Aangeroepen bij het
laden van zowel het stal-overzicht als het eigenaar-dashboard.
Herinnering: voeg reminderSentAt DateTime? toe op Invoice (nullable migratie).
markeerHerinnerd zet het op new Date() (toegestaan op VERVALLEN; zie open punt voor
VERZONDEN). Geen aparte tabel/historie nodig voor de MVP.
Verzenden (geen e-mail): geen nieuw verzendkanaal. De PDF wordt al door Fact 05
gegenereerd; deze story ontsluit haar in het overzicht/eigenaar via de bestaande
signed-URL-helpers. Een gedeelde "verzonden"-betekenis = status VERZONDEN.
Badges: gebruik bestaande badge-varianten (badge-neutral, badge-gold, badge-warning,
badge-danger, badge-navy) voor de statussen; geen nieuwe CSS-tokens.
Sidebar: buildMainLinks() in SidebarClient.tsx uitbreiden met het Facturen-item (alleen
stalleden-tak); een NavIcon-case toevoegen in de bestaande SVG-stijl.
Prisma CLI draait via npx prisma in C:\Claude\velaro en leest .env. Schemawijzigingen
mogen zonder vooraf overleg (project memory); houd het strikt bij het ene
reminderSentAt-veld.
Alle autorisatie server-side; geen localStorage voor kernlogica (CLAUDE.md).
Open vragen
Geen blokkerende open vragen. De keuzes hieronder zijn als onderbouwd voorstel verwerkt; de
bouwer mag bij sterke voorkeur afwijken zolang de acceptatiecriteria gehaald worden.
"Verzenden" zonder e-mail. Voorstel (verwerkt): "verzenden" blijft het bestaande minimum
-- maakFactuurDefinitief zet de status op VERZONDEN en maakt de PDF (signed URL)
beschikbaar; de ontvanger ziet de factuur in zijn eigenaar-weergave. Een echt e-mailkanaal
valt buiten de MVP (CLAUDE.md: geen externe integraties tenzij expliciet) en zou een
aparte epic zijn. Niet blokkerend.
Herinnering = administratieve markering. Voorstel (verwerkt): een reminderSentAt-veld +
actie "markeer als herinnerd", zonder mailing. Toegestaan op een VERVALLEN-factuur;
eventueel ook op een VERZONDEN-factuur die de vervaldatum nadert (de bouwer kiest de
eenvoudigste sluitende variant -- minimaal VERVALLEN). Geen herinneringshistorie/-batches
in de MVP. Niet blokkerend.
Status terug naar VERZONDEN. Voorstel (verwerkt): geen terugweg vanuit BETAALD of
GEANNULEERD. VERVALLEN -> VERZONDEN is niet nodig (auto-VERVALLEN is lazy en herstelt zich
niet). De bouwer hoeft geen "markeren als verzonden"-knop te bouwen buiten het bestaande
definitief-maken. Niet blokkerend.
Samenvattende cijfers. Voorstel (verwerkt): eenvoudige optelling per status (openstaand =
VERZONDEN + VERVALLEN, betaald = BETAALD, omzet = niet-CONCEPT en niet-GEANNULEERD), met
Prisma.Decimal/formatEuro; geen grafieken/rapportage. Niet blokkerend.
User Story
Als staleigenaar of stalmedewerker (OWNER/STAFF)
wil ik een overzicht van alle facturen van mijn stal met hun status en filters, de
betaalstatus van een factuur kunnen bijwerken (verzonden / betaald / geannuleerd), zien
welke facturen vervallen zijn en daar een herinnering voor kunnen markeren
zodat ik de volledige factuur-levenscyclus na het uitreiken kan opvolgen -- wie heeft
betaald, wat staat open, wat is te laat -- vanuit een centraal scherm dat via de sidebar
bereikbaar is, terwijl de paardeigenaar/leaser zijn eigen verzonden facturen blijft inzien.
Context
De facturatie-keten is opgebouwd van fundament naar feature: datamodel + autorisatie
(Fact 01/02), concept opstellen + voorvullen (Fact 03/04), definitief maken met PDF en
opvolgende nummering (Fact 05) en betaalwijze/SEPA op de PDF (Fact 06). Wat ontbreekt is
het sluiten van de levenscyclus na uitreiken en een centrale ingang:
bewust naar deze story doorgeschoven (gecontroleerd in src/components/SidebarClient.tsx:
Dashboard, Mijn stallen, Paarden, Lease, Contracten, Team, Accounts, Taken, Instellingen
-- geen Facturen). Facturen zijn nu alleen via een directe URL bereikbaar
(/stal/facturen/nieuw, /stal/facturen/[id]/bewerken).
bewogen worden naar BETAALD, VERVALLEN of GEANNULEERD. Er is geen statusbeheer en geen
automatisch vervallen.
gesignaleerd; er is geen herinnering.
"Verzenden" -- afbakening. Fact 05 koos er bewust voor om bij het definitief maken de
status meteen op VERZONDEN te zetten en de afgeschermde PDF (signed URL) beschikbaar te
maken. Daadwerkelijke e-mailverzending valt onder "externe integraties" die de MVP volgens
CLAUDE.md niet bouwt tenzij expliciet, en er is geen mail-infrastructuur in het project.
Deze story houdt "verzenden" daarom op het bestaande minimum: een factuur is "verzonden"
zodra ze definitief is (VERZONDEN + opvraagbare PDF via signed URL), en de ontvanger ziet
haar in zijn eigenaar-weergave. We bouwen geen e-mailkanaal. Wel maakt deze story het
versturen zichtbaar/beheerbaar vanuit het overzicht (status, PDF openen). Zie open punten.
Herinneringen -- afbakening. Zonder e-mailkanaal is een herinnering in deze epic-fase
een administratieve markering: het overzicht toont de vervallen facturen apart, en de stal
kan een factuur als "herinnerd" markeren (met datum) zodat ze bijhoudt dat er een
herinnering is uitgegaan (telefonisch/persoonlijk). Geen geautomatiseerde mailings of
herinneringsbatches.
Conform CLAUDE.md: Nederlandstalige UI, design-tokens uit src/styles/globals.css,
sidebar-navigatie via SidebarClient.tsx, autorisatie als server-side kernlogica, mutaties
via server actions. Het overzicht spiegelt stal/contracten/page.tsx (incl. de
ALLE_STALLEN-modus en de lazy-overgangsverwerking) en de auto-VERVALLEN-mechaniek spiegelt
verwerkTijdgebondenOvergangen uit de contract-module.
Bestaande bouwstenen om te hergebruiken (niet dupliceren)
getInvoicesForStable / getInvoicesForAllStables (stal-overzicht, incl.
ALLE_STALLEN-scoping op memberships), getInvoicesForRecipient (eigenaar/leaser-inzage,
sluit CONCEPT uit), getSignedUrlVoorLaatsteFactuurDocument (PDF-inzage).
ALLE_STALLEN-modus per stal, panel/badge-UI) en ContractOverzicht.
src/features/contracten/actions.ts.
deze story voegt een facturen-paneel toe met getInvoicesForRecipient).
Duplicatie en user journey (actief gecontroleerd)
gelezen SidebarClient.tsx): de routes /stal/facturen/nieuw en /stal/facturen/[id]/bewerken
bestaan, maar er is geen overzichtspagina (/stal/facturen/page.tsx) en geen sidebar-item.
Deze story is de enige, juiste plek voor beide. Geen concurrerend ingangspunt.
deze story toont die op het bestaande eigenaar-dashboard (/eigenaar) als extra paneel --
naast "Mijn contracten" -- zodat er geen tweede eigenaar-scherm ontstaat.
contractstatus; geen overlap.
Scope
Binnen scope:
1. Facturen-overzicht (stal) -- src/app/(app)/stal/facturen/page.tsx
stalrol -> /eigenaar.
per stal getInvoicesForStable. Specifieke stal: via getInvoicesForActiveStable /
getInvoicesForStable.
(naam/e-mail), factuurdatum, vervaldatum, totaal incl. btw (formatEuro), en de status
als badge (CONCEPT/VERZONDEN/BETAALD/VERVALLEN/GEANNULEERD met passende badge-variant).
Nieuwste eerst (queries leveren dat al).
geannuleerd). Filter mag server-side (querystring) of client-side; bron van waarheid
blijft de server-query.
VERVALLEN), betaald, en totaal-omzet (som van niet-CONCEPT, niet-GEANNULEERD totalen).
Eenvoudige optelling met Prisma.Decimal/formatEuro; geen grafieken.
knop "Nieuwe factuur" naar /stal/facturen/nieuw.
waarvan de dueDate is verstreken naar VERVALLEN gezet (idempotent), daarna opnieuw
ophalen zodat het overzicht klopt -- exact het patroon van verwerkTijdgebondenOvergangen.
2. "Facturen"-sidebar-item -- src/components/SidebarClient.tsx
(label "Facturen", exact: false), op een logische plek (na "Contracten"). Een passend
SVG-icoon mag worden toegevoegd aan NavIcon (in de bestaande stijl, geen externe
iconenlib). Het item is zichtbaar voor stalleden (OWNER/STAFF), niet voor de
eigenaar-/admin-navigatie.
3. Statusbeheer (server actions) -- src/features/facturen/actions.ts
een centrale statusovergang-validatie die ongeldige overgangen weigert (nette NL-melding):
geannuleerd worden (creditfacturen vallen buiten scope).
helper (lazy bij overzicht-/eigenaar-bezoek), niet als handmatige knop.
een assertGeldigeStatusovergang-helper) zodat ze consistent worden afgedwongen en
herbruikbaar zijn; geen verspreide ad-hoc checks.
4. Herinnering bij vervallen facturen
punt) factuur een herinnerings-momentopname vastlegt: minimaal een reminderSentAt
DateTime? op Invoice (zodat de stal ziet of/wanneer er een herinnering is uitgegaan).
Geen e-mail/notificatie; puur administratieve markering.
5. Eigenaar-/leaser-inzage -- src/app/(app)/eigenaar/page.tsx
factuur het nummer, datum, vervaldatum, totaal, status-badge en -- bij een beschikbare
PDF -- een "PDF openen"-knop via getFactuurPdfUrl/de signed-URL-helper (autorisatie wordt
door Fact 02 afgedwongen). Concept-facturen verschijnen hier nooit (query sluit ze uit).
Read-only: de eigenaar beheert geen status.
6. Statusbeheer-UI op de detail-/bewerkpagina
staat) knoppen voor de toegestane statusovergangen ("Markeren als betaald", "Annuleren",
"Markeren als herinnerd"), die alleen verschijnen wanneer de overgang geldig is. Voor een
concept blijven de bestaande Fact 03-acties + "Factuur definitief maken" ongewijzigd.
Buiten scope (blijft buiten MVP / latere keuze):
geautomatiseerde herinneringsbatches. Geen mail-infrastructuur in de MVP (CLAUDE.md);
"verzenden" = VERZONDEN + opvraagbare PDF.
daadwerkelijke incasso-uitvoering (epic [Facturatie] Epic: Facturatie-module #145 buiten scope; Fact 06 legt enkel vast en toont).
definitieve factuur (een definitieve factuur blijft read-only; statusbeheer wijzigt alleen
de status/herinnering, niet de regels/bedragen).
platform-niveau).
het ene reminderSentAt-veld, de overzichtspagina, het sidebar-item en het eigenaar-paneel.
De PDF-, nummering- en betaalwijze-stack worden hergebruikt, niet gewijzigd.
Acceptatiecriteria
de facturen van zijn (actieve) stal met factuurnummer/Concept, ontvanger, factuur- en
vervaldatum, totaal incl. btw en de status als badge, nieuwste eerst; in de
ALLE_STALLEN-modus een overzicht per stal waar hij lid van is.
overzicht of een statusactie aanroept, dan wordt dit server-side geweigerd
(redirect resp. "Geen toegang") -- niet alleen in de UI.
/stal/facturen linkt; het verschijnt niet in de eigenaar- of admin-navigatie.
verstreken dueDate automatisch op VERVALLEN gezet (idempotent, lazy) en toont het
overzicht de bijgewerkte status.
concept / geannuleerd), en het overzicht toont samenvattende cijfers (openstaand
bedrag, betaald, omzet) voor de actieve stal.
dan wordt de status BETAALD; als hij een ongeldige overgang probeert (bv.
BETAALD -> VERZONDEN, of een BETAALD-factuur annuleren), dan wordt dit geweigerd
met een nette Nederlandse melding.
(toegestaan vanuit CONCEPT/VERZONDEN/VERVALLEN, niet vanuit BETAALD) en verdwijnt ze
uit de openstaand-/omzetcijfers.
een ongeldige overgang is server-side onmogelijk.
(badge/filter) en kan de OWNER/STAFF haar als herinnerd markeren, waarna het overzicht
toont dat (en wanneer) er een herinnering is genoteerd (reminderSentAt). Er wordt geen
e-mail/notificatie verstuurd.
een paneel "Mijn facturen" met uitsluitend zijn eigen, niet-CONCEPT facturen (status,
datums, totaal) en kan hij de PDF openen via een signed URL; hij kan geen status
wijzigen.
(page-container, panel, badge, btn-primary/btn-ghost, etc.); er is geen
tailwind.config.ts of nieuw token/kleur geintroduceerd.
bestaande data ongemoeid); npx prisma generate en npx tsc --noEmit slagen;
statusovergang- en auto-VERVALLEN-logica zit in herbruikbare helpers in
src/features/facturen/.
Technische notities
stal/contracten/page.tsx (auth-guards, ALLE_STALLEN per stal, panel/badge-UI). Overweeg
een FactuurOverzicht-component analoog aan ContractOverzicht.
src/app/(app)/stal/facturen/[id]/bewerken/page.tsx (niet-concept tak; daar staat al
FactuurDefinitiefActie). Een client-component voor de statusknoppen mag, maar de
overgang wordt server-side gevalideerd.
getFactuurPdfUrl.
voor het stal-overzicht; getInvoicesForRecipient voor de eigenaar. Voeg eventueel een
dunne query voor de samenvattende cijfers toe in queries.ts (som per status), of bereken
ze uit de al opgehaalde lijst.
STATUS_OVERGANGEN-map Record<InvoiceStatus, InvoiceStatus[]> of een
assertGeldigeStatusovergang(huidig, nieuw)-helper) in src/features/facturen/. De server
actions (markeerBetaald, annuleerFactuur, markeerHerinnerd) lopen via
assertCanManageInvoice en deze validatie. Spiegel de stijl van de contract-acties (Error
bij overtreding, daarna revalidatePath).
VERZONDEN-facturen met dueDate < vandaag naar VERVALLEN zet, idempotent, fouttolerant per
factuur (een falende overgang mag het laden niet breken) -- exact het patroon van
verwerkTijdgebondenOvergangen in src/features/contracten/actions.ts. Aangeroepen bij het
laden van zowel het stal-overzicht als het eigenaar-dashboard.
markeerHerinnerd zet het op new Date() (toegestaan op VERVALLEN; zie open punt voor
VERZONDEN). Geen aparte tabel/historie nodig voor de MVP.
gegenereerd; deze story ontsluit haar in het overzicht/eigenaar via de bestaande
signed-URL-helpers. Een gedeelde "verzonden"-betekenis = status VERZONDEN.
badge-danger, badge-navy) voor de statussen; geen nieuwe CSS-tokens.
stalleden-tak); een NavIcon-case toevoegen in de bestaande SVG-stijl.
mogen zonder vooraf overleg (project memory); houd het strikt bij het ene
reminderSentAt-veld.
Open vragen
Geen blokkerende open vragen. De keuzes hieronder zijn als onderbouwd voorstel verwerkt; de
bouwer mag bij sterke voorkeur afwijken zolang de acceptatiecriteria gehaald worden.
-- maakFactuurDefinitief zet de status op VERZONDEN en maakt de PDF (signed URL)
beschikbaar; de ontvanger ziet de factuur in zijn eigenaar-weergave. Een echt e-mailkanaal
valt buiten de MVP (CLAUDE.md: geen externe integraties tenzij expliciet) en zou een
aparte epic zijn. Niet blokkerend.
actie "markeer als herinnerd", zonder mailing. Toegestaan op een VERVALLEN-factuur;
eventueel ook op een VERZONDEN-factuur die de vervaldatum nadert (de bouwer kiest de
eenvoudigste sluitende variant -- minimaal VERVALLEN). Geen herinneringshistorie/-batches
in de MVP. Niet blokkerend.
GEANNULEERD. VERVALLEN -> VERZONDEN is niet nodig (auto-VERVALLEN is lazy en herstelt zich
niet). De bouwer hoeft geen "markeren als verzonden"-knop te bouwen buiten het bestaande
definitief-maken. Niet blokkerend.
VERZONDEN + VERVALLEN, betaald = BETAALD, omzet = niet-CONCEPT en niet-GEANNULEERD), met
Prisma.Decimal/formatEuro; geen grafieken/rapportage. Niet blokkerend.