Skip to content

[Fact 07] Betaalstatus, verzenden, herinneringen & facturatie-overzicht #152

Description

@MiniMaxi-user

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)

  • Queries (Fact 02, src/features/facturen/queries.ts): getInvoicesForActiveStable /
    getInvoicesForStable / getInvoicesForAllStables (stal-overzicht, incl.
    ALLE_STALLEN-scoping op memberships), getInvoicesForRecipient (eigenaar/leaser-inzage,
    sluit CONCEPT uit), getSignedUrlVoorLaatsteFactuurDocument (PDF-inzage).
  • Guards (Fact 02, authorization.ts): assertCanManageInvoice, assertCanManageInvoicesForStable.
  • Actions (actions.ts): maakFactuurDefinitief (zet al VERZONDEN + PDF), getFactuurPdfUrl.
  • Berekeningen (berekeningen.ts): formatEuro, VAT_RATE_LABEL.
  • 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.

Scope

Binnen scope:

1. Facturen-overzicht (stal) -- src/app/(app)/stal/facturen/page.tsx

  • 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.

2. "Facturen"-sidebar-item -- src/components/SidebarClient.tsx

  • 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.

3. Statusbeheer (server actions) -- src/features/facturen/actions.ts

  • 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.)

5. Eigenaar-/leaser-inzage -- src/app/(app)/eigenaar/page.tsx

  • 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.
  • Externe boekhoudkoppelingen, bank-/incassobestanden (PAIN.008/SEPA-XML), PSP/iDEAL,
    daadwerkelijke incasso-uitvoering (epic [Facturatie] Epic: Facturatie-module #145 buiten scope; Fact 06 legt enkel vast en toont).
  • Automatische periodieke facturatie-runs (epic [Facturatie] Epic: Facturatie-module #145: handmatig/triggerbaar opstellen).
  • 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).
  • Mandaat-historie/-intrekking (Fact 06-afbakening).
  • 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions