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