Why this exists before any screen mocks

SDIApp Phase 7 is fundamentally two-sided commerce. A retailer's action becomes a distributor's notification. A distributor's confirmation becomes a retailer's timeline entry. If the two halves don't feel like one system, the design fails — no matter how polished individual screens look.

This page reads left-to-right across one complete transaction. The distributor's phone is the top lane; the retailer's phone is the bottom lane. Each step calls out the Cloud Function that fires and the data that crosses the boundary. The downstream surface mocks (catalog, cart, order detail, invoice) are deep dives into individual frames you see here.

Distributor lane Retailer lane → Arrows show causality, not navigation. A retailer never sees the distributor's screen and vice versa.

Pasul 0 — premisă: legătura org-la-org există deja

Story-ul de mai jos începe cu un retailer și un distribuitor deja legați. În realitatea B2B FMCG moldovenească, această legătură nu apare prin retailer pulling — apare aproape întotdeauna prin agentul distribuitorului care vizitează magazinul și apelează inviteExistingRetailerToLink (dacă magazinul are deja cont) sau inviteRetailerToOnboard (dacă magazinul e nou — atomic creează cont + link prin claim-ul retailerului). Vezi vizite.html pentru fluxul agentului și link-inbox.html pentru ce vede fiecare parte după acel moment fizic.

Phase 3 a livrat două callable-uri distincte ce acoperă această premisă: inviteExistingRetailerToLink (retailerul are deja cont) și pereche inviteRetailerToOnboard + claimRetailerOnboardingInvitation (retailer nou — bootstrap atomic org + link). UI-ul le ascunde sub un singur flux de agent — vezi vizite.html.

Tip: scroll horizontally below. The strip is 11 steps wide.

