Files
comun/programe/recunoastere_articol_ef.prg
Marius Mutu 940bb39701 Import eFactura in lot: borderou cu ce lipseste, coada de contabilizare, alegerea contului
Borderoul tine per factura ce mai are de completat si daca are articole de gestiune
(camp gest, coloana si filtru), coada contabilizeaza in serie facturile bifate si
incheie cu un rezumat, iar contul de furnizor/client se alege din planul de conturi.
Cheia normalizata de articol, rezolvarea automata a partenerului si anularea in bloc
intra tot aici. Pozitia in lista se pastreaza peste reaplicarea filtrului.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Aroafxp4z8bmM5oVECZRXY
2026-09-09 22:06:48 +03:00

656 lines
33 KiB
Plaintext

*!* 08.09.2026
*!* marius.mutu
*!* RecunoastereArticolEF - cascada de potrivire cont/acont/id_articol/id_gestiune pentru liniile de
*!* eFactura, calculata intr-o singura interogare per factura (PotrivesteFactura): catalogul ROA
*!* (denumire/codbare/codmat/codmatf/cod partener), apoi text exact, apoi cheie normalizata in
*!* istoricul propriu al furnizorului, apoi articolul contat oriunde in firma pe aceeasi cheie (ultima
*!* factura), apoi contul sintetic dominant al furnizorului din ACT, apoi contul majoritar al
*!* celorlalte linii ale facturii; id_articol/id_gestiune vin doar din catalog/istoric propriu/articol
*!* firma-wide, niciodata din furnizor/factura
Define Class RecunoastereArticolEF As Custom
nNormalizat = 1 && EFACTURA_ART_NORMALIZAT: 0 = doar text exact
nPrefix = 12 && lungimea cheii normalizate comparate, fixa - indexul functional de pe
&& ANAF_EFACTURA_DETALII e creat pe exact aceasta lungime, nu mai e configurabila din optiuni
nIstoric = 1 && EFACTURA_ART_ISTORIC: 0 = fara treapta pe cont sintetic din istoricul furnizorului
oGol = Null && ultimul obiect gol produs de cascada, pentru apelantii care asteapta obiect, nu NULL
Procedure Init
This.IncarcaOptiuni()
Endproc && Init
Procedure IncarcaOptiuni
Local lcVal
This.nNormalizat = 1
This.nIstoric = 1
Try
lcVal = citeste_optiune('EFACTURA_ART_NORMALIZAT')
If Type('lcVal') = 'C' And !Empty(Alltrim(m.lcVal))
This.nNormalizat = Int(Val(m.lcVal))
Endif
lcVal = citeste_optiune('EFACTURA_ART_ISTORIC')
If Type('lcVal') = 'C' And !Empty(Alltrim(m.lcVal))
This.nIstoric = Int(Val(m.lcVal))
Endif
Catch
Endtry
Endproc && IncarcaOptiuni
*!* Fragmentul SQL al cheii normalizate (cifre si separatori scosi, trunchiata la nPrefix caractere),
*!* aplicat identic pe coloana din tabela si pe valoarea cautata - functia Oracle FN_EFACT_ARTNORM
*!* si nPrefix sunt cele de pe indexul functional IDX_EFACT_DET_ARTNORM; REGEXP_REPLACE nu poate fi
*!* indexat direct (ORA-01743), de-aia expresia sta intr-o functie deterministica in Oracle
Procedure CheieSql
Lparameters tcColoana
Return "FN_EFACT_ARTNORM(" + m.tcColoana + "," + Transform(This.nPrefix) + ")"
Endproc && CheieSql
*!* Fragmentul SQL al ferestrei de istoric (ultimele 24 de luni)
Procedure FereastraIstoricSql
Lparameters tcColoanaData
Return m.tcColoanaData + " >= ADD_MONTHS(TRUNC(SYSDATE,'MM'), -24)"
Endproc && FereastraIstoricSql
*!* Cascada pentru o singura denumire de articol, in istoricul aceluiasi furnizor/client din
*!* ultimele 24 de luni (exclude factura curenta). tcSursa (referinta): 'EXACT'/'APROX' la gasire,
*!* '' altfel. Intoarce obiectul gasit (campurile ANAF_VEFACTURA_DETALII) sau NULL cand nu s-a gasit nimic.
Procedure PotrivesteArticol
Lparameters tcCodFiscal, tlPrimite, tcDenumire, tnIdEfactura, tcSursa
Local lcSql, llSucces, loArticol, lcColoanaCodFiscal, lcCheieCol, lcCheieVal, lcIst, lcFereastra
Private pcDenumire, pcCodFiscal, pnIdEfactura
This.oGol = Null
tcSursa = ''
loArticol = Null
pcDenumire = Upper(Alltrim(Nvl(m.tcDenumire, '')))
pcCodFiscal = Upper(Alltrim(Nvl(m.tcCodFiscal, '')))
pnIdEfactura = Iif(!Empty(Nvl(m.tnIdEfactura, 0)), m.tnIdEfactura, 0)
lcColoanaCodFiscal = Iif(m.tlPrimite, "e.cod_fiscal_emitent", "e.cod_fiscal_beneficiar")
lcCheieCol = This.CheieSql('articol')
lcCheieVal = This.CheieSql('?pcDenumire')
lcFereastra = This.FereastraIstoricSql('e.xdata_act')
Text To lcIst Textmerge Noshow
WITH ist AS (SELECT /*+ MATERIALIZE */ d.*, TRIM(UPPER(d.articol)) a_ex, <<m.lcCheieCol>> a_no
FROM ANAF_VEFACTURA_DETALII d LEFT JOIN ANAF_EFACTURA e ON (e.id = d.id_efactura)
WHERE <<m.lcColoanaCodFiscal>> = ?pcCodFiscal AND e.id <> ?pnIdEfactura AND <<m.lcFereastra>>)
Endtext
Text To lcSql Textmerge Noshow
<<m.lcIst>>
SELECT * FROM (SELECT * FROM ist WHERE a_ex = ?pcDenumire AND id_articol IS NOT NULL ORDER BY id DESC) WHERE ROWNUM = 1
Endtext
llSucces = goExecutor.oSelecteaza2Object(m.lcSql, @loArticol)
If !m.llSucces
Return Null
Endif
If !Empty(Nvl(loArticol.id_articol, 0))
tcSursa = 'EXACT'
Return loArticol
Endif
Text To lcSql Textmerge Noshow
<<m.lcIst>>
SELECT * FROM (SELECT * FROM ist WHERE a_ex = ?pcDenumire ORDER BY id DESC) WHERE ROWNUM = 1
Endtext
llSucces = goExecutor.oSelecteaza2Object(m.lcSql, @loArticol)
If !m.llSucces
Return Null
Endif
If !Empty(Nvl(loArticol.cont, ''))
tcSursa = 'EXACT'
Return loArticol
Endif
* pasii 3-4 (cheie normalizata) au comutatorul lor; treapta 5 de mai jos are alt comutator
If This.nNormalizat # 0
Text To lcSql Textmerge Noshow
<<m.lcIst>>
SELECT * FROM (SELECT * FROM ist WHERE LENGTH(<<m.lcCheieVal>>) >= 3
AND a_no = <<m.lcCheieVal>> AND id_articol IS NOT NULL ORDER BY id DESC) WHERE ROWNUM = 1
Endtext
llSucces = goExecutor.oSelecteaza2Object(m.lcSql, @loArticol)
If !m.llSucces
Return Null
Endif
If !Empty(Nvl(loArticol.id_articol, 0))
tcSursa = 'APROX'
Return loArticol
Endif
Text To lcSql Textmerge Noshow
<<m.lcIst>>
SELECT * FROM (SELECT * FROM ist WHERE LENGTH(<<m.lcCheieVal>>) >= 3
AND a_no = <<m.lcCheieVal>> ORDER BY id DESC) WHERE ROWNUM = 1
Endtext
llSucces = goExecutor.oSelecteaza2Object(m.lcSql, @loArticol)
If !m.llSucces
Return Null
Endif
If !Empty(Nvl(loArticol.cont, ''))
tcSursa = 'APROX'
Return loArticol
Endif
Endif
If This.nIstoric # 0
Local loIstoric
Text To lcSql Textmerge Noshow
<<m.lcIst>>
SELECT cont FROM (SELECT cont FROM (SELECT TRIM(cont) AS cont FROM ist
WHERE cont IS NOT NULL AND TRIM(cont) IS NOT NULL ORDER BY id DESC) WHERE ROWNUM <= 5)
GROUP BY cont HAVING COUNT(*) = 5
Endtext
llSucces = goExecutor.oSelecteaza2Object(m.lcSql, @loIstoric)
If !m.llSucces
Return Null
Endif
If !Empty(Nvl(loIstoric.cont, ''))
loArticol.cont = Alltrim(loIstoric.cont)
tcSursa = 'ISTORIC'
Return loArticol
Endif
Endif
This.oGol = m.loArticol
Return Null
Endproc && PotrivesteArticol
*!* Cascada completa (catalog ROA, istoric propriu, articol firma-wide, cont dominant al
*!* furnizorului, cont majoritar al facturii) pentru toate liniile facturii tnIdEfactura, intr-o
*!* singura interogare Oracle. Lasa deschis cursorul CRSPOTRIVIRELINII (NR, ID_DETALIU, CONT,
*!* ACONT, ID_ARTICOL, ARTICOL_ROA, CODMAT_ROA, IN_STOC, ID_GESTIUNE, SURSA_CONT, IN_STOC_SUGERAT),
*!* pozitionat pe primul rand, sau fara randuri daca factura nu are linii. tnIdPart (optional):
*!* id_part ROA al furnizorului/clientului, pentru cautarea in codurile lui de articol
*!* (parteneri_articole_coduri) - fara el, acea sursa a catalogului nu propune nimic.
*!* Intoarce .T./.F. (goExecutor.cEroare)
Procedure PotrivesteFactura
Lparameters tnIdEfactura, tcCodFiscal, tlPrimite, tnIdPart
Local lcSql, lnSucces, lcColoanaCodFiscal, lcCheieColD, llNormalizat, lcX5, lcIst, lcFereastra
Local lcCat, lcS2, lcS1, lcEmisa, lcCodMatCol, lcCodMatFCol, lcCodMatPCol, lcCheieColDl, lcCheieColDFw
Local lcColPartener, lcColCredit, lcCod401, lcColDebit
Private pcCodFiscal, pnIdEfactura, pnIdPart
pcCodFiscal = Upper(Alltrim(Nvl(m.tcCodFiscal, '')))
pnIdEfactura = Iif(!Empty(Nvl(m.tnIdEfactura, 0)), m.tnIdEfactura, 0)
pnIdPart = Nvl(m.tnIdPart, 0)
lcColoanaCodFiscal = Iif(m.tlPrimite, "e.cod_fiscal_emitent", "e.cod_fiscal_beneficiar")
llNormalizat = (This.nNormalizat # 0)
lcCheieColD = This.CheieSql('articol')
*!* a_no pe linia curenta - NULL cand comutatorul e oprit, ca LENGTH(dl.a_no)>=3 sa dezactiveze
*!* singur x3/x4/fw (aceeasi garda folosita deja pentru cheia scurta)
lcCheieColDl = Iif(m.llNormalizat, m.lcCheieColD, "NULL")
*!* varianta calificata cu alias "d." - fw0 are si dl si d in FROM, ambele cu coloana articol
lcCheieColDFw = This.CheieSql('d.articol')
lcFereastra = This.FereastraIstoricSql('e.xdata_act')
lcEmisa = Iif(m.tlPrimite, '0', '1')
*!* codclient/codfurnizor pe linie, dupa acelasi tipar ca GetArticolByCodDenumire
*!* (oproceduri_comune.prg): primite = codClient pe codmat, codFurnizor pe codmatf;
*!* emise = codFurnizor pe codmat, codClient pe codul de partener
lcCodMatCol = Iif(m.tlPrimite, "dl.codclient", "dl.codfurnizor")
lcCodMatFCol = Iif(m.tlPrimite, "dl.codfurnizor", "CAST(NULL AS VARCHAR2(100))")
lcCodMatPCol = Iif(m.tlPrimite, "CAST(NULL AS VARCHAR2(100))", "dl.codclient")
*!* ACT: contul de furnizor/client (401/4111) identifica documentul, celalalt cont se agrega
lcColPartener = Iif(m.tlPrimite, "a.id_partc", "a.id_partd")
lcColCredit = Iif(m.tlPrimite, "a.scc", "a.scd")
lcCod401 = Iif(m.tlPrimite, "'401'", "'4111'")
lcColDebit = Iif(m.tlPrimite, "a.scd", "a.scc")
Text To lcIst Textmerge Noshow
ist AS (SELECT /*+ MATERIALIZE */ d.*, TRIM(UPPER(d.articol)) a_ex, <<m.lcCheieColD>> a_no
FROM ANAF_VEFACTURA_DETALII d JOIN ANAF_EFACTURA e ON (e.id = d.id_efactura)
WHERE <<m.lcColoanaCodFiscal>> = ?pcCodFiscal AND e.id <> ?pnIdEfactura AND <<m.lcFereastra>>),
ist2 AS (SELECT /*+ MATERIALIZE */ ist.*,
ROW_NUMBER() OVER (PARTITION BY a_ex ORDER BY id DESC) rn_ex,
ROW_NUMBER() OVER (PARTITION BY CASE WHEN id_articol IS NOT NULL THEN a_ex END ORDER BY id DESC) rn_ex_art,
ROW_NUMBER() OVER (PARTITION BY a_no ORDER BY id DESC) rn_no,
ROW_NUMBER() OVER (PARTITION BY CASE WHEN id_articol IS NOT NULL THEN a_no END ORDER BY id DESC) rn_no_art
FROM ist),
Endtext
If This.nIstoric # 0
Text To lcX5 Textmerge Noshow
x5c AS (SELECT cont FROM (SELECT cont FROM (SELECT TRIM(cont) cont FROM ist
WHERE cont IS NOT NULL AND TRIM(cont) IS NOT NULL
ORDER BY id DESC) WHERE ROWNUM <= 5) GROUP BY cont HAVING COUNT(*) = 5),
Endtext
Else
lcX5 = "x5c AS (SELECT NULL cont FROM DUAL WHERE 1 = 0),"
Endif
*!* Catalogul ROA (azi GetArticolByCodDenumire, o interogare per linie): cinci candidati
*!* egalitate, in ordinea denumire/codbare/codmat/codmatf/cod partener, alegere determinista
*!* (id_articol maxim) - inlocuieste OR-ul cu ROWNUM=1 arbitrar de azi. NOM_ARTICOLE direct
*!* (nu vnom_articole), catalogul intreg fiind ieftin de scanat si fara nevoie de coloanele lui
*!* in plus (grupa, tara origine, timp productie).
Text To lcCat Textmerge Noshow
catk AS (SELECT id_articol, denumire, codbare, codmat, codmatf, cont, acont, in_stoc,
ROW_NUMBER() OVER (PARTITION BY UPPER(denumire) ORDER BY id_articol DESC) rn_den,
ROW_NUMBER() OVER (PARTITION BY UPPER(codbare) ORDER BY id_articol DESC) rn_bare,
ROW_NUMBER() OVER (PARTITION BY UPPER(codmat) ORDER BY id_articol DESC) rn_mat,
ROW_NUMBER() OVER (PARTITION BY UPPER(codmatf) ORDER BY id_articol DESC) rn_matf
FROM NOM_ARTICOLE WHERE sters = 0 AND inactiv = 0),
catp AS (SELECT c.cod, a.id_articol, a.denumire, a.codmat, a.cont, a.acont, a.in_stoc,
ROW_NUMBER() OVER (PARTITION BY UPPER(c.cod) ORDER BY a.id_articol DESC) rn_p
FROM PARTENERI_ARTICOLE_CODURI c JOIN NOM_ARTICOLE a ON (a.id_part = c.id_part AND a.sters = 0 AND a.inactiv = 0)
WHERE c.id_part = ?pnIdPart AND c.sters = 0),
cat AS (SELECT dl.nr,
COALESCE(a1.id_articol, a2.id_articol, a3.id_articol, a4.id_articol, a5.id_articol) id_articol,
COALESCE(a1.denumire, a2.denumire, a3.denumire, a4.denumire, a5.denumire) articol_roa,
COALESCE(a1.codmat, a2.codmat, a3.codmat, a4.codmat, a5.codmat) codmat_roa,
COALESCE(a1.cont, a2.cont, a3.cont, a4.cont, a5.cont) cont,
COALESCE(a1.acont, a2.acont, a3.acont, a4.acont, a5.acont) acont,
COALESCE(a1.in_stoc, a2.in_stoc, a3.in_stoc, a4.in_stoc, a5.in_stoc) in_stoc
FROM dl
LEFT JOIN catk a1 ON (a1.rn_den = 1 AND UPPER(a1.denumire) = NULLIF(TRIM(UPPER(dl.articol)),''))
LEFT JOIN catk a2 ON (a2.rn_bare = 1 AND UPPER(a2.codbare) = NULLIF(TRIM(UPPER(dl.codbare)),''))
LEFT JOIN catk a3 ON (a3.rn_mat = 1 AND UPPER(a3.codmat) = NULLIF(TRIM(UPPER(<<m.lcCodMatCol>>)),''))
LEFT JOIN catk a4 ON (a4.rn_matf = 1 AND UPPER(a4.codmatf) = NULLIF(TRIM(UPPER(<<m.lcCodMatFCol>>)),''))
LEFT JOIN catp a5 ON (a5.rn_p = 1 AND UPPER(a5.cod) = NULLIF(TRIM(UPPER(<<m.lcCodMatPCol>>)),''))
),
Endtext
*!* Articolul contat oriunde in firma (ARTICOL): ultima linie, indiferent de furnizor, cu
*!* aceeasi cheie normalizata ca linia curenta, in aceeasi directie. Corelata pe cheile
*!* facturii curente (JOIN pe expresia indexului functional), nu materializata pe toata firma -
*!* ROWNUM=1 pe fereastra "ultima factura" e deja un singur candidat, garda de dominanta 70%
*!* nu are ce sa mai filtreze aici.
If m.llNormalizat
Text To lcS2 Textmerge Noshow
fw0 AS (SELECT /*+ USE_NL(d) INDEX(d IDX_EFACT_DET_ARTNORM) */ dl.nr, d.cont, d.acont, d.id_articol,
na.denumire articol_roa, na.codmat codmat_roa, na.in_stoc,
ROW_NUMBER() OVER (PARTITION BY dl.nr ORDER BY e.xdata_act DESC, d.id DESC) rn_fw
FROM dl
JOIN ANAF_EFACTURA_DETALII d ON (<<m.lcCheieColDFw>> = dl.a_no)
JOIN ANAF_EFACTURA e ON (e.id = d.id_efactura AND NVL(e.factura_emisa,0) = <<m.lcEmisa>>
AND e.id <> ?pnIdEfactura AND <<m.lcFereastra>>)
LEFT JOIN NOM_ARTICOLE na ON (na.id_articol = d.id_articol)
WHERE LENGTH(dl.a_no) >= 3 AND d.cont IS NOT NULL AND TRIM(d.cont) IS NOT NULL),
fw AS (SELECT nr, cont, acont, id_articol, articol_roa, codmat_roa, in_stoc FROM fw0 WHERE rn_fw = 1),
Endtext
Else
lcS2 = "fw AS (SELECT NULL nr, NULL cont, NULL acont, NULL id_articol, NULL articol_roa, NULL codmat_roa, NULL in_stoc FROM DUAL WHERE 1 = 0),"
Endif
*!* Contul sintetic dominant al furnizorului (FURNIZOR): ultimele 3 documente in care apare
*!* creditat pe 401/4111, majoritar 90% peste liniile lor de debit (exclus 442). Cautat o
*!* singura data per factura (toate liniile au acelasi furnizor), prin IDX_PARTC/IDX_PARTD -
*!* partenerul se rezolva intai separat (s1part), REGEXP_REPLACE nu intra in join-ul cu ACT.
Text To lcS1 Textmerge Noshow
efact AS (SELECT xdata_act FROM ANAF_EFACTURA WHERE id = ?pnIdEfactura),
s1part AS (SELECT id_part FROM NOM_PARTENERI
WHERE NVL(sters,0) = 0 AND REGEXP_REPLACE(UPPER(cod_fiscal),'[^0-9]','') = REGEXP_REPLACE(?pcCodFiscal,'[^0-9]','')),
s1doc AS (SELECT cod FROM (SELECT a.cod, MIN(a.dataact) d
FROM ACT a JOIN s1part p ON p.id_part = <<m.lcColPartener>>
WHERE NVL(a.sters,0) = 0 AND SUBSTR(<<m.lcColCredit>>,1,3) = <<m.lcCod401>>
AND a.dataact < (SELECT xdata_act FROM efact)
GROUP BY a.cod ORDER BY MIN(a.dataact) DESC) WHERE ROWNUM <= 3),
s1line AS (SELECT TRIM(<<m.lcColDebit>>) c FROM ACT a JOIN s1doc s ON s.cod = a.cod
WHERE NVL(a.sters,0) = 0 AND <<m.lcColDebit>> IS NOT NULL AND TRIM(<<m.lcColDebit>>) IS NOT NULL
AND SUBSTR(<<m.lcColDebit>>,1,3) <> '442'),
s1agg AS (SELECT c, COUNT(*) cnt, SUM(COUNT(*)) OVER () tot FROM s1line GROUP BY c),
s1top AS (SELECT c s1_cont FROM
(SELECT c, cnt FROM (SELECT c, cnt FROM s1agg ORDER BY cnt DESC, c) WHERE ROWNUM = 1) t,
(SELECT tot FROM s1agg WHERE ROWNUM = 1) tt
WHERE t.cnt/tt.tot >= 0.9),
Endtext
*!* pre: rezultatul S0+catalog+S2+S1 per linie. id_gestiune ramane a aceluiasi rand cu
*!* id_articol (nu se amesteca daca vine din catalog). S3 (contul majoritar al facturii) se
*!* calculeaza o singura data pe rezultatul de aici (s3syn/s3full), nu per linie ramasa goala.
Text To lcSql Textmerge Noshow
WITH <<m.lcIst>>
<<m.lcX5>>
dl AS (SELECT dd.*, TRIM(UPPER(dd.articol)) a_ex, <<m.lcCheieColDl>> a_no FROM ANAF_VEFACTURA_DETALII dd WHERE dd.id_efactura = ?pnIdEfactura),
<<m.lcCat>>
<<m.lcS2>>
<<m.lcS1>>
pre AS (SELECT dl.nr, dl.id id_detaliu,
NVL(cat.cont, NVL(x1.cont, NVL(x2.cont, NVL(x3.cont, NVL(x4.cont, NVL(x5.cont, NVL(x6.cont, s1top.s1_cont))))))) cont_full,
NVL(cat.acont, NVL(x1.acont, NVL(x2.acont, NVL(x3.acont, NVL(x4.acont, x6.acont))))) acont,
NVL(cat.id_articol, NVL(x1.id_articol, NVL(x3.id_articol, x6.id_articol))) id_articol,
NVL(cat.articol_roa, NVL(x1.articol_roa, NVL(x3.articol_roa, x6.articol_roa))) articol_roa,
NVL(cat.codmat_roa, NVL(x1.codmat_roa, NVL(x3.codmat_roa, x6.codmat_roa))) codmat_roa,
NVL(cat.in_stoc, NVL(x1.in_stoc, NVL(x3.in_stoc, x6.in_stoc))) in_stoc,
CASE WHEN cat.id_articol IS NOT NULL THEN NULL
WHEN x1.id_articol IS NOT NULL THEN x1.id_gestiune
WHEN x3.id_articol IS NOT NULL THEN x3.id_gestiune END id_gestiune,
CASE WHEN cat.cont IS NOT NULL THEN 'CATALOG'
WHEN x1.cont IS NOT NULL OR x2.cont IS NOT NULL THEN 'EXACT'
WHEN x3.cont IS NOT NULL OR x4.cont IS NOT NULL THEN 'APROX'
WHEN x5.cont IS NOT NULL THEN 'ISTORIC'
WHEN x6.cont IS NOT NULL THEN 'ARTICOL'
WHEN s1top.s1_cont IS NOT NULL THEN 'FURNIZOR' END sursa_cont
FROM dl
LEFT JOIN ist2 x1 ON (x1.a_ex = dl.a_ex AND x1.id_articol IS NOT NULL AND x1.rn_ex_art = 1)
LEFT JOIN ist2 x2 ON (x2.a_ex = dl.a_ex AND x2.rn_ex = 1)
LEFT JOIN ist2 x3 ON (LENGTH(dl.a_no) >= 3 AND x3.a_no = dl.a_no AND x3.id_articol IS NOT NULL AND x3.rn_no_art = 1)
LEFT JOIN ist2 x4 ON (LENGTH(dl.a_no) >= 3 AND x4.a_no = dl.a_no AND x4.rn_no = 1)
LEFT JOIN x5c x5 ON (1 = 1)
LEFT JOIN fw x6 ON (x6.nr = dl.nr)
LEFT JOIN cat ON (cat.nr = dl.nr)
LEFT JOIN s1top ON (1 = 1)
),
s3syn AS (SELECT syn FROM (SELECT SUBSTR(cont_full,1,3) syn, COUNT(*) cnt FROM pre WHERE cont_full IS NOT NULL
GROUP BY SUBSTR(cont_full,1,3) ORDER BY COUNT(*) DESC, SUBSTR(cont_full,1,3)) WHERE ROWNUM = 1),
s3full AS (SELECT cont_full FROM (SELECT o.cont_full, COUNT(*) cnt FROM pre o, s3syn s
WHERE SUBSTR(o.cont_full,1,3) = s.syn GROUP BY o.cont_full ORDER BY COUNT(*) DESC, o.cont_full) WHERE ROWNUM = 1)
SELECT p.nr, p.id_detaliu,
NVL(p.cont_full, s3.cont_full) AS cont,
CASE WHEN p.cont_full IS NOT NULL THEN p.acont END AS acont,
p.id_articol, p.articol_roa, p.codmat_roa, p.in_stoc, p.id_gestiune,
CASE WHEN p.cont_full IS NOT NULL THEN p.sursa_cont
WHEN s3.cont_full IS NOT NULL THEN 'FACTURA' END AS sursa_cont,
CAST(NULL AS NUMBER(1)) AS in_stoc_sugerat
FROM pre p
LEFT JOIN s3full s3 ON (1 = 1)
ORDER BY p.nr
Endtext
lnSucces = goExecutor.oExecute(m.lcSql, 'crsPotrivireLinii')
If m.lnSucces >= 0
This.SugereazaInStoc(m.tlPrimite)
Endif
Return (m.lnSucces >= 0)
Endproc && PotrivesteFactura
*!* Sirul de ID-uri (virgula) din campul id al cursorului de facturi, pentru clauza IN a
*!* interogarii de lot. Nu repozitioneaza cursorul
*!* ponytail: fara chunking, plafon Oracle IN ~1000 - de spart pe loturi daca lista afisata creste peste atat
Procedure ListaIdFacturi
Lparameters tcAliasFacturi
Local lcSelect, lcLista, lnRecno
lcSelect = Select()
Select (m.tcAliasFacturi)
lnRecno = Recno()
lcLista = ''
Scan
lcLista = m.lcLista + Iif(Empty(m.lcLista), '', ',') + Transform(Nvl(id, 0))
Endscan
Try
Go m.lnRecno
Catch
Go Top
Endtry
Select (m.lcSelect)
Return m.lcLista
Endproc && ListaIdFacturi
*!* Varianta pe lista intreaga a cascadei din PotrivesteFactura, o singura interogare Oracle
*!* pentru toate facturile din tcAliasFacturi (campul id). Lasa deschis CRSLIPSURILISTA, indexat
*!* pe id_efactura, un rand per factura cu macar o linie de detaliu: ID_EFACTURA, LINII,
*!* FARA_CONT_CATALOG (linii ramase fara cont daca s-ar aplica doar sursa CATALOG),
*!* FARA_CONT_ORICE (linii ramase fara cont cu toata cascada), STOC_FARA_GESTIUNE (linii
*!* stocabile fara gestiune propusa de cascada, inclusiv in_stoc_sugerat din cTipArticoleP/E),
*!* FARA_ANALITIC (linii cu cont care cere analitic in vplcont_ANALITIC pe gnAn si acont gol).
*!* Intoarce .T./.F.; la esec Oracle nu arunca dialog (ruleaza si in lot)
Procedure LipsuriLista
Lparameters tcAliasFacturi, tlPrimite
Local lcSql, lnSucces, lcIdList, lcColFiscalCol, lcCheieColD, llNormalizat, lcX5, lcIst, lcFereastra
Local lcCat, lcS2, lcS1, lcEmisa, lcCodMatCol, lcCodMatFCol, lcCodMatPCol, lcCheieColDl, lcCheieColDFw
Local lcColPartener, lcColCredit, lcCod401, lcColDebit, lcSelect, lcTabelTip, lcContIst
If !Used(m.tcAliasFacturi)
Return .F.
Endif
lcSelect = Select()
lcIdList = This.ListaIdFacturi(m.tcAliasFacturi)
If Used('crsLipsuriLista')
Use In (Select('crsLipsuriLista'))
Endif
If Empty(m.lcIdList)
Create Cursor crsLipsuriLista (id_efactura N(20), linii N(6), fara_cont_catalog N(6), ;
fara_cont_orice N(6), stoc_fara_gestiune N(6), stoc_total N(6), n_istoric N(6), x5_cont C(20), fara_analitic N(6))
Index On id_efactura Tag idefact
Select (m.lcSelect)
Return .T.
Endif
llNormalizat = (This.nNormalizat # 0)
lcColFiscalCol = Iif(m.tlPrimite, "cod_fiscal_emitent", "cod_fiscal_beneficiar")
lcCheieColD = This.CheieSql('articol')
lcCheieColDl = Iif(m.llNormalizat, m.lcCheieColD, "NULL")
lcCheieColDFw = This.CheieSql('d.articol')
lcFereastra = This.FereastraIstoricSql('e.xdata_act')
lcEmisa = Iif(m.tlPrimite, '0', '1')
lcCodMatCol = Iif(m.tlPrimite, "dl.codclient", "dl.codfurnizor")
lcCodMatFCol = Iif(m.tlPrimite, "dl.codfurnizor", "CAST(NULL AS VARCHAR2(100))")
lcCodMatPCol = Iif(m.tlPrimite, "CAST(NULL AS VARCHAR2(100))", "dl.codclient")
lcColPartener = Iif(m.tlPrimite, "a.id_partc", "a.id_partd")
lcColCredit = Iif(m.tlPrimite, "a.scc", "a.scd")
lcCod401 = Iif(m.tlPrimite, "'401'", "'4111'")
lcColDebit = Iif(m.tlPrimite, "a.scd", "a.scc")
Text To lcIst Textmerge Noshow
lst AS (SELECT id, <<m.lcColFiscalCol>> AS cod_fiscal, xdata_act FROM ANAF_EFACTURA WHERE id IN (<<m.lcIdList>>)),
lstpart AS (SELECT lst_id, MIN(id_part) id_part FROM (
SELECT l.id lst_id, p.id_part FROM lst l JOIN NOM_PARTENERI p
ON (NVL(p.sters,0)=0 AND REGEXP_REPLACE(UPPER(p.cod_fiscal),'[^0-9]','') = REGEXP_REPLACE(l.cod_fiscal,'[^0-9]',''))
) GROUP BY lst_id),
dl AS (SELECT dd.*, TRIM(UPPER(dd.articol)) a_ex, <<m.lcCheieColDl>> a_no
FROM ANAF_VEFACTURA_DETALII dd WHERE dd.id_efactura IN (SELECT id FROM lst)),
ist AS (SELECT /*+ MATERIALIZE */ d.*, TRIM(UPPER(d.articol)) a_ex, <<m.lcCheieColD>> a_no, e.<<m.lcColFiscalCol>> lst_cod_fiscal
FROM ANAF_VEFACTURA_DETALII d JOIN ANAF_EFACTURA e ON (e.id = d.id_efactura)
WHERE e.<<m.lcColFiscalCol>> IN (SELECT DISTINCT cod_fiscal FROM lst) AND <<m.lcFereastra>>),
x1raw AS (SELECT dl.id id_detaliu, i.cont, i.acont, i.id_articol, i.id_gestiune, i.in_stoc,
ROW_NUMBER() OVER (PARTITION BY dl.id ORDER BY i.id DESC) rnk
FROM dl JOIN lst l ON (l.id = dl.id_efactura) JOIN ist i ON (i.lst_cod_fiscal = l.cod_fiscal AND i.a_ex = dl.a_ex
AND i.id_articol IS NOT NULL AND i.id_efactura <> dl.id_efactura)),
x1 AS (SELECT * FROM x1raw WHERE rnk = 1),
x2raw AS (SELECT dl.id id_detaliu, i.cont, i.acont,
ROW_NUMBER() OVER (PARTITION BY dl.id ORDER BY i.id DESC) rnk
FROM dl JOIN lst l ON (l.id = dl.id_efactura) JOIN ist i ON (i.lst_cod_fiscal = l.cod_fiscal AND i.a_ex = dl.a_ex
AND i.id_efactura <> dl.id_efactura)),
x2 AS (SELECT * FROM x2raw WHERE rnk = 1),
x3raw AS (SELECT dl.id id_detaliu, i.cont, i.acont, i.id_articol, i.id_gestiune, i.in_stoc,
ROW_NUMBER() OVER (PARTITION BY dl.id ORDER BY i.id DESC) rnk
FROM dl JOIN lst l ON (l.id = dl.id_efactura) JOIN ist i ON (i.lst_cod_fiscal = l.cod_fiscal AND LENGTH(dl.a_no) >= 3
AND i.a_no = dl.a_no AND i.id_articol IS NOT NULL AND i.id_efactura <> dl.id_efactura)),
x3 AS (SELECT * FROM x3raw WHERE rnk = 1),
x4raw AS (SELECT dl.id id_detaliu, i.cont, i.acont,
ROW_NUMBER() OVER (PARTITION BY dl.id ORDER BY i.id DESC) rnk
FROM dl JOIN lst l ON (l.id = dl.id_efactura) JOIN ist i ON (i.lst_cod_fiscal = l.cod_fiscal AND LENGTH(dl.a_no) >= 3
AND i.a_no = dl.a_no AND i.id_efactura <> dl.id_efactura)),
x4 AS (SELECT * FROM x4raw WHERE rnk = 1),
Endtext
If This.nIstoric # 0
Text To lcX5 Textmerge Noshow
x5rank AS (SELECT l.id lst_id, i.cont,
ROW_NUMBER() OVER (PARTITION BY l.id ORDER BY i.id DESC) rn
FROM lst l JOIN ist i ON (i.lst_cod_fiscal = l.cod_fiscal AND i.id_efactura <> l.id
AND i.cont IS NOT NULL AND TRIM(i.cont) IS NOT NULL)),
x5top5 AS (SELECT lst_id, TRIM(cont) cont FROM x5rank WHERE rn <= 5),
x5c AS (SELECT lst_id, cont FROM x5top5 GROUP BY lst_id, cont HAVING COUNT(*) = 5),
Endtext
Else
lcX5 = "x5c AS (SELECT NULL lst_id, NULL cont FROM DUAL WHERE 1 = 0),"
Endif
Text To lcCat Textmerge Noshow
catk AS (SELECT id_articol, denumire, codbare, codmat, codmatf, cont, acont, in_stoc,
ROW_NUMBER() OVER (PARTITION BY UPPER(denumire) ORDER BY id_articol DESC) rn_den,
ROW_NUMBER() OVER (PARTITION BY UPPER(codbare) ORDER BY id_articol DESC) rn_bare,
ROW_NUMBER() OVER (PARTITION BY UPPER(codmat) ORDER BY id_articol DESC) rn_mat,
ROW_NUMBER() OVER (PARTITION BY UPPER(codmatf) ORDER BY id_articol DESC) rn_matf
FROM NOM_ARTICOLE WHERE sters = 0 AND inactiv = 0),
catp AS (SELECT lp.lst_id, c.cod, a.id_articol, a.denumire, a.codmat, a.cont, a.acont, a.in_stoc,
ROW_NUMBER() OVER (PARTITION BY lp.lst_id, UPPER(c.cod) ORDER BY a.id_articol DESC) rn_p
FROM lstpart lp JOIN PARTENERI_ARTICOLE_CODURI c ON (c.id_part = lp.id_part AND c.sters = 0)
JOIN NOM_ARTICOLE a ON (a.id_part = c.id_part AND a.sters = 0 AND a.inactiv = 0)),
cat AS (SELECT dl.id id_detaliu,
COALESCE(a1.id_articol, a2.id_articol, a3.id_articol, a4.id_articol, a5.id_articol) id_articol,
COALESCE(a1.cont, a2.cont, a3.cont, a4.cont, a5.cont) cont,
COALESCE(a1.acont, a2.acont, a3.acont, a4.acont, a5.acont) acont,
COALESCE(a1.in_stoc, a2.in_stoc, a3.in_stoc, a4.in_stoc, a5.in_stoc) in_stoc
FROM dl
LEFT JOIN catk a1 ON (a1.rn_den = 1 AND UPPER(a1.denumire) = NULLIF(TRIM(UPPER(dl.articol)),''))
LEFT JOIN catk a2 ON (a2.rn_bare = 1 AND UPPER(a2.codbare) = NULLIF(TRIM(UPPER(dl.codbare)),''))
LEFT JOIN catk a3 ON (a3.rn_mat = 1 AND UPPER(a3.codmat) = NULLIF(TRIM(UPPER(<<m.lcCodMatCol>>)),''))
LEFT JOIN catk a4 ON (a4.rn_matf = 1 AND UPPER(a4.codmatf) = NULLIF(TRIM(UPPER(<<m.lcCodMatFCol>>)),''))
LEFT JOIN catp a5 ON (a5.lst_id = dl.id_efactura AND a5.rn_p = 1 AND UPPER(a5.cod) = NULLIF(TRIM(UPPER(<<m.lcCodMatPCol>>)),''))
),
Endtext
If m.llNormalizat
Text To lcS2 Textmerge Noshow
fw0 AS (SELECT /*+ USE_NL(d) INDEX(d IDX_EFACT_DET_ARTNORM) */ dl.id id_detaliu, d.cont, d.acont, d.id_articol, na.in_stoc,
ROW_NUMBER() OVER (PARTITION BY dl.id ORDER BY e.xdata_act DESC, d.id DESC) rn_fw
FROM dl
JOIN ANAF_EFACTURA_DETALII d ON (<<m.lcCheieColDFw>> = dl.a_no)
JOIN ANAF_EFACTURA e ON (e.id = d.id_efactura AND NVL(e.factura_emisa,0) = <<m.lcEmisa>>
AND e.id <> dl.id_efactura AND <<m.lcFereastra>>)
LEFT JOIN NOM_ARTICOLE na ON (na.id_articol = d.id_articol)
WHERE LENGTH(dl.a_no) >= 3 AND d.cont IS NOT NULL AND TRIM(d.cont) IS NOT NULL),
fw AS (SELECT id_detaliu, cont, acont, id_articol, in_stoc FROM fw0 WHERE rn_fw = 1),
Endtext
Else
lcS2 = "fw AS (SELECT NULL id_detaliu, NULL cont, NULL acont, NULL id_articol, NULL in_stoc FROM DUAL WHERE 1 = 0),"
Endif
Text To lcS1 Textmerge Noshow
efact AS (SELECT id lst_id, xdata_act, cod_fiscal FROM lst),
s1doc AS (SELECT lst_id, cod FROM (
SELECT ef.lst_id, a.cod, MIN(a.dataact) d,
ROW_NUMBER() OVER (PARTITION BY ef.lst_id ORDER BY MIN(a.dataact) DESC) rn3
FROM efact ef JOIN lstpart lp ON lp.lst_id = ef.lst_id
JOIN ACT a ON (<<m.lcColPartener>> = lp.id_part AND NVL(a.sters,0)=0 AND SUBSTR(<<m.lcColCredit>>,1,3)=<<m.lcCod401>>
AND a.dataact < ef.xdata_act)
GROUP BY ef.lst_id, a.cod
) WHERE rn3 <= 3),
s1line AS (SELECT s.lst_id, TRIM(<<m.lcColDebit>>) c FROM ACT a JOIN s1doc s ON s.cod = a.cod
WHERE NVL(a.sters,0)=0 AND <<m.lcColDebit>> IS NOT NULL AND TRIM(<<m.lcColDebit>>) IS NOT NULL AND SUBSTR(<<m.lcColDebit>>,1,3) <> '442'),
s1agg AS (SELECT lst_id, c, COUNT(*) cnt FROM s1line GROUP BY lst_id, c),
s1tot AS (SELECT lst_id, SUM(cnt) tot FROM s1agg GROUP BY lst_id),
s1top AS (SELECT a.lst_id, a.c s1_cont FROM (
SELECT lst_id, c, cnt, ROW_NUMBER() OVER (PARTITION BY lst_id ORDER BY cnt DESC, c) rnk
FROM s1agg) a JOIN s1tot t ON t.lst_id = a.lst_id
WHERE a.rnk = 1 AND a.cnt/t.tot >= 0.9),
Endtext
Text To lcSql Textmerge Noshow
WITH <<m.lcIst>>
<<m.lcX5>>
<<m.lcCat>>
<<m.lcS2>>
<<m.lcS1>>
pre AS (SELECT dl.id id_detaliu, dl.id_efactura, dl.cont dl_cont,
NVL(dl.cont, NVL(cat.cont, NVL(x1.cont, NVL(x2.cont, NVL(x3.cont, NVL(x4.cont, NVL(x5.cont, NVL(x6.cont, s1top.s1_cont)))))))) cont_full,
NVL(dl.acont, NVL(cat.acont, NVL(x1.acont, NVL(x2.acont, NVL(x3.acont, NVL(x4.acont, x6.acont)))))) acont,
NVL(dl.in_stoc, NVL(cat.in_stoc, NVL(x1.in_stoc, NVL(x3.in_stoc, x6.in_stoc)))) in_stoc,
NVL(dl.id_gestiune, CASE WHEN cat.id_articol IS NOT NULL THEN NULL
WHEN x1.id_articol IS NOT NULL THEN x1.id_gestiune
WHEN x3.id_articol IS NOT NULL THEN x3.id_gestiune END) id_gestiune,
CASE WHEN cat.cont IS NOT NULL THEN 'CATALOG'
WHEN x1.cont IS NOT NULL OR x2.cont IS NOT NULL THEN 'EXACT'
WHEN x3.cont IS NOT NULL OR x4.cont IS NOT NULL THEN 'APROX'
WHEN x5.cont IS NOT NULL THEN 'ISTORIC'
WHEN x6.cont IS NOT NULL THEN 'ARTICOL'
WHEN s1top.s1_cont IS NOT NULL THEN 'FURNIZOR' END sursa_cont,
x5.cont x5_cont
FROM dl
LEFT JOIN x1 ON (x1.id_detaliu = dl.id)
LEFT JOIN x2 ON (x2.id_detaliu = dl.id)
LEFT JOIN x3 ON (x3.id_detaliu = dl.id)
LEFT JOIN x4 ON (x4.id_detaliu = dl.id)
LEFT JOIN x5c x5 ON (x5.lst_id = dl.id_efactura)
LEFT JOIN fw x6 ON (x6.id_detaliu = dl.id)
LEFT JOIN cat ON (cat.id_detaliu = dl.id)
LEFT JOIN s1top ON (s1top.lst_id = dl.id_efactura)
),
s3synraw AS (SELECT id_efactura, SUBSTR(cont_full,1,3) syn, COUNT(*) cnt
FROM pre WHERE cont_full IS NOT NULL GROUP BY id_efactura, SUBSTR(cont_full,1,3)),
s3syn AS (SELECT id_efactura, syn FROM (
SELECT id_efactura, syn, ROW_NUMBER() OVER (PARTITION BY id_efactura ORDER BY cnt DESC, syn) rnk
FROM s3synraw) WHERE rnk = 1),
s3fullraw AS (SELECT o.id_efactura, o.cont_full, COUNT(*) cnt
FROM pre o JOIN s3syn s ON (s.id_efactura = o.id_efactura AND SUBSTR(o.cont_full,1,3) = s.syn)
GROUP BY o.id_efactura, o.cont_full),
s3full AS (SELECT id_efactura, cont_full FROM (
SELECT id_efactura, cont_full, ROW_NUMBER() OVER (PARTITION BY id_efactura ORDER BY cnt DESC, cont_full) rnk
FROM s3fullraw) WHERE rnk = 1),
final_pre AS (SELECT p.id_efactura,
NVL(p.cont_full, s3.cont_full) AS cont,
CASE WHEN p.cont_full IS NOT NULL THEN p.acont END AS acont,
NVL(p.in_stoc,0) in_stoc, p.id_gestiune, p.sursa_cont, p.x5_cont,
CASE WHEN p.dl_cont IS NOT NULL THEN 0
WHEN p.sursa_cont IS NULL OR p.sursa_cont <> 'CATALOG' THEN 1 ELSE 0 END fara_catalog
FROM pre p
LEFT JOIN s3full s3 ON (s3.id_efactura = p.id_efactura)
)
SELECT fp.id_efactura,
COUNT(*) linii,
SUM(fp.fara_catalog) fara_cont_catalog,
SUM(CASE WHEN fp.cont IS NULL THEN 1 ELSE 0 END) fara_cont_orice,
SUM(CASE WHEN fp.in_stoc = 1 AND fp.id_gestiune IS NULL THEN 1 ELSE 0 END) stoc_fara_gestiune,
SUM(CASE WHEN fp.in_stoc = 1 THEN 1 ELSE 0 END) stoc_total,
SUM(CASE WHEN fp.sursa_cont = 'ISTORIC' THEN 1 ELSE 0 END) n_istoric,
SUM(CASE WHEN fp.acont IS NULL AND EXISTS (SELECT 1 FROM vplcont_ANALITIC va
WHERE va.cont = fp.cont AND va.inactiv = 0 AND va.an = ?gnAn) THEN 1 ELSE 0 END) fara_analitic,
MAX(fp.x5_cont) x5_cont
FROM final_pre fp
GROUP BY fp.id_efactura
Endtext
lnSucces = goExecutor.oExecute(m.lcSql, 'crsLipsuriLista')
If m.lnSucces < 0
Select (m.lcSelect)
Return .F.
Endif
Select crsLipsuriLista
Index On id_efactura Tag idefact
*!* corectie in_stoc_sugerat: liniile propuse DOAR de treapta ISTORIC (fara id_articol, deci
*!* fara in_stoc/id_gestiune din cascada) - daca x5_cont e in cTipArticoleP/E cu in_stoc=1,
*!* se aduna n_istoric la stoc_fara_gestiune si stoc_total (acelasi cont pentru toata factura)
lcTabelTip = Iif(m.tlPrimite, 'cTipArticoleP', 'cTipArticoleE')
If Used(m.lcTabelTip)
Select crsLipsuriLista
Scan For n_istoric > 0 And !Empty(Nvl(x5_cont, ''))
lcContIst = Alltrim(x5_cont)
Select (m.lcTabelTip)
Locate For Alltrim(cont) = m.lcContIst
If Found() And in_stoc = 1
Select crsLipsuriLista
Replace stoc_fara_gestiune With stoc_fara_gestiune + n_istoric, ;
stoc_total With stoc_total + n_istoric
Endif
Select crsLipsuriLista
Endscan
Go Top
Endif
Select (m.lcSelect)
Return .T.
Endproc && LipsuriLista
*!* Completeaza IN_STOC_SUGERAT pe crsPotrivireLinii, doar pentru liniile propuse de treapta
*!* ISTORIC (fara id_articol), din cursorul cTipArticoleP/cTipArticoleE (cont -> in_stoc,
*!* import_efactura.prg:88-132). Nu deduce nimic daca acel cursor nu e deschis in contextul
*!* apelantei - propunerea ramane goala, nu o duplica
Procedure SugereazaInStoc
Lparameters tlPrimite
Local lcTabelTip, lcCont
lcTabelTip = Iif(m.tlPrimite, 'cTipArticoleP', 'cTipArticoleE')
If !Used('crsPotrivireLinii') Or !Used(m.lcTabelTip)
Return
Endif
Select crsPotrivireLinii
Scan For Upper(Alltrim(Nvl(sursa_cont, ''))) = 'ISTORIC' And !Empty(Nvl(cont, ''))
lcCont = Alltrim(cont)
Select (m.lcTabelTip)
Locate For Alltrim(cont) = m.lcCont
If Found()
Select crsPotrivireLinii
Replace in_stoc_sugerat With Evaluate(m.lcTabelTip + '.in_stoc')
Endif
Endscan
Select crsPotrivireLinii
Go Top
Endproc && SugereazaInStoc
Enddefine