Files
comun/docs/fluxuri_calcul_facturare.md
Marius Mutu 69c5290a8d Calcul tranzit: coeficient distanta rotunjit la 3 zecimale + protectie impartire la 0
Pentru distante scurte (ex. 0.2 km) coeficientul de distanta rotunjit la 2
zecimale iesea 0, deci valoarea facturata era 0. Rotunjire la 3 zecimale in
do_executa si in afisarea "mod de calcul". Protectie la impartirea cu 0 la
valoarea finala cand valoarea convoiului calculat este 0.

Adauga docs/fluxuri_calcul_facturare.md (lant facturare end-to-end pe tipuri)
si referinta in CLAUDE.md. Changelog 2.0.5.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 13:45:20 +03:00

21 KiB
Raw Blame History

Fluxuri de calcul și facturare pe tipuri de prestații

Acest document descrie lanțul complet de facturare end-to-end pentru fiecare tip de prestație: Import → Calcul → Salvare → Listare. Scopul este să nu mai fie nevoie de fiecare dată să se caute prin cod cum se ajunge de la datele sursă la valoarea facturată.

Documente complementare (a NU se duplica):

  • docs/facturare.md — fluxul general de salvare a facturii (form frm_facturafactura_salvare_dbPACK_FACTURARE.pck), structura crsFactura, formulele de TVA.
  • docs/cautare_vcx_vct.md — cum se caută cod în binarele .vcx/.scx.

Constante tipuri prestații: Include/roaacnpro.h (string TIP_*, numeric NTIP_*). Codul de calcul al claselor de formular stă în binarul Clase/oacnpro.vcx; referințele de mai jos folosesc cache-ul TEXT _textcache/clase/oacnpro.vc2 (vezi docs/cautare_vcx_vct.md).

Notație: fișier:linie. Pentru clase, oacnpro.vc2:NNNN = _textcache/clase/oacnpro.vc2.


0. Convenții de rotunjire

Rotunjirile folosesc constante globale de precizie, promovate din RoaApp (framework COMUN):

Constantă Rol Valoare uzuală
gnPC precizie valori în monedă națională (lei) 2
gnPVal precizie valori în valută 2
gnZ precizie penalități (testat în cod: 2 — proceduri_acnpro.prg:1542) 2
gnPCant precizie cantitate (config)
gnPPret / gnPPretV precizie preț unitar (lei / valută) (config)
gnPcurs precizie curs valutar (config)

Aceste constante sunt setate la inițializarea aplicației din configurația firmei (COMUN); nu sunt hard-codate în acest repo. Acolo unde formula folosește un număr fix de zecimale (ex. Round(..., 2), Round(..., 3), Round(..., 4)) acela este intenționat și independent de configurație.


1. IMPORT DATE (sursă RORIS)

1.1. Sinonime Oracle către schema roris

Datele sursă sunt tabele din sistemul extern RORIS, expuse aplicației prin sinonime create în database.txt. Toate ips_* sunt sinonime spre roris.ips_*:

  • ips_voyages, ips_voyage_members, ips_voyage_locks — voiaje, membrii convoiului, ecluzări (database.txt:2-3,11).
  • ips_berthings / ips_vberthings — staționări la dană (cheiaj) (database.txt:15,17).
  • ips_cargoes, ips_goods, ips_goods_groups — mărfuri și grupe de marfă (database.txt:7-9).
  • ips_routes / ips_vroutes, ips_route_points — rute și puncte de rută (distanțe) (database.txt:5,16,24).
  • ips_vessels, ips_vessel_types, ips_vas_echivalent, ips_grup_tip_nave — nave, tipuri, echivalențe UM, grupe de tip navă (database.txt:4,10,13,14).
  • ips_vas_um — unități de măsură ale tarifelor (database.txt:12).
  • Vederile ips_v* (ex. ips_vberthings, ips_vvoyages, ips_vroute_points) sunt versiuni pre-join ale acelorași date.

Tabelele proprii ale aplicației (NU sinonime): ips_voyages_vanzari, ips_voyage_members_vanzari, ips_regdoc, vanzari, factura, penalitati, contracte, ips_vtarife_tranzit, tarifele de cheiaj/contract.

