Agency Flow · Prezentare de flux

F2 · Cost/h + fee/h — orele furnizorului, nu ale echipei

Arată ca un tip „hourly", dar e supplier sadea: orele sunt ale furnizorului, nu vin din pontaje interne. Ratele (cost/h și fee/h) se fixează la setup, specialistul introduce lunar doar orele — de aceea F2 sare peste awaiting_fee: fee-ul e decis dinainte.

Account manager — setup Engine — automat Specialist — orele lunare Finance — facturi
1

O singură dată · la vânzarea serviciului

Setup: două rate, fixate o dată

Account manager

Negocierea se întâmplă aici, nu lunar: cât ne costă ora furnizorului (100) și cât adăugăm noi peste (50). Din momentul ăsta, singura variabilă a lunii sunt orele.

Drawer · New service
Northwind Retail — Magento · northwind.ro
Webmastering
grup derivat: SEO
Supplier — cost/hour + fee/hour (F2)
Webify
100 RON
50 RON — marja agenției
nume compus → Northwind Retail — SEO — Webmastering — Webify
În baza de date · la Save
INSERT service + service_pricing_supplier_hourly
billing_type: 'supplier_hourly' supplier_id: → Webify cost_per_hour: 100.00 fee_per_hour: 50.00
2

1 august → sfârșitul lunii

Perioada așteaptă orele

Engine Specialist

Perioada se naște cost_entry, ca la E — dar inputul e altul: orele raportate de furnizor. La „Enter hours", strategia calculează tot lanțul din cele două rate de setup, iar perioada trece direct ready.

Formular · Enter hours — August 2024 rol: Specialist
26 h — din raportul Webify
3.900 RON = 26 × (100 + 50)
În baza de date · la salvare
UPDATE service_billing_period
input_supplier_hours: 26.00 snapshot_supplier_cost: 2600.00 snapshot_fee_profit: 1300.00 snapshot_client_amount: 3900.00 state: 'cost_entry' → 'ready'
whitelist: la F2 doar input_supplier_hours e scriibil.
INSERT service_cost_line · materializată
line_type: 'supplier_hours' quantity: 26 — orele sunt cantitatea unit_cost: 100.00 unit_fee: 50.00 client_amount: 3900.00
aceeași anatomie ca o linie F1 — doar că aici o scrie engine-ul, cu ratele din setup.
De ce nu există awaiting_fee la F2? Pentru că fee-ul nu se decide lunar — e în contract (50/h, la setup). Ce rămâne lunar e doar o cifră factuală (orele), deci un singur rol e de ajuns. Exact asta separă F2 de F1.
3

1 septembrie + la sosirea facturii Webify

Facturile: 3.900 în AR (Accounts Receivable), 2.600 în AP (Accounts Payable)

Finance modulul Billing — urmează

Clientul primește o linie de 3.900 (echivalent 150/h, fără split). Webify facturează 2.600, legat de linia de cost. Istoricul lunar arată variația orelor: iulie 31 h → 4.650, august 26 h → 3.900.

Istoricul perioadelor
periodorecost (×100)fee (×50)clientstate
2024-07-0131,03.1001.5504.650invoiced
2024-08-0126,02.6001.3003.900ready
client_invoice · aug
3.900
26 × 150 combinat
supplier_cost
2.600
AP → linia de cost
fee_profit
1.300
50 × 26 — marjă curată
margin
33,3%
constantă prin construcție
Observație de business pe care schema o face vizibilă: la F2 marja procentuală e constantă (50 / 150 = 33,3% indiferent de ore) — profitul absolut crește cu volumul. La F3, procentul e fixat direct; la F1, fiecare linie are marja ei.
DB

service_billing_period — ce se scrie și ce se actualizează

F2 se naște cost_entry; la introducerea orelor furnizorului derivă tot din ratele de setup și sare DIRECT în ready (fee-ul e în contract, fără awaiting_fee).

