Import parteneri: se curata doar CNP-urile invalide, codul persoanelor juridice ramane neatins

This commit is contained in:
2026-08-02 10:29:34 +03:00
parent 349130c369
commit da19090ed9
10 changed files with 754 additions and 139 deletions

View File

@@ -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
*