1.2. Importul propriu-zis

Importul rulează ca executabil separat importroris.exe (proiect importroris.pjx), NU din aplicația principală:

  • Punct de intrare: Programe/importroris_main.prg — rulează silentios (importroris s nnn, unde nnn = nr. zile) sau cu formular (Do Form importroris) (importroris_main.prg:32-37).
  • Importul efectiv este delegat unui pachet Oracle: pack_acn_import.import_roris_job(?ptLastGenerationTime) (importroris_proceduri.prg:33). Toată extragerea/normalizarea din roris.* în tabelele locale ips_* se face în pachetul PL/SQL, nu în VFP.

1.3. Câmpuri sursă relevante pentru calcul

La calcul, datele importate sunt citite din vederi de calcul:

  • Tranzit: vederea ips_vvoyage_members_calcul (citită în calcul_tranzit, proceduri_acnpro.prg:835). Câmpuri-cheie: distanta, distanta_cdmn (km pe canal), tcap (capacitate/tone), trn (NRT), lbd, hp, cuplat, periculos, procdiv, gds_id/gdsg_id (marfă/grupă marfă), vtp_id/vgrp_id (tip/grupă navă), rte_id (rută), cargo (cantitate marfă). Schema cursorului crsVoyages în proceduri_acnpro.prg:838-844.
  • Cheiaj: vederea ips_vberthings (citită în calcul_cheiaj, proceduri_acnpro.prg:1023). Câmpuri: datai/datap (intrare/plecare → durată), cap, trn, lbd, hp, lung, tipn_id, dana_id/rpt_id (dana). Schema crsBerthings în proceduri_acnpro.prg:1028.
  • Ecluzări (tranzit): ips_vvoyage_locks (defalcate pe membru și cameră de ecluzare), citite la distribuția pe ecluze în completeaza_regdoc_tranzit (proceduri_acnpro.prg:2325).

2. CALCUL TARIFE ȘI VALORI

Calculul se face în clasele de formular din Clase/oacnpro.vcx. Lanțul pentru tranzit/cheiaj este: frm_tranzit.do_executacalcul_tranzit/calcul_cheiaj (umplu cursoarele din RORIS) → formularul de calcul (frm_calcul_tranzit / frm_calcul_cheiaj) → metoda de calcul tarife.

2.1. TRANZIT

  • Pregătire date: calcul_tranzit(tcVyeIds)proceduri_acnpro.prg:740. Creează crsVoyage (convoaie) și crsVoyages (membrii, cu indicatorii din ips_vvoyage_members_calcul).
  • Calcul valoare: clasa frm_calcul_tranzit, metoda do_executaoacnpro.vc2:3651-3918.

De unde vin tariful și cantitatea:

  • Tarifele: IPS_VTARIFE_TRANZIT (join cu IPS_VVAS_UM), selectate de DOUĂ ori:
    • tarife pe contract (ctr_id = lnClientCtrId) — oacnpro.vc2:3688-3694 (cursor tTarife);
    • tarife generale/fără contract (ctr_id = get_ctr_id_fara_contract()) — oacnpro.vc2:3698-3703 (cursor tTarife2).
  • Selecția tarifului se face pe tip UM (TC/TRN/LBD/HP), încadrarea pe capacitate (TCAP1/TCAP2 la capacitatea totală a convoiului, TCCAP1/TCCAP2 la capacitatea cumulată a clientului), rută (RTE_ID) și grupă/tip marfă (GDSG_ID/GDS_ID) — oacnpro.vc2:3750-3812. Ordinea de căutare: contract+marfă → general+marfă → contract+grupă → general+grupă → "orice marfă" (GDSG_ID=0).
  • Cantitatea (cant) depinde de UM: TCtcap, TRNtrn, LBDlbd, HPhp (oacnpro.vc2:3768-3795).

Coeficienți (oacnpro.vc2:3717-3727, 3861-3865):

  • corect (coeficient corector): pornește de la 100, +gnACNPFmarfa (fără marfă), +gnIpsPCuplat (necuplat), +gnIpsPPericulos (marfă periculoasă); apoi normalizat: corect = Round(lnCoeficient/100, 3) (oacnpro.vc2:3861).
  • procdiv — coeficient suplimentar adus din sursă (Nvl(procdiv,0)).
  • procdist (coeficient distanță): km din 64.4 (lungimea canalului), plafonat la 1:
    Replace procdist With MIN(Round(Abs(distanta) / 64.4, 2), 1.00)   && oacnpro.vc2:3864
    

