From 349130c369a2463e7ed257bcb974d39706d0d162 Mon Sep 17 00:00:00 2001 From: Marius Mutu Date: Sun, 2 Aug 2026 10:02:31 +0300 Subject: [PATCH] Import parteneri: coduri fiscale/CNP invalide se salveaza gol, partenerii straini nu se valideaza romaneste --- ALTE/check_cf_litere.ps1 | 12 +++++ ALTE/check_cnp.ps1 | 50 ++++++++++++++++++ Teste/ruleaza_teste.ps1 | 3 +- Teste/test_cod_fiscal.prg | 104 +++++++++++++++++++++++++++++++++++++ frm_import.sc2 | 41 +++++++-------- myutils.prg | 106 +++++++++++++++++++++++++++++++++++++- 6 files changed, 291 insertions(+), 25 deletions(-) create mode 100644 ALTE/check_cf_litere.ps1 create mode 100644 ALTE/check_cnp.ps1 create mode 100644 Teste/test_cod_fiscal.prg diff --git a/ALTE/check_cf_litere.ps1 b/ALTE/check_cf_litere.ps1 new file mode 100644 index 0000000..a7ca5c5 --- /dev/null +++ b/ALTE/check_cf_litere.ps1 @@ -0,0 +1,12 @@ +# Ce coduri fiscale NENUMERICE apar in XML-urile Fidelio (posibili parteneri straini) +param([string]$Dir = "D:\PROIECTE\FLORA\FLORA2ROA\EXEMPLE\hotel") +$map = @{} +Get-ChildItem -Path $Dir -Filter 'bts_detalii_facturi_rep_*.xml' | ForEach-Object { + $t = Get-Content $_.FullName -Raw + foreach ($m in [regex]::Matches($t, '(?i)([^<]*)')) { + $v = $m.Groups[1].Value.Trim() + if ($v -ne '' -and $v -match '[A-Za-z]') { $map[$v] = 1 } + } +} +"coduri cu litere = $($map.Count)" +$map.Keys | Sort-Object diff --git a/ALTE/check_cnp.ps1 b/ALTE/check_cnp.ps1 new file mode 100644 index 0000000..7c10e27 --- /dev/null +++ b/ALTE/check_cnp.ps1 @@ -0,0 +1,50 @@ +# Simulare a regulii GetCodFiscalValid pe TOATE codurile din XML-urile Fidelio. +# Raporteaza cate coduri se pastreaza, cate se sterg si cate sunt straine (nevalidate). +param([string]$Dir = "D:\PROIECTE\FLORA\FLORA2ROA\EXEMPLE\hotel") + +function Test-CNP([string]$c) { + if ($c -notmatch '^\d{13}$') { return $false } + $w = @(2,7,9,1,4,6,3,5,8,2,7,9); $s = 0 + for ($i = 0; $i -lt 12; $i++) { $s += [int]::Parse($c[$i]) * $w[$i] } + $r = $s % 11; if ($r -eq 10) { $r = 1 } + return ($r -eq [int]::Parse($c[12])) +} +function Test-CUI([string]$c) { + if ($c -notmatch '^\d{2,10}$') { return $false } + $c = $c.PadLeft(10, '0'); $k = '7532175321'; $s = 0 + for ($i = 0; $i -lt 9; $i++) { $s += [int]::Parse($c[$i]) * [int]::Parse($k[$i]) } + $r = ($s * 10) % 11; if ($r -eq 10) { $r = 0 } + return ($r -eq [int]::Parse($c[9])) +} + +$map = @{} +Get-ChildItem -Path $Dir -Filter 'bts_detalii_facturi_rep_*.xml' | ForEach-Object { + $t = Get-Content $_.FullName -Raw + foreach ($m in [regex]::Matches($t, '(?i)([^<]*)')) { + $v = $m.Groups[1].Value.Trim().ToUpper() + if ($v -ne '') { $map[$v] = 1 } + } +} + +$pastratCNP = 0; $pastratCUI = 0; $strain = 0; $sters = 0 +$listaStersi = @() +foreach ($cod in $map.Keys) { + $prefix = ($cod -replace '[^A-Z]', '') + $roPrefix = @('RO', 'CUI', 'CF', 'CIF') -contains $prefix + $romanesc = ($prefix -eq '' -or $roPrefix) + if (-not $romanesc) { $strain++; continue } + $cifre = ($cod -replace '[^\d]', '') + if ($cifre.Length -eq 13 -and -not $roPrefix) { + if (Test-CNP $cifre) { $pastratCNP++ } else { $sters++; $listaStersi += $cod } + } elseif ($cifre.Length -ge 2 -and $cifre.Length -le 10) { + if (Test-CUI $cifre) { $pastratCUI++ } else { $sters++; $listaStersi += $cod } + } else { $sters++; $listaStersi += $cod } +} + +"total coduri distincte : $($map.Count)" +"CNP valide pastrate : $pastratCNP" +"CUI valide pastrate : $pastratCUI" +"straine (nevalidate) : $strain" +"STERSE (invalide) : $sters" +'--- primele 30 sterse ---' +$listaStersi | Select-Object -First 30 diff --git a/Teste/ruleaza_teste.ps1 b/Teste/ruleaza_teste.ps1 index f20037a..020b411 100644 --- a/Teste/ruleaza_teste.ps1 +++ b/Teste/ruleaza_teste.ps1 @@ -33,7 +33,8 @@ $Root = Split-Path $Teste -Parent # nume test -> etichetele pasilor asteptate de vfp_ui_harness.ps1 (o captura per pas) $suita = @( @{ Prg = 'test_frm_import_ui.prg'; Steps = @('afisat', 'trace', 'date') } - @{ Prg = 'test_export_date.prg'; Steps = @('afisat', 'importat') } + @{ Prg = 'test_export_date.prg'; Steps = @('afisat', 'importat') } + @{ Prg = 'test_cod_fiscal.prg'; Steps = @('validari') } ) if ($Doar) { $suita = $suita | Where-Object { $_.Prg -like "*$Doar*" } } diff --git a/Teste/test_cod_fiscal.prg b/Teste/test_cod_fiscal.prg new file mode 100644 index 0000000..8459184 --- /dev/null +++ b/Teste/test_cod_fiscal.prg @@ -0,0 +1,104 @@ +* test_cod_fiscal.prg - test unitar pentru GetCodFiscalValid (validare CNP / CUI la import parteneri). +* Verifica regula ceruta: cod fiscal / CNP invalid => se salveaza GOL in ROA, ca sa nu dea +* erori la declaratii (D394 / D406). +* +* Rulare directa: +* vfp9.exe -A -T "D:\PROIECTE\FLORA\FLORA2ROA\Teste\test_cod_fiscal.prg" + +SET SAFETY OFF +SET TALK OFF + +PUBLIC gcUILog, gcSyncDir, gnTestFail, gnMockRaspuns, gcMockUltimMesaj, gnMockUltimTip +gnTestFail = 0 +gnMockRaspuns = 6 +gcMockUltimMesaj = '' +gnMockUltimTip = 0 + +LOCAL lcTestDir +lcTestDir = ADDBS(JUSTPATH(SYS(16, 0))) +gcUILog = lcTestDir + 'test_cod_fiscal_log.txt' +gcSyncDir = lcTestDir + 'uisync\' +IF !DIRECTORY(gcSyncDir) + MD (gcSyncDir) +ENDIF +STRTOFILE('START ' + TTOC(DATETIME()) + CHR(13) + CHR(10), gcUILog) + +DO (lcTestDir + 'test_init_env_auto.prg') WITH .F. +SET PROCEDURE TO (lcTestDir + 'ui_harness.prg') ADDITIVE +SET PROCEDURE TO (lcTestDir + 'mock_amessagebox.prg') ADDITIVE + +DO HarnessInit + +LOCAL lnTip, lcRez + +*--- CNP valid: se pastreaza, tip persoana fizica --- +lnTip = 0 +lcRez = GetCodFiscalValid('1800101410013', @lnTip) +TestAssertEqual('1800101410013', m.lcRez, 'CNP valid pastrat') +TestAssertEqual(2, m.lnTip, 'CNP valid => persoana fizica') + +*--- CNP valid murdar (spatii, punct): se curata si ramane --- +TestAssertEqual('1800101410013', GetCodFiscalValid(' 1800101410013. '), 'CNP valid curatat de spatii/punct') + +*--- CNP cu cifra de control gresita => sir gol --- +TestAssertEqual('', GetCodFiscalValid('1800101410018'), 'CNP cu cifra de control alterata se sterge') +TestAssertEqual('', GetCodFiscalValid('2650407170374'), 'CNP invalid real din Fidelio (1)') +TestAssertEqual('', GetCodFiscalValid('1550622400577'), 'CNP invalid real din Fidelio (2)') + +*--- lungimi gresite (12 / 14 cifre) => sir gol --- +TestAssertEqual('', GetCodFiscalValid('171030126201'), '12 cifre => invalid') +TestAssertEqual('', GetCodFiscalValid('29050709211182'), '14 cifre => invalid') +TestAssertEqual('', GetCodFiscalValid('60109121600600'), '14 cifre => invalid (2)') +* '2920121295914.' are 13 cifre dupa curatarea punctului si CNP-ul e valid => se pastreaza +TestAssertEqual('2920121295914', GetCodFiscalValid('2920121295914.'), 'CNP valid cu punct la final se curata') + +*--- CUI persoana juridica --- +lnTip = 0 +lcRez = GetCodFiscalValid('RO21675175', @lnTip) +TestAssertEqual('RO21675175', m.lcRez, 'CUI cu prefix RO pastrat') +TestAssertEqual(1, m.lnTip, 'CUI => persoana juridica') + +TestAssertEqual('RO23565004', GetCodFiscalValid('RO 23565004'), 'CUI cu spatiu normalizat') + +lnTip = 0 +lcRez = GetCodFiscalValid('24536740', @lnTip) +TestAssertEqual('24536740', m.lcRez, 'CUI fara RO pastrat') +TestAssertEqual(1, m.lnTip, 'CUI fara RO => persoana juridica') + +*--- CUI cu cifra de control gresita => sir gol --- +TestAssertEqual('', GetCodFiscalValid('24536741'), 'CUI cu cifra de control gresita se sterge') + +*--- valori goale / fara cifre utile --- +TestAssertEqual('', GetCodFiscalValid(''), 'sir gol') +TestAssertEqual('', GetCodFiscalValid(' '), 'spatii') + +*--- parteneri STRAINI: codul nu respecta algoritmul romanesc, deci NU se sterge --- +lnTip = 0 +lcRez = GetCodFiscalValid('BE0325777171', @lnTip) +TestAssertEqual('BE0325777171', m.lcRez, 'TVA belgian pastrat ca atare') +TestAssertEqual(1, m.lnTip, 'cod strain => persoana juridica') + +TestAssertEqual('DICA9712015/99KS', GetCodFiscalValid('DICA9712015/99KS'), 'cod strain cu slash pastrat') +TestAssertEqual('NN9J17714', GetCodFiscalValid('NN9J17714'), 'cod strain alfanumeric pastrat') +TestAssertEqual('XD 061964', GetCodFiscalValid('XD 061964'), 'cod strain cu spatiu pastrat') +TestAssertEqual('ABC', GetCodFiscalValid('ABC'), 'cod strain fara cifre pastrat') + +*--- prefixele romanesti se trateaza tot cu regulile RO --- +TestAssertEqual('RO14770212', GetCodFiscalValid('CUI 14770212'), 'prefix CUI => CUI romanesc valid') +TestAssertEqual('RO12870', GetCodFiscalValid('RO 12870'), 'CUI scurt valid, spatiu eliminat') +TestAssertEqual('', GetCodFiscalValid('RO 12871'), 'CUI romanesc invalid se sterge') + +*--- codul de tara declarat opreste validarea romaneasca --- +lnTip = 0 +lcRez = GetCodFiscalValid('123456789012345', @lnTip, 'BE') +TestAssertEqual('123456789012345', m.lcRez, 'cod numeric cu tara straina nu se valideaza romaneste') +TestAssertEqual(1, m.lnTip, 'tara straina => persoana juridica') + +TestAssertEqual('', GetCodFiscalValid('1800101410018', 0, 'RO'), 'tara RO => se valideaza romaneste') + +DO HarnessStep WITH 0, 'validari cod fiscal' + +HarnessLog('REZULTAT: ' + TestRezultat()) +DO HarnessDone WITH TestRezultat() + +QUIT diff --git a/frm_import.sc2 b/frm_import.sc2 index 4cb7f51..a8d3998 100644 --- a/frm_import.sc2 +++ b/frm_import.sc2 @@ -272,7 +272,7 @@ DEFINE CLASS form1 AS form * Completez id_part Local lcBanca, lcCod_fiscal, lcCont_Banca, lcCorespDel, lcDenumire, lcIdString, lcId_categ_ent Local lcId_loc_inreg, lcId_util, lcMesaj, lcMotiv_inactiv, lcNume, lcPrefix, lcPrenume, lcReg_comert - Local lcSql, lcSqlInsert, lcSufix, lcTip_persoana, lcinactiv, lnSucces + Local lcSql, lcSqlInsert, lcSufix, lcTip_persoana, lcinactiv, lnSucces, lcCodValid, lnTip_persoana, lcCodTara Local lcAdresa, lcAdreseParteneri, lcApart, lcBloc, lcCaleImport, lcCod, lcCodpostal, lcDA_apare Local lcDenumire_adresa, lcEmail, lcEtaj, lcFax, lcFile, lcIdPart, lcId_Judet, lcId_loc, lcId_tara Local lcItem1, lcItem2, lcItem3, lcItem4, lcJudet, lcJudetBucuresti, lcLocalitate, lcNumar @@ -401,29 +401,24 @@ DEFINE CLASS form1 AS form *************************** lcCod=Alltrim(Upper(Nvl(m.lcCod_fiscal,""))) lcTip_persoana="2" && 1 = juridica, 2 = fizica - * Prefer persoanele fizice pentru ca sunt mai multe fata de persoanele juridice - * Sunt multe persoane fizice cu CNP-uri gresite + * Prefer persoanele fizice pentru ca sunt mai multe fata de persoanele juridice. + * Sunt multe persoane fizice cu CNP-uri gresite (lungime gresita sau cifra de control gresita). + * GetCodFiscalValid curata si valideaza codul; daca este invalid intoarce sir gol, + * deci partenerul se salveaza in ROA FARA cod fiscal si nu mai da erori la D394/D406. If !Empty(m.lcCod) - If Left(lcCod,2)="RO" - lcCod=Substr(m.lcCod,3) - lcTip_persoana="1" - ENDIF - IF ISALPHA(m.lcCod) - lcTip_persoana="1" - ENDIF - - If Len(m.lcCod)=13 And Isdigit(m.lcCod) - If !cValidCNP(m.lcCod) - * daca CNP invalid, sterg CNP, sa nu dea eroare la D406 - lcCod_fiscal = '' - Endif - ELSE - * PJ din ROMANIA au coduri fiscale intre 2-10 cifre - * Exista si PJ din alte tari care pot avea coduri fiscale cu alt numar de cifre, dar sunt exceptii - If Len(m.lcCod)>=2 And Len(m.lcCod)<=10 And Isdigit(m.lcCod) - lcTip_persoana="1" - Endif - Endif + lnTip_persoana = 0 + lcCodTara = Alltrim(Upper(Nvl(cod_tara, ""))) + lcCodValid = GetCodFiscalValid(m.lcCod, @lnTip_persoana, m.lcCodTara) + Do Case + Case !Empty(m.lcCodValid) + lcCod_fiscal = m.lcCodValid + lcTip_persoana = Iif(m.lnTip_persoana = 1, "1", "2") + Otherwise + * numeric dar invalid (12/14/15 cifre, cifra de control gresita): sterg codul + Thisform.trace('Cod fiscal invalid, se salveaza gol: [' + m.lcCod + '] ' + Alltrim(m.lcDenumire)) + lcCod_fiscal = '' + lcTip_persoana = "2" + Endcase ENDIF IF m.lcTip_Persoana = "2" lnPos = At(' ', m.lcNume) diff --git a/myutils.prg b/myutils.prg index a973cb5..8d9936d 100644 --- a/myutils.prg +++ b/myutils.prg @@ -1664,6 +1664,25 @@ FUNCTION GetDataCNP RETURN CTOD(SUBSTR(plcnp, 6, 2) + "." + SUBSTR(plcnp, 4, 2) + "." + mlan) ENDFUNC * +* +* Copiata din D:\ROA\ROACONT\COMUN\programe\oproceduri_comune.prg (GetNrFromString), +* ca sa nu duplic logica: taie de la inceputul sirului literele si separatorii, +* pana la prima cifra. Ex. 'RO 15613488' -> '15613488', 'CUI 14770212' -> '14770212'. +FUNCTION GetNrFromString + LPARAMETERS plstr + LOCAL mlenstr + mlenstr = LEN(ALLTRIM(plstr)) + + DO WHILE ISALPHA(plstr) .OR. LEFT(plstr, 1) == " "; + .OR. LEFT(plstr, 1) == "&" .OR. LEFT(plstr, 1) == "/"; + .OR. LEFT(plstr, 1) == "-" .OR. LEFT(plstr, 1) == "_"; + .OR. LEFT(plstr, 1) == "." .OR. LEFT(plstr, 1) == ":" + plstr = RTRIM(SUBSTR(plstr, 2, mlenstr)) + ENDDO + + RETURN plstr +ENDFUNC +* FUNCTION VerifCF LPARAMETERS plcfisc LOCAL mlsuma, mlrest @@ -1704,4 +1723,89 @@ FUNCTION GetCodFiscalFRO RETURN m.lcCodFiscalFRO ENDFUNC -* \ No newline at end of file +* +* Normalizeaza codul fiscal / CNP primit din import (Fidelio hotel, BIZ restaurant). +* Intoarce codul curatat daca este valid, altfel SIR GOL, ca sa nu dea erori la D394/D406. +* +* ATENTIE: algoritmul de validare (CNP si CUI) este DOAR pentru Romania. Partenerii straini +* au coduri care nu respecta regulile romanesti (BE0325777171, DICA9712015/99KS, NN9J17714), +* deci NU se valideaza si NU se sterg: se pastreaza asa cum vin, ca persoana juridica. +* +* Cod considerat ROMANESC: +* - numai cifre (eventual cu spatii, puncte, liniute), SAU +* - prefix 'RO' / 'CUI' / 'CF' urmat de cifre +* Pentru codurile romanesti: +* - 13 cifre => CNP, validat cu cifra de control (cValidCNP); invalid => '' +* - 2-10 cifre => CUI persoana juridica, validat cu VerifCF; invalid => '' +* - alta lungime (12, 14, 15 cifre etc.) => invalid => '' +* Pentru orice alt cod cu litere (partener strain, pasaport) => se intoarce codul curatat de +* spatii duble, fara validare. +* +* Parametri: +* tcCod - codul brut din import +* tnTipPersoana - optional, prin referinta: 1 = juridica, 2 = fizica, 0 = necunoscut +* tcCodTara - optional, codul de tara al partenerului ('RO', 'BE', ...). Daca este +* completat si nu este 'RO', codul NU se valideaza cu regulile romanesti. +FUNCTION GetCodFiscalValid + LPARAMETERS tcCod, tnTipPersoana, tcCodTara + + LOCAL lcCod, lcCifre, lcPrefix, lcTara, llRO, llRomanesc, lnParam + lnParam = PCOUNT() + IF m.lnParam > 1 + tnTipPersoana = 0 + ENDIF + + lcCod = ALLTRIM(UPPER(TRANSFORM(NVL(m.tcCod, '')))) + lcCod = ALLTRIM(CHRTRAN(m.lcCod, CHR(9) + CHR(13) + CHR(10), ' ')) + IF EMPTY(m.lcCod) + RETURN '' + ENDIF + + lcTara = IIF(m.lnParam > 2 AND VARTYPE(m.tcCodTara) = 'C', ALLTRIM(UPPER(m.tcCodTara)), '') + + * partea numerica: GetNrFromString taie prefixul de litere/separatori ('RO 15613488' -> '15613488') + lcCifre = ALLTRIM(GetNrFromString(m.lcCod)) + lcCifre = CHRTRAN(m.lcCifre, CHRTRAN(m.lcCifre, '0123456789', ''), '') + + * prefixul de litere ramas in fata cifrelor (RO, CUI, CF, BE, DICA...) + lcPrefix = ALLTRIM(CHRTRAN(m.lcCod, CHRTRAN(m.lcCod, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', ''), '')) + llRO = INLIST(m.lcPrefix, 'RO', 'CUI', 'CF', 'CIF') + + * romanesc = fara litere deloc, sau cu prefix romanesc, si fara tara straina declarata + llRomanesc = (EMPTY(m.lcPrefix) OR m.llRO) AND ; + (EMPTY(m.lcTara) OR m.lcTara == 'RO' OR m.lcTara == 'ROMANIA') + + IF !m.llRomanesc + * partener strain: nu pot valida cu algoritmul romanesc, pastrez codul asa cum vine + IF m.lnParam > 1 + tnTipPersoana = 1 + ENDIF + RETURN ALLTRIM(STRTRAN(m.lcCod, ' ', ' ')) + ENDIF + + IF EMPTY(m.lcCifre) + RETURN '' + ENDIF + + DO CASE + CASE LEN(m.lcCifre) = 13 AND !m.llRO + IF cValidCNP(m.lcCifre) + IF m.lnParam > 1 + tnTipPersoana = 2 + ENDIF + RETURN m.lcCifre + ENDIF + RETURN '' + CASE BETWEEN(LEN(m.lcCifre), 2, 10) + IF VerifCF(m.lcCifre) + IF m.lnParam > 1 + tnTipPersoana = 1 + ENDIF + RETURN IIF(m.llRO, 'RO' + m.lcCifre, m.lcCifre) + ENDIF + RETURN '' + OTHERWISE + RETURN '' + ENDCASE +ENDFUNC +*