Hoe werkt dit: kaders, budget en mandaat
Per commissie per seizoen ligt vast wat het budget is, binnen welke kaders de commissie vrij is en wat zij zelf beslist. Ook staat erbij wat al is uitgegeven. Zo zie je het besluit “zelfsturende teams” terug op het scherm.
Voor de club
/kaders begint met Vraagt om aandacht: commissies die over hun budget gaan of er
bijna doorheen zijn, verzoeken en declaraties die op een besluit wachten, en commissies zonder
kader. Dat is het bestuursbeeld. Het staat bewust boven de lijst waar de cijfers vandaan
komen, en niet op een tweede scherm met dezelfde getallen.
Daaronder staan alle commissies naast elkaar: het budget, of het kader al is vastgesteld en waar nog niets ligt. Iedereen in het kader ziet dit scherm, ook de kaders van commissies waar je zelf niet in zit. Dat is bewust, want transparantie is het uitgangspunt van de ALV-lijn.
Drie standen, elk met een woord erbij:
| Stand | Betekent |
|---|---|
| Vastgesteld | De penningmeester heeft het kader vastgesteld. Dit geldt |
| Voorstel | Er ligt een voorstel dat nog niet is vastgesteld |
| Geen kader | Deze commissie heeft voor dit seizoen niets vastliggen |
/kaders/<commissie> heeft vijf tabbladen: Wat geldt nu, Realisatie, Buiten
kader (de verzoeken buiten kader), Voorstel (het voorstel waar aan gewerkt wordt) en
Eerdere versies.
De weg van een kader
- De commissieleider (de functie met mandaat) start een voorstel. Ligt er al een geldig kader, dan begint het voorstel als kopie daarvan. Je wijzigt dus alleen wat er anders wordt.
- De commissieleider vult het voorstel in: de kaders in gewone taal, de doelen van het seizoen, de budgetposten met bedragen, het mandaat en een korte onderbouwing.
- Indienen zet het op de lijst van de penningmeester. Die krijgt er een taak van, zodat het niet in een mailbox blijft hangen.
- De penningmeester stelt het kader vast. Datum, versie en wie het deed, worden vastgelegd. Vanaf dat moment staat het kader stil.
- Wijzigen kan alleen als nieuwe versie. De oude blijft leesbaar onder “Eerdere versies”. Wat er veranderde, zie je aan de versies naast elkaar.
Het mandaat
Elke regel zegt waar het besluit over gaat en wie het neemt: de commissie zelf, met het bestuurslid, met het bestuur, of pas na de ALV. Naar die lijst kijkt een commissieleider, in plaats van een appje te sturen met “mag dit?”.
Budget
Eén regel per post. Een post is een kostenpost, tenzij je in het blad van de post Dit is een opbrengst aanvinkt. Dat doe je voor wat de commissie zelf binnenhaalt, bijvoorbeeld de eigen bijdrage van deelnemers. Het bedrag blijft altijd een gewoon getal. Typ je toch een minteken, dan zet de app het vinkje voor je aan en haalt het minteken weg.
Een opbrengst krijgt op het scherm nergens een minteken. De richting staat in een woord: kosten, opbrengst, “levert op” of “kost”. Wat geen opbrengst is, blijft zoals het was: een commissie die over haar kostenbudget gaat, heeft bij Resterend een bedrag onder de nul, en in de begrotingsronde staat een lager kostenbedrag dan vorig seizoen met een min.
Heeft een commissie alleen kosten, dan ziet ze het scherm zoals altijd. Zodra er een opbrengst bij staat, splitst Wat geldt nu in drie blokken:
- Kosten, met Totaal kosten.
- Opbrengst, met Totaal opbrengst.
- Per saldo: “Levert € 6.000 op” of “Kost € 6.000”. Dat is wat de club netto draagt, kosten min opbrengst.
Eerdere versies en het scherm van een oude versie splitsen op dezelfde manier.
De app stuurt op het bedrag per saldo. Een commissie met € 4.000 aan kosten en € 10.000 aan opbrengst levert € 6.000 per saldo op. Dat bedrag is dan haar doel, en geen bedrag dat ze mag uitgeven.
Bedragen in de app zijn richtinggevend. Het boekhoudpakket is de waarheid over wat er werkelijk is uitgegeven.
Het overzicht bij een opbrengst
Heeft geen enkele vastgestelde commissie een opbrengst, dan blijft /kaders zoals het was,
met de tegel Vastgesteld budget. Is er wel een opbrengst, dan komen er drie tegels voor in
de plaats: Budget voor kosten, Opbrengst begroot en Levert per saldo op. Kost het
geheel per saldo geld, dan heet de derde tegel Kost per saldo, en komt het precies uit, dan
Komt per saldo uit.
Budget voor kosten en Opbrengst begroot zijn bruto. Besteed en Verplicht zijn netto: kosten min opbrengst, en zo rekent ook het stoplicht. Zodra er een opbrengst is, zegt een noot “netto” dat onder die twee tegels. Is er per saldo meer binnengekomen dan uitgegeven, dan staat bij Besteed of Verplicht het bedrag zonder minteken met de noot “levert per saldo op”. In de tabel krijgt een budget met opbrengst een klein bijschrift onder het bedrag: “netto”, “levert op”, “nog te halen” of “erboven”.
Het woord bij het budget
Het stoplicht in de kolom Ruimte zegt in één woord hoe een commissie ervoor staat.
| Woord | Betekent |
|---|---|
| Ruimte | Er is minder dan negen tiende van het budget weg |
| Bijna op | Negen tiende of meer is weg |
| Over budget | Er is meer weg dan het budget |
| Onderweg | Het kader moet per saldo iets opleveren en het doel is nog niet gehaald |
| Doel gehaald | Wat er netto is binnengekomen en vastligt haalt het doel of gaat erover |
| Komt uit | Het kader komt per saldo precies op nul uit en bevat een opbrengst |
| Geen budget | Deze commissie heeft geen vastgesteld kader |
Onderweg en Doel gehaald bestaan alleen voor een commissie die per saldo iets oplevert. Zo’n commissie is nooit Over budget: zolang het doel niet is gehaald, is ze onderweg.
De tegel Over budget boven het overzicht telt alleen commissies die meer hebben weggezet dan hun budget, met een budget boven nul. Een commissie die per saldo iets oplevert telt er niet meer in mee, ook niet zolang het doel nog niet is gehaald. Dezelfde regel geldt voor de lijst Vraagt om aandacht. De tegel kan daardoor lager uitvallen dan je gewend was.
Realisatie, verzoeken en declaraties en het uitbetalen staan sinds ADR 42 in hun eigen module, met hun eigen pagina: realisatie, declaraties en uitbetalen.
De begrotingsronde
Op /begroting staat de ronde voor het seizoen dat nog moet beginnen. /kaders blijft over
het lopende seizoen gaan. Twee schermen met elk één seizoen is eerlijker dan één scherm met
een seizoenkiezer.
De penningmeester of de beheerder kiest Ronde starten. Je geeft het komende seizoen een naam en een periode, en noteert de post uit de clubbegroting waar de kaders onder vallen, met het bedrag dat daar staat. De app zet dat seizoen klaar zonder het lopende te onderbreken, en geeft elke commissie met een vastgesteld kader alvast een voorstel met dezelfde budget- en mandaatregels. Een commissie die vorig jaar niets had vastgesteld, krijgt niets. Een bedrag voortzetten dat niemand heeft goedgekeurd is erger dan een leeg scherm.
De commissie past haar voorstel zelf aan. Vanaf de ronde open je het kader van een commissie voor het komende seizoen. Dat scherm zegt er met zoveel woorden bij dat het over een ander seizoen gaat. Verder werkt het als altijd: de commissieleider stelt voor, de penningmeester stelt vast.
In de ronde vergelijk je per commissie drie getallen die naast elkaar staan: wat er vorig seizoen in het kader stond, wat daar werkelijk van is uitgegeven en wat er nu wordt gevraagd. Dat laatste is het vastgestelde bedrag zodra dat er is, en anders het voorstel. Tik op een commissie voor de posten: gewijzigd met het verschil, nieuw of vervallen. Posten worden gekoppeld op hun naam, want er is geen nummer dat een seizoen overleeft. Een post die je hernoemt, verschijnt dus als nieuw naast een vervallen post.
Bovenaan staat wat de commissies samen vragen, naast het bedrag van de post uit de begroting, met de ruimte of het tekort ertussen. Dat is het gesprek dat de penningmeester met het bestuur voert. Met Post uit de begroting pas je die post aan.
Het gevraagde bedrag is altijd het bedrag per saldo. Heeft een commissie een opbrengst, dan staat onder Gevraagd “na € X opbrengst” en zie je bij de commissie de kosten en de opbrengst naast het bedrag per saldo. Speelt er een opbrengst mee, dan staat het verschil met vorig seizoen in woorden: “€ 500 meer opbrengst”, “€ 500 minder opbrengst”, “€ 500 meer kosten” of “€ 500 minder kosten”. Een opbrengstpost die verandert, staat als “Opbrengst: € 1.200 → € 2.400”. De vergelijking met de post uit de begroting blijft ook per saldo.
De uitdraai (Uitdraai voor de ALV) is een CSV met per commissie de vier bedragen en het verschil, en twee slotregels: het totaal van de commissies en de post uit de begroting. Op die twee naast elkaar kijkt de ALV. Is er ergens in de ronde een opbrengst, dan komen er twee kolommen bij, Kosten en Opbrengst, naast het bedrag per saldo bij Gevraagd. Een ronde met alleen kosten houdt de kolommen die ze had.
Met Ronde afronden zet je het komende seizoen aan. Het is één handeling: vanaf dat moment wijst alles in de app naar het nieuwe seizoen, ook de realisatie en de declaraties. Je moet het bevestigen en het kan niet ongedaan worden gemaakt. Commissies zonder vastgesteld kader blokkeren het niet. De app telt ze en zegt het, en het bestuur beslist.
Wie het kader mag lezen
Standaard leest elk kaderlid van de club het kader. Transparantie is de standaard voor elke nieuwe club.
Een club kan dat omzetten bij Instellingen, onder Rechten. Dan leest een kaderlid alleen het kader, het budget, het mandaat, de taken en het overleg van de commissies waar hij zelf in zit. Een commissie kan van de clubstandaard afwijken, in beide richtingen. Die keuze staat op haar eigen pagina (ADR 71).
Wie er altijd bij mag: de beheerder, de clubrol bestuur, elk bestuurslid, iedereen met een functie in die commissie en het bestuurslid dat haar in portefeuille heeft.
Op het kaderoverzicht staat een besloten commissie er gewoon tussen, met de stand Besloten. Dat is iets anders dan “nog geen kader”: of er een kader ligt, weet de app op dat scherm niet. Zulke commissies tellen dan ook niet mee in “zonder kader”.
Voor wie eraan bouwt
- Migratie
20260908120000_module_b_kaders.sql:kader,budgetregel,mandaatregel. Alle drie met audit, want dit is financieel (conventie 3). - Twee invarianten liggen in de database en niet alleen in het scherm:
app.kader_levensloop()bewaakt de statuswissels. Voorstel wordt vastgesteld, vastgesteld wordt vervangen, meer paden zijn er niet. Vaststellen zonder de vlag financieel geeft42501. Zonder ingelogde gebruiker (seed, cron) slaat de rechtencontrole over. Die paden draaien buiten een sessie om.app.kaderregel_alleen_in_voorstel()bevriest budget- en mandaatregels zodra het kader is vastgesteld (23514).
- Twee partiële unieke indexen houden het simpel: per commissie per seizoen hoogstens één voorstel en hoogstens één vastgesteld kader. Het vervangen van de oude versie gebeurt in de before-trigger, anders botst de nieuwe rij met die index.
- Let op bij het testen van rechten: een before-trigger vuurt vóór de RLS-policy. Wil je
toetsen dat iemand iets niet mag, richt de test dan op een kader met status
voorstel. Op een vastgesteld kader krijg je23514van de trigger, en dan bewijst de test niets over rechten. src/modules/kaders/model.tsbevat de typen en de niveauwoorden zonder database eromheen. Een client-component importeert daaruit. Viaindex.tszou de serverclient in de browserbundel belanden.- Een opbrengst blijft een negatieve
budgetregel.bedrag; er is geen soortkolom (ADR 102). Het teken lees je alleen metsplitsBudgetenrichtingVanuitmodel.ts, en het woord bij het budget komt uitstandwoordinsrc/modules/realisatie/model.ts. Een scherm toetst het teken niet zelf en geeft een opbrengst nergens een minteken. Een overschrijding van een kostenbudget blijft een bedrag onder de nul (stand.resterend), zoals het scherm van vroeger. src/modules/kaders/acties.tsheeft de server actions. Vaststellen schrijft de gebeurteniskaders.kader_vastgesteld. Indienen schrijftkaders.voorstel_ingediendplus een taak voor de penningmeester (met idempotentiesleutel, dus twee keer indienen levert één taak op).- Bedragen:
src/lib/bedrag.ts.alsBedraglaat centen weg bij een rond bedrag,leesBedragaccepteert2500,2.500en2.500,50.
Realisatie (werkpakket 2.2)
- Migratie
20260908130000_module_b_realisatie.sql:kostenplaats,realisatie_importenrealisatiepost, alle drie met audit. De viewrealisatie_standtelt besteed en verplicht per commissie op. Hij staat opsecurity_invoker, dus hij toont alleen wat de vragende gebruiker mag zien. app.mag_realisatie_zien(org, commissie)is de leesregel.app.realisatiepost_herkomst()weigert een geboekt bedrag van iemand zonder de vlag financieel (42501). Een commissieleider mag alleenbron = 'verplichting'schrijven.- Let op de vorm van de policies: lezen loopt via één
for select-policy, schrijven via apartefor insert,for updateenfor delete. Eenfor all-policy geldt ook voor select. Dan zou ieder bestuurslid via de schrijfregel alsnog de bedragen van elke commissie lezen. Dat vangt de policytest af. - Een import is een batch:
realisatie_importverwijderen haalt zijn posten mee (cascade). Verplichtingen blijven staan, omdat die geenimport_idhebben. De check(bron = 'boekhouding_import') = (import_id is not null)houdt dat waar. src/modules/kaders/import.tsis de rekenkant zonder database:maakStandenmaakImportvoorbeeld(kolomherkenning, Nederlandse bedragen en datums). Het is een apart bestand, zodatkaders.test.tsdat kan narekenen zonder Supabase.- De app rekent zelf uit welke commissies iemand mag inzien (
haalRealisatierechten). Een commissie zonder posten levert geen rij in de view, en dat is niet te onderscheiden van “mag je niet zien”.
Verzoek buiten kader (werkpakket 2.3)
- Migratie
20260908140000_module_b_uitzondering.sql:uitzonderingsverzoekengoedkeuring_beoordeling. De tweede breidt de generieke goedkeuring uit fase 0 uit met één rij per beoordelaar. De declaraties (WP2.4) gebruiken dezelfde tabel. - Indienen en beoordelen lopen via
app.uitzondering_indienenenapp.uitzondering_beoordelen, met een dun doorgeefluik inpubliczoalsgebeurtenis_schrijven. De reden: één besluit raakt drie rijen (de beoordeling, de goedkeuring en het verzoek) en die moeten samen kloppen. De drempelregel staat daar ook, zodat een tweede scherm hem niet opnieuw hoeft te bedenken. - Leesregel met één verruiming: hetzelfde als de realisatie, plus elk bestuurslid. Wie moet beslissen, kan niet iets beoordelen dat hij niet mag openen. Dat gat kwam als eerste in de policytest naar boven.
- De mail loopt via de outbox en niet vanuit de handeling. De meldingentabel staat dicht voor
gewone gebruikers, en dat hoort ook zo: een commissieleider mag geen bericht in andermans postvak
schrijven. De handeling schrijft de gebeurtenis.
afhandelaars.tsmaakt er in de cron met de service role een melding van. De taak wordt wel direct gemaakt, want die mag een commissieleider voor de eigen commissie wel maken.
Declaraties (werkpakket 2.4a)
- Migratie
20260908150000_module_b_declaraties.sql: de tabeldeclaratie, de opslagbakbonnenmet twee policies, enapp.declaratie_indienenenapp.declaratie_beoordelen. - De bon gaat eerst naar de opslag, dan pas de declaratie. Anders kan de regel “vanaf dit
bedrag hoort er een bon bij” niet werken, want die moet bij het indienen al kloppen.
Daarom staat de declaratie ook niet in het opslagpad. Dat pad is
<organisatie>/<persoon>/<uuid>en de koppeling loopt via de documentrij. - Goedkeuren maakt in dezelfde functie een
realisatiepostmet brondeclaratie. Het nummer daarvan staat op de declaratie, zodat te zien is dat het bedrag één keer meetelt. - De herkomsttrigger uit WP2.2 is versmald. Die liet alleen de penningmeester iets anders dan
een verplichting boeken, en blokkeerde daarmee de commissieleider die een declaratie goedkeurt. Nu
geldt de beperking alleen voor
boekhouding_import: dat is het enige bedrag dat beweert uit de boekhouding te komen. De andere bronnen geven geen extra macht, want een commissieleider mag toch al een verplichting vastleggen met hetzelfde effect.
Uitbetalen (werkpakket 2.4b)
- Migratie
20260910120000_betaalronde.sql:bankrekening,betaalronde,betaalregel, plusapp.iban_geldig,app.bankrekening_zetten,app.betaalronde_makenenapp.betaalronde_vaststellen. - Het rekeningnummer staat in een eigen tabel en niet op
persoon. Persoon wordt door het hele kader gelezen, en een IBAN hoort dat niet te zijn. Een kolom erbij zou de leesregel van de halve app hebben verruimd voor één gegeven. - De elfproef staat twee keer: in
app.iban_geldig(waar het nummer binnenkomt) en inibanGeldiginsrc/modules/kaders/sepa.ts. Die tweede is er voor het IBAN van de club zelf. Dat komt uitorganisatie.instellingenen is nooit langs de databasefunctie gekomen. - Een declaratie zit in hoogstens één ronde. Dat staat als unieke index op
betaalregel.declaratie_iden is geen afspraak in de code. - Vaststellen verschuift de stand, downloaden niet. Het bestand mag je twee keer ophalen. Daarom is het ook pas op te halen als de ronde is vastgesteld. Een concept kan nog veranderen, en dan liggen er twee verschillende bestanden met hetzelfde nummer bij de bank.
- Het bestand is
pain.001.001.09. Wat er in de praktijk misgaat, is de tekenset (accenten, een euroteken) en de veldlengte.sepaTekensvangt allebei af en de unit tests leggen het vast. - Nog niet gebouwd uit module B: het bestuursdashboard (WP2.5) en de AFAS-koppeling (WP2.7, ADR 33).
- De begrotingsronde (F3) staat in
begroting.ts(de rekenkant, puur),begroting-queries.ts,begroting-acties.tsenbegroting-uitdraai.ts. Het starten en het afronden zijn databasefuncties (app.begrotingsronde_startenen..._afronden), omdat het aanmaken van een seizoen, het openen van de ronde en het overzetten van de kaders één transactie moeten zijn. Aan het kader zelf is niets veranderd: geen kolom, geen status, dezelfde levensloop.haalKaderVanCommissieenstartVoorstelnemen een seizoen aan. Zonder dat seizoen blijft alles het lopende seizoen, precies als eerst.
Begrotingsposten uit Excel overnemen
Open bij de commissie het tabblad Voorstel en kies Plakken uit Excel. Kopieer uit je werkblad twee of drie kolommen: de naam van de post, het bedrag en eventueel een toelichting. Een kopregel mag mee. Neem maximaal 200 posten tegelijk over.
Bekijk eerst de voorvertoning. Controleer de namen, de bedragen en het totaal. Een opbrengst
plak je met een minteken voor het bedrag, bijvoorbeeld -500. Bij het plakken maakt het minteken
er meteen een opbrengst van. De voorvertoning zegt hoeveel kosten en hoeveel opbrengst erbij komen, en in het
voorstel staat de post daarna als opbrengst, zonder minteken. Zowel 1.234,50 als 1,234.50
wordt herkend. Formules worden niet berekend, dus kopieer de uitkomsten uit Excel.
Na je bevestiging worden alle posten samen toegevoegd. Bestaande posten blijven staan. Komt een naam al voor, pas die post dan apart aan of haal hem uit de geplakte lijst. Klopt een regel niet, dan wordt de hele lijst geweigerd en blijft je invoer zichtbaar. Er ontstaat dus nooit een half overgenomen begroting.
Een vastgesteld kader blijft vaststaan. Wil je daarna nieuwe posten toevoegen, maak dan eerst een nieuw voorstel. Dat geldt ook als iemand het kader vaststelt terwijl jij nog naar de voorvertoning kijkt.
Een versie vanuit een project bekijken
Een project kan verwijzen naar een specifieke kaderversie. Via die koppeling lees je de bijbehorende doelen, afspraken, begrotingsregels en mandaten. De pagina is alleen-lezen: je verandert er geen actief seizoen of geldend kader mee. Controleer het versienummer en de status voordat je een oude afspraak gebruikt. De leesrechten van de commissie blijven gelden, ook als het project met meer mensen wordt gedeeld.