Formula valorii pe navă (oacnpro.vc2:3867):

valval = Round(tarif * cant * procdist * (corect + procdiv), 2)

Valoare minimă / valoare finală manuală (oacnpro.vc2:3877-3910):

  • Suma pe convoi: lnValoareConvoi = SUM(valval) FOR ales=1.
  • Dacă lnValoareConvoi <= valoare_minima_convoi (gnIpsValoareMinConvoi) și > 0 și nu s-a scris manual, valoarea finală devine valoarea minimă (oacnpro.vc2:3883-3885).
  • Dacă utilizatorul a scris o valoare finală diferită, valorile se distribuie proporțional:
    Replace All VALVAL With Round(lnValoareFinala * VALVAL / lnValoareConvoi, gnPC) For ales = 1   && oacnpro.vc2:3891
    
    Diferența de rotunjire dintre suma recalculată și valoarea finală se pune pe primul rând (oacnpro.vc2:3903-3907). La stornare (val. finală < 0) se recalculează cantitatea (oacnpro.vc2:3895); tariful NU se recalculează (apare tariful de încadrare pe recapitulație).

Rotunjiri tranzit: procdist→2 zecimale, corect→3 zecimale, valval→2 zecimale fix; distribuția valorii finale→gnPC.

2.2. CHEIAJ

  • Pregătire date: calcul_cheiaj(tcVyeIds)proceduri_acnpro.prg:963. Creează crsVoyage, crsBerthings (staționări din ips_vberthings) și crsBerthingDetails (prestații de facturat).
  • Calcul valoare: clasa frm_calcul_cheiaj, metoda do_calcul_tarifeoacnpro.vc2:1191-1375.

Durata (oră → UM tarif): lnDurata1 = Round((datap - datai)/3600, 3) ore (oacnpro.vc2:1228), apoi împărțită la corect pentru transformare în UM tarifului (ore sau zile) și, dacă tariful are flag plus, rotunjită în sus cu Ceiling() (oacnpro.vc2:1289-1292).

Tariful: crsTarifeDiverseContract construit de make_tarife_cheiaj_contract cu tarifele de contract și cele generale (oacnpro.vc2:1215). Încadrarea pe interval de durată: lim1*corect < durata AND lim2*corect >= durata, după ctr_id, id_articol, intern (oacnpro.vc2:1247-1248).

Cantitatea după UM tarif (oacnpro.vc2:1298-1313): ZI→1 (ex. cheiaj iernat), TCcap, TRNtrn, MLlung, CP/HPhp, LBP/LBDlbd. Dacă UM conține ZI, valoarea se înmulțește și cu numărul de zile (llZile).

Formula valorii (oacnpro.vc2:1320-1345):

  • Caz simplu (fără bază): valval = tarif * cant * IIF(llZile, durata, 1) (oacnpro.vc2:1342).
  • Caz cu tarif pe tranșe (arebaza=1): se cumulează tarifele tranșelor inferioare (baza=1) plus tranșa curentă pentru durata rămasă (oacnpro.vc2:1321-1343).
  • valval = Round(valval, gnPVal) (oacnpro.vc2:1345).

Valoare finală manuală (oacnpro.vc2:1356-1367): distribuție proporțională Round(lnValoareFinala * valval / lnValoareConvoi, gnPVal), recalcul cantitate Round(valval/tarif, 2), diferența pe primul rând.

2.3. CHIRII (și contracte similare)

  • Calcul valoare: clasa frm_calcul_contractoacnpro.vc2:1615. Lucrează cu crsArticoleContract (linii introduse pe baza articolelor de contract), NU pe date RORIS.
  • Metode: calculeazapreturi (oacnpro.vc2:2240) → calculeazapreturirand (oacnpro.vc2:2248) → calculeazatotaluri (oacnpro.vc2:2267).

