Agency Flow · Prezentare de flux
A1 · Fixed fee — de la fixed_amount la factură
Cel mai simplu billing type, urmărit cap-coadă pe un serviciu real: ce completează omul, ce rânduri se inserează, ce calculează engine-ul, ce calculează baza de date singură — și în ce moment îngheață fiecare cifră.
O singură dată · la vânzarea serviciului
Setup: drawerul de serviciu
Account managerSe aleg assetul (clientul se derivă din el, nu se alege separat), departamentul (grupul se derivă din el) și billing type-ul Fixed fee (A1). Singurul parametru de preț: suma lunară fixă. Numele serviciului nu se scrie — se compune.
CHK_SERVICE_SUPPLIER_BY_TYPE ✓ — A1 e labor, supplier-ul trebuie să fie NULL.
UNIQ_SERVICE_COMPOSED_NAME ✓ — un singur serviciu DMK–DMK pe acest asset.
service_billing_period,
nici service_cost_line — ele aparțin lunilor, nu serviciului.
1 august · automat
Luna se deschide: engine-ul creează perioada
Engine
Un generator lunar creează câte un rând service_billing_period pentru fiecare
serviciu activ. Pentru A1 nu există nimic de introdus și suma nu depinde de ore,
așa că rândul se naște direct ready: strategia rulează la creare și sumele
facturabile îngheață pe loc. (A1 e singurul cu privilegiul ăsta — la A2/B factura
depinde de orele lunii, deci perioada lor se naște cost_entry.)
FixedFeeStrategy::calculate(ctx) → BillingBreakdown {
clientInvoice: 3.500, supplierCost: 0, labourCost: ctx.labour,
directProfit: 0, feeProfit: 0, labourRecovery: 0
}
input_* rămân NULL — la A1 nu participă (whitelist per tip, regulă de engine).ready_at e setat → cele 3.500 nu mai pot fi modificate de nimeni.
Dacă account managerul schimbă mâine fixed_amount la 4.000, se recalculează
doar lunile viitoare — augustul rămâne 3.500.
Pe parcursul lui august
Echipa lucrează: pontajele curg
Echipa task & time_entry — pasul următor de implementare
Factura A1 e fixă, dar profitul nu e — depinde de cât timp consumă echipa.
Orele se loghează pe task-urile serviciului; costul intern e per angajat, din payroll
(cost/h efectiv la data pontajului). După fiecare pontaj, engine-ul actualizează
snapshot_labour_cost, iar DB-ul recalculează singur netul.
| angajat | task | ore | cost/h intern | cost |
|---|---|---|---|---|
| Ana — Senior DMK | Campanie always-on | 15,0 | 100 | 1.500 |
| Radu — DMK Specialist | Rapoarte + optimizări | 8,0 | 80 | 640 |
| total august (planificat: 24 h) | 23,0 | 2.140 | ||
ready_at îngheață doar facturabilele; labour-ul curge liber
până la invoiced_at — pontajele sosesc mereu cu întârziere, iar factura clientului
nu depinde de ele la A1.
1 septembrie · batch lunar
Facturarea: generate → review → issue
Finance modulul Billing — urmează
Fluxul ghidat de facturare (clienți → assets → servicii → generate) adună toate
perioadele ready din august ale clientului și produce o factură per client,
cu câte o linie per serviciu. Linia noastră vine direct din snapshot — nimic nu se recalculează.
| linie | qty | preț unitar | TVA 21% | total |
|---|---|---|---|---|
| Made by society RO — DMK — DMK · aug 2024 | 1 | 3.500,00 | 735,00 | 4.235,00 |
| total factură (1 serviciu la acest client) | 4.235,00 | |||
invoiced_at setat → și labour-ul îngheață. Un pontaj întârziat pe august e respins — se corectează doar explicit, prin storno.Oricând după aceea
Raportarea: doar citiri, zero calcule
Engine
Profitabilitatea pe serviciu / client / agenție e un SUM peste rândurile de
perioadă — cifrele au fost înghețate la momentul potrivit, deci raportul e stabil istoric
și nu re-rulează nicio strategie.
service_billing_period — ce se scrie și ce se actualizează
A1 se naște direct ready — suma facturabilă e constantă din setup. Singurul lucru care se mai mișcă lunar e labour-ul (din pontaje), până la invoiced.
gross / net / recovery_net sunt coloane generate — DB-ul le derivă singur, nimeni nu le scrie.client_amount NU se schimbă (fix); doar labour_cost / labour_hours / net se mișcă.invoiced, pontajele pe luna asta se resping; editarea lui fixed_amount în setup NU atinge lunile cu ready_at setat (A1 e ready din naștere) — se aplică lunilor viitoare.service_cost_line — niciuna la acest tip
A1/A2/B sunt labor pur (supplier_id NULL, impus de CHECK) — nu există cost de furnizor, deci zero linii în service_cost_line. Rândul lunar din service_billing_period e singura evidență: factura clientului + profitabilitatea din pontaje.
Simulare · factură fixă, profit variabil
interactiv · doar vizual
Esența A1: clientul plătește suma fixă indiferent de ore, dar profitul depinde de cât
lucrează echipa. Echipa adaugă pontaje (oră × cost/h) care ating doar
labour_cost și net_profit; Finance emite factura →
invoiced și îngheață labour-ul. Schimbă orele și vezi cum un serviciu profitabil
la puține ore devine neprofitabil dacă orele cresc. Nimic nu se salvează — e o schiță.
client_invoice — nu depinde de ore
| angajat | ore | cost/h | cost linie | acțiune |
|---|---|---|---|---|
| total labour | — | 0 |
labour_cost și net_profit, până la invoiced_at
(înghețare în două faze: ready îngheață factura, invoiced îngheață labour-ul).
Un serviciu profitabil la puține ore devine neprofitabil dacă orele cresc — de-aia se pontează
chiar dacă factura e fixă.
Recapitulare: cine scrie ce
Fiecare coloană din schemă are exact un stăpân — de aici vine predictibilitatea fluxului.
Omul
- setup-ul serviciului:
service+service_pricing_fixed_fee - pontajele (task / time_entry)
- acțiunea de facturare (generate → issue)
Engine-ul
- creează perioada lunară, direct
ready - rulează
FixedFeeStrategy→ scrie cele 6 primitive - actualizează
labour_costla fiecare pontaj - mișcă stările + setează
ready_at/invoiced_at
Baza de date
gross / net / recovery_net— coloane generate- numele compus unic (
supplier_key) - CHECK-uri: supplier NULL la A1, period = ziua 1, sume ≥ 0
input_* (rămân NULL) și
service_cost_line (zero rânduri) — pentru că A1 n-are furnizor și n-are nimic de
introdus lunar. Exact în golurile astea intră celelalte tipuri: E își pune costul în
input_supplier_cost, F2 orele, Platform spend-ul, iar F1 își aduce liniile de
deliverable. Aceleași tabele, alt actor pe aceleași locuri — ăsta e tot strategy pattern-ul.