e-Factură (SFS / SPV)
ASYDATA se conectează la Serviciul Fiscal de Stat (SFS) prin sistemul SPV de e-Factură ca să importe
automat facturile primite și emise ale fiecărei firme, să le parseze, să sincronizeze partenerii și
conturile bancare și să permită emiterea (semnare electronică + postare la SFS). Toată logica de
domeniu stă în src/Asy.Domain/Efactura/, integrarea efectivă în src/Asy.Infrastructure/Efactura/ și
clientul SOAP în src/Asy.Infrastructure/Sfs/. Documentul de modul de referință e
docs/modules/facturare.md.
Această integrare alimentează direct operarea zilnică: o factură nouă primită declanșează cardul Telegram și notificările. Emiterea unei facturi trece prin MSign.
Credențiale SFS
Secțiune intitulată „Credențiale SFS”Fiecare companie are o entitate EfacturaIntegration
(src/Asy.Domain/Efactura/EfacturaIntegration.cs) care ține credențialele de API SFS:
| Câmp | Stocare |
|---|---|
Username, FirstName, Surname, Email | text simplu |
Password | tranzient — nu se persistă |
PasswordEncrypted | criptat la rest cu ASP.NET Core DataProtection |
Parola se criptează cu un IDataProtector cu purpose "Asy.Efactura.Credentials.v1"
(src/Asy.Infrastructure/Efactura/EfacturaService.cs). Cheile DataProtection sunt persistate într-un volum
Docker (dp-keys).
Restul stării de integrare: Enabled, Environment (Test | Prod), SyncIntervalMinutes,
LastSyncAt, LastSyncStatus, LastSyncError, LastSyncedPeriodFrom/To, LastInvoiceCount. După primul
sync reușit în Test, mediul se auto-promovează la Prod.
Roluri și coduri de status SFS
Secțiune intitulată „Roluri și coduri de status SFS”SFS lucrează cu roluri de actor și coduri numerice de status. Rolul (enum intern SfsActorRole):
| Rol | Valoare | Sens | Direcție factură |
|---|---|---|---|
Seller | 1 | Furnizor / emitent | Outgoing (firma emite) |
Buyer | 2 | Cumpărător | Incoming (firma primește) |
Codurile de status pe care le interoghează sync-ul (int[] statuses = [1, 3, 4, 6, 7, 8, 10];):
| Cod | Sens | Mapare internă EFacturaStatus |
|---|---|---|
| 1 | Semnat Furnizor | SentToEFactura |
| 3 | Acceptat | Accepted |
| 4 | Anulat | null (+ InvoiceStatus.Cancelled) |
| 6 | Arhivat | Accepted |
| 7 | Trimis | SentToEFactura |
| 8 | Semnat Cumpărător | Accepted |
| 10 | Transportat | Accepted |
Maparea e în MapSfsToEFacturaStatus(int) din
src/Asy.Infrastructure/Efactura/EfacturaService.cs. La deduplicare, statusul 4 (Anulat) câștigă mereu
— o factură marcată anulată rămâne anulată chiar dacă o interogare anterioară a găsit-o vie.
Fluxul de sincronizare (import)
Secțiune intitulată „Fluxul de sincronizare (import)”Metoda centrală e SyncInvoicesAsync(companyId, userId, fromDate, toDate, direction) din
src/Asy.Infrastructure/Efactura/EfacturaService.cs. Parametrul direction ∈
"incoming" | "outgoing" | "both" decide rolurile interogate.
1. DISCOVERY → SearchInvoices(ActorRole, status ∈ {1,3,4,6,7,8,10}, interval IssuedOn) → dedup pe (Seria, Number), status 4 are prioritate
2. PER FACTURĂ: a. GetInvoicesBySeriaNumber({Seria, Number}) → XML brut b. EfacturaXmlParser.Parse(xml) → ParsedEfacturaInvoice c. direcție = (firmIdno == Supplier.Idno) ? Outgoing : Incoming d. upsert PartnerMaster (după IDNO) + PartnerRelationship per direcție e. upsert MasterBankAccount din IBAN-ul XML (Source = Sfs) f. salvează XML în MinIO → bucket "asy-efacturi", key {firmId}/{seria}-{number}.xml g. fetch + salvează PDF → key {firmId}/{seria}-{number}.pdf (best-effort) h. creează/actualizează Invoice (Direction, Series, Number, totaluri, EFacturaSfsStatus, …)
3. EVENIMENTE: dacă Incoming și status 7 (sau 8 + IsShortCircle) → invoice.RaiseEFacturaReceived(...) → card Telegram + notificări dacă status s-a schimbat pe o factură existentă → invoice.RaiseEFacturaStatusChanged() → re-editare card
4. STARE: actualizează EfacturaIntegration (LastSyncAt, LastSyncStatus, LastInvoiceCount, …)Parsarea XML
Secțiune intitulată „Parsarea XML”EfacturaXmlParser (src/Asy.Infrastructure/Efactura/EfacturaXmlParser.cs) citește rădăcina
<SupplierInfo> și extrage Seria, Number, IssuedDate, DeliveryDate, părțile Supplier/Buyer
(IDNO, titlu, adresă, cod TVA, IBAN), liniile din Merchandises/Row[], Total, TotalTVA,
TaxareaInversa (reverse charge), IsFarma, documente atașate și puncte de încărcare/descărcare. Cotele
TVA "-"/"" devin -1 („fără TVA”), iar "20"/"8"/"0" devin fracții (0.20, 0.08, 0.00).
Upsert partener + cont bancar
Secțiune intitulată „Upsert partener + cont bancar”Pe baza părții opuse din XML (firmIsSupplier ? Buyer : Supplier), se face upsert pe PartnerMaster
(cheie IDNO) și pe PartnerRelationship per (OwnerFirmId, MasterId), cu conturi GL implicite pe
direcție. IBAN-ul din XML devine un MasterBankAccount cu Source = BankAccountSource.Sfs. Importul
setează TradeType = National când țara partenerului e MD.
Worker-ul de poll
Secțiune intitulată „Worker-ul de poll”EfacturaPollWorker (src/Asy.Infrastructure/Efactura/EfacturaPollWorker.cs) rulează automat la fiecare
20 de minute. Pentru fiecare firmă cu integrare Enabled și credențiale setate:
- incoming pe ultimele 7 zile — importă facturile primite noi, ridică notificările;
- outgoing pe ultimele 30 de zile — reîmprospătează doar statusul facturilor emise (fără notificări).
Pentru fiecare factură nou importată în rulajul curent, creează o notificare in-app către membrii firmei
(Type: "efactura.incoming", Link: /facturi/{id}) și emite evenimentul consumat de Telegram. Vezi
Notificări.
Emiterea unei facturi (Validate → MSign → PostInvoices)
Secțiune intitulată „Emiterea unei facturi (Validate → MSign → PostInvoices)”Pentru facturile emise, orchestrarea e în InvoiceEFacturaService.InitiateSignAndSendAsync(...)
(src/Asy.Infrastructure/Invoicing/InvoiceEFacturaService.cs):
1. VALIDATE → încarcă factura (Outgoing, nu Cancelled), firma (supplier), partenerul (buyer), IBAN-urile
2. BUILD XML → EFacturaInvoiceXmlBuilder.Build(...) → <TaxInvoice><SupplierInfo>… (Supplier/Buyer, Merchandises/Row[], Total, TotalTVA, TaxareaInversa)
3. MSIGN → MsignService.InitiateXmlAsync(..., postBackTarget: "efactura:postinvoices:1") → redirectUrl → user semnează cu certificat
4. CALLBACK → /api/msign/callback → fetch rezultat → reconstrucție plic XAdES → InvokeSfsAsync("PostInvoices", { ActorRole:1, InvoicesXmlStatus:1, InvoicesXml })
5. STATUS → re-citire GetInvoicesBySeriaNumber → EFacturaSfsStatus actualizat → invoice.MarkEFacturaSigned(...) → EFacturaStatus = SentToEFactura → invoice.RaiseIssued() → card Telegram „Factură emisă"postBackTarget are formatul "efactura:postinvoices:<actorRole>" și e ce semnalează callback-ului MSign
să posteze automat XML-ul semnat la SFS după semnare. Detalii despre semnare, callback și auto-post în
MSign.
Clientul SFS
Secțiune intitulată „Clientul SFS”src/Asy.Infrastructure/Sfs/SfsEfacturaClient.cs e un client SOAP (BasicHttpsBinding,
TransportWithMessageCredential, UsernameToken). Dispatcher-ul generic
InvokeAsync(operation, requestJson) mapează numele operației la metoda SOAP și serializează JSON ↔
DataContract. Operațiile folosite includ SearchInvoices, GetInvoicesBySeriaNumber,
GetInvoicesForSigning, GetInvoicesContentForPrint, GetBankAccountInfo, PostInvoices.
Câmpurile e-Factură pe Invoice
Secțiune intitulată „Câmpurile e-Factură pe Invoice”Aggregate-ul Invoice (src/Asy.Domain/Invoicing/Invoice.cs) ține:
| Câmp | Rol |
|---|---|
Direction | Outgoing | Incoming (init-only, comută comportamentul) |
EFacturaSfsStatus | codul SFS brut (1/3/4/6/7/8/10) |
EFacturaStatus | status local (Pending/SentToEFactura/Accepted/Rejected) |
EFacturaXmlObjectKey, EFacturaPdfObjectKey | chei MinIO |
EFacturaSignedByName | semnatarul de la SFS (poate ≠ aprobatorul intern din Telegram) |
ImportedFromSpvAt, EFacturaMetaJson | metadate import |
- Operare zilnică — unde se vede sync-ul în business flow
- MSign — semnarea facturilor emise și primite
- Telegram — cardul de e-Factură
- Domenii — contextele Efactura și Invoicing
- Schema vizuală pe Figma