Formula (calculeazapreturirand, oacnpro.vc2:2256-2262):

  • la modificarea cantității/prețului: valval = Round(pret * cantitate, gnPC);
  • la modificarea valorii (recalc preț unitar): pret = Round(valval/cantitate, 4).

Tariful (pret) provine din articolul de contract (introdus de utilizator / tarif contract); nu există coeficient de distanță sau corecție.

2.4. PENALITĂȚI

  • Calcul valoare: clasa frm_penalitatioacnpro.vc2:8366. Metoda do_executa (oacnpro.vc2:9671) apelează calcul_penalitati2 (proceduri_acnpro.prg:1286); varianta cu vânzări reale este calcul_penalitati (proceduri_acnpro.prg:1166).
  • Recalcul pe o linie editată: calcul_penalitati_linie (proceduri_acnpro.prg:1455).

De unde vin datele: facturi/încasări din vanzari, vact, ireg_parteneri; cotele de penalitate din contracte (coef_penalitatippenzi, coef_penalitati2ppenzi2, zile_penalitatipenzile, zile_gratie_penalitatizilegratie) (proceduri_acnpro.prg:1183,1225). Cursorul de lucru: crsCalculPenalitati.

Zile de întârziere (proceduri_acnpro.prg:1257-1265):

  • databaza = Max(datascad, sfârșit lună precedentă) sau datascad (după modul de calcul);
  • dataref = Min(dataref, pdDataReferinta);
  • nr_zile = IIF(dataref - databaza <= zilegratie, 0, dataref - databaza) (zile de grație).

Formula penalitate, o singură cotă (proceduri_acnpro.prg:1266):

penalitati = Round(Round(ppenzi/100*suma, gnZ) * nr_zile, gnZ)

Formula penalitate, două cote (CalculeazaPenalitati, proceduri_acnpro.prg:1510-1539):

  • se împart zilele restante între cota 1 (până la penzile2-penzile) și cota 2 (peste); vezi lnZileProcent1/lnZileProcent2 (proceduri_acnpro.prg:1531-1534).
  • penalitati = Round(Round(ppenzi /100*suma, gnZ)*lnZileProcent1, gnZ)
               + Round(Round(ppenzi2/100*suma, gnZ)*lnZileProcent2, gnZ)   && proceduri_acnpro.prg:1536
    
  • Aplicată în calcul_penalitati doar pentru contracte cu ppenzi2 <> 0 (proceduri_acnpro.prg:1271-1277).

Rotunjiri: toate la gnZ (dublă rotunjire: întâi penalitatea/zi, apoi totalul). Funcția de test cu valori așteptate: TestCalculeazaPenalitati (proceduri_acnpro.prg:1541).

2.5. APA / PILOTAJ / ALTE / DIVERSE

Aceste tipuri nu au un motor de calcul propriu bazat pe RORIS. Sunt facturate prin lanțul generic de contract/articol:

  • Calculul valorii este pret * cantitate (vezi 2.3 CHIRII, frm_calcul_contract / calculeazapreturirand, oacnpro.vc2:2256-2262), respectiv introducere directă pe linia de factură în frm_factura (crsFactura, vezi docs/facturare.md).
  • Tariful provine din articol/contract (ips_vprestatii_locatii, contracte), nu există coeficienți de distanță/corecție.
  • Constantele de tip: NTIP_APA 2, NTIP_PILOTAJ 4, NTIP_ALTE 5, NTIP_DIVERSE 9 (Include/roaacnpro.h).
  • Salvarea acestor linii se face în ips_regdoc prin salvare_contract (proceduri_acnpro.prg:1918).

3. SALVARE FACTURĂ

Fluxul general (cap factură, articole, vânzări, contabilitate) este în docs/facturare.md; sursa de adevăr pentru valori/TVA este PACK_FACTURARE.pck. Aici se documentează doar specificul fiecărui tip.