service_billing_period · ciclul de scriere al rândului lunar
INSERT service_billing_period · la generarea perioadei
state: 'cost_entry' input_supplier_hours: NULL — se așteaptă input_supplier_cost: NULL — nu aparține F2 input_amount_spend: NULL — nu aparține F2 snapshot_*: NULL — toate ready_at: NULL invoiced_at: NULL
rândul se naște gol în afară de state — singurul input al lunii sunt orele furnizorului.
UPDATE service_billing_period · specialistul introduce orele (input + recompute)
input_supplier_hours: 26 — scris de specialist snapshot_client_amount: 3.900 = (cost/h + fee/h) × ore snapshot_supplier_cost: 2.600 = cost/h × ore snapshot_fee_profit: 1.300 = fee/h × ore snapshot_labour_cost: 0 snapshot_labour_hours: 0 snapshot_direct_profit: 0 snapshot_labour_recovery: 0 state: 'cost_entry' → 'ready' ready_at: setat
engine-ul materializează linia supplier_hours, quantity = orele (26).
UPDATE service_billing_period · reeditare ore înainte de facturare (reset)
input_supplier_hours: valoare nouă state: 'ready' → 'cost_entry' ready_at: NULL
orele schimbate readuc perioada la cost_entry — trebuie reconfirmată.
UPDATE service_billing_period · la emitere
state: 'ready' → 'invoiced' invoiced_at: setat
DB

service_cost_line — linia materializată de engine

La F2, engine-ul materializează linia la introducerea orelor. Particularitatea: quantity NU e 1 — e chiar numărul de ore, iar unit_cost/unit_fee sunt ratele de setup.

service_cost_line · linia lunii (materializată la input)
INSERT service_cost_line · la introducerea orelor (input_supplier_hours 26 → materializare)
billing_period_id: → perioada supplier_id: → service.supplier_id line_type: 'supplier_hours' description: auto quantity: 26 — ORELE, nu 1 unit_cost: = cost/h (100) unit_fee: = fee/h (50) supplier_cost_amount: 2.600 (= 100 × 26) fee_amount: 1.300 (= 50 × 26) client_amount: 3.900 state: 'ready' — oglindește perioada position: 1
o singură linie pe lună: quantity = orele, ratele de setup stau pe unit_cost/unit_fee.
UPDATE service_cost_line · reeditare ore înainte de facturare (reset)
quantity: recalculat la noile ore supplier_cost_amount: recalculat la noile ore fee_amount: recalculat la noile ore client_amount: recalculat la noile ore state: 'ready' → 'cost_entry'
orele schimbate rescriu linia și readuc perioada la cost_entry.
UPDATE service_cost_line · la emitere
state: 'ready' → 'invoiced' (Locked)
La F2, quantity = orele furnizorului — aici e cheia, nu e 1 ca la o linie clasică. Linia e ancora AP a facturii furnizorului pe cost (2.600), iar fee_amount (1.300) e marja noastră adăugată peste.

Simulare · orele decid tot

interactiv · doar vizual

Exact cum funcționează F2: cele două rate se fixează o dată, la setup (cost/h și fee/h), apoi specialistul introduce lunar doar orele furnizorului. Restul se derivă: client = (cost/h + fee/h) × ore. Fee-ul fiind decis dinainte, perioada sare peste awaiting_fee și trece direct în ready. Nimic nu se salvează — e o schiță.

Specialist — orele lunii
Setup · rate din contract fixate o dată
ratele nu se schimbă lunar — negocierea e la vânzarea serviciului.
Enter hours starea perioadei: cost_entry
line_typequantity (ore)unit_cost unit_feeclientstate
client_invoice
0
(cost/h + fee/h) × ore
supplier_cost
0
cost/h × ore — AP
fee_profit
0
fee/h × ore
net_profit
0
= fee_profit (labour 0)
Ce ilustrează: fee-ul e fix în contract (la setup) — de aceea F2 nu folosește awaiting_fee, un singur rol lunar e de ajuns. quantity pe linia de cost e chiar orele furnizorului, iar tot fee-ul e marjă curată: nu există cost de manoperă internă, deci net_profit = fee_profit.