diff --git a/.gitignore b/.gitignore
index 7657b18..ea78113 100644
--- a/.gitignore
+++ b/.gitignore
@@ -92,6 +92,7 @@ Teste/uisync/
Teste/screenshots/
Teste/_erori_test.txt
Teste/*_log.txt
+Teste/raport_*.csv
# --- Cache text extern (inlocuit de fluxul in-arbore .??2) ---
_textcache/
diff --git a/ALTE/check_semnale_pj.ps1 b/ALTE/check_semnale_pj.ps1
new file mode 100644
index 0000000..a649bd7
--- /dev/null
+++ b/ALTE/check_semnale_pj.ps1
@@ -0,0 +1,91 @@
+# Cat de bine putem distinge persoana fizica (PF) de persoana juridica (PJ) in Fidelio,
+# FARA sa ne bazam pe validitatea codului fiscal.
+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]))
+}
+
+# indicii de denumire pentru persoana juridica
+$cuvintePJ = @('SRL','S.R.L','SA','S.A','SC','S.C','PFA','P.F.A','SNC','SCS','SRL-D','II','I.I',
+ 'ASOCIATIA','ASOCIATIE','FUNDATIA','SOCIETATE','COMPANY','LTD','LIMITED','GMBH','GMBH.',
+ 'BV','NV','INC','CORP','GROUP','HOLDING','UNIVERSITE','UNIVERSITATEA','UNIVERSITY',
+ 'SPITAL','PRIMARIA','MINISTERUL','AGENTIA','INSTITUTUL','SCOALA','LICEUL','COLEGIUL',
+ 'CLUB','ACS','A.C.S','CAR','CFR','TRANS','TOURS','TRAVEL','IMOB','CONSTRUCT','CONSULT',
+ 'SOLUTIONS','TECHTRONIC','WORKSHOP','STUDIO','CONCEPT','SERV','COMERCIAL','PROFESSIONAL')
+
+function Get-TipDupaNume([string]$nume) {
+ $cuv = $nume -split '[^A-Z0-9\.]+' | Where-Object { $_ -ne '' }
+ foreach ($c in $cuv) { if ($cuvintePJ -contains $c) { return 'PJ' } }
+ return ''
+}
+function Get-TipDupaReg([string]$reg) {
+ if ($reg -eq '') { return '' }
+ if ($reg -match '^[JFC]\s?\d{1,2}\s?/\s?\d+\s?/\s?\d{4}') { return 'PJ' } # J40/1234/2018
+ if ($reg -match '^[JL]\d{6,}') { return 'PJ' } # L2019000159348
+ if ($reg -match '^\d{1,2}/\d{4}$') { return 'PJ' } # 54/1997
+ if ($reg -match '^[A-Z]{2}\s?\d{6}$') { return 'PF' } # serie CI: ZV 093375
+ return ''
+}
+
+$rows = @{}
+Get-ChildItem -Path $Dir -Filter 'bts_detalii_facturi_rep_*.xml' | ForEach-Object {
+ $t = Get-Content $_.FullName -Raw
+ foreach ($m in [regex]::Matches($t, '(?is)([^<]*).*?([^<]*)\s*([^<]*)')) {
+ $nume = $m.Groups[1].Value.Trim().ToUpper()
+ $cod = $m.Groups[2].Value.Trim().ToUpper()
+ $reg = $m.Groups[3].Value.Trim().ToUpper()
+ if ($cod -ne '') { $rows["$nume|$cod|$reg"] = [pscustomobject]@{ Nume=$nume; Cod=$cod; Reg=$reg } }
+ }
+}
+
+$rez = foreach ($r in $rows.Values) {
+ $d = $r.Cod -replace '[^\d]', ''
+ $roPrefix = ($r.Cod -replace '[^A-Z]', '') -in @('RO','CUI','CF','CIF')
+ $tipNume = Get-TipDupaNume $r.Nume
+ $tipReg = Get-TipDupaReg $r.Reg
+ $cnpOk = Test-CNP $d
+ $cuiOk = (Test-CUI $d) -and -not $cnpOk
+
+ # decizie: semnale tari intai (nume/reg comert/prefix RO), apoi forma codului
+ $tip = ''
+ if ($roPrefix) { $tip = 'PJ' }
+ elseif ($tipNume -eq 'PJ') { $tip = 'PJ' }
+ elseif ($tipReg -eq 'PJ') { $tip = 'PJ' }
+ elseif ($cnpOk) { $tip = 'PF' }
+ elseif ($tipReg -eq 'PF') { $tip = 'PF' }
+ elseif ($cuiOk) { $tip = 'PJ' }
+ else { $tip = 'PF?' } # necunoscut, presupunem PF
+
+ [pscustomobject]@{ Nume=$r.Nume; Cod=$r.Cod; Reg=$r.Reg; Tip=$tip; CnpOk=$cnpOk; CuiOk=$cuiOk }
+}
+
+"total = $($rez.Count)"
+$rez | Group-Object Tip | Sort-Object Count -Descending | Format-Table Name, Count | Out-String -Width 100
+
+''
+'=== PJ cu cod care NU trece validarea de CUI (raman cu cod, ca sa atentioneze declaratia) ==='
+$pjInvalid = $rez | Where-Object { $_.Tip -eq 'PJ' -and -not $_.CuiOk }
+"numar = $($pjInvalid.Count)"
+$pjInvalid | Select-Object -First 25 | Format-Table -AutoSize Nume, Cod, Reg | Out-String -Width 200
+
+''
+'=== PF cu CNP invalid (se vor salva CU CNP GOL) ==='
+$pfInvalid = $rez | Where-Object { $_.Tip -like 'PF*' -and -not $_.CnpOk }
+"numar = $($pfInvalid.Count)"
+$pfInvalid | Select-Object -First 25 | Format-Table -AutoSize Nume, Cod, Reg | Out-String -Width 200
+
+''
+'=== risc: clasificate PJ dar codul e CNP valid (nu trebuie sa existe) ==='
+($rez | Where-Object { $_.Tip -eq 'PJ' -and $_.CnpOk }) | Format-Table -AutoSize Nume, Cod, Reg | Out-String -Width 200
diff --git a/ALTE/check_swap_tara.ps1 b/ALTE/check_swap_tara.ps1
new file mode 100644
index 0000000..6f2920e
--- /dev/null
+++ b/ALTE/check_swap_tara.ps1
@@ -0,0 +1,49 @@
+# Cazuri in care CODFISCAL_CNP e invalid dar REGCOMERT_CI_PASS contine un CNP valid (campuri inversate).
+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]))
+}
+
+$rows = @{}
+Get-ChildItem -Path $Dir -Filter 'bts_detalii_facturi_rep_*.xml' | ForEach-Object {
+ $t = Get-Content $_.FullName -Raw
+ foreach ($m in [regex]::Matches($t, '(?is)([^<]*).*?([^<]*)\s*([^<]*)')) {
+ $nume = $m.Groups[1].Value.Trim().ToUpper()
+ $cod = $m.Groups[2].Value.Trim().ToUpper()
+ $reg = $m.Groups[3].Value.Trim().ToUpper()
+ if ($cod -ne '') { $rows["$nume|$cod|$reg"] = [pscustomobject]@{ Nume=$nume; Cod=$cod; Reg=$reg } }
+ }
+}
+
+$swap = $rows.Values | Where-Object {
+ $d = $_.Cod -replace '[^\d]', ''
+ $r = $_.Reg -replace '[^\d]', ''
+ -not (Test-CNP $d) -and (Test-CNP $r)
+}
+"cazuri cu campuri inversate (CNP valid in REGCOMERT) = $($swap.Count)"
+$swap | Format-Table -AutoSize Nume, Cod, Reg | Out-String -Width 200
+
+# exista informatie de tara la cumparator?
+$f = Get-ChildItem -Path $Dir -Filter 'bts_detalii_facturi_rep_*.xml' | Select-Object -First 1
+$t = Get-Content $f.FullName -Raw
+$m = [regex]::Match($t, '(?is).*?
')
+''
+'=== campurile de tara existente in XML (primul rand) ==='
+[regex]::Matches($m.Value, '(?i)<(\w*TARA\w*)>([^<]*)\1>') | ForEach-Object { " $($_.Groups[1].Value) = [$($_.Groups[2].Value)]" }
+
+''
+'=== cate randuri au COMPANIE_TARA completat ==='
+$cuTara = 0; $total = 0
+Get-ChildItem -Path $Dir -Filter 'bts_detalii_facturi_rep_*.xml' | ForEach-Object {
+ $txt = Get-Content $_.FullName -Raw
+ foreach ($mm in [regex]::Matches($txt, '(?i)([^<]*)')) {
+ $total++
+ if ($mm.Groups[1].Value.Trim() -ne '') { $cuTara++ }
+ }
+}
+"COMPANIE_TARA completat pe $cuTara din $total randuri"
diff --git a/Teste/recompileaza_binare.ps1 b/Teste/recompileaza_binare.ps1
new file mode 100644
index 0000000..d851a49
--- /dev/null
+++ b/Teste/recompileaza_binare.ps1
@@ -0,0 +1,59 @@
+<#
+.SYNOPSIS
+ Recompileaza formularele/clasele VFP dupa un write-back din text (bin_din_text.ps1).
+
+.DESCRIPTION
+ FoxBin2Prg regenereaza .scx/.sct DIN TEXT, dar fara codul obiect compilat. Un formular
+ regenerat si NErecompilat ruleaza fara `#INCLUDE`-uri: constantele din comun.h (CT_SUCCES,
+ CT_INSUCCES...) devin "Variable not found" abia in RUNTIME, nu la verificarea de sintaxa.
+
+ De aceea, dupa FIECARE bin_din_text.ps1 se ruleaza si scriptul asta.
+
+.EXAMPLE
+ powershell -ExecutionPolicy Bypass -File Teste\bin_din_text.ps1 -TextFile frm_import.sc2
+ powershell -ExecutionPolicy Bypass -File Teste\recompileaza_binare.ps1
+#>
+[CmdletBinding()]
+param(
+ [string[]]$Formular = @('frm_import.scx'),
+ [string]$ProjectRoot
+)
+$ErrorActionPreference = 'Stop'
+
+if (-not $ProjectRoot) { $ProjectRoot = Split-Path $PSScriptRoot -Parent }
+
+$vfp = 'C:\Program Files (x86)\Microsoft Visual FoxPro 9\vfp9.exe'
+if (-not (Test-Path $vfp)) { throw "Nu gasesc $vfp" }
+
+$prg = Join-Path $env:TEMP ("recompile_" + [guid]::NewGuid().ToString('N') + '.prg')
+$linii = @(
+ "SET DEFAULT TO `"$ProjectRoot`"",
+ "SET PATH TO `"$ProjectRoot`""
+)
+foreach ($f in $Formular) {
+ $cale = Join-Path $ProjectRoot $f
+ if (-not (Test-Path $cale)) { throw "Nu exista $cale" }
+ $linii += "COMPILE FORM `"$f`""
+}
+$linii += 'QUIT'
+[IO.File]::WriteAllLines($prg, $linii, [Text.Encoding]::GetEncoding(1252))
+
+try {
+ & $vfp -A -T $prg | Out-Null
+} finally {
+ Remove-Item $prg -ErrorAction SilentlyContinue
+}
+
+# verificare: .sct trebuie sa contina cod obiect, nu doar textul metodelor
+$rezultate = foreach ($f in $Formular) {
+ $sct = Join-Path $ProjectRoot ([IO.Path]::ChangeExtension($f, '.sct'))
+ [pscustomobject]@{
+ Formular = $f
+ SCT_KB = if (Test-Path $sct) { [math]::Round((Get-Item $sct).Length / 1KB) } else { 0 }
+ Stare = if ((Test-Path $sct) -and (Get-Item $sct).Length -gt 0) { 'OK' } else { 'LIPSA' }
+ }
+}
+$rezultate | Format-Table -AutoSize
+
+if ($rezultate | Where-Object { $_.Stare -ne 'OK' }) { exit 1 }
+Write-Host 'Recompilare terminata.' -ForegroundColor Green
diff --git a/Teste/ruleaza_teste.ps1 b/Teste/ruleaza_teste.ps1
index 020b411..23fdb2f 100644
--- a/Teste/ruleaza_teste.ps1
+++ b/Teste/ruleaza_teste.ps1
@@ -34,7 +34,8 @@ $Root = Split-Path $Teste -Parent
$suita = @(
@{ Prg = 'test_frm_import_ui.prg'; Steps = @('afisat', 'trace', 'date') }
@{ Prg = 'test_export_date.prg'; Steps = @('afisat', 'importat') }
- @{ Prg = 'test_cod_fiscal.prg'; Steps = @('validari') }
+ @{ Prg = 'test_cod_fiscal.prg'; Steps = @('validari') }
+ @{ Prg = 'test_cod_fiscal_real.prg'; Steps = @('analiza') }
)
if ($Doar) { $suita = $suita | Where-Object { $_.Prg -like "*$Doar*" } }
diff --git a/Teste/test_cod_fiscal.prg b/Teste/test_cod_fiscal.prg
index 8459184..16756a8 100644
--- a/Teste/test_cod_fiscal.prg
+++ b/Teste/test_cod_fiscal.prg
@@ -1,6 +1,11 @@
-* 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).
+* test_cod_fiscal.prg - teste pentru clasificarea partenerilor importati si curatarea CNP-urilor.
+*
+* Regula verificata:
+* - PERSOANA FIZICA cu CNP invalid => cod fiscal GOL (permis la D394/D406)
+* - PERSOANA FIZICA cu CNP valid => CNP pastrat, curatat de spatii/puncte
+* - PERSOANA JURIDICA => codul NU se sterge NICIODATA, chiar daca nu trece
+* validarea romaneasca (poate fi partener strain),
+* ca sa fim atentionati la validarea declaratiei
*
* Rulare directa:
* vfp9.exe -A -T "D:\PROIECTE\FLORA\FLORA2ROA\Teste\test_cod_fiscal.prg"
@@ -31,70 +36,172 @@ DO HarnessInit
LOCAL lnTip, lcRez
-*--- CNP valid: se pastreaza, tip persoana fizica ---
+*==============================================================
+* 1. PERSOANE FIZICE - singurele la care se sterge codul
+*==============================================================
lnTip = 0
-lcRez = GetCodFiscalValid('1800101410013', @lnTip)
-TestAssertEqual('1800101410013', m.lcRez, 'CNP valid pastrat')
-TestAssertEqual(2, m.lnTip, 'CNP valid => persoana fizica')
+lcRez = GetCodFiscalValid('1800101410013', @lnTip, 'POPESCU ION', 'ZV 093375')
+TestAssertEqual('1800101410013', m.lcRez, 'PF: CNP valid pastrat')
+TestAssertEqual(2, m.lnTip, 'PF: CNP valid => persoana fizica')
-*--- CNP valid murdar (spatii, punct): se curata si ramane ---
-TestAssertEqual('1800101410013', GetCodFiscalValid(' 1800101410013. '), 'CNP valid curatat de spatii/punct')
+TestAssertEqual('1800101410013', GetCodFiscalValid(' 1800101410013. ', @lnTip, 'POPESCU ION', ''), ;
+ 'PF: CNP valid curatat de spatii si 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 ---
+* CNP-uri invalide reale din Fidelio: codul se goleste
lnTip = 0
-lcRez = GetCodFiscalValid('RO21675175', @lnTip)
-TestAssertEqual('RO21675175', m.lcRez, 'CUI cu prefix RO pastrat')
-TestAssertEqual(1, m.lnTip, 'CUI => persoana juridica')
+lcRez = GetCodFiscalValid('2650407170374', @lnTip, 'TRUTA CLAUDIA MIHAELA', 'RK 800386')
+TestAssertEqual('', m.lcRez, 'PF: CNP cu cifra de control gresita se sterge')
+TestAssertEqual(2, m.lnTip, 'PF: ramane persoana fizica')
-TestAssertEqual('RO23565004', GetCodFiscalValid('RO 23565004'), 'CUI cu spatiu normalizat')
+TestAssertEqual('', GetCodFiscalValid('171030126201', @lnTip, 'TARTA GAVRIL SILVIU', ''), ;
+ 'PF: CNP de 12 cifre se sterge')
+TestAssertEqual('', GetCodFiscalValid('29050709211182', @lnTip, 'HOGESCU CRISTINA', ''), ;
+ 'PF: CNP de 14 cifre se sterge')
+TestAssertEqual('', GetCodFiscalValid('60109121600600', @lnTip, 'IOANCA DAMARIS', ''), ;
+ 'PF: CNP de 14 cifre se sterge (2)')
+
+* CNP valid scris cu punct la final
+TestAssertEqual('2920121295914', GetCodFiscalValid('2920121295914.', @lnTip, 'ANDREI MARIA', ''), ;
+ 'PF: CNP valid cu punct la final se curata')
+
+* persoana fizica fara niciun semnal, cod invalid => tot persoana fizica, cod gol
+lnTip = 0
+lcRez = GetCodFiscalValid('287101935573', @lnTip, 'OPREA ANDREEA', '')
+TestAssertEqual('', m.lcRez, 'PF: fara semnale, cod invalid => gol')
+TestAssertEqual(2, m.lnTip, 'PF: fara semnale => persoana fizica')
+
+*==============================================================
+* 2. PERSOANE JURIDICE - codul NU se sterge niciodata
+*==============================================================
+lnTip = 0
+lcRez = GetCodFiscalValid('RO21675175', @lnTip, 'JUST MULTISERV SRL', 'J03/245/2007')
+TestAssertEqual('RO21675175', m.lcRez, 'PJ: CUI cu prefix RO pastrat')
+TestAssertEqual(1, m.lnTip, 'PJ: prefix RO => persoana juridica')
+
+TestAssertEqual('RO23565004', GetCodFiscalValid('RO 23565004', @lnTip, 'FIRMA SRL', ''), ;
+ 'PJ: spatiul din cod se elimina')
lnTip = 0
-lcRez = GetCodFiscalValid('24536740', @lnTip)
-TestAssertEqual('24536740', m.lcRez, 'CUI fara RO pastrat')
-TestAssertEqual(1, m.lnTip, 'CUI fara RO => persoana juridica')
+lcRez = GetCodFiscalValid('24536740', @lnTip, 'KALVA SOLUTIONS SRL', 'J29/2677/2008')
+TestAssertEqual('24536740', m.lcRez, 'PJ: CUI fara RO pastrat')
+TestAssertEqual(1, m.lnTip, 'PJ: denumire cu SRL => 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 ---
+* CUI romanesc GRESIT la o persoana juridica: NU se sterge, ca sa dea eroare la declaratie
lnTip = 0
-lcRez = GetCodFiscalValid('BE0325777171', @lnTip)
-TestAssertEqual('BE0325777171', m.lcRez, 'TVA belgian pastrat ca atare')
-TestAssertEqual(1, m.lnTip, 'cod strain => persoana juridica')
+lcRez = GetCodFiscalValid('24536741', @lnTip, 'KALVA SOLUTIONS SRL', 'J29/2677/2008')
+TestAssertEqual('24536741', m.lcRez, 'PJ: CUI invalid NU se sterge')
+TestAssertEqual(1, m.lnTip, 'PJ: CUI invalid ramane 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 ---
+* partener strain cu cod alfanumeric
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')
+lcRez = GetCodFiscalValid('BE0325777171', @lnTip, 'UNIVERSITE DE LIEGE', '')
+TestAssertEqual('BE0325777171', m.lcRez, 'PJ straina: TVA belgian pastrat')
+TestAssertEqual(1, m.lnTip, 'PJ straina: cod TVA intracomunitar => persoana juridica')
-TestAssertEqual('', GetCodFiscalValid('1800101410018', 0, 'RO'), 'tara RO => se valideaza romaneste')
+TestAssertEqual('DICA9712015/99KS', GetCodFiscalValid('DICA9712015/99KS', @lnTip, 'FIRMA STRAINA SRL', ''), ;
+ 'PJ straina: cod cu slash pastrat cand denumirea are forma juridica')
+
+* cod cu litere care NU e cod de TVA si fara alt semnal => pasaport de persoana fizica
+lnTip = 0
+lcRez = GetCodFiscalValid('NN9J17714', @lnTip, 'TORRES MELISSA', '')
+TestAssertEqual('', m.lcRez, 'PF straina: pasaportul nu se salveaza pe cod fiscal')
+TestAssertEqual(2, m.lnTip, 'PF straina: pasaport fara alt semnal => persoana fizica')
+
+* prefixul romanesc se normalizeaza la RO
+lnTip = 0
+lcRez = GetCodFiscalValid('CUI 14770212', @lnTip, 'ASOCIATIA JUDETEANA DE HANDBAL', '')
+TestAssertEqual('14770212', m.lcRez, 'PJ: eticheta CUI se scoate, fara sa se adauge RO')
+TestAssertEqual(1, m.lnTip, 'PJ: eticheta CUI => persoana juridica')
+
+* 'RO' inseamna platitor de TVA: in ROA e ALT partener decat neplatitorul cu acelasi numar,
+* deci prefixul se pastreaza exact cum vine si NU se adauga niciodata de noi
+TestAssertEqual('RO24536740', GetCodFiscalValid('RO24536740', @lnTip, 'KALVA SOLUTIONS SRL', ''), ;
+ 'PJ platitoare de TVA: prefixul RO se pastreaza')
+TestAssertEqual('24536740', GetCodFiscalValid('24536740', @lnTip, 'KALVA SOLUTIONS SRL', ''), ;
+ 'PJ neplatitoare: NU se adauga RO')
+
+* partener strain cu cod NUMAI DIN CIFRE, recunoscut dupa tara
+lnTip = 0
+lcRez = GetCodFiscalValid('123456789012345', @lnTip, 'HOTEL PARTNER', '', 'BE')
+TestAssertEqual('123456789012345', m.lcRez, 'PJ straina: cod numeric cu tara straina se pastreaza')
+TestAssertEqual(1, m.lnTip, 'PJ straina: tara straina => persoana juridica')
+
+* persoana juridica recunoscuta dupa numarul de registrul comertului
+lnTip = 0
+lcRez = GetCodFiscalValid('99999999', @lnTip, 'DENUMIRE FARA FORMA JURIDICA', 'J40/5790/2018')
+TestAssertEqual('99999999', m.lcRez, 'PJ: cod invalid pastrat cand exista nr. reg. comertului')
+TestAssertEqual(1, m.lnTip, 'PJ: nr. registrul comertului => persoana juridica')
+
+lnTip = 0
+lcRez = GetCodFiscalValid('88888888', @lnTip, 'TICMAR EMMA CONCEPT', 'J2018000211289')
+TestAssertEqual(1, m.lnTip, 'PJ: nr. reg. comertului fara slash => persoana juridica')
+
+lnTip = 0
+lcRez = GetCodFiscalValid('9924188', @lnTip, 'CAR CFR SECTIA L2 SI CT1 BRAILA', '54/1997')
+TestAssertEqual(1, m.lnTip, 'PJ: numar/an => persoana juridica')
+
+* tipul venit direct din import bate toate celelalte semnale
+lnTip = 0
+lcRez = GetCodFiscalValid('1800101410013', @lnTip, 'POPESCU ION', '', '', '1')
+TestAssertEqual('1800101410013', m.lcRez, 'tip impus PJ: codul se pastreaza')
+TestAssertEqual(1, m.lnTip, 'tip impus din import => persoana juridica')
+
+*==============================================================
+* 3. Recuperare din campuri inversate in Fidelio
+*==============================================================
+* operatorul a scris pasaportul pe cod fiscal si CNP-ul pe serie CI
+lnTip = 0
+lcRez = GetCodFiscalValid('XD 061964', @lnTip, 'POPITIU IOANA', '2640626364228')
+TestAssertEqual('2640626364228', m.lcRez, 'recuperare: CNP valid luat din campul de serie CI')
+TestAssertEqual(2, m.lnTip, 'recuperare: ramane persoana fizica')
+
+* daca in campul de serie CI nu exista CNP valid, nu inventam nimic
+TestAssertEqual('', GetCodFiscalValid('XD 061964', @lnTip, 'POPITIU IOANA', 'ZV 093375'), ;
+ 'recuperare: fara CNP valid in serie CI ramane gol')
+
+*==============================================================
+* 4. Cazuri limita
+*==============================================================
+lnTip = 0
+TestAssertEqual('', GetCodFiscalValid('', @lnTip, 'POPESCU ION', ''), 'cod gol ramane gol')
+TestAssertEqual(2, m.lnTip, 'cod gol + nume de persoana => persoana fizica')
+
+lnTip = 0
+TestAssertEqual('', GetCodFiscalValid(' ', @lnTip, 'POPESCU ION', ''), 'spatii => gol')
+
+lnTip = 0
+lcRez = GetCodFiscalValid('', @lnTip, 'ALFA CONSTRUCT SRL', '')
+TestAssertEqual('', m.lcRez, 'PJ fara cod ramane fara cod')
+TestAssertEqual(1, m.lnTip, 'PJ fara cod este tot persoana juridica')
+
+*--- functiile ajutatoare ---
+TestAssert(EstePersoanaJuridica('KALVA SOLUTIONS SRL'), 'EstePersoanaJuridica: SRL')
+TestAssert(EstePersoanaJuridica('ASOCIATIA SPORTIVA X'), 'EstePersoanaJuridica: ASOCIATIA')
+TestAssert(EstePersoanaJuridica('UNIVERSITE DE LIEGE'), 'EstePersoanaJuridica: UNIVERSITE')
+TestAssert(!EstePersoanaJuridica('SAVU MARIA'), 'EstePersoanaJuridica: SAVU nu e SA')
+TestAssert(!EstePersoanaJuridica('MIHAII ION'), 'EstePersoanaJuridica: MIHAII nu e II')
+TestAssert(!EstePersoanaJuridica('POPESCU ION'), 'EstePersoanaJuridica: nume simplu')
+
+TestAssert(EsteNrRegComert('J40/5790/2018'), 'EsteNrRegComert: J40/5790/2018')
+TestAssert(EsteNrRegComert('J2018000211289'), 'EsteNrRegComert: J2018000211289')
+TestAssert(EsteNrRegComert('54/1997'), 'EsteNrRegComert: 54/1997')
+TestAssert(!EsteNrRegComert('ZV 093375'), 'EsteNrRegComert: serie CI nu e reg. comert')
+TestAssert(!EsteNrRegComert(''), 'EsteNrRegComert: gol')
+
+TestAssert(EsteSerieCI('ZV 093375'), 'EsteSerieCI: cu spatiu')
+TestAssert(EsteSerieCI('IF834409'), 'EsteSerieCI: fara spatiu')
+TestAssert(!EsteSerieCI('J40/5790/2018'), 'EsteSerieCI: nr. reg. comert nu e serie CI')
+TestAssert(!EsteSerieCI('2990118070015'), 'EsteSerieCI: CNP nu e serie CI')
+
+TestAssert(EsteCodTVAStrain('BE0325777171'), 'EsteCodTVAStrain: BE')
+TestAssert(EsteCodTVAStrain('DE811234567'), 'EsteCodTVAStrain: DE')
+TestAssert(!EsteCodTVAStrain('NN9J17714'), 'EsteCodTVAStrain: pasaport nu e cod TVA')
+TestAssert(!EsteCodTVAStrain('XD061964'), 'EsteCodTVAStrain: serie pasaport nu e cod TVA')
+
+TestAssertEqual('RO23565004', GetCodFiscalCurat('RO 23565004'), 'GetCodFiscalCurat: spatii, RO pastrat')
+TestAssertEqual('14770212', GetCodFiscalCurat('CUI 14770212'), 'GetCodFiscalCurat: eticheta CUI se scoate')
+TestAssertEqual('14770212', GetCodFiscalCurat('CF 14770212'), 'GetCodFiscalCurat: eticheta CF se scoate')
+TestAssertEqual('2920121295914', GetCodFiscalCurat('2920121295914.'), 'GetCodFiscalCurat: punct')
DO HarnessStep WITH 0, 'validari cod fiscal'
diff --git a/Teste/test_cod_fiscal_real.prg b/Teste/test_cod_fiscal_real.prg
new file mode 100644
index 0000000..4af0dad
--- /dev/null
+++ b/Teste/test_cod_fiscal_real.prg
@@ -0,0 +1,133 @@
+* test_cod_fiscal_real.prg - ruleaza GetCodFiscalValid pe TOATE codurile din XML-urile Fidelio
+* si scrie un raport, ca sa vedem efectul real inainte de import.
+*
+* Verifica invariantii care conteaza pentru declaratii:
+* - nicio persoana JURIDICA nu ramane fara cod din cauza noastra (codul nu se sterge)
+* - se sterge cod DOAR la persoane fizice si DOAR cand CNP-ul chiar este invalid
+* - niciun CNP valid nu se pierde
+*
+* Rulare: vfp9.exe -A -T "D:\PROIECTE\FLORA\FLORA2ROA\Teste\test_cod_fiscal_real.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_real_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 lcCale, lcFisier, lnFisiere, lcRaport
+lcCale = ADDBS(ALLTRIM(goApp.oSettings.cale_import_fidelio))
+HarnessLog('Citesc XML-urile din: ' + m.lcCale)
+
+CREATE CURSOR cTot (denumire C(200), cod C(50), reg_com C(50))
+
+lnFisiere = 0
+lnSarite = 0
+lcFisier = SYS(2000, m.lcCale + 'bts_detalii_facturi_rep_*.xml')
+DO WHILE !EMPTY(m.lcFisier)
+ lnRanduri = 0
+ * fisierele goale sau fara coloanele asteptate se sar controlat
+ TRY
+ lnRanduri = XMLTOCURSOR(m.lcCale + m.lcFisier, 'cUnul', 512)
+ CATCH
+ lnRanduri = 0
+ ENDTRY
+
+ IF m.lnRanduri > 0 AND USED('cUnul') AND ;
+ TYPE('cUnul.codfiscal_cnp') = 'C' AND TYPE('cUnul.cumparator') = 'C'
+ SELECT DISTINCT CAST(ALLTRIM(cumparator) AS C(200)) AS denumire, ;
+ CAST(ALLTRIM(codfiscal_cnp) AS C(50)) AS cod, ;
+ CAST(ALLTRIM(regcomert_ci_pass) AS C(50)) AS reg_com ;
+ FROM cUnul WHERE !EMPTY(NVL(codfiscal_cnp, '')) INTO CURSOR cTemp
+ SELECT cTot
+ APPEND FROM DBF('cTemp')
+ USE IN (SELECT('cTemp'))
+ lnFisiere = m.lnFisiere + 1
+ ELSE
+ lnSarite = m.lnSarite + 1
+ ENDIF
+
+ USE IN (SELECT('cUnul'))
+ lcFisier = SYS(2000, m.lcCale + 'bts_detalii_facturi_rep_*.xml', 1)
+ENDDO
+
+HarnessLog('Fisiere citite: ' + TRANSFORM(m.lnFisiere) + ', sarite (goale/fara coloane): ' + TRANSFORM(m.lnSarite))
+TestAssert(m.lnFisiere > 0, 'exista fisiere XML de analizat')
+
+SELECT DISTINCT denumire, cod, reg_com FROM cTot INTO CURSOR cUnic
+HarnessLog('Combinatii distincte nume+cod+regcom: ' + TRANSFORM(RECCOUNT('cUnic')))
+
+LOCAL lnPF, lnPJ, lnSters, lnPJNevalidat, lnCnpPierdut, lnPJFaraCod
+LOCAL lnTip, lcRez, lcCifre, lcCod
+STORE 0 TO lnPF, lnPJ, lnSters, lnPJNevalidat, lnCnpPierdut, lnPJFaraCod
+lcRaport = 'denumire;cod_import;reg_com;tip;cod_salvat' + CHR(13) + CHR(10)
+
+SELECT cUnic
+SCAN
+ lcCod = ALLTRIM(cUnic.cod)
+ lnTip = 0
+ lcRez = GetCodFiscalValid(m.lcCod, @lnTip, cUnic.denumire, cUnic.reg_com)
+
+ IF m.lnTip = 1
+ lnPJ = m.lnPJ + 1
+ * invariant 1: la persoana juridica NU stergem niciodata codul
+ IF EMPTY(m.lcRez) AND !EMPTY(m.lcCod)
+ lnPJFaraCod = m.lnPJFaraCod + 1
+ HarnessLog('EROARE invariant: PJ ramasa fara cod: ' + ALLTRIM(cUnic.denumire) + ' [' + m.lcCod + ']')
+ ENDIF
+ IF !VerifCF(m.lcRez)
+ lnPJNevalidat = m.lnPJNevalidat + 1
+ ENDIF
+ ELSE
+ lnPF = m.lnPF + 1
+ IF EMPTY(m.lcRez)
+ lnSters = m.lnSters + 1
+ * invariant 2: nu stergem CNP-uri VALIDE
+ lcCifre = CHRTRAN(m.lcCod, CHRTRAN(m.lcCod, '0123456789', ''), '')
+ IF LEN(m.lcCifre) = 13 AND cValidCNP(m.lcCifre)
+ lnCnpPierdut = m.lnCnpPierdut + 1
+ HarnessLog('EROARE invariant: CNP valid pierdut: ' + ALLTRIM(cUnic.denumire) + ' [' + m.lcCod + ']')
+ ENDIF
+ ENDIF
+ ENDIF
+
+ lcRaport = m.lcRaport + ALLTRIM(cUnic.denumire) + ';' + m.lcCod + ';' + ALLTRIM(cUnic.reg_com) + ';' + ;
+ IIF(m.lnTip = 1, 'PJ', 'PF') + ';' + m.lcRez + CHR(13) + CHR(10)
+ENDSCAN
+
+STRTOFILE(m.lcRaport, m.lcTestDir + 'raport_cod_fiscal_real.csv', .F.)
+
+HarnessLog('persoane fizice : ' + TRANSFORM(m.lnPF))
+HarnessLog('persoane juridice : ' + TRANSFORM(m.lnPJ))
+HarnessLog('PF cu CNP sters : ' + TRANSFORM(m.lnSters))
+HarnessLog('PJ cu cod nevalidat RO : ' + TRANSFORM(m.lnPJNevalidat) + ' (raman cu cod, atentioneaza declaratia)')
+
+TestAssertEqual(0, m.lnPJFaraCod, 'nicio persoana juridica nu ramane fara cod')
+TestAssertEqual(0, m.lnCnpPierdut, 'niciun CNP valid nu se pierde')
+TestAssert(m.lnPF + m.lnPJ > 1000, 's-au analizat toti partenerii din exemple')
+TestAssert(m.lnSters > 0, 'exista CNP-uri invalide care se curata')
+TestAssert(m.lnPJ > 0, 'exista si persoane juridice detectate')
+
+DO HarnessStep WITH 0, 'analiza pe date reale'
+
+HarnessLog('REZULTAT: ' + TestRezultat())
+DO HarnessDone WITH TestRezultat()
+
+QUIT
diff --git a/docs/depanare_testare_flora2roa.md b/docs/depanare_testare_flora2roa.md
index 529f004..ec970a4 100644
--- a/docs/depanare_testare_flora2roa.md
+++ b/docs/depanare_testare_flora2roa.md
@@ -31,6 +31,11 @@ Specific FLORA2ROA:
`settings.ini`, `Trace` scrie in `edtInfo`.
- `test_export_date.prg` - functional: `ExportDate()` citeste XML-urile din interval si
populeaza cursoarele (fara Oracle).
+- `test_cod_fiscal.prg` - unitar: clasificarea partenerilor (persoana fizica / juridica) si
+ curatarea CNP-urilor invalide (`GetCodFiscalValid` si functiile ajutatoare din `myutils.prg`).
+- `test_cod_fiscal_real.prg` - ruleaza aceeasi logica pe TOATE codurile din `EXEMPLE\hotel`
+ si verifica invariantii: nicio persoana juridica nu ramane fara cod, niciun CNP valid nu se
+ pierde. Scrie `Teste\raport_cod_fiscal_real.csv` cu decizia luata pentru fiecare partener.
- `test_conectare_roa.prg` - verifica drumul CU conectare la Oracle: fie se conecteaza si
executa o interogare de proba, fie raporteaza esecul si iese controlat. NU e in suita
implicita (depinde de reteaua/serverul Oracle).
@@ -66,8 +71,14 @@ powershell -ExecutionPolicy Bypass -File Teste\text_din_bin.ps1 # binar
# [IO.File]::WriteAllText($p, $t, [Text.Encoding]::GetEncoding(1252))
powershell -ExecutionPolicy Bypass -File Teste\bin_din_text.ps1 -TextFile _textcache\frm_import.sc2 -DryRun
powershell -ExecutionPolicy Bypass -File Teste\bin_din_text.ps1 -TextFile _textcache\frm_import.sc2
+powershell -ExecutionPolicy Bypass -File Teste\recompileaza_binare.ps1 # OBLIGATORIU dupa write-back
```
+> **Write-back-ul NU compileaza.** FoxBin2Prg reface `.scx`/`.sct` din text, dar fara codul
+> obiect, deci formularul ruleaza fara `#INCLUDE "comun.h"`: constantele (`CT_SUCCES`,
+> `CT_INSUCCES`) devin *"Variable not found"* abia in RUNTIME. Verificarea de sintaxa trece,
+> testul pica. `Teste\recompileaza_binare.ps1` face `COMPILE FORM` si repara asta.
+
Write-back-ul face fidelity check (bin -> text, comparat pe octeti) inainte de a atinge
binarul si refuza daca `frm_import.scx` e blocat de IDE sau de `flora2roa.exe`.
diff --git a/frm_import.sc2 b/frm_import.sc2
index a8d3998..043ffd9 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, lcCodValid, lnTip_persoana, lcCodTara
+ Local lcSql, lcSqlInsert, lcSufix, lcTip_persoana, lcinactiv, lnSucces, 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
@@ -400,24 +400,26 @@ DEFINE CLASS form1 AS form
lcReg_comert = Nvl(Alltrim(Upper(reg_com)), "")
***************************
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 (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.
+ lcTip_persoana = "2" && 1 = juridica, 2 = fizica
+ * GetCodFiscalValid decide tipul de persoana din denumire, nr. registrul comertului si
+ * forma codului, apoi curata DOAR CNP-urile invalide ale persoanelor fizice.
+ * Codul unei persoane juridice nu se sterge niciodata: daca e gresit, vrem sa fim
+ * atentionati la validarea declaratiei si sa il corectam in Fidelio, nu sa il ascundem.
If !Empty(m.lcCod)
lnTip_persoana = 0
lcCodTara = Alltrim(Upper(Nvl(cod_tara, "")))
- lcCodValid = GetCodFiscalValid(m.lcCod, @lnTip_persoana, m.lcCodTara)
+ lcCod_fiscal = GetCodFiscalValid(m.lcCod, @lnTip_persoana, m.lcDenumire, m.lcReg_comert, m.lcCodTara)
+ lcTip_persoana = Iif(m.lnTip_persoana = 1, "1", "2")
+
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"
+ Case m.lcTip_persoana = "2" And Empty(m.lcCod_fiscal)
+ * persoana fizica cu CNP invalid: se salveaza fara CNP (permis la D394/D406)
+ Thisform.trace('CNP invalid, partenerul se salveaza fara CNP: [' + m.lcCod + '] ' + Alltrim(m.lcDenumire))
+ Case m.lcTip_persoana = "1" And !VerifCF(m.lcCod_fiscal)
+ * persoana juridica cu cod fiscal care nu trece validarea romaneasca: poate fi
+ * partener strain (cod valid la el in tara) sau o eroare de operare. Codul ramane,
+ * dar il semnalam in log ca sa poata fi verificat.
+ Thisform.trace('ATENTIE: persoana juridica cu cod fiscal nevalidat: [' + m.lcCod_fiscal + '] ' + Alltrim(m.lcDenumire))
Endcase
ENDIF
IF m.lcTip_Persoana = "2"
diff --git a/myutils.prg b/myutils.prg
index 8d9936d..14be332 100644
--- a/myutils.prg
+++ b/myutils.prg
@@ -1724,88 +1724,249 @@ FUNCTION GetCodFiscalFRO
RETURN m.lcCodFiscalFRO
ENDFUNC
*
-* 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.
+* Clasifica partenerul importat (Fidelio hotel, BIZ restaurant) in persoana fizica / juridica
+* si curata DOAR CNP-urile invalide.
*
-* 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.
+* De ce asa:
+* - la declaratii (D394/D406) o PERSOANA FIZICA poate fi raportata FARA CNP, deci un CNP
+* invalid se poate goli fara sa pierdem nimic si fara sa dea eroare de validare;
+* - o PERSOANA JURIDICA trebuie sa aiba cod fiscal, deci codul ei NU se sterge niciodata:
+* daca e gresit, ramane asa ca sa fie semnalat la validarea declaratiei si sa fie corectat
+* la sursa (in Fidelio), nu ascuns aici.
*
-* 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.
+* De ce NU ne putem baza pe validarea romaneasca ca sa decidem tipul:
+* codurile straine pot fi si numai cifre (fara prefix de tara), deci un cod numeric care nu
+* trece algoritmul romanesc NU inseamna automat "gresit". Tipul se decide din alte semnale,
+* in ordinea increderii:
+* 1. tcTipCunoscut - tip venit din import, daca sistemul sursa il stie (cel mai sigur)
+* 2. prefix de tara pe cod (RO/BE/DE...) sau litere in cod => persoana juridica
+* 3. denumire cu forma juridica (SRL, SA, PFA, ASOCIATIA...) => persoana juridica
+* 4. numar de registrul comertului in tcRegCom (J40/1234/2018, L2019...) => juridica
+* serie+numar de CI (2 litere + 6 cifre) => fizica
+* 5. CNP valid pe 13 cifre => fizica; CUI romanesc valid => juridica
+* 6. altfel => fizica (majoritatea covarsitoare a clientilor de hotel)
*
* 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.
+* tcCod - codul brut din import (CODFISCAL_CNP)
+* tnTipPersoana - IESIRE prin referinta: 1 = juridica, 2 = fizica
+* tcDenumire - optional, denumirea partenerului (pentru forma juridica)
+* tcRegCom - optional, REGCOMERT_CI_PASS (nr. registrul comertului sau serie CI)
+* tcCodTara - optional, codul/denumirea tarii daca importul o stie
+* tcTipCunoscut - optional, tipul deja stiut din import: '1'/'PJ' sau '2'/'PF'
+*
+* Intoarce: codul de pastrat in ROA. Sir GOL doar cand este persoana fizica cu CNP invalid.
FUNCTION GetCodFiscalValid
- LPARAMETERS tcCod, tnTipPersoana, tcCodTara
+ LPARAMETERS tcCod, tnTipPersoana, tcDenumire, tcRegCom, tcCodTara, tcTipCunoscut
- LOCAL lcCod, lcCifre, lcPrefix, lcTara, llRO, llRomanesc, lnParam
+ LOCAL lcCod, lcBrut, lcCifre, lcCifreReg, lcPrefix, lcPrefixBrut, lcTara, lcTip
+ LOCAL llLitere, llPrefixFiscal, 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...)
+ lcCod = GetCodFiscalCurat(m.tcCod)
+ lcCifre = CHRTRAN(m.lcCod, CHRTRAN(m.lcCod, '0123456789', ''), '')
lcPrefix = ALLTRIM(CHRTRAN(m.lcCod, CHRTRAN(m.lcCod, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', ''), ''))
- llRO = INLIST(m.lcPrefix, 'RO', 'CUI', 'CF', 'CIF')
+ llLitere = !EMPTY(m.lcPrefix)
- * 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')
+ * Eticheta din codul BRUT ('CUI 14770212') e semnal de persoana juridica, chiar daca
+ * GetCodFiscalCurat o scoate din codul salvat. 'RO' se pastreaza in cod pentru ca inseamna
+ * platitor de TVA, iar in ROA acela e alt partener decat neplatitorul cu acelasi numar.
+ lcBrut = ALLTRIM(UPPER(TRANSFORM(NVL(m.tcCod, ''))))
+ lcPrefixBrut = ALLTRIM(CHRTRAN(m.lcBrut, CHRTRAN(m.lcBrut, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', ''), ''))
+ llPrefixFiscal = INLIST(m.lcPrefixBrut, 'RO', 'CUI', 'CF', 'CIF')
- 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
+ lcTara = IIF(m.lnParam > 4 AND VARTYPE(m.tcCodTara) = 'C', ALLTRIM(UPPER(m.tcCodTara)), '')
+ lcTip = IIF(m.lnParam > 5 AND VARTYPE(m.tcTipCunoscut) = 'C', ALLTRIM(UPPER(m.tcTipCunoscut)), '')
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 ''
+ CASE INLIST(m.lcTip, '1', 'PJ', 'J')
+ tnTipPersoana = 1
+ CASE INLIST(m.lcTip, '2', 'PF', 'F')
+ tnTipPersoana = 2
+ CASE m.llPrefixFiscal
+ * eticheta romaneasca de cod fiscal (RO/CUI/CF/CIF) => persoana juridica
+ tnTipPersoana = 1
+ CASE !EMPTY(m.lcTara) AND !INLIST(m.lcTara, 'RO', 'ROU', 'ROMANIA')
+ * tara straina declarata: nu putem valida romaneste, tratam ca juridica
+ tnTipPersoana = 1
+ CASE EstePersoanaJuridica(m.tcDenumire)
+ tnTipPersoana = 1
+ CASE EsteNrRegComert(m.tcRegCom)
+ tnTipPersoana = 1
+ CASE LEN(m.lcCifre) = 13 AND cValidCNP(m.lcCifre)
+ tnTipPersoana = 2
+ CASE EsteSerieCI(m.tcRegCom)
+ tnTipPersoana = 2
+ CASE m.llLitere
+ * cod cu litere fara alt semnal: TVA intracomunitar (BE0325777171) daca incepe cu
+ * un cod de tara UE, altfel e cel mai probabil pasaport de persoana fizica
+ tnTipPersoana = IIF(EsteCodTVAStrain(m.lcCod), 1, 2)
+ CASE BETWEEN(LEN(m.lcCifre), 2, 10) AND VerifCF(m.lcCifre)
+ tnTipPersoana = 1
OTHERWISE
- RETURN ''
+ * clientii de hotel sunt in marea lor majoritate persoane fizice
+ tnTipPersoana = 2
ENDCASE
+
+ IF m.tnTipPersoana = 1
+ * persoana juridica: codul NU se sterge niciodata, chiar daca nu trece validarea.
+ * Vrem sa fim atentionati la declaratie, nu sa ascundem problema.
+ RETURN m.lcCod
+ ENDIF
+
+ * persoana fizica: pastram CNP-ul doar daca este valid, altfel gol (permis la declaratii).
+ * Pasapoartele si seriile de CI nu sunt CNP-uri, deci nu se salveaza pe cod fiscal.
+ IF LEN(m.lcCifre) = 13 AND cValidCNP(m.lcCifre)
+ RETURN m.lcCifre
+ ENDIF
+
+ * recuperare: in Fidelio se intampla ca operatorul sa inverseze campurile si sa scrie CNP-ul
+ * in 'serie CI / pasaport'. Daca acolo gasim un CNP valid, il folosim pe acela.
+ lcCifreReg = GetCodFiscalCurat(m.tcRegCom)
+ lcCifreReg = CHRTRAN(m.lcCifreReg, CHRTRAN(m.lcCifreReg, '0123456789', ''), '')
+ IF LEN(m.lcCifreReg) = 13 AND cValidCNP(m.lcCifreReg)
+ RETURN m.lcCifreReg
+ ENDIF
+
+ RETURN ''
+ENDFUNC
+*
+* .T. daca sirul arata a cod de TVA intracomunitar: 2 litere = cod de tara UE, urmate de cifre.
+* Ex: 'BE0325777171', 'DE811234567', 'HU12345678'. NU include 'RO' (tratat separat).
+FUNCTION EsteCodTVAStrain
+ LPARAMETERS tcCod
+
+ LOCAL lcCod, lcTari, lcTara, lcRest
+ lcCod = ALLTRIM(UPPER(TRANSFORM(NVL(m.tcCod, ''))))
+ lcCod = CHRTRAN(m.lcCod, ' .-', '')
+ IF LEN(m.lcCod) < 6
+ RETURN .F.
+ ENDIF
+
+ * coduri de tara UE + cateva uzuale in turism
+ lcTari = ' AT BE BG CY CZ DE DK EE EL ES FI FR GR HR HU IE IT LT LU LV MT NL PL PT SE SI SK ' + ;
+ ' GB CH NO MD UA RS TR US '
+
+ lcTara = LEFT(m.lcCod, 2)
+ IF !(' ' + m.lcTara + ' ' $ m.lcTari)
+ RETURN .F.
+ ENDIF
+
+ * dupa codul de tara trebuie sa urmeze numai cifre
+ lcRest = SUBSTR(m.lcCod, 3)
+ RETURN LEN(CHRTRAN(m.lcRest, CHRTRAN(m.lcRest, '0123456789', ''), '')) = LEN(m.lcRest)
+ENDFUNC
+*
+* Curata un cod fiscal / CNP de spatii, puncte si separatori redundanti, pastrand literele
+* semnificative.
+* 'RO 23565004' -> 'RO23565004'
+* 'CUI 14770212' -> '14770212'
+* '2920121295914.' -> '2920121295914'
+*
+* ATENTIE: prefixul 'RO' NU se adauga niciodata de la noi. In ROA, 'RO12345678' (platitor de
+* TVA) si '12345678' (neplatitor) sunt DOI parteneri diferiti, deci prefixul se pastreaza
+* exact cum a venit din import. Etichetele 'CUI'/'CF'/'CIF' sunt doar text descriptiv, nu
+* atribut fiscal, deci se sterg fara sa fie inlocuite cu 'RO'.
+FUNCTION GetCodFiscalCurat
+ LPARAMETERS tcCod
+
+ LOCAL lcCod, lcCifre, lcPrefix
+ lcCod = ALLTRIM(UPPER(TRANSFORM(NVL(m.tcCod, ''))))
+ lcCod = CHRTRAN(m.lcCod, CHR(9) + CHR(13) + CHR(10), ' ')
+ * scot spatiile si punctele; liniuta si slash-ul pot face parte din coduri straine
+ lcCod = ALLTRIM(CHRTRAN(m.lcCod, ' .', ''))
+
+ * 'CUI 14770212' -> '14770212': eticheta nu spune nimic despre calitatea de platitor de TVA
+ lcPrefix = ALLTRIM(CHRTRAN(m.lcCod, CHRTRAN(m.lcCod, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', ''), ''))
+ IF INLIST(m.lcPrefix, 'CUI', 'CF', 'CIF')
+ lcCifre = CHRTRAN(m.lcCod, CHRTRAN(m.lcCod, '0123456789', ''), '')
+ IF !EMPTY(m.lcCifre)
+ lcCod = m.lcCifre
+ ENDIF
+ ENDIF
+
+ RETURN m.lcCod
+ENDFUNC
+*
+* .T. daca denumirea contine o forma juridica (SRL, SA, PFA, ASOCIATIA, LTD, GMBH...).
+* Compara pe cuvinte intregi, ca sa nu ia 'SA' din 'SAVU' sau 'II' din 'MIHAII'.
+FUNCTION EstePersoanaJuridica
+ LPARAMETERS tcDenumire
+
+ LOCAL lcDen, lcForme, lcCuv, lnI, lnCuvinte
+ lcDen = ALLTRIM(UPPER(TRANSFORM(NVL(m.tcDenumire, ''))))
+ IF EMPTY(m.lcDen)
+ RETURN .F.
+ ENDIF
+
+ * separatorii devin spatii, ca sa pot compara cuvant cu cuvant
+ lcDen = CHRTRAN(m.lcDen, '.,-/()"' + CHR(9), ' ')
+
+ lcForme = ' SRL SA SC SNC SCS SCA PFA II IF SRLD RA RL ' + ;
+ ' ASOCIATIA ASOCIATIE FUNDATIA FUNDATIE SOCIETATEA COOPERATIVA CABINET ' + ;
+ ' PRIMARIA MINISTERUL AGENTIA INSTITUTUL SPITALUL SCOALA LICEUL COLEGIUL ' + ;
+ ' UNIVERSITATEA UNIVERSITE UNIVERSITY UNIVERSITAT INSPECTORATUL DIRECTIA ' + ;
+ ' LTD LIMITED GMBH AG BV NV INC LLC PLC CORP CORPORATION COMPANY HOLDING KFT SPZOO OOD '
+
+ lnCuvinte = GETWORDCOUNT(m.lcDen, ' ')
+ FOR lnI = 1 TO m.lnCuvinte
+ lcCuv = ALLTRIM(GETWORDNUM(m.lcDen, m.lnI, ' '))
+ IF !EMPTY(m.lcCuv) AND (' ' + m.lcCuv + ' ') $ m.lcForme
+ RETURN .T.
+ ENDIF
+ ENDFOR
+
+ RETURN .F.
+ENDFUNC
+*
+* .T. daca sirul arata a numar de inregistrare la Registrul Comertului.
+* Forme acceptate: 'J40/1234/2018', 'F13/45/2005', 'C23/9/2019', 'L2019000159348', '54/1997'
+FUNCTION EsteNrRegComert
+ LPARAMETERS tcRegCom
+
+ LOCAL lcReg, lcRest
+ lcReg = ALLTRIM(UPPER(TRANSFORM(NVL(m.tcRegCom, ''))))
+ lcReg = CHRTRAN(m.lcReg, ' ', '')
+ IF EMPTY(m.lcReg)
+ RETURN .F.
+ ENDIF
+
+ * J40/1234/2018 - litera + cifre + doua slash-uri
+ IF INLIST(LEFT(m.lcReg, 1), 'J', 'F', 'C') AND OCCURS('/', m.lcReg) = 2 AND ;
+ ISDIGIT(SUBSTR(m.lcReg, 2, 1))
+ RETURN .T.
+ ENDIF
+
+ * L2019000159348 / J2018000211289 - litera urmata de minim 10 cifre
+ IF INLIST(LEFT(m.lcReg, 1), 'J', 'L') AND LEN(m.lcReg) >= 11
+ lcRest = SUBSTR(m.lcReg, 2)
+ IF ISDIGIT(m.lcRest) AND LEN(CHRTRAN(m.lcRest, CHRTRAN(m.lcRest, '0123456789', ''), '')) = LEN(m.lcRest)
+ RETURN .T.
+ ENDIF
+ ENDIF
+
+ * 54/1997 - numar/an, tipic institutiilor publice
+ IF OCCURS('/', m.lcReg) = 1 AND LEN(m.lcReg) <= 8 AND ;
+ ISDIGIT(m.lcReg) AND LEN(GETWORDNUM(m.lcReg, 2, '/')) = 4
+ RETURN .T.
+ ENDIF
+
+ RETURN .F.
+ENDFUNC
+*
+* .T. daca sirul arata a serie + numar de carte de identitate: 2 litere urmate de 6 cifre.
+* Ex: 'ZV 093375', 'IF834409', 'RK 340434'
+FUNCTION EsteSerieCI
+ LPARAMETERS tcRegCom
+
+ LOCAL lcReg
+ lcReg = ALLTRIM(UPPER(TRANSFORM(NVL(m.tcRegCom, ''))))
+ lcReg = CHRTRAN(m.lcReg, ' -.', '')
+ IF LEN(m.lcReg) <> 8
+ RETURN .F.
+ ENDIF
+
+ RETURN ISALPHA(LEFT(m.lcReg, 1)) AND ISALPHA(SUBSTR(m.lcReg, 2, 1)) AND ;
+ ISDIGIT(SUBSTR(m.lcReg, 3, 1)) AND ;
+ LEN(CHRTRAN(SUBSTR(m.lcReg, 3), CHRTRAN(SUBSTR(m.lcReg, 3), '0123456789', ''), '')) = 6
ENDFUNC
*