Agency Flow · Prezentare de flux

G · Budget overlay — factură fixă, servicii ca actuals

Singurul caz care nu e un billing type. Bugetul e un overlay la nivel de client (Client.budget, spec §1.11b): clientul primește o factură fixă = bugetul, iar serviciile lui rulează normal, doar că devin actuals — referință pentru reconciliere, nu sursa facturii. Un singur loc din sistem se comportă diferit: generatorul de factură.

Account manager — setează bugetul pe client Serviciile — rulează normal (actuals) Finance — factură = bugetul
Ce nu e: nu e un rând în tabelul de tipuri (A1–PL). Acele tipuri răspund la „cum se naște cifra dintr-un serviciu". Bugetul răspunde la „ce sumă finală pleacă pe factură". De-aia trăiește pe client (Brand), nu pe serviciu, și e 0 sau 1 per client.
1

O singură dată · la nivel de client

Setup: Billing mode = Budget, pe client

Account manager

Nu atingi niciun serviciu. În drawerul de editare al clientului comuti Billing mode pe Budget și scrii plafonul lunar. Clientul primește badge-ul „Budget client". Atât — restul e neschimbat.

Drawer · Edit client
Northwind Retail · eCommerce
Budget
alternativa: „Per service" (suma serviciilor)
20.000 RON
apare doar când mode = Budget · obligatoriu atunci
RON
RON / EUR · moneda facturii · serviciile trebuie să fie în aceeași monedă (regula A)
rezultat → badge „Budget client" pe pagina clientului
În baza de date · la Save
UPDATE brand (clientul)
billing_mode: 'per_service' → 'budget' budget: 20000.00 (parametrul modului 'budget') budget_currency: 'RON' (moneda facturii)
Trei câmpuri pe brand = clientul (spec §1.5): billing_mode alege strategia de asamblare a facturii, iar budget + budget_currency sunt parametrii ei când modul e 'budget'. Nicio modificare în service sau în tabelele de pricing — serviciile își păstrează billing type-urile lor reale.
Regulă de UI (drawer client): câmpul Monthly budget e condiționat de mod — ascuns când billing_mode = per_service, vizibil și obligatoriu când = budget (spec §1.11b: „required when billing_mode = budget"). Altfel s-ar putea salva un buget pe un client fără mod buget → dată moartă. Implementat în prototip: drawerul comută câmpul la schimbarea modului (agency-flow-pages/*.html, drawer #drawer-client).
Regula A · monedă unică, moștenită. Sistemul e multi-monedă (RON + EUR), dar factura de buget se emite în budget_currency, iar toate serviciile billabile ale clientului-buget moștenesc automat acea monedă. La adăugarea/editarea unui serviciu sub un client-buget, service.currency e locked la brand.budget_currency (nu e editable manual). Așa, variația de scop și net_profit rămân un singur număr curat — consistent cu „o factură = o monedă" (§1.13) și cu „adnotează mixt, nu converti". Pentru clienți per-service, moneda serviciilor rămâne editable independent.
2

În fiecare lună · neschimbat

Serviciile rulează normal — ca actuals

Serviciile (orice tip)

Fiecare serviciu al clientului își păstrează propriul billing type și-și trăiește ciclul normal (cost_entry → ready → invoiced, snapshot-uri). Nimic nu știe că clientul are buget. Suma valorilor lor facturabile = actual-ul lunii.

Services this month · actuals (aug) — Northwind Retail
serviciubillingbillable value (snapshot_client_amount)
SEO retainerA2 · Hourly flat8.000,00
Content productionA1 · Fixed fee5.000,00
Outreach placementsF1 · Supplier deliverables4.500,00
Actual = Σ billable value17.500,00
Actual = Σ snapshot_client_amount al serviciilor lunii — exact suma pe care fiecare serviciu ar fi facturat-o normal (A1 fix · A2/B ore×rată · C/E pass-through · D/F cost+fee). Fără calcul nou — se refolosește același snapshot per serviciu.
3

La închiderea lunii · reconciliere

Variația de scop: buget − actual

Finance / Management

Diferența dintre bugetul fix și actual e variația de scop (over/under-serviced) — o măsură de efort-vs-fee, nu de profitabilitate. Se ține intern, ca sold curent: o lună „under" compensează o lună „over". Clientul nu o vede niciodată.

Budget reconciliation · intern
lunăbuget (invoiced)actual (billable)variațiesold curent
iun 202420.000,0021.200,00−1.200,00−1.200,00
iul 202420.000,0018.500,00+1.500,00+300,00
aug 2024 · curent20.000,0017.500,00+2.500,00+2.800,00
Semnul: pozitiv = under-serviced (ai livrat mai puțină valoare decât fee-ul încasat) · negativ = over-serviced (ai livrat peste buget → semnal de renegociere). Variație = budget − Σ actual.
4

1 septembrie · batch lunar

Factura către client (AR — Accounts Receivable) — o singură linie

Finance modulul Billing — urmează

În fluxul ghidat (select clients → assets → services → generate → review → issue), când generatorul dă de un client cu budget setat, produce o singură linie = bugetulnu câte o linie per serviciu. Singurul caz în care factura nu e suma serviciilor.

Client invoice · DR-2024-0148
linieamountTVA 21%total
Northwind Retail — buget lunar · aug 202420.000,004.200,0024.200,00
client_invoice
20.000
AR — = bugetul, fix
delivered_value
17.500
Σ actuals — doar intern
net_profit
?
buget − supplier − labour
Profitul se calculează față de buget, nu față de actual: net_profit = budget − supplier_cost − labour_cost. Venitul-titlu e bugetul fix; costurile rămân cele reale, agregate din serviciile lunii.

Cele două cifre paralele — nu se amestecă

Un client cu buget raportează mereu două numere pentru aceeași lună. În raportul de profitabilitate apar unul lângă altul, etichetate distinct (spec §1.12).

cifrăce ede undeo vede clientul?
invoicedbugetul, o linie fixăClient.budgetda — singura cifră
delivered_valuevaloarea reală livratăΣ snapshot_client_amountnu — doar intern
variance = budget − delivered_value se raportează separat. net_profit e întotdeauna față de buget. A confunda cele două cifre = a rupe atât factura, cât și raportul.
DB

Ce se schimbă în schemă — aproape nimic

Puterea cazului G e că nu cere entități noi. Trei câmpuri pe client + o strategie explicită de asamblare a facturii.

CHANGE brand · trei câmpuri
billing_mode: ENUM 'per_service' | 'budget' (default 'per_service') budget: DECIMAL(12,2), NULL-abil — parametrul modului 'budget' budget_currency: VARCHAR(3) 'RON'|'EUR', NULL-abil — moneda facturii (regula A)
STRATEGY invoice assembly · selectată de billing_mode
'per_service': o linie / serviciu → Σ snapshot_client_amount (normal) 'budget': o singură linie = Client.budget (constant)
Strategia e explicită, nu un if budget IS NULL ascuns: billing_mode e un selector de nivel-client, testabil și extensibil (un al treilea mod de asamblare ar avea unde intra). E o strategie de factură, distinctă de billing type-ul (per-serviciu) — două niveluri diferite.
service, service_billing_period, service_cost_line, snapshot-urile — toate neatinse. Serviciile clientului cu buget sunt indistinctibile, la nivel de schemă, de ale oricărui alt client. Doar consumatorul (factura + raportul) le citește diferit.
Divergență spec ↔ prototip (de aliniat). Spec-ul §1.11b cheamă comportamentul prin prezența lui budget (if budget IS NULL). Prototipul modelează deja un toggle explicit Billing mode: Per service / Budget în toate drawerele. Direcția recomandată e a prototipului: un enum billing_mode pe client, cu budget ca parametru — strategia devine un lucru numit, nu o consecință a unui câmp nul.
Invariant de verificat: pentru un client cu billing_mode='budget', client_invoice al lunii = Client.budget (constant), indiferent de Σ snapshot_client_amount. Egalitatea dintre ele e o coincidență, nu o regulă.
Σ

Unde stă variația — derivată, nu stocată

Variația și soldul curent nu se scriu într-un tabel dedicat. Sunt un raport, exact ca profitabilitatea: un SUM peste snapshot-uri deja înghețate (spec §1.11b — „no new computation").

DERIVED per lună · stabil prin construcție
diff_lună(M): Client.budget − Σ snapshot_client_amount (perioadele lunii M) sold_curent(M): Σ diff_lună (de la prima lună până la M)
snapshot_client_amount e înghețat la ready/invoiced și nu se mai mișcă — de-aia diff-ul lunar e stabil: aceeași lună, același număr, oricând îl citești. Diff-urile lunare le ai „pe gratis", fără stocare nouă.
„Reconcilierea anuală" nu e un eveniment separat: soldul curge continuu. La orice tăietură (an, sfârșit de contract) citești pur și simplu sold_curent — deja e suma cumulată a diff-urilor lunare.
Decizie deschisă · decontarea soldului. Modelul de mai sus (derivat, zero tabele) acoperă monitorizarea perpetuă. Dacă businessul va decide să deconteze soldul periodic (factură/credit pe over/under-ul cumulat + repornire de la zero), atunci — și doar atunci — apare nevoia unei mici entități de settlement (dată + sumă decontată), fiindcă un pur derivat nu poate reprezenta „am decontat până în luna X, soldul repornește". Nedecis încă — până atunci: doar diff-uri lunare + sold derivat.

Simulare · buget vs. actual

interactiv · doar vizual

Mișcă bugetul (ce facturezi, fix) și actual-ul (ce livrezi, suma serviciilor). Vezi cum factura rămâne mereu = bugetul, iar diferența devine variația de scop (under/over-serviced) care intră în soldul curent. Nimic nu se salvează — e o schiță.

Account manager — fixează bugetul Serviciile — produc actual-ul
Setup + luna curentă rol: Account manager · Serviciile
Factura clientului = budget, mereu. variance = budget − actual: pozitiv → under-serviced, negativ → over-serviced. Clientul vede doar bugetul.
client_invoice
20.000
AR — = bugetul, fix
delivered_value
17.500
Σ actuals — intern
variație scop
+2.500
under-serviced
Ce e constant: coloana client_invoice nu se mișcă cu actual-ul — doar cu bugetul. Asta e toată ideea overlay-ului: o factură previzibilă pentru client, adevărul livrării ținut separat, intern.