3.1. Lanțul comun

  • factura_acn(tcTip, loDate)proceduri_acnpro.prg:3064 — orchestrarea facturii.
  • factura_salvare_db(tcTip)proceduri_acnpro.prg:3290 — persistarea:
    • pack_facturare.initializeaza_date_factura(...) (cap factură), adauga_articol_factura_deviz (articole), scrie_in_vanzari (proceduri_acnpro.prg:3343,3382,3412);
    • reg. doc.: pack_acn.salveaza_regdoc(...) pentru fiecare rând din cRegDoc (proceduri_acnpro.prg:3430);
    • pentru TRANZIT/CHEIAJ: actualizează ips_voyages_vanzari.vz_id cu id-ul vânzării (proceduri_acnpro.prg:3448-3451);
    • pentru PENALITĂȚI: inserează în tabela penalitati (proceduri_acnpro.prg:3475-3488).

3.2. TRANZIT — specific

  • salvare_tranzitproceduri_acnpro.prg:1779:
    • inserează capul de convoi facturat în IPS_VOYAGES_VANZARI (...:1788-1791);
    • inserează fiecare navă în IPS_VOYAGE_MEMBERS_VANZARI cu toate valorile calculate (procdist, corect, tarif, cant, valval, distanta, distanta_cdmn, etc.) (...:1794-1849). Tranzacție manuală cu COMMIT/ROLLBACK.
  • completeaza_regdoc_tranzit(tcVVIds)proceduri_acnpro.prg:2259distribuția pe canale și ecluze:
    • procentul CDMN al fiecărei nave: Round(DISTANTA_CDMN / DISTANTA, 4) (...:2332);
    • valori CDMN = valval * procentCDMN, PAMN = restul (...:2355-2364);
    • valori pe senale navigabile = valoarea CDMN/PAMN × procentul de senal (100 - procente_ecluze)/100 (...:2369-2385);
    • procentele pe ecluze (AGIGEA/CERNAVODA/OVIDIU/NAVODARI) din ips_vvoyage_locks și constantele gnIPSTranzitProcAG/CV/OV/NV (...:2289-2324).

3.3. CHEIAJ — specific

Salvarea se face tot prin IPS_VOYAGES_VANZARI / IPS_VOYAGE_MEMBERS_VANZARI (același mecanism ca tranzit, cu poVoyage.tip = NTIP_CHEIAJ) și prin lanțul comun din 3.1 (vz_id actualizat la factura_salvare_db, proceduri_acnpro.prg:3448). make_factura_cheiaj (proceduri_acnpro.prg:3615) construiește linia de articol în crsFactura.

3.4. CHIRII / APA / PILOTAJ / ALTE / DIVERSE — specific

  • salvare_contractproceduri_acnpro.prg:1918 — inserează în ips_regdoc (tip, articol, locație, valută, client, contract, preț, cantitate, valval, TVA, perioadă) (...:1924-1953).

3.5. PENALITĂȚI — specific

  • salvare_penalitatiproceduri_acnpro.prg:2008 — marchează înregistrările alese/șterse; scrierea reală în tabela penalitati se face în factura_salvare_db (proceduri_acnpro.prg:3475-3488).
  • make_factura_penalitatiproceduri_acnpro.prg:3775 — construiește linia de factură.

4. LISTARE FACTURĂ + RECAPITULAȚIE

Rapoartele citesc, în general, valorile deja calculate din tabelele de vânzări; recapitulațiile recalculează doar agregări/distribuiri pentru afișare, nu tarife.

4.1. Construirea liniilor de factură (crsFactura)

  • make_factura_tranzitproceduri_acnpro.prg:3517: citește valorile însumate din ips_voyage_members_vanzari (SUM(valval) pe valută, proceduri_acnpro.prg:3561-3567), numele convoiului din ips_vvoyages și completează crsFactura (un singur rând, cantitate 1, preț = valval în monedă națională). NU recalculează tarife.
  • make_factura_cheiajproceduri_acnpro.prg:3615: analog, pentru cheiaj.
  • make_factura_penalitatiproceduri_acnpro.prg:3775.

