Sari la conținut

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.

Fiecare companie are o entitate EfacturaIntegration (src/Asy.Domain/Efactura/EfacturaIntegration.cs) care ține credențialele de API SFS:

CâmpStocare
Username, FirstName, Surname, Emailtext simplu
Passwordtranzient — nu se persistă
PasswordEncryptedcriptat 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.

SFS lucrează cu roluri de actor și coduri numerice de status. Rolul (enum intern SfsActorRole):

RolValoareSensDirecție factură
Seller1Furnizor / emitentOutgoing (firma emite)
Buyer2CumpărătorIncoming (firma primește)

Codurile de status pe care le interoghează sync-ul (int[] statuses = [1, 3, 4, 6, 7, 8, 10];):

CodSensMapare internă EFacturaStatus
1Semnat FurnizorSentToEFactura
3AcceptatAccepted
4Anulatnull (+ InvoiceStatus.Cancelled)
6ArhivatAccepted
7TrimisSentToEFactura
8Semnat CumpărătorAccepted
10TransportatAccepted

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.

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, …)

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).

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.

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.

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.

Aggregate-ul Invoice (src/Asy.Domain/Invoicing/Invoice.cs) ține:

CâmpRol
DirectionOutgoing | Incoming (init-only, comută comportamentul)
EFacturaSfsStatuscodul SFS brut (1/3/4/6/7/8/10)
EFacturaStatusstatus local (Pending/SentToEFactura/Accepted/Rejected)
EFacturaXmlObjectKey, EFacturaPdfObjectKeychei MinIO
EFacturaSignedByNamesemnatarul de la SFS (poate ≠ aprobatorul intern din Telegram)
ImportedFromSpvAt, EFacturaMetaJsonmetadate import