diff --git a/clase/anaf_efactura.vc2 b/clase/anaf_efactura.vc2
index 2f7fbd5..a9545e6 100644
--- a/clase/anaf_efactura.vc2
+++ b/clase/anaf_efactura.vc2
@@ -377,6 +377,7 @@ DEFINE CLASS anaf_efactura AS _frmbase OF "_frm_base.vcx"
*m: bifeazatrimismanual && Bifeaza o factura ca trimisa manual
*m: citesteraspunsuri
*m: do_ataseaza
+ *m: do_storno_efactura
*m: gettoken
*m: ltest_assign
*m: refreshdetaliifacturi
@@ -5656,7 +5657,7 @@ DEFINE CLASS anaf_efactura AS _frmbase OF "_frm_base.vcx"
lcCursor = 'crsFacturiEmise'
CASE m.lnPaginaActiva = 2
* Facturi TRIMISE
- lnOptiune = xmenu('Centralizator facturi;Lista facturi trimise - detaliat;Factura trimisa curenta;\-;PDF ANAF facturi alese;PDF ANAF factura curenta;\-;Editare in browser')
+ lnOptiune = xmenu('Centralizator facturi;Lista facturi trimise - detaliat;Factura trimisa curenta;\-;PDF ANAF facturi alese;PDF ANAF factura curenta;\-;Editare in browser;\-;Generare XML storno si trimitere la ANAF')
lcCursor = 'crsFacturiTrimise'
CASE m.lnPaginaActiva = 3
* Facturi PRIMITE
@@ -5822,11 +5823,15 @@ DEFINE CLASS anaf_efactura AS _frmbase OF "_frm_base.vcx"
loSender = NEWOBJECT("eFacturaXmlPreview", "xmlefactura-preview.prg")
loSender.webURL = "https://romfast.ro/efactura-generator/"
loSender.SendXML(m.lcDetalii, m.llVariable)
+
+ CASE m.lnOptiune = 10 AND m.lnPaginaActiva = 2
+ * Generare XML storno si trimitere la ANAF - doar pagina Trimise (D6)
+ This.do_storno_efactura()
Endcase
Select (m.lcSelect)
-
+
ENDPROC
PROCEDURE do_sterge
@@ -5877,6 +5882,144 @@ DEFINE CLASS anaf_efactura AS _frmbase OF "_frm_base.vcx"
ENDPROC
+ PROCEDURE do_storno_efactura
+ *!* 18.09.2026 stornare XML eFactura din borderou (docs\plan_storno_efactura.md)
+ LOCAL lnId, lcXmlOriginal, lcMesaj, lnRecno, lcNumarOriginal, ldDataOriginal
+ LOCAL lcClient, lcMoneda, lnTotal, lnTestRand, lcCifFirma, lcTempXml, loDom
+ LOCAL lcCifXml, loNod, lcIssueDateStr, ldIssueDate, lcCustomerCif, llB2C, loNoduri, lnI
+ LOCAL lcNumarNou, lcNumarNouSafe, lcFisierStorno, lcNotaIni, llSucces
+ LOCAL laModificari[1], lcAvertizare, laDubl[1], laProcesate[1], loForma
+ IF AMESSAGEBOX('Se genereaza XML-ul de stornare al facturii selectate (aceleasi linii, valori negative).' + CHR(13) + CHR(13) + ;
+ 'Nu se trimite nimic la ANAF pana nu verificati XML-ul in fereastra urmatoare si apasati "Trimite la ANAF".', 1+64, _screen.Caption) <> 1
+ RETURN
+ ENDIF
+
+ SELECT crsFacturiTrimise
+ lnRecno = Recno()
+
+ IF ALLTRIM(NVL(tip_mesaj_raspuns, '')) <> 'FACTURA TRIMISA'
+ AMESSAGEBOX('Stornarea este posibila doar pentru facturi acceptate (FACTURA TRIMISA).', 0+48, _screen.Caption)
+ RETURN
+ ENDIF
+
+ lnId = NVL(crsFacturiTrimise.Id, 0)
+ IF Empty(m.lnId)
+ RETURN
+ ENDIF
+ lcNumarOriginal = ALLTRIM(NVL(crsFacturiTrimise.numar_act, ''))
+ lcClient = ALLTRIM(NVL(crsFacturiTrimise.xclient, ''))
+ lcMoneda = ALLTRIM(NVL(crsFacturiTrimise.nume_valuta, ''))
+ lnTotal = NVL(crsFacturiTrimise.total_cu_tva, 0)
+ lnTestRand = NVL(crsFacturiTrimise.test, 0)
+
+ lcCifFirma = ALLTRIM(Thisform.oAnaf.AnafeFacturaServer.cCodFiscal)
+ IF lnTestRand <> IIF(Thisform.oAnaf.AnafeFacturaServer.lTest, 1, 0)
+ AMESSAGEBOX('Documentul original e din mediul ' + IIF(m.lnTestRand=1,'TEST','PRODUCTIE') + ;
+ ', dar borderoul este pe ' + IIF(Thisform.oAnaf.AnafeFacturaServer.lTest,'TEST','PRODUCTIE') + '. Comutati mediul inainte de stornare.', 0+16, _screen.Caption)
+ RETURN
+ ENDIF
+
+ IF !GetXmlOriginalEFactura(m.lnId, @lcXmlOriginal, @lcMesaj)
+ AMESSAGEBOX('Factura nu are XML descarcat: ' + m.lcMesaj, 0+16, _screen.Caption)
+ RETURN
+ ENDIF
+
+ * parsez XML-ul pe fisier (nu .loadXML pe sir), ca sa nu risc reinterpretarea octetilor UTF-8
+ lcTempXml = Addbs(Sys(2023)) + 'storno_check_' + Sys(2015) + '.xml'
+ Strtofile(m.lcXmlOriginal, m.lcTempXml)
+ loDom = Createobject('MSXML2.DOMDocument.6.0')
+ loDom.setProperty('SelectionLanguage', 'XPath')
+ loDom.preserveWhiteSpace = .T.
+ llSucces = loDom.load(m.lcTempXml)
+ TRY
+ Delete File (m.lcTempXml)
+ CATCH
+ ENDTRY
+
+ IF !m.llSucces OR !Inlist(loDom.documentElement.baseName, 'Invoice', 'CreditNote')
+ AMESSAGEBOX('Documentul nu e Invoice/CreditNote.', 0+16, _screen.Caption)
+ RETURN
+ ENDIF
+
+ *!* 18.09.2026 CIF-ul poate fi in PartyTaxScheme sau PartyLegalEntity (acolo e uneori nr. Reg. Com.)
+ lcCifXml = ''
+ loNoduri = loDom.selectNodes("//*[local-name()='AccountingSupplierParty']//*[local-name()='CompanyID']")
+ FOR lnI = 0 TO m.loNoduri.length - 1
+ loNod = m.loNoduri.item(m.lnI)
+ lcCifXml = GetNrFromString(ALLTRIM(m.loNod.Text))
+ IF m.lcCifXml == ALLTRIM(m.lcCifFirma)
+ EXIT
+ ENDIF
+ ENDFOR
+ IF Empty(m.lcCifXml) OR !(m.lcCifXml == ALLTRIM(m.lcCifFirma))
+ AMESSAGEBOX('Factura e emisa de alta firma (CIF ' + m.lcCifXml + ').', 0+16, _screen.Caption)
+ RETURN
+ ENDIF
+
+ loNod = loDom.selectSingleNode("//*[local-name()='IssueDate']")
+ lcIssueDateStr = IIF(Isnull(m.loNod), '', ALLTRIM(m.loNod.Text))
+ ldIssueDate = IIF(Len(m.lcIssueDateStr) < 10, {}, Date(Val(Substr(m.lcIssueDateStr,1,4)), Val(Substr(m.lcIssueDateStr,6,2)), Val(Substr(m.lcIssueDateStr,9,2))))
+ IF Empty(m.ldIssueDate)
+ AMESSAGEBOX('Nu s-a putut citi data facturii din XML.', 0+16, _screen.Caption)
+ RETURN
+ ENDIF
+ IF is_luna_blocata(gcS, gnId_Prg_Owner, Year(m.ldIssueDate), Month(m.ldIssueDate))
+ AMESSAGEBOX('Luna ' + Padl(Month(m.ldIssueDate),2,'0') + '.' + Transform(Year(m.ldIssueDate)) + ' este inchisa.', 0+16, _screen.Caption)
+ RETURN
+ ENDIF
+
+ loNod = loDom.selectSingleNode("//*[local-name()='AccountingCustomerParty']//*[local-name()='PartyLegalEntity']/*[local-name()='CompanyID']")
+ lcCustomerCif = IIF(Isnull(m.loNod), '', ALLTRIM(m.loNod.Text))
+ llB2C = (Len(m.lcCustomerCif) = 13)
+
+ lcNumarNou = m.lcNumarOriginal + '-S'
+ lcNumarNouSafe = WindowsSpecialCharacters(m.lcNumarNou)
+ lcFisierStorno = GetPdfPath('EFACTURA','TRIMISE') + 'storno_' + m.lcNumarNouSafe + '_' + m.lcCifFirma + '.xml'
+ lcNotaIni = 'Stornare factura ' + m.lcNumarOriginal + ' din data ' + Dtoc(m.ldIssueDate)
+
+ llSucces = StornoEFacturaXML(m.lcXmlOriginal, m.lcNumarNou, m.lcNotaIni, m.lcFisierStorno, @laModificari, @lcMesaj)
+ IF !m.llSucces
+ AMESSAGEBOX('Nu s-a putut genera XML-ul de stornare: ' + m.lcMesaj, 0+16, _screen.Caption)
+ RETURN
+ ENDIF
+
+ * Avertizari (nu blocheaza): termen transmitere, storno dublu, storno deja trimis local, sursa CreditNote/negativa (D13)
+ lcAvertizare = ''
+ IF (Date() - m.ldIssueDate) > 5
+ lcAvertizare = m.lcAvertizare + 'Au trecut mai mult de 5 zile de la data facturii (' + Dtoc(m.ldIssueDate) + ') - verificati termenul de transmitere la ANAF.' + Chr(13) + Chr(10)
+ ENDIF
+ SELECT Count(*) as nCnt FROM crsFacturiTrimise WHERE Alltrim(numar_act) == m.lcNumarNou INTO CURSOR cVerifDubl
+ IF cVerifDubl.nCnt > 0
+ lcAvertizare = m.lcAvertizare + 'Exista deja un document ' + m.lcNumarNou + ' in Trimise - posibila stornare dubla.' + Chr(13) + Chr(10)
+ ENDIF
+ USE IN (SELECT('cVerifDubl'))
+ IF !Empty(Adir(laProcesate, GetPdfPath('EFACTURA','TRIMISE') + 'procesat\*_storno_' + m.lcNumarNouSafe + '_' + m.lcCifFirma + '*.xml'))
+ lcAvertizare = m.lcAvertizare + 'Un storno cu acest numar a fost deja trimis de pe acest calculator.' + Chr(13) + Chr(10)
+ ENDIF
+ IF loDom.documentElement.baseName == 'CreditNote' OR m.lnTotal < 0
+ lcAvertizare = m.lcAvertizare + 'Documentul original e nota de credit / are total negativ - stornarea va produce valori pozitive.' + Chr(13) + Chr(10)
+ ENDIF
+
+ CREATE CURSOR crsStornoDetalii (element C(60), original C(250), nou C(250), tip N(1))
+
+ loForma = NEWOBJECT('frm_storno_efactura', 'anaf_efactura.vcx', '', ;
+ Thisform.oAnaf.AnafeFacturaServer, m.lnId, m.lcXmlOriginal, m.lcNumarOriginal, m.ldIssueDate, ;
+ m.lcClient, m.lcCifFirma, m.lnTotal, m.lcMoneda, Thisform.oAnaf.AnafeFacturaServer.lTest, m.llB2C, ;
+ m.lcFisierStorno, m.lcNumarNou, m.lcAvertizare)
+ loForma.UmpleGrid(@laModificari)
+ loForma.Show(1)
+
+ IF Used('crsStornoDetalii')
+ USE IN (SELECT('crsStornoDetalii'))
+ ENDIF
+
+ SELECT crsFacturiTrimise
+ TRY
+ Goto m.lnRecno
+ CATCH
+ ENDTRY
+ ENDPROC
+
PROCEDURE do_verifica
Local lcText, lnActivePage
lnActivePage = This.pgfeFactura.ActivePage
@@ -15360,3 +15503,504 @@ DEFINE CLASS frm_import_efactura AS _frmbase OF "_frm_base.vcx"
ENDPROC
ENDDEFINE
+
+DEFINE CLASS frm_storno_efactura AS _frmbase OF "_frm_base.vcx"
+ *< CLASSDATA: Baseclass="form" Timestamp="" Scale="Pixels" Uniqueid="" />
+
+ *-- OBJECTDATA items order determines ZOrder / El orden de los items OBJECTDATA determina el ZOrder
+ *< OBJECTDATA: ObjPath="But_renunt1" UniqueID="" Timestamp="" />
+ *< OBJECTDATA: ObjPath="lblAntet" UniqueID="" Timestamp="" />
+ *< OBJECTDATA: ObjPath="lblNumar" UniqueID="" Timestamp="" />
+ *< OBJECTDATA: ObjPath="txtNumar" UniqueID="" Timestamp="" />
+ *< OBJECTDATA: ObjPath="lblMotiv" UniqueID="" Timestamp="" />
+ *< OBJECTDATA: ObjPath="txtMotiv" UniqueID="" Timestamp="" />
+ *< OBJECTDATA: ObjPath="lblAvertisment" UniqueID="" Timestamp="" />
+ *< OBJECTDATA: ObjPath="grdModificari" UniqueID="" Timestamp="" />
+ *< OBJECTDATA: ObjPath="grdModificari.cElement.Header1" UniqueID="" Timestamp="" />
+ *< OBJECTDATA: ObjPath="grdModificari.cElement.Text1" UniqueID="" Timestamp="" />
+ *< OBJECTDATA: ObjPath="grdModificari.cOriginal.Header1" UniqueID="" Timestamp="" />
+ *< OBJECTDATA: ObjPath="grdModificari.cOriginal.Text1" UniqueID="" Timestamp="" />
+ *< OBJECTDATA: ObjPath="grdModificari.cNou.Header1" UniqueID="" Timestamp="" />
+ *< OBJECTDATA: ObjPath="grdModificari.cNou.Text1" UniqueID="" Timestamp="" />
+ *< OBJECTDATA: ObjPath="lblStare" UniqueID="" Timestamp="" />
+ *< OBJECTDATA: ObjPath="edtStatus" UniqueID="" Timestamp="" />
+ *< OBJECTDATA: ObjPath="cmdValideaza" UniqueID="" Timestamp="" />
+ *< OBJECTDATA: ObjPath="cmdPdf" UniqueID="" Timestamp="" />
+ *< OBJECTDATA: ObjPath="cmdTrimite" UniqueID="" Timestamp="" />
+
+ *
+ *m: enablebutoane
+ *m: regenereazaxml
+ *m: setstare
+ *m: umplegrid
+ *p: pcciffirma
+ *p: pcclient
+ *p: pcfisierstorno
+ *p: pcmoneda
+ *p: pcmotiv
+ *p: pcnumarnou
+ *p: pcnumaroriginal
+ *p: pcxmloriginal
+ *p: pddataoriginal
+ *p: plb2c
+ *p: pltest
+ *p: pnidefactura
+ *p: pnstare
+ *p: pntotal
+ *p: poanaf
+ *
+
+ *
+ BorderStyle = 1
+ DoCreate = .T.
+ Height = 560
+ Name = "frm_storno_efactura"
+ pcciffirma = ''
+ pcclient = ''
+ pcfisierstorno = ''
+ pcmoneda = ''
+ pcmotiv = ''
+ pcnumarnou = ''
+ pcnumaroriginal = ''
+ pcxmloriginal = ''
+ pddataoriginal = {}
+ plb2c = .F.
+ pltest = .F.
+ pnidefactura = 0
+ pnstare = 0
+ pntotal = 0
+ poanaf = .NULL.
+ Width = 760
+ _memberdata =
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ _shape1.Height = 29
+ _shape1.Left = 0
+ _shape1.Name = "_shape1"
+ _shape1.Top = 0
+ _shape1.Width = 760
+ _shape2.Left = 701
+ _shape2.Name = "_shape2"
+ _shape2.Top = 0
+ Lb_titlu_alb_b121.Caption = "Stornare eFactura"
+ Lb_titlu_alb_b121.Name = "Lb_titlu_alb_b121"
+ BUT_TERMIN1.Name = "BUT_TERMIN1"
+ BUT_TERMIN1.Visible = .F.
+ Gridsort1.Name = "Gridsort1"
+ *
+
+ ADD OBJECT 'But_renunt1' AS but_renunt WITH ;
+ Anchor = 8, ;
+ Left = 701, ;
+ Name = "But_renunt1", ;
+ Top = 2
+ *< END OBJECT: ClassLib="cmd_butoane.vcx" BaseClass="commandbutton" />
+
+ ADD OBJECT 'cmdPdf' AS cmd_executa WITH ;
+ Caption = "\
+
+ ADD OBJECT 'cmdTrimite' AS cmd_executa WITH ;
+ Caption = "\
+
+ ADD OBJECT 'cmdValideaza' AS cmd_executa WITH ;
+ Caption = "\
+
+ ADD OBJECT 'edtStatus' AS _editbox WITH ;
+ Height = 50, ;
+ Left = 220, ;
+ Name = "edtStatus", ;
+ ReadOnly = .T., ;
+ Top = 448, ;
+ Width = 528
+ *< END OBJECT: ClassLib="_baza.vcx" BaseClass="editbox" />
+
+ ADD OBJECT 'grdModificari' AS _grdbase WITH ;
+ ColumnCount = 3, ;
+ DeleteMark = .F., ;
+ FontSize = 10, ;
+ Height = 280, ;
+ Left = 12, ;
+ Name = "grdModificari", ;
+ Panel = 1, ;
+ ReadOnly = .T., ;
+ RecordSource = "crsStornoDetalii", ;
+ RowHeight = 19, ;
+ TabIndex = 6, ;
+ Top = 164, ;
+ Width = 736, ;
+ Column1.ControlSource = "crsStornoDetalii.element", ;
+ Column1.FontSize = 10, ;
+ Column1.Name = "cElement", ;
+ Column1.ReadOnly = .T., ;
+ Column1.Width = 300, ;
+ Column2.Alignment = 1, ;
+ Column2.ControlSource = "crsStornoDetalii.original", ;
+ Column2.FontSize = 10, ;
+ Column2.Name = "cOriginal", ;
+ Column2.ReadOnly = .T., ;
+ Column2.Width = 200, ;
+ Column3.Alignment = 1, ;
+ Column3.ControlSource = "crsStornoDetalii.nou", ;
+ Column3.FontSize = 10, ;
+ Column3.Name = "cNou", ;
+ Column3.ReadOnly = .T., ;
+ Column3.Width = 200
+ *< END OBJECT: ClassLib="_grd_base.vcx" BaseClass="grid" />
+
+ ADD OBJECT 'grdModificari.cElement.Header1' AS header WITH ;
+ Caption = "Element", ;
+ FontSize = 10, ;
+ Name = "Header1"
+ *< END OBJECT: BaseClass="header" />
+
+ ADD OBJECT 'grdModificari.cElement.Text1' AS textbox WITH ;
+ BackColor = 255,255,255, ;
+ BorderStyle = 0, ;
+ FontSize = 10, ;
+ ForeColor = 0,0,0, ;
+ Margin = 0, ;
+ Name = "Text1", ;
+ ReadOnly = .T., ;
+ SelectedBackColor = 100,185,255
+ *< END OBJECT: BaseClass="textbox" />
+
+ ADD OBJECT 'grdModificari.cNou.Header1' AS header WITH ;
+ Alignment = 1, ;
+ Caption = "Storno", ;
+ FontSize = 10, ;
+ Name = "Header1"
+ *< END OBJECT: BaseClass="header" />
+
+ ADD OBJECT 'grdModificari.cNou.Text1' AS textbox WITH ;
+ Alignment = 1, ;
+ BackColor = 255,255,255, ;
+ BorderStyle = 0, ;
+ FontSize = 10, ;
+ ForeColor = 0,0,0, ;
+ Margin = 0, ;
+ Name = "Text1", ;
+ ReadOnly = .T., ;
+ SelectedBackColor = 100,185,255
+ *< END OBJECT: BaseClass="textbox" />
+
+ ADD OBJECT 'grdModificari.cOriginal.Header1' AS header WITH ;
+ Alignment = 1, ;
+ Caption = "Original", ;
+ FontSize = 10, ;
+ Name = "Header1"
+ *< END OBJECT: BaseClass="header" />
+
+ ADD OBJECT 'grdModificari.cOriginal.Text1' AS textbox WITH ;
+ Alignment = 1, ;
+ BackColor = 255,255,255, ;
+ BorderStyle = 0, ;
+ FontSize = 10, ;
+ ForeColor = 0,0,0, ;
+ Margin = 0, ;
+ Name = "Text1", ;
+ ReadOnly = .T., ;
+ SelectedBackColor = 100,185,255
+ *< END OBJECT: BaseClass="textbox" />
+
+ ADD OBJECT 'lblAntet' AS _label WITH ;
+ AutoSize = .F., ;
+ Caption = "", ;
+ Height = 46, ;
+ Left = 12, ;
+ Name = "lblAntet", ;
+ Top = 38, ;
+ Width = 736, ;
+ WordWrap = .T.
+ *< END OBJECT: ClassLib="_baza.vcx" BaseClass="label" />
+
+ ADD OBJECT 'lblAvertisment' AS _label WITH ;
+ AutoSize = .F., ;
+ Caption = "", ;
+ ForeColor = 192,0,0, ;
+ Height = 40, ;
+ Left = 12, ;
+ Name = "lblAvertisment", ;
+ Top = 118, ;
+ Width = 736, ;
+ WordWrap = .T.
+ *< END OBJECT: ClassLib="_baza.vcx" BaseClass="label" />
+
+ ADD OBJECT 'lblMotiv' AS _label WITH ;
+ Caption = "Motiv (optional):", ;
+ Left = 270, ;
+ Name = "lblMotiv", ;
+ Top = 90
+ *< END OBJECT: ClassLib="_baza.vcx" BaseClass="label" />
+
+ ADD OBJECT 'lblNumar' AS _label WITH ;
+ Caption = "Numar storno:", ;
+ Left = 12, ;
+ Name = "lblNumar", ;
+ Top = 90
+ *< END OBJECT: ClassLib="_baza.vcx" BaseClass="label" />
+
+ ADD OBJECT 'lblStare' AS _label WITH ;
+ Caption = "Nevalidat", ;
+ FontBold = .T., ;
+ Left = 12, ;
+ Name = "lblStare", ;
+ Top = 452, ;
+ Width = 200
+ *< END OBJECT: ClassLib="_baza.vcx" BaseClass="label" />
+
+ ADD OBJECT 'txtMotiv' AS _textbox WITH ;
+ ControlSource = "Thisform.pcmotiv", ;
+ Height = 23, ;
+ Left = 375, ;
+ MaxLength = 200, ;
+ Name = "txtMotiv", ;
+ TabIndex = 2, ;
+ Top = 87, ;
+ Width = 373
+ *< END OBJECT: ClassLib="_baza.vcx" BaseClass="textbox" />
+
+ ADD OBJECT 'txtNumar' AS _textbox WITH ;
+ ControlSource = "Thisform.pcnumarnou", ;
+ Height = 23, ;
+ Left = 105, ;
+ Name = "txtNumar", ;
+ TabIndex = 1, ;
+ Top = 87, ;
+ Width = 150
+ *< END OBJECT: ClassLib="_baza.vcx" BaseClass="textbox" />
+
+ PROCEDURE Activate
+ DODEFAULT()
+ TRY
+ This.cmdValideaza.SetFocus()
+ CATCH
+ ENDTRY
+ ENDPROC
+
+ PROCEDURE EnableButoane
+ LPARAMETERS tlEnable
+ This.cmdValideaza.Enabled = m.tlEnable
+ This.cmdPdf.Enabled = m.tlEnable
+ This.cmdTrimite.Enabled = m.tlEnable
+ This.txtNumar.Enabled = m.tlEnable
+ This.txtMotiv.Enabled = m.tlEnable
+ _screen.MousePointer = IIF(m.tlEnable, 0, 11)
+ ENDPROC
+
+ PROCEDURE Init
+ LPARAMETERS toAnaf, tnIdEfactura, tcXmlOriginal, tcNumarOriginal, tdDataOriginal, ;
+ tcClient, tcCifFirma, tnTotal, tcMoneda, tlTest, tlB2C, tcFisierStorno, tcNumarNou, ;
+ tcAvertizare
+
+ This.poAnaf = toAnaf
+ This.pnIdEfactura = tnIdEfactura
+ This.pcXmlOriginal = tcXmlOriginal
+ This.pcNumarOriginal = tcNumarOriginal
+ This.pdDataOriginal = tdDataOriginal
+ This.pcClient = tcClient
+ This.pcCifFirma = tcCifFirma
+ This.pnTotal = tnTotal
+ This.pcMoneda = tcMoneda
+ This.plTest = tlTest
+ This.plB2C = tlB2C
+ This.pcFisierStorno = tcFisierStorno
+ This.pcNumarNou = tcNumarNou
+
+ This.Caption = 'Stornare eFactura ' + m.tcNumarOriginal + ' - ' + IIF(m.tlTest, 'TEST', 'PRODUCTIE')
+ This.Lb_titlu_alb_b121.Caption = This.Caption
+ This.lblAntet.Caption = 'Factura ' + m.tcNumarOriginal + ' din ' + Dtoc(m.tdDataOriginal) + ' -> Storno' + Chr(13) + Chr(10) + ;
+ 'Client: ' + m.tcClient + ' CIF firma: ' + m.tcCifFirma + ' Moneda: ' + m.tcMoneda
+ This.lblAvertisment.Caption = m.tcAvertizare
+
+ This.grdModificari.SetAll("DynamicFontBold", "tip = 2", "Column")
+
+ This.SetStare(0)
+ ENDPROC
+
+ PROCEDURE RegenereazaXml
+ LOCAL lcNumarNou, lcMotiv, lcNotaFinal, laModif[1], lcMesaj, llSucces
+ lcNumarNou = ALLTRIM(This.pcNumarNou)
+ lcMotiv = ALLTRIM(This.pcMotiv)
+ IF Empty(m.lcNumarNou)
+ AMESSAGEBOX('Numarul stornului nu poate fi gol.', 0+48, _screen.Caption)
+ RETURN .F.
+ ENDIF
+ lcNotaFinal = 'Stornare factura ' + This.pcNumarOriginal + ' din data ' + Dtoc(This.pdDataOriginal) + IIF(Empty(m.lcMotiv), '', '. ' + m.lcMotiv)
+
+ llSucces = StornoEFacturaXML(This.pcXmlOriginal, m.lcNumarNou, m.lcNotaFinal, This.pcFisierStorno, @laModif, @lcMesaj)
+ IF !m.llSucces
+ This.edtStatus.Value = 'Eroare la regenerare: ' + m.lcMesaj
+ This.SetStare(0)
+ RETURN .F.
+ ENDIF
+ This.UmpleGrid(@laModif)
+ RETURN .T.
+ ENDPROC
+
+ PROCEDURE SetStare
+ LPARAMETERS tnStare
+ This.pnStare = m.tnStare
+ This.cmdPdf.Enabled = .T.
+ DO CASE
+ CASE m.tnStare = 2
+ * trimis - stare terminala (D26/D35)
+ This.lblStare.Caption = 'Trimis'
+ This.cmdValideaza.Enabled = .F.
+ This.cmdTrimite.Enabled = .F.
+ This.txtNumar.Enabled = .F.
+ This.txtMotiv.Enabled = .F.
+ CASE m.tnStare = 1
+ This.lblStare.Caption = 'Valid'
+ This.cmdValideaza.Enabled = .T.
+ This.cmdTrimite.Enabled = .T.
+ This.txtNumar.Enabled = .T.
+ This.txtMotiv.Enabled = .T.
+ OTHERWISE
+ This.lblStare.Caption = 'Nevalidat'
+ This.cmdValideaza.Enabled = .T.
+ This.cmdTrimite.Enabled = .F.
+ This.txtNumar.Enabled = .T.
+ This.txtMotiv.Enabled = .T.
+ ENDCASE
+ ENDPROC
+
+ PROCEDURE UmpleGrid
+ LPARAMETERS taModificari
+ LOCAL lnI, lcSelect
+ lcSelect = Select()
+ SELECT crsStornoDetalii
+ ZAP
+ FOR lnI = 1 TO Alen(taModificari, 1)
+ INSERT INTO crsStornoDetalii (element, original, nou, tip) VALUES ;
+ (taModificari[m.lnI,1], Transform(taModificari[m.lnI,2]), Transform(taModificari[m.lnI,3]), taModificari[m.lnI,4])
+ ENDFOR
+ SELECT (m.lcSelect)
+ This.grdModificari.Refresh()
+ ENDPROC
+
+ PROCEDURE cmdPdf.Click
+ LOCAL lcXmlCurent, lcFisierPdf
+ lcXmlCurent = Filetostr(Thisform.pcFisierStorno)
+ lcFisierPdf = GetPdfPath('EFACTURA','TRIMISE') + 'storno_' + WindowsSpecialCharacters(ALLTRIM(Thisform.pcNumarNou)) + '.pdf'
+ lcFisierPdf = Thisform.poAnaf.Xml2PdfAnaf(m.lcXmlCurent, m.lcFisierPdf, .T.)
+ IF !Empty(m.lcFisierPdf) AND File(m.lcFisierPdf)
+ open_default_app(m.lcFisierPdf)
+ ENDIF
+ ENDPROC
+
+ PROCEDURE cmdTrimite.Click
+ LOCAL lcMesajConf, loReturn, lcDirProcesat, lcFisierProcesat
+ IF Thisform.pnStare <> 1
+ RETURN
+ ENDIF
+ lcMesajConf = 'Trimiteti la ANAF (' + IIF(Thisform.plTest,'TEST','PRODUCTIE') + ') stornarea facturii ' + Thisform.pcNumarOriginal + ;
+ ' ca ' + ALLTRIM(Thisform.pcNumarNou) + ', total ' + Transform(Thisform.pnTotal) + ' ' + Thisform.pcMoneda + '? Operatia nu se poate anula.'
+ IF AMESSAGEBOX(m.lcMesajConf, 4+32+256, _screen.Caption) <> 6
+ RETURN
+ ENDIF
+
+ Thisform.EnableButoane(.F.)
+ WAIT WINDOW 'Se trimite...' NOWAIT
+ Thisform.poAnaf.RefreshTokenAuto()
+ loReturn = Thisform.poAnaf.TrimiteFacturaXML(Thisform.pcFisierStorno, .F., .F., .F., .F., Thisform.plB2C)
+ CLEAR TYPEAHEAD
+ WAIT CLEAR
+
+ IF !Empty(loReturn.cIdIncarcare)
+ lcDirProcesat = GetPdfPath('EFACTURA','TRIMISE') + 'procesat\'
+ lcFisierProcesat = loReturn.cIdIncarcare + '_' + Justfname(Thisform.pcFisierStorno)
+ IF Thisform.poAnaf.SaveFile(Thisform.pcFisierStorno, .F., m.lcDirProcesat, m.lcFisierProcesat)
+ TRY
+ Delete File (Thisform.pcFisierStorno)
+ CATCH
+ ENDTRY
+ ENDIF
+ goLog.Log('Storno eFactura: ' + Thisform.pcNumarOriginal + ' -> ' + ALLTRIM(Thisform.pcNumarNou) + ', index ' + loReturn.cIdIncarcare + ', ' + IIF(Thisform.plTest,'TEST','PRODUCTIE'))
+ Thisform.edtStatus.Value = 'Trimis. Index incarcare ' + loReturn.cIdIncarcare + ' (' + IIF(Thisform.plTest,'TEST','PRODUCTIE') + '). ' + ;
+ 'Stergeti factura din ROA si emiteti factura corecta cu acelasi numar. Storno-ul apare in Trimise dupa descarcarea mesajelor.'
+ Thisform.EnableButoane(.T.)
+ Thisform.SetStare(2)
+ ELSE
+ Thisform.edtStatus.Value = 'Eroare: ' + loReturn.cMesaj + '. Verificati in Trimise, dupa descarcare, inainte de a retrimite.'
+ Thisform.EnableButoane(.T.)
+ Thisform.SetStare(0)
+ ENDIF
+ ENDPROC
+
+ PROCEDURE cmdValideaza.Click
+ LOCAL llValid, lcMesajValid, loSrv, loExport
+ IF !Thisform.RegenereazaXml()
+ RETURN
+ ENDIF
+ loSrv = Thisform.poAnaf
+ Thisform.EnableButoane(.F.)
+ WAIT WINDOW 'Se valideaza...' NOWAIT
+ loSrv.RefreshTokenAuto()
+ loExport = CREATEOBJECT('ExportEFactura', .T., loSrv.cToken)
+ llValid = m.loExport.ValidareEFacturaANAF(Thisform.pcFisierStorno, @lcMesajValid)
+ CLEAR TYPEAHEAD
+ WAIT CLEAR
+ Thisform.EnableButoane(.T.)
+ IF m.llValid
+ Thisform.edtStatus.Value = 'Valid.'
+ Thisform.SetStare(1)
+ ELSE
+ Thisform.edtStatus.Value = IIF(Empty(m.lcMesajValid), 'Validare indisponibila - verificati tokenul/reteaua.', 'Invalid: ' + m.lcMesajValid)
+ Thisform.SetStare(0)
+ ENDIF
+ ENDPROC
+
+ PROCEDURE txtMotiv.InteractiveChange
+ IF Thisform.pnStare <> 0
+ Thisform.SetStare(0)
+ ENDIF
+ ENDPROC
+
+ PROCEDURE txtNumar.InteractiveChange
+ IF Thisform.pnStare <> 0
+ Thisform.SetStare(0)
+ ENDIF
+ ENDPROC
+
+ENDDEFINE
diff --git a/docs/depanare_testare_vfp.md b/docs/depanare_testare_vfp.md
index 6b6ddaf..2cbe834 100644
--- a/docs/depanare_testare_vfp.md
+++ b/docs/depanare_testare_vfp.md
@@ -132,6 +132,8 @@ Cand un bug apare "de azi", compara cu clasa veche fara sa atingi SVN:
`INKEY(0)` asteapta, timerele VFP nu se declanseaza, deci un dialog/o bucla blocata asa nu poate
fi deblocata din interiorul procesului - in rulare headless arata ca un proces viu care nu mai
scrie in log.
+- **Doua constructii care NU compileaza, dar trec de `txt2vcx`** (storno eFactura, 18.09.2026): `NEWOBJECT('c', 'lib.vcx', , p1, ...)` cu argumentul 3 omis ("Function argument value, type, or count is invalid" - pune `''`) si `CREATEOBJECT(...).Metoda()` inlantuit ("Syntax error"). `txt2vcx` (si `-DryRun`) NU compileaza metodele: dupa write-back, copiaza `.vcx`/`.vct` in ACELASI folder sub alt nume (parintii se rezolva relativ), `COMPILE CLASSLIB` headless, citeste `.ERR`, sterge copia.
+- **`FOR EACH` pe `loDom.selectNodes(...)`** da la rulare "Expression evaluator failed". Foloseste `loN = loDom.selectNodes(...)` + `FOR i = 0 TO loN.length - 1` / `loN.item(i)`.
## 7. Capcane la SCRIEREA scriptului de test
@@ -178,3 +180,14 @@ Cand un bug apare "de azi", compara cu clasa veche fara sa atingi SVN:
si apoi apeleaza cod care citeste globalul masoara valoarea veche, fara niciun semn de eroare.
Verifica intai pe ce cale citeste codul testat; daca e globalul, seteaza-l direct in test (si
restaureaza-l la final), nu prin `scrie_optiune()`.
+- **Liniile de dinainte de `ON ERROR` dau dialog nativ pe ecranul utilizatorului.** `ALLTRIM(NVL(tcParam, ''))` pe un
+ parametru nepasat (`.F.`) = "Function argument value..." ca dialog "Program Error", nu in log. Seteaza `ON ERROR` pe
+ primele linii si foloseste `TRANSFORM(tcParam)` pentru parametri optionali.
+- **Eroarea SQL prin `goExecutor` e o forma modala INTERNA VFP**: `watchdog_vfp.ps1` nu o vede (nu e fereastra top-level),
+ procesul sta pana la timeout. Verifica fiecare `SELECT` din test cu `sqlplus` inainte de rulare (ex. `anaf_efactura`
+ nu are `numar_act`/`data_act` - sunt in view-urile `anaf_vefactura_*`).
+- **Forma din `.vcx` cu `CREATEOBJECT` direct poate pica in `Init` cu erori de binding** ("Error with _checkbox1 - Value :
+ Data type mismatch", CREATEOBJECT intoarce `.F.`), desi in aplicatie merge. Fa un stub `.scx` bazat pe clasa si deschide-l
+ cu `DO FORM ... NAME lo NOSHOW` - asa se instantiaza ca in aplicatie, iar erorile ar arata calea completa a controlului.
+- **Rularile de test se fac DOAR prin `watchdog_vfp.ps1 -AutoDismiss`**: un lansator propriu fara plasa a lasat 3 dialoguri
+ modale pe ecranul utilizatorului (18.09.2026).
diff --git a/programe/anaf_efactura.prg b/programe/anaf_efactura.prg
index 975140a..3cedb65 100644
--- a/programe/anaf_efactura.prg
+++ b/programe/anaf_efactura.prg
@@ -802,8 +802,9 @@ select a.*, cast(NULL as varchar2(1000)) as factura_detalii, 0 as modificat
* parseaza un director cu xml efactura si le trimite la API ANAF
********************************
Procedure TrimiteFacturaXML
- LPARAMETERS tcFile, tlValidare, tlValidareOnline, tlNuTrimiteEFacturaANAF, tlExtern
-
+ LPARAMETERS tcFile, tlValidare, tlValidareOnline, tlNuTrimiteEFacturaANAF, tlExtern, tlB2C
+ * tlB2C: (optional) forteaza uploadb2c la trimitere, vezi SendEfactura
+
Local loXmlEFactura As 'ExportEFactura'
Local lcErrorMessage, lcExecutionStatus, lcFile, lcIndexIncarcare, lcMesaj, llSucces, llValid, loEx
Local loXML, lcMesajValidare, llExtern, lcToken
@@ -833,7 +834,7 @@ select a.*, cast(NULL as varchar2(1000)) as factura_detalii, 0 as modificat
ENDIF
IF m.llValid AND !m.tlNuTrimiteEFacturaANAF
- loReturn = This.SendEfactura(m.lcFile, m.llExtern)
+ loReturn = This.SendEfactura(m.lcFile, m.llExtern, .F., m.tlB2C)
llSucces = loReturn.lSucces
AddProperty(loReturn, "cIdIncarcare", '')
AddProperty(loReturn, "cMesaj", '')
@@ -915,15 +916,16 @@ select a.*, cast(NULL as varchar2(1000)) as factura_detalii, 0 as modificat
* Genereaza xml eFactura, valideaza xml si trimite la ANAF
Function SendEfactura
- Lparameters tcFile, tlExtern, tlMesaj
+ Lparameters tcFile, tlExtern, tlMesaj, tlB2C
* tcFile: calea catre fisierul xml efactura / sau xml mesaj
* tlExtern: daca se apeleaza cu parametrul Extern=DA (pentru clientii externi)
* tlMesaj: daca mesajul este o factura sau un mesaj catre furnizor
+ * tlB2C: (optional) forteaza uploadb2c, pentru xml-uri cu prefixe straine unde detectia CIF nu se aplica
Local loEx As Exception
Local loHTTP As 'winHTTP.winHTTPrequest.5.1'
Local loReturn As "empty"
- Local lcCodFiscal, lcExtern, lcFile, lcSend, lcServer, lcToken, llOk, llTest, llB2C
+ Local lcCodFiscal, lcExtern, lcFile, lcSend, lcServer, lcStandard, lcToken, llOk, llTest, llB2C
LOCAL lcCustomer, lcPartyLegal, lcCodFiscalClient
loReturn = Createobject("empty")
@@ -948,15 +950,16 @@ select a.*, cast(NULL as varchar2(1000)) as factura_detalii, 0 as modificat
* Verific daca clientul este persoana fizica si folosesc upload2bc in loc de upload
lcCustomer = STREXTRACT(lcSend, '', '',1,1)
lcPartyLegal = STREXTRACT(lcCustomer, '', '',1,1)
- lcCodFiscalClient = ALLTRIM(STREXTRACT(m.lcPartyLegal, '','',1,1))
- llB2C = (LEN(m.lcCodFiscalClient) = 13) && persoane fizice
-
- TRY
+ lcCodFiscalClient = ALLTRIM(STREXTRACT(m.lcPartyLegal, '','',1,1))
+ llB2C = (LEN(m.lcCodFiscalClient) = 13) OR m.tlB2C && persoane fizice
+ lcStandard = IIF('CreditNote'$m.lcSend, 'CN', 'UBL')
+
+ TRY
IF !m.tlMesaj
- lcServer = [https://api.anaf.ro/] + Iif(m.llTest, [test], [prod]) + [/FCTEL/rest/upload] + IIF(m.llB2C,'b2c','') + [?standard=UBL&cif=] + m.lcCodFiscal + m.lcExtern
+ lcServer = [https://api.anaf.ro/] + Iif(m.llTest, [test], [prod]) + [/FCTEL/rest/upload] + IIF(m.llB2C,'b2c','') + [?standard=] + m.lcStandard + [&cif=] + m.lcCodFiscal + m.lcExtern
ELSE
lcServer = [https://api.anaf.ro/] + Iif(m.llTest, [test], [prod]) + [/FCTEL/rest/upload] + IIF(m.llB2C,'b2c','') + [?standard=RASP&cif=] + m.lcCodFiscal
- ENDIF
+ ENDIF
loHTTP = Createobject('winHTTP.winHTTPrequest.5.1')
loHTTP.Open('POST', lcServer, .F.)
@@ -4069,6 +4072,432 @@ Procedure ParseEFactura
Return loReturn
Endproc && ParseEFactura
+****************************************
+* Extrage xml-ul original descarcat de la ANAF pentru un rand ANAF_EFACTURA: dezarhiveaza
+* detalii_zip (BLOB din baza), cu rezerva fisierul zip de pe disc din filepath.
+* tnId: ANAF_EFACTURA.ID
+* tcXml: OUT continutul xml (octetii bruti din arhiva ANAF)
+* tcMesaj: OUT mesaj de eroare daca RETURN = .F.
+****************************************
+FUNCTION GetXmlOriginalEFactura
+ LPARAMETERS tnId, tcXml, tcMesaj
+
+ LOCAL lcSelect, lcFiltru, lcSchema, lcOrder, llAfiseaza, lcGroup, llModParam, lcFiltruOriginal
+ LOCAL lcIdIncarcare, lcFilePath, lcZip, lcDir, lcXmlFile, lcSemnatura, llSucces, llResult
+ PRIVATE poXmlOriginalZip
+
+ tcXml = ''
+ tcMesaj = ''
+ lcIdIncarcare = ''
+ lcFilePath = ''
+ lcZip = ''
+ llResult = .F.
+ poXmlOriginalZip = Null
+
+ IF Vartype(m.tnId) <> 'N' OR m.tnId <= 0
+ tcMesaj = 'Id factura invalid.'
+ RETURN .F.
+ ENDIF
+
+ * RETURN e interzis in TRY/CATCH (eroare 4165) - iesire unica prin EXIT, RETURN dupa ENDTRY
+ TRY
+ DO WHILE .T.
+ lcSchema = [id_incarcare V(36), lc_filepath V(250), fisier W]
+ lcSelect = [select id_incarcare, filepath, detalii_zip from anaf_efactura where id = ] + Transform(m.tnId)
+ lcFiltru = ''
+ lcOrder = ''
+ llAfiseaza = .F.
+ lcGroup = ''
+ llModParam = .T.
+ lcFiltruOriginal = ''
+
+ Gencursor('poXmlOriginalZip', 'cXmlOriginalTemp', m.lcSelect, m.lcFiltru, m.lcSchema, m.lcOrder, m.llAfiseaza, m.lcGroup, m.llModParam, m.lcFiltruOriginal)
+ poXmlOriginalZip.ca_baza1.afisare()
+
+ IF Used('cXmlOriginalTemp') AND Reccount('cXmlOriginalTemp') = 1
+ SELECT cXmlOriginalTemp
+ lcIdIncarcare = Alltrim(Nvl(id_incarcare, ''))
+ lcFilePath = Alltrim(Nvl(lc_filepath, ''))
+ IF !Empty(Nvl(fisier, ''))
+ lcZip = Addbs(Sys(2023)) + 'storno_' + Transform(m.tnId) + '_' + Sys(2015) + '.zip'
+ Strtofile(fisier, m.lcZip)
+ ENDIF
+ USE IN (SELECT('cXmlOriginalTemp'))
+ ENDIF
+
+ IF Empty(m.lcZip) AND !Empty(m.lcFilePath) AND File(m.lcFilePath)
+ lcZip = m.lcFilePath
+ ENDIF
+
+ IF Empty(m.lcZip) OR !File(m.lcZip)
+ tcMesaj = 'Factura nu are XML descarcat.'
+ EXIT
+ ENDIF
+
+ IF Empty(m.lcIdIncarcare)
+ tcMesaj = 'Factura nu are id_incarcare.'
+ EXIT
+ ENDIF
+
+ lcDir = ''
+ llSucces = MyUnzip(m.lcZip, @lcDir, @tcMesaj, .T.)
+ IF m.lcZip <> m.lcFilePath
+ Delete File (m.lcZip)
+ ENDIF
+ IF !m.llSucces
+ EXIT
+ ENDIF
+
+ lcXmlFile = Addbs(m.lcDir) + m.lcIdIncarcare + '.xml'
+ lcSemnatura = Addbs(m.lcDir) + 'semnatura_' + m.lcIdIncarcare + '.xml'
+
+ IF !File(m.lcXmlFile)
+ tcMesaj = 'Arhiva nu contine fisierul ' + m.lcIdIncarcare + '.xml.'
+ EXIT
+ ENDIF
+
+ tcXml = Filetostr(m.lcXmlFile)
+ Delete File (m.lcXmlFile)
+ IF File(m.lcSemnatura)
+ Delete File (m.lcSemnatura)
+ ENDIF
+
+ llResult = .T.
+ EXIT
+ ENDDO
+ CATCH TO loEx
+ tcMesaj = 'GetXmlOriginalEFactura eroare: ' + loEx.Message
+ ENDTRY
+ RETURN m.llResult
+ENDFUNC && GetXmlOriginalEFactura
+
+****************************************
+* Insereaza loNewNode inaintea lui loRefNode, sau la sfarsit daca loRefNode nu exista
+* (insertBefore cu referinta null nu se poate apela sigur din VFP)
+****************************************
+FUNCTION InsertBeforeSafe
+ LPARAMETERS loParent, loNewNode, loRefNode
+
+ IF Isnull(m.loRefNode)
+ RETURN loParent.appendChild(m.loNewNode)
+ ENDIF
+ RETURN loParent.insertBefore(m.loNewNode, m.loRefNode)
+ENDFUNC && InsertBeforeSafe
+
+****************************************
+* Neaga (schimba semnul) unui sir numeric din xml, pastrand zecimalele exact ca in original.
+* tcValoare: valoarea trim-uita din xml
+* tlOk: OUT .F. daca tcValoare nu e un numar valid (^[+-]?(\d+(\.\d*)?|\.\d+)$)
+* Zero (orice forma, inclusiv -0.00) ramane neschimbat.
+****************************************
+FUNCTION NegheazaValoareXml
+ LPARAMETERS tcValoare, tlOk
+
+ LOCAL lcMatch, lcNumeric
+
+ tlOk = .F.
+ lcMatch = GetRegExp(m.tcValoare, '^[+-]?(\d+(\.\d*)?|\.\d+)$')
+ IF Empty(m.lcMatch) OR m.lcMatch <> m.tcValoare
+ RETURN ''
+ ENDIF
+ tlOk = .T.
+
+ DO CASE
+ CASE Left(m.tcValoare, 1) = '-'
+ lcNumeric = Substr(m.tcValoare, 2)
+ RETURN Iif(Val(m.lcNumeric) = 0, m.tcValoare, m.lcNumeric)
+ CASE Left(m.tcValoare, 1) = '+'
+ lcNumeric = Substr(m.tcValoare, 2)
+ RETURN Iif(Val(m.lcNumeric) = 0, m.tcValoare, '-' + m.lcNumeric)
+ OTHERWISE
+ RETURN Iif(Val(m.tcValoare) = 0, m.tcValoare, '-' + m.tcValoare)
+ ENDCASE
+ENDFUNC && NegheazaValoareXml
+
+****************************************
+* '' daca loNod nu e sub o linie (InvoiceLine/CreditNoteLine), altfel 'Linia - '
+****************************************
+FUNCTION LineiaEticheta
+ LPARAMETERS loNod
+
+ LOCAL loParinte, loIdLinie, lcNr
+
+ loParinte = loNod.parentNode
+ DO WHILE !Isnull(m.loParinte)
+ IF Inlist(loParinte.baseName, 'InvoiceLine', 'CreditNoteLine')
+ lcNr = ''
+ loIdLinie = loParinte.selectSingleNode("*[local-name()='ID']")
+ IF !Isnull(m.loIdLinie)
+ lcNr = Alltrim(loIdLinie.Text)
+ ENDIF
+ RETURN 'Linia ' + m.lcNr + ' - '
+ ENDIF
+ loParinte = loParinte.parentNode
+ ENDDO
+ RETURN ''
+ENDFUNC && LineiaEticheta
+
+****************************************
+* Eticheta romana pentru gridul Element | Original | Storno al unui nod negat
+****************************************
+FUNCTION EtichetaNodXmlStorno
+ LPARAMETERS loNod
+
+ LOCAL lcBase, lcMoneda, lcLinie, loAttrCur, lcEticheta
+
+ lcBase = loNod.baseName
+ lcMoneda = ''
+ loAttrCur = loNod.Attributes.getNamedItem('currencyID')
+ IF !Isnull(m.loAttrCur)
+ lcMoneda = ' (' + loAttrCur.Text + ')'
+ ENDIF
+ lcLinie = LineiaEticheta(m.loNod)
+
+ DO CASE
+ CASE Inlist(m.lcBase, 'InvoicedQuantity', 'CreditedQuantity')
+ lcEticheta = 'Cantitate'
+ CASE m.lcBase = 'LineExtensionAmount'
+ lcEticheta = Iif(Empty(m.lcLinie), 'Valoare neta totala', 'Valoare linie')
+ CASE m.lcBase = 'TaxAmount'
+ lcEticheta = 'TVA'
+ CASE m.lcBase = 'TaxableAmount'
+ lcEticheta = 'Baza TVA'
+ CASE m.lcBase = 'Amount'
+ lcEticheta = 'Reducere/majorare'
+ CASE m.lcBase = 'BaseAmount'
+ lcEticheta = 'Baza reducere/majorare'
+ CASE m.lcBase = 'TaxExclusiveAmount'
+ lcEticheta = 'Total fara TVA'
+ CASE m.lcBase = 'TaxInclusiveAmount'
+ lcEticheta = 'Total cu TVA'
+ CASE m.lcBase = 'AllowanceTotalAmount'
+ lcEticheta = 'Total reduceri'
+ CASE m.lcBase = 'ChargeTotalAmount'
+ lcEticheta = 'Total taxe suplimentare'
+ CASE m.lcBase = 'PrepaidAmount'
+ lcEticheta = 'Avans platit'
+ CASE m.lcBase = 'PayableRoundingAmount'
+ lcEticheta = 'Rotunjire'
+ CASE m.lcBase = 'PayableAmount'
+ lcEticheta = 'Total de plata'
+ OTHERWISE
+ lcEticheta = m.lcBase
+ ENDCASE
+
+ RETURN m.lcLinie + m.lcEticheta + m.lcMoneda
+ENDFUNC && EtichetaNodXmlStorno
+
+****************************************
+* Genereaza xml-ul de stornare al unei facturi eFactura deja trimise la ANAF: neaga
+* sumele/cantitatile, schimba numarul, adauga BillingReference catre factura originala si o Nota.
+* tcXml: continutul original al xml-ului (FILETOSTR pe fisierul din arhiva ANAF, GetXmlOriginalEFactura)
+* tcNumarNou: numarul noii facturi (gol = numarul original + '-S')
+* tcNota: text Nota adaugat pe document (gol = nu se adauga)
+* tcFisier: calea unde se salveaza xml-ul storno
+* taModificari: OUT array [n,4] (eticheta romana, original, nou, tip: 1 antet/2 total/3 linie)
+* tcMesaj: OUT mesaj de eroare daca RETURN = .F. (numeste nodul/motivul blocarii)
+****************************************
+FUNCTION StornoEFacturaXML
+ LPARAMETERS tcXml, tcNumarNou, tcNota, tcFisier, taModificari, tcMesaj
+
+ LOCAL loDOM, loRoot, loNodID, loNodSupplier, loNodIssueDate, loNod, loNoduri
+ LOCAL lcPrefixCbc, lcUriCbc, lcPrefixCac, lcUriCac, lcNumarVechi, lcDataOriginala
+ LOCAL lcXPath, lcTemp, lcValoare, lcValoareNoua, lcLinieEticheta, lnI, lnModif, llOk, llValoareInvalida, llResult
+ LOCAL loBillingRef, loInvDocRef, loIdRef, loDateRef, loChild, loInsertBefore
+ LOCAL loNoteNode, loLastNote, loTypeCode
+
+ Dimension taModificari[1, 4]
+ taModificari[1, 1] = ''
+ tcMesaj = ''
+ lnModif = 0
+ llResult = .F.
+
+ IF Vartype(m.tcNumarNou) <> 'C'
+ tcNumarNou = ''
+ ENDIF
+ IF Vartype(m.tcNota) <> 'C'
+ tcNota = ''
+ ENDIF
+
+ IF Empty(m.tcXml)
+ tcMesaj = 'XML original gol.'
+ RETURN .F.
+ ENDIF
+
+ lcTemp = Addbs(Sys(2023)) + 'storno_src_' + Sys(2015) + '.xml'
+
+ * RETURN e interzis in TRY/CATCH (eroare 4165) - iesire unica prin EXIT, RETURN dupa ENDTRY
+ TRY
+ DO WHILE .T.
+ Strtofile(m.tcXml, m.lcTemp)
+
+ loDOM = Createobject('MSXML2.DOMDocument.6.0')
+ loDOM.Async = .F.
+ loDOM.preserveWhiteSpace = .T.
+ loDOM.setProperty('SelectionLanguage', 'XPath')
+ loDOM.Load(m.lcTemp)
+
+ IF loDOM.parseError.errorcode <> 0
+ tcMesaj = 'Eroare parsare XML: ' + loDOM.parseError.reason
+ EXIT
+ ENDIF
+
+ loRoot = loDOM.documentElement
+ IF Isnull(m.loRoot) OR !Inlist(loRoot.baseName, 'Invoice', 'CreditNote')
+ tcMesaj = 'Documentul nu e Invoice/CreditNote.'
+ EXIT
+ ENDIF
+
+ loNodID = loRoot.selectSingleNode("*[local-name()='ID']")
+ loNodSupplier = loRoot.selectSingleNode("*[local-name()='AccountingSupplierParty']")
+ loNodIssueDate = loRoot.selectSingleNode("*[local-name()='IssueDate']")
+ IF Isnull(m.loNodID) OR Isnull(m.loNodSupplier) OR Isnull(m.loNodIssueDate)
+ tcMesaj = 'Lipseste ID, IssueDate sau AccountingSupplierParty pe radacina.'
+ EXIT
+ ENDIF
+ lcPrefixCbc = loNodID.Prefix
+ lcUriCbc = loNodID.namespaceURI
+ lcPrefixCac = loNodSupplier.Prefix
+ lcUriCac = loNodSupplier.namespaceURI
+
+ lcNumarVechi = Alltrim(loNodID.Text)
+ lcDataOriginala = Alltrim(loNodIssueDate.Text)
+ IF Empty(m.tcNumarNou)
+ tcNumarNou = m.lcNumarVechi + '-S'
+ ENDIF
+ IF Empty(m.tcNumarNou)
+ tcMesaj = 'Numar factura gol.'
+ EXIT
+ ENDIF
+
+ taModificari[1, 1] = 'Numar factura'
+ taModificari[1, 2] = m.lcNumarVechi
+ taModificari[1, 3] = m.tcNumarNou
+ taModificari[1, 4] = 1
+ lnModif = 1
+ loNodID.Text = m.tcNumarNou
+
+ * BillingReference catre factura originala, inaintea primului nod din multimea UBL cunoscuta
+ loInvDocRef = loDOM.createNode(1, Iif(Empty(m.lcPrefixCac), 'InvoiceDocumentReference', m.lcPrefixCac + ':InvoiceDocumentReference'), m.lcUriCac)
+ loIdRef = loDOM.createNode(1, Iif(Empty(m.lcPrefixCbc), 'ID', m.lcPrefixCbc + ':ID'), m.lcUriCbc)
+ loIdRef.Text = m.lcNumarVechi
+ loInvDocRef.appendChild(m.loIdRef)
+ loDateRef = loDOM.createNode(1, Iif(Empty(m.lcPrefixCbc), 'IssueDate', m.lcPrefixCbc + ':IssueDate'), m.lcUriCbc)
+ loDateRef.Text = m.lcDataOriginala
+ loInvDocRef.appendChild(m.loDateRef)
+ loBillingRef = loDOM.createNode(1, Iif(Empty(m.lcPrefixCac), 'BillingReference', m.lcPrefixCac + ':BillingReference'), m.lcUriCac)
+ loBillingRef.appendChild(m.loInvDocRef)
+
+ loInsertBefore = Null
+ loChild = loRoot.firstChild
+ DO WHILE !Isnull(m.loChild)
+ IF Inlist(loChild.baseName, 'DespatchDocumentReference', 'ReceiptDocumentReference', 'StatementDocumentReference', ;
+ 'OriginatorDocumentReference', 'ContractDocumentReference', 'AdditionalDocumentReference', ;
+ 'ProjectReference', 'Signature', 'AccountingSupplierParty')
+ loInsertBefore = m.loChild
+ EXIT
+ ENDIF
+ loChild = loChild.nextSibling
+ ENDDO
+ IF Isnull(m.loInsertBefore)
+ loInsertBefore = m.loNodSupplier
+ ENDIF
+ InsertBeforeSafe(m.loRoot, m.loBillingRef, m.loInsertBefore)
+
+ lnModif = m.lnModif + 1
+ Dimension taModificari[m.lnModif, 4]
+ taModificari[m.lnModif, 1] = 'Referinta (factura originala)'
+ taModificari[m.lnModif, 2] = ''
+ taModificari[m.lnModif, 3] = m.lcNumarVechi + ' / ' + m.lcDataOriginala
+ taModificari[m.lnModif, 4] = 1
+
+ * Nota, dupa ultima Note existenta sau dupa TypeCode-ul radacinii
+ IF !Empty(m.tcNota)
+ loNoteNode = loDOM.createNode(1, Iif(Empty(m.lcPrefixCbc), 'Note', m.lcPrefixCbc + ':Note'), m.lcUriCbc)
+ loNoteNode.Text = m.tcNota
+
+ loLastNote = Null
+ loChild = loRoot.firstChild
+ DO WHILE !Isnull(m.loChild)
+ IF loChild.baseName = 'Note'
+ loLastNote = m.loChild
+ ENDIF
+ loChild = loChild.nextSibling
+ ENDDO
+
+ IF !Isnull(m.loLastNote)
+ InsertBeforeSafe(m.loRoot, m.loNoteNode, m.loLastNote.nextSibling)
+ ELSE
+ loTypeCode = loRoot.selectSingleNode("*[local-name()='InvoiceTypeCode' or local-name()='CreditNoteTypeCode']")
+ IF !Isnull(m.loTypeCode)
+ InsertBeforeSafe(m.loRoot, m.loNoteNode, m.loTypeCode.nextSibling)
+ ELSE
+ InsertBeforeSafe(m.loRoot, m.loNoteNode, m.loRoot.firstChild)
+ ENDIF
+ ENDIF
+
+ lnModif = m.lnModif + 1
+ Dimension taModificari[m.lnModif, 4]
+ taModificari[m.lnModif, 1] = 'Nota'
+ taModificari[m.lnModif, 2] = ''
+ taModificari[m.lnModif, 3] = m.tcNota
+ taModificari[m.lnModif, 4] = 1
+ ENDIF
+
+ * negare sume/cantitati: o singura interogare, fiecare nod o singura data, in ordinea din document
+ lcXPath = "//*[(local-name()='InvoicedQuantity' or local-name()='CreditedQuantity'" + ;
+ " or local-name()='LineExtensionAmount' or local-name()='TaxAmount' or local-name()='TaxableAmount'" + ;
+ " or local-name()='TaxExclusiveAmount' or local-name()='TaxInclusiveAmount' or local-name()='AllowanceTotalAmount'" + ;
+ " or local-name()='ChargeTotalAmount' or local-name()='PrepaidAmount' or local-name()='PayableRoundingAmount'" + ;
+ " or local-name()='PayableAmount'" + ;
+ " or ((local-name()='Amount' or local-name()='BaseAmount') and local-name(parent::*)='AllowanceCharge'))" + ;
+ " and not(ancestor::*[local-name()='Price'])]"
+
+ loNoduri = loRoot.selectNodes(m.lcXPath)
+ llValoareInvalida = .F.
+ FOR lnI = 0 TO loNoduri.length - 1
+ loNod = loNoduri.Item(m.lnI)
+ lcValoare = Alltrim(loNod.Text)
+ lcValoareNoua = NegheazaValoareXml(m.lcValoare, @llOk)
+ IF !m.llOk
+ tcMesaj = 'Valoare nenumerica in ' + loNod.baseName + ': "' + m.lcValoare + '".'
+ llValoareInvalida = .T.
+ EXIT
+ ENDIF
+ IF m.lcValoareNoua <> m.lcValoare
+ lcLinieEticheta = LineiaEticheta(m.loNod)
+ lnModif = m.lnModif + 1
+ Dimension taModificari[m.lnModif, 4]
+ taModificari[m.lnModif, 1] = EtichetaNodXmlStorno(m.loNod)
+ taModificari[m.lnModif, 2] = m.lcValoare
+ taModificari[m.lnModif, 3] = m.lcValoareNoua
+ taModificari[m.lnModif, 4] = Iif(Empty(m.lcLinieEticheta), 2, 3)
+ loNod.Text = m.lcValoareNoua
+ ENDIF
+ ENDFOR
+ IF m.llValoareInvalida
+ EXIT
+ ENDIF
+
+ loDOM.save(m.tcFisier)
+ IF !File(m.tcFisier)
+ tcMesaj = 'Salvarea xml-ului storno a esuat: ' + m.tcFisier
+ EXIT
+ ENDIF
+
+ llResult = .T.
+ EXIT
+ ENDDO
+ CATCH TO loEx
+ tcMesaj = 'StornoEFacturaXML eroare: ' + loEx.Message
+ FINALLY
+ IF File(m.lcTemp)
+ Delete File (m.lcTemp)
+ ENDIF
+ ENDTRY
+ RETURN m.llResult
+ENDFUNC && StornoEFacturaXML
+
******************************
PROCEDURE TestParseEFactura
diff --git a/programe/xmlefactura.prg b/programe/xmlefactura.prg
index 7ab30f2..ad1dbbd 100644
--- a/programe/xmlefactura.prg
+++ b/programe/xmlefactura.prg
@@ -1240,7 +1240,7 @@ Define Class ExportEFactura As Custom
* lReturn: .T. valid, .F. invalid
Local loReturn, llValid
Local loHTTP As 'winHTTP.winHTTPrequest.5.1'
- Local lcFileName, lcFisierRaspuns, lcMesaj, lcSend, lcServer, lcStare, llOk, llSilentios, llSucces
+ Local lcFileName, lcFisierRaspuns, lcMesaj, lcSend, lcServer, lcStare, lcTip, llOk, llSilentios, llSucces
Local lnMesaj, lnMesaje, loJson, loMessage, loEx as Exception, lcToken
llValid = .F.
@@ -1268,13 +1268,14 @@ Define Class ExportEFactura As Custom
If !Empty(m.lcFileName) And Type('lcFileName') = 'C' And File(m.lcFileName)
TRY
lcSend = Filetostr(m.lcFileName)
+ lcTip = IIF('CreditNote'$m.lcSend, 'FCN', 'FACT1')
IF !EMPTY(m.lcToken)
* validare oauth
- lcServer = [https://api.anaf.ro/prod/FCTEL/rest/validare/FACT1]
+ lcServer = [https://api.anaf.ro/prod/FCTEL/rest/validare/] + m.lcTip
ELSE
* validare fara autentificare
- lcServer = [https://webservicesp.anaf.ro/prod/FCTEL/rest/validare/FACT1]
+ lcServer = [https://webservicesp.anaf.ro/prod/FCTEL/rest/validare/] + m.lcTip
ENDIF
loHTTP = Createobject('winHTTP.winHTTPrequest.5.1')
diff --git a/utile/Teste/storno/frmtest_anaf.sc2 b/utile/Teste/storno/frmtest_anaf.sc2
new file mode 100644
index 0000000..27c7891
--- /dev/null
+++ b/utile/Teste/storno/frmtest_anaf.sc2
@@ -0,0 +1,19 @@
+*--------------------------------------------------------------------------------------------------------------------------------------------------------
+* (EN) AUTOGENERATED - ATTENTION!! - NOT INTENDED FOR EXECUTION!! USE ONLY FOR MERGING CHANGES AND STORING WITH SCM TOOLS!!
+*--------------------------------------------------------------------------------------------------------------------------------------------------------
+*< FOXBIN2PRG: Version="1.21" SourceFile="frmtest_anaf.scx" CPID="1252" /> (Solo para binarios VFP 9 / Only for VFP 9 binaries)
+*
+*
+DEFINE CLASS dataenvironment AS dataenvironment
+ *< CLASSDATA: Baseclass="dataenvironment" Timestamp="" Scale="" Uniqueid="" ClassIcon="1" />
+
+ *
+ DataSource = .NULL.
+ Height = 0
+ Left = 0
+ Name = "Dataenvironment"
+ Top = 0
+ Width = 0
+ *
+
+ENDDEFINE