4.2. Recapitulații (rapoarte Rapoarte/*.frx)

Procedurile de generare a cursoarelor de raport: Programe/proceduri_acnpro_rapoarte.prg și proceduri din proceduri_acnpro.prg:

  • Recapitulație tranzit: listeaza_recapitulatie_tranzitproceduri_acnpro.prg:5504. Citește ips_vvoyage_members_vanzari WHERE vz_id = ?pnIdVanzare (...:5515-5584) — toate valorile (tarif, cant, procdist, corect, valval, valctva...) sunt deja calculate și salvate; raportul le afișează. Capacitatea totală a convoiului se recalculează cu fereastră LAST_VALUE(...) OVER(PARTITION BY vms_id) (...:5599-5613). Modul de calcul afișat (textul formulei) se generează cu mod_calcul_tranzit (proceduri_acnpro.prg:5728): TARIF * CANT * %DIST * (%COR + %SUPL) = TOTAL (...:5736,5746).
  • Recapitulație cheiaj: listeaza_recapitulatie_cheiajproceduri_acnpro.prg:5368.
  • Recapitulație penalități: listeaza_recapitulatie_penalitatiproceduri_acnpro.prg:5777. Din factură citește din vederea vpenalitati WHERE id_fact_pen = ?pnIdFact (...:5809-5836); din formularul de calcul (evaluare) folosește cursorul crsRecapitulatie deja calculat. Valorile penalităților NU se recalculează la listare.
  • Rapoarte .frx asociate: Rapoarte/factura*.frx, recapitulatie_tranzit, recapitulatie_cheiaj, recapitulatie_penalitati, registru tranzit.

Regula generală: la listare se citesc valori deja calculate (din ips_voyage_members_vanzari, vpenalitati, ips_regdoc/vanzari). Singurele recalculări la listare sunt: textul „mod de calcul" (mod_calcul_tranzit) și agregările/încadrările de capacitate pe recapitulația de tranzit.


5. Capcane cunoscute

  1. Coeficientul de distanță rotunjit la 2 zecimale poate deveni 0. procdist = MIN(Round(Abs(distanta)/64.4, 2), 1.00) (oacnpro.vc2:3864). Pentru distanțe sub ~0.32 km (0.005 × 64.4) Round(.../64.4, 2) dă 0 → valval = tarif*cant*0*(...) = 0. Câmpul PROCDIST este N(6,3) în schema cursorului (proceduri_acnpro.prg:843) și în ips_voyage_members_vanzari, deci ar putea stoca 3 zecimale, dar calculul rotunjește la 2, nu la 3 — distanțele foarte mici se facturează cu valoare 0.

  2. Împărțire la 0 la valoarea finală. La distribuirea valorii finale introduse manual: Round(lnValoareFinala * VALVAL / lnValoareConvoi, gnPC) (oacnpro.vc2:3891, tranzit; analog oacnpro.vc2:1360, cheiaj). Dacă valoarea convoiului calculat (lnValoareConvoi) este 0 (de ex. toate valval=0 din cauza capcanei 1, sau tarife 0), împărțirea dă eroare/NaN. Codul protejează parțial (condiția lnValoareConvoi > 0 la valoarea minimă, oacnpro.vc2:3883), dar ramura cu valoare finală scrisă manual (oacnpro.vc2:3887) nu verifică lnValoareConvoi <> 0 înainte de împărțire.

  3. Tariful NU se recalculează la valoarea finală (tranzit). Intenționat: pe recapitulație apare tariful de încadrare, nu cel rezultat din valoarea finală (oacnpro.vc2:3896-3899). Diferența de rotunjire ajunge pe primul rând (oacnpro.vc2:3906), deci valval al primului rând poate diferi de tarif*cant*procdist*(corect+procdiv).

  4. Penalități — dublă rotunjire. Formula rotunjește întâi penalitatea/zi și apoi totalul (Round(Round(ppenzi/100*suma, gnZ)*nr_zile, gnZ), proceduri_acnpro.prg:1266). Pentru cele două cote, fiecare termen este rotunjit separat și apoi însumat (proceduri_acnpro.prg:1536), deci totalul poate să nu fie egal cu Round(total_brut, gnZ).

  5. calcul_penalitati2 folosește ireg_parteneri în loc de vanzari (temporar, până la importul vânzărilor) — comentariu în cod (proceduri_acnpro.prg:1284-1286, oacnpro.vc2:9678). Rezultatele pot diferi de calcul_penalitati până la stabilizare.

  6. Distribuția CDMN depinde de DISTANTA <> 0. Round(DISTANTA_CDMN/DISTANTA, 4) (proceduri_acnpro.prg:2332) — dacă DISTANTA este 0, distribuția pe canale/ecluze eșuează.