Vitafor SRL
distribuitor
Magazin Central
retailer
1Distribuitorul adaugă un produs nou
createProduct()
Produse
V
Vitafor
PM
Produs nou
Apare imediat în catalogul retailerilor activi
NumeLapte Sec 12%
SKULAP-12-1L
UMlitru
Preț fără TVA22,22 lei
TVA8%
Stoc inițial120 buc
Salvează produs
Distribuitor: crează produs cu stoc de deschidere. Scrie produsul + intrarea de stoc inițial în inventoryLedger.
Retailerul nu vede încă schimbarea
— niciun apel —
Catalog
M
Magazin
AG
Catalog
2 distribuitori activi
Vitafor SRLactiv
Lacto Plusactiv
Lista produselor se actualizează în timp real.
Listenerul Firestore va aduce produsul la pasul următor — nicio acțiune din partea retailerului.
snapshot listener push
2Catalogul rămâne la zi
read-only
Produse
V
Vitafor
PM
Catalogul tău
128 de produse · 14 retaileri legați
Lapte Sec 12%
22,22 lei · 120 buc
Iaurt natural 350g
14,40 lei · 88 buc
Smântână 20%
18,75 lei · 42 buc
Distribuitor: produsul apare în lista lui catalog; statistica retaileri rămâne neschimbată.
Retailerul vede produsul nou
listener fires
Catalog
M
Magazin
AG
Vitafor SRL
42 produse disponibile
Lapte Sec 12% nou
22,22 lei / l
+
Iaurt natural 350g
14,40 lei / buc
+
Smântână 20%
18,75 lei / buc
+
Retailer: produsul "nou" e marcat vizibil. Un singur tap pe + îl adaugă în coș.
addDraftLine()
3Distribuitorul nu vede coșul
— invisible by design —
Comenzi
V
Vitafor
PM
Comenzi primite
3 active · 2 livrate astăzi
Bakal Vasilede confirmat
Universal Martde pachetat
Coșul retailerului trăiește într-un sub-colecție privat (orderDrafts). Distribuitorul nu vede draft.
Retailerul construiește coșul
setDraftOutlet() + addDraftLine()
Coș
M
Magazin
AG
Livrare laLocație Centru ▾
Coș · Vitafor
Lapte Sec 12%6 × 22,22
Iaurt natural10 × 14,40
Fără TVA277,32 lei
TVA 8%22,19 lei
Total299,51 lei
Trimite comanda
Retailer: locația livrării e bannerul sticky de sus (default-outlet pre-selectat) — eroarea "outlet missing" devine imposibilă.
submitOrder()
(transactional)
4Comanda apare la distribuitor
submitOrder() → events[submitted]
Comenzi
V
Vitafor
PM
Comenzi primite
4 active
nou · acum 8s#1294
Magazin Central299,51 lei
Loc.Centru
Bakal Vasile #1293de confirmat
Universal #1291de pachetat
Comanda nouă apare urgentă (accent + signature card). Stocul se rezervă atomic în aceeași tranzacție.
Coșul devine comandă trimisă
cart cleared → order created
Comenzi
M
Magazin
AG
Comanda #1294
trimisă acum 8s
Staretrimisă
Total299,51 lei
LivrareLoc. Centru
Trimisă de Andrei
acum 8s
În așteptare — confirmare Vitafor
Retailer: timeline-ul comenzii arată clar unde se află — și ce urmează.
confirmOrder()
5Distribuitorul confirmă
confirmOrder() → events[confirmed]
Comandă
V
Vitafor
PM
#1294 Magazin Central
2 linii · Loc. Centru
Lapte Sec 12%6 × 22,22
Iaurt natural10 × 14,40
Total299,51 lei
Confirmă
Respinge
Distribuitor owner: confirmare = un tap. Respingerea (owner-only) eliberează stocul rezervat.
Retailerul vede confirmarea
listener fires
Comandă
M
Magazin
AG
Comanda #1294
confirmată acum 14s
Trimisă
acum 24s
Confirmată de Vitafor
acum 14s
În așteptare — pachetare
În așteptare — expediere
În așteptare — livrare
Anuleazăposibil până la expediere
Cele două laturi sunt sincronizate via listener. Numele și ora actorului apar în timeline.
packOrder()
shipOrder()
6Pachet → expediere
packOrder() / shipOrder()
Comandă
V
Vitafor
PM
#1294
stare: pachetată
Confirmată
acum 18m
Pachetată de Maria
acum 4m
Expediată
Atențiestoc rezervat se descarcă la expediere
Marchează expediată
La shipOrder(): rezervarea → realizare (stoc-pe-mână scade, stoc-rezervat scade). O singură tranzacție.
Anularea nu mai e posibilă
cancel CTA disappears
Comandă
M
Magazin
AG
#1294
expediată — sosește mâine
Trimisă
Confirmată
Pachetată
Expediată
acum 2m
În drum spre tine
Retailer: lipsa CTA-ului "Anulează" comunică în mod natural — fără popup, fără explicație separată.
deliverOrder()
7Livrată — gata de facturare
deliverOrder()
Comandă
V
Vitafor
PM
#1294
livrată · neîncasată
Pachetată
Expediată
Livrată
acum 1h
Următorul pas
Emite factura
Emite factură
Distribuitor owner vede CTA accent — singurul moment "urgent" din ecran. Bridge către fluxul fiscal.
Comandă marcată livrată
listener fires
Comandă
M
Magazin
AG
#1294
livrată · așteaptă factura
Expediată
Livrată
acum 1h
În așteptare — factură
Confirmă primirealivrată
Retailer: starea evoluează singură, fără apăsări. Confirmarea fizică a livrării rămâne pe seama distribuitorului.
issueInvoice()
(counter tx)
8Factura fiscală emisă
issueInvoice() + renderInvoicePdf()
Factură
V
Vitafor
PM
FACTURĂ FISCALĂSeria SDI · Nr 000042
emisă 18.05.2026 · scadență 17.06.2026
FurnizorVitafor SRL
BeneficiarMagazin Central
Total299,51 lei
în litere
Două sute nouăzeci și nouă lei 51 bani
Vezi PDF
Înreg. plată
Counter atomic per (distribuitor, serie). Numărul nu se mai poate schimba — document fiscal imutabil.
Retailerul primește factura
listener fires
Factură
M
Magazin
AG
FACTURĂ FISCALĂSeria SDI · Nr 000042
de plătit până la 17.06.2026
Total299,51 lei
Stareneîncasată
Scrie distribuitorului pentru detalii de plată
Vezi PDF
Retailer: vede aceeași factură imutabilă, fără CTA "Înregistrează plată" (acel drept aparține distribuitorului).
recordPayment()
9Plata înregistrată — comandă închisă
recordPayment() → settled
Factură
V
Vitafor
PM
FACTURĂ FISCALĂSeria SDI · Nr 000042
închisă · plătită integral
Total299,51 lei
Stareachitată
Plata 1 — virament17.05 · 299,51 lei
Mulțumire — ciclu complet
creare → factură → încasare
Plata atinge totalul → comanda trece din invoiced în settled în aceeași tranzacție.
Cicl închis — ambii partidi văd "achitată"
listener fires
Factură
M
Magazin
AG
FACTURĂ FISCALĂSeria SDI · Nr 000042
achitată 17.05.2026
Stareachitată
Plata 1virament · 299,51 lei
Comandă închisăsettled
Comandă din nou
Retailer: ciclu închis. CTA-ul "Comandă din nou" deschide automatic un draft nou cu aceleași linii.

Ce traversează granița (data crossing summary)

Pas Cloud Function Stare comandă Date scrise / efect
1createProduct()Produs + ledger inițial (reason: restock).
2— (listener)Snapshot Firestore — retailerul primește produsul fără refresh.
3addDraftLine()Draft retailer-privat. Distribuitorul nu vede.
4submitOrder()[no doc] → submittedSnapshot linii + totaluri; stoc rezervat per linie; draft șters; primul events/.
5confirmOrder()submitted → confirmedEveniment "confirmed" + actor.
6packOrder() · shipOrder()confirmed → packed → shippedLa shipOrder: rezervare → realizare (stockOnHand −= qty).
7deliverOrder()shipped → deliveredAnularea retailerului devine indisponibilă (vizibil în UI prin lipsa CTA).
8issueInvoice() + renderInvoicePdf()delivered → invoicedCounter atomic per (distribuitor, serie); doc fiscal imutabil; PDF render queued.
9recordPayment()invoiced → settledPlată în sub-colecție; dacă cumulul ≥ total, comanda trece la settled.