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>
21 KiB
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 (formfrm_factura→factura_salvare_db→PACK_FACTURARE.pck), structuracrsFactura, 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, undennn= 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 dinroris.*în tabelele localeips_*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ă încalcul_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 cursoruluicrsVoyagesînproceduri_acnpro.prg:838-844. - Cheiaj: vederea
ips_vberthings(citită încalcul_cheiaj,proceduri_acnpro.prg:1023). Câmpuri:datai/datap(intrare/plecare → durată),cap,trn,lbd,hp,lung,tipn_id,dana_id/rpt_id(dana). SchemacrsBerthingsînproceduri_acnpro.prg:1028. - Ecluzări (tranzit):
ips_vvoyage_locks(defalcate pe membru și cameră de ecluzare), citite la distribuția pe ecluze încompleteaza_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_executa → calcul_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) șicrsVoyages(membrii, cu indicatorii dinips_vvoyage_members_calcul). - Calcul valoare: clasa
frm_calcul_tranzit, metodado_executa—oacnpro.vc2:3651-3918.
De unde vin tariful și cantitatea:
- Tarifele:
IPS_VTARIFE_TRANZIT(join cuIPS_VVAS_UM), selectate de DOUĂ ori:- tarife pe contract (
ctr_id = lnClientCtrId) —oacnpro.vc2:3688-3694(cursortTarife); - tarife generale/fără contract (
ctr_id = get_ctr_id_fara_contract()) —oacnpro.vc2:3698-3703(cursortTarife2).
- tarife pe contract (
- Selecția tarifului se face pe tip UM (
TC/TRN/LBD/HP), încadrarea pe capacitate (TCAP1/TCAP2la capacitatea totală a convoiului,TCCAP1/TCCAP2la 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:TC→tcap,TRN→trn,LBD→lbd,HP→hp(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:
Diferența de rotunjire dintre suma recalculată și valoarea finală se pune pe primul rând (
Replace All VALVAL With Round(lnValoareFinala * VALVAL / lnValoareConvoi, gnPC) For ales = 1 && oacnpro.vc2:3891oacnpro.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 dinips_vberthings) șicrsBerthingDetails(prestații de facturat). - Calcul valoare: clasa
frm_calcul_cheiaj, metodado_calcul_tarife—oacnpro.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),
TC→cap, TRN→trn, ML→lung, CP/HP→hp, LBP/LBD→lbd. 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_contract—oacnpro.vc2:1615. Lucrează cucrsArticoleContract(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_penalitati—oacnpro.vc2:8366. Metodado_executa(oacnpro.vc2:9671) apeleazăcalcul_penalitati2(proceduri_acnpro.prg:1286); varianta cu vânzări reale estecalcul_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_penalitati→ppenzi, coef_penalitati2→ppenzi2,
zile_penalitati→penzile, zile_gratie_penalitati→zilegratie)
(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ă)saudatascad(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); vezilnZileProcent1/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_penalitatidoar pentru contracte cuppenzi2 <> 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ă înfrm_factura(crsFactura, vezidocs/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_regdocprinsalvare_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 dincRegDoc(proceduri_acnpro.prg:3430); - pentru TRANZIT/CHEIAJ: actualizează
ips_voyages_vanzari.vz_idcu 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_tranzit—proceduri_acnpro.prg:1779:- inserează capul de convoi facturat în
IPS_VOYAGES_VANZARI(...:1788-1791); - inserează fiecare navă în
IPS_VOYAGE_MEMBERS_VANZARIcu toate valorile calculate (procdist,corect,tarif,cant,valval,distanta,distanta_cdmn, etc.) (...:1794-1849). Tranzacție manuală cu COMMIT/ROLLBACK.
- inserează capul de convoi facturat în
completeaza_regdoc_tranzit(tcVVIds)—proceduri_acnpro.prg:2259— distribuț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) dinips_vvoyage_locksși constantelegnIPSTranzitProcAG/CV/OV/NV(...:2289-2324).
- procentul CDMN al fiecărei nave:
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_contract—proceduri_acnpro.prg:1918— inserează înips_regdoc(tip, articol, locație, valută, client, contract, preț, cantitate, valval, TVA, perioadă) (...:1924-1953).
3.5. PENALITĂȚI — specific
salvare_penalitati—proceduri_acnpro.prg:2008— marchează înregistrările alese/șterse; scrierea reală în tabelapenalitatise face înfactura_salvare_db(proceduri_acnpro.prg:3475-3488).make_factura_penalitati—proceduri_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_tranzit—proceduri_acnpro.prg:3517: citește valorile însumate dinips_voyage_members_vanzari(SUM(valval)pe valută,proceduri_acnpro.prg:3561-3567), numele convoiului dinips_vvoyagesși completeazăcrsFactura(un singur rând, cantitate 1, preț = valval în monedă națională). NU recalculează tarife.make_factura_cheiaj—proceduri_acnpro.prg:3615: analog, pentru cheiaj.make_factura_penalitati—proceduri_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_tranzit—proceduri_acnpro.prg:5504. Citeșteips_vvoyage_members_vanzariWHERE 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ă cumod_calcul_tranzit(proceduri_acnpro.prg:5728):TARIF * CANT * %DIST * (%COR + %SUPL) = TOTAL(...:5736,5746). - Recapitulație cheiaj:
listeaza_recapitulatie_cheiaj—proceduri_acnpro.prg:5368. - Recapitulație penalități:
listeaza_recapitulatie_penalitati—proceduri_acnpro.prg:5777. Din factură citește din vedereavpenalitatiWHERE id_fact_pen = ?pnIdFact(...:5809-5836); din formularul de calcul (evaluare) folosește cursorulcrsRecapitulatiedeja calculat. Valorile penalităților NU se recalculează la listare. - Rapoarte
.frxasociate: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
-
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âmpulPROCDISTesteN(6,3)în schema cursorului (proceduri_acnpro.prg:843) și înips_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. -
Împărțire la 0 la valoarea finală. La distribuirea valorii finale introduse manual:
Round(lnValoareFinala * VALVAL / lnValoareConvoi, gnPC)(oacnpro.vc2:3891, tranzit; analogoacnpro.vc2:1360, cheiaj). Dacă valoarea convoiului calculat (lnValoareConvoi) este 0 (de ex. toatevalval=0din cauza capcanei 1, sau tarife 0), împărțirea dă eroare/NaN. Codul protejează parțial (condițialnValoareConvoi > 0la valoarea minimă,oacnpro.vc2:3883), dar ramura cu valoare finală scrisă manual (oacnpro.vc2:3887) nu verificălnValoareConvoi <> 0înainte de împărțire. -
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), decivalvalal primului rând poate diferi detarif*cant*procdist*(corect+procdiv). -
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 cuRound(total_brut, gnZ). -
calcul_penalitati2foloseșteireg_parteneriîn loc devanzari(temporar, până la importul vânzărilor) — comentariu în cod (proceduri_acnpro.prg:1284-1286,oacnpro.vc2:9678). Rezultatele pot diferi decalcul_penalitatipână la stabilizare. -
Distribuția CDMN depinde de
DISTANTA <> 0.Round(DISTANTA_CDMN/DISTANTA, 4)(proceduri_acnpro.prg:2332) — dacăDISTANTAeste 0, distribuția pe canale/ecluze eșuează.