feat(process_bon): script complet procesare bonuri fiscale
- OCR via roa2web API - SQLite via API (payment_methods, tva_breakdown) - Oracle: partener, TVA încasare (4426/4428), ID_FDOC=17 - ID_JTVA_COLOANA per cotă TVA - TAXCODE (TVAI pentru firma TVA încasare) - Suport multiple cote TVA în același bon - Plată CARD: fără 401=5311 (se face la extras)
This commit is contained in:
386
memory/kb/projects/roa2web-telegram-import/README.md
Normal file
386
memory/kb/projects/roa2web-telegram-import/README.md
Normal file
@@ -0,0 +1,386 @@
|
|||||||
|
# Proiect: Import Bonuri Fiscale via Telegram/WhatsApp → ROA
|
||||||
|
|
||||||
|
**Data:** 2026-02-03
|
||||||
|
**Status:** Analiză completă
|
||||||
|
**Tags:** @work @project
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## TL;DR
|
||||||
|
|
||||||
|
Sistem pentru importul bonurilor fiscale de achiziție din Telegram/WhatsApp în contabilitatea ROA. OCR prin Doctr (cost zero, local). Integrare cu Oracle prin proceduri existente (PACK_CONTAFIN).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Obiective
|
||||||
|
|
||||||
|
- **Input:** Bonuri fiscale trimise pe Telegram/WhatsApp (PDF/imagine)
|
||||||
|
- **Procesare:** OCR local (Doctr) - cost zero
|
||||||
|
- **Output:** Note contabile în Oracle (ACT, BAL, etc.)
|
||||||
|
- **Aprobare:** Opțional - contabil verifică înainte de salvare finală
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Arhitectură
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
|
||||||
|
│ Telegram │ │ │ │ │ │ │
|
||||||
|
│ WhatsApp │────▶│ Clawdbot │────▶│ roa2web │────▶│ ROA Oracle │
|
||||||
|
└─────────────┘ │ (Bridge) │ │ (OCR+API) │ │ (PACK_ │
|
||||||
|
└─────────────┘ └─────────────┘ │ CONTAFIN) │
|
||||||
|
│ │ └─────────────┘
|
||||||
|
▼ ▼ │
|
||||||
|
• Primește fișier • Doctr OCR • ACT_TEMP
|
||||||
|
• Forward la API • PDF text layer • ACT, BAL
|
||||||
|
• Răspuns user • JSON extrase • IREG_PART
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Componente Existente
|
||||||
|
|
||||||
|
### 3.1 roa2web (LXC 171 - claude-agent)
|
||||||
|
|
||||||
|
| Component | Status | Locație |
|
||||||
|
|-----------|--------|---------|
|
||||||
|
| **OCR Engine** | ✅ Complet | `/workspace/roa2web/backend/modules/data_entry/services/ocr_engine.py` |
|
||||||
|
| **Doctr** | ✅ Instalat | PyTorch backend, db_resnet50 + crnn_vgg16_bn |
|
||||||
|
| **PaddleOCR** | ✅ Instalat | Backup engine |
|
||||||
|
| **API OCR** | ✅ Funcțional | `POST /api/data-entry/ocr/extract` |
|
||||||
|
| **Job Queue** | ✅ Complet | Async processing cu worker pool |
|
||||||
|
| **Bot Telegram** | ⚠️ Parțial | Dashboard/sold, NU handler fișiere |
|
||||||
|
|
||||||
|
### 3.2 Schema Date OCR (ExtractionData)
|
||||||
|
|
||||||
|
```python
|
||||||
|
ExtractionData:
|
||||||
|
partner_name: str # nume furnizor
|
||||||
|
cui: str # CUI furnizor
|
||||||
|
receipt_date: date # data bon
|
||||||
|
receipt_number: str # număr bon
|
||||||
|
receipt_series: str # serie bon
|
||||||
|
amount: Decimal # total
|
||||||
|
tva_entries: List[TvaEntry] # TVA pe cote (A=19%, B=9%, etc.)
|
||||||
|
tva_total: Decimal # total TVA
|
||||||
|
payment_methods: List # CARD/NUMERAR
|
||||||
|
address: str # adresă furnizor
|
||||||
|
confidence_*: float # scoruri încredere
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.3 Oracle (MARIUSM_AUTO)
|
||||||
|
|
||||||
|
**Tabele principale:**
|
||||||
|
|
||||||
|
| Tabel | Scop |
|
||||||
|
|-------|------|
|
||||||
|
| **ACT** | Note contabile |
|
||||||
|
| **ACT_TEMP** | Staging pentru note |
|
||||||
|
| **RUL** | Rulaje gestiuni |
|
||||||
|
| **STOC** | Solduri stoc |
|
||||||
|
| **BAL** | Balanță verificare |
|
||||||
|
| **IREG_PARTENERI** | Evidență parteneri |
|
||||||
|
| **BALANTA_PARTENERI** | Balanță parteneri |
|
||||||
|
| **ATAS_ATASAMENTE** | Fișiere atașate (BLOB) |
|
||||||
|
| **ATAS_REFERINTE** | Legături atașamente |
|
||||||
|
| **NOM_FDOC** | Nomenclator tipuri documente |
|
||||||
|
|
||||||
|
**Package-uri relevante:**
|
||||||
|
|
||||||
|
| Package | Scop |
|
||||||
|
|---------|------|
|
||||||
|
| **PACK_CONTAFIN** | Salvare note contabile (principal) |
|
||||||
|
| **PACK_FACTURARE** | Exemple de salvare (pentru vânzări) |
|
||||||
|
| **PACK_PARTENERI** | Gestiune parteneri |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Formula Contabilă - Bon Fiscal Achiziție
|
||||||
|
|
||||||
|
### 4.1 Bon simplu (plătit cash)
|
||||||
|
|
||||||
|
```
|
||||||
|
Cheltuială = Furnizor: 6xx = 401 (valoare fără TVA)
|
||||||
|
TVA deductibil = Furn: 4426 = 401 (TVA)
|
||||||
|
Plată cash: 401 = 5311 (total cu TVA)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Exemplu bon benzină 119 RON (100 + 19 TVA):**
|
||||||
|
```sql
|
||||||
|
-- Linia 1: Cheltuială combustibil
|
||||||
|
SCD='6022', SCC='401', SUMA=100.00
|
||||||
|
|
||||||
|
-- Linia 2: TVA deductibil
|
||||||
|
SCD='4426', SCC='401', SUMA=19.00, PROC_TVA=1.19
|
||||||
|
|
||||||
|
-- Linia 3: Plată din casă
|
||||||
|
SCD='401', SCC='5311', SUMA=119.00
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 Bon cu TVA la încasare
|
||||||
|
|
||||||
|
```
|
||||||
|
Cheltuială = Furnizor: 6xx = 401
|
||||||
|
TVA neexigibil = Furn: 4428 = 401
|
||||||
|
Plată cash: 401 = 5311
|
||||||
|
Exigibilizare TVA: 4426 = 4428
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.3 Conturi cheltuieli uzuale
|
||||||
|
|
||||||
|
| Tip | Cont | Descriere |
|
||||||
|
|-----|------|-----------|
|
||||||
|
| Combustibil | 6022 | Combustibili |
|
||||||
|
| Materiale | 6021 | Materiale auxiliare |
|
||||||
|
| Rechizite | 6021 | Materiale auxiliare |
|
||||||
|
| Piese | 6024 | Piese schimb |
|
||||||
|
| Întreținere | 611 | Cheltuieli întreținere |
|
||||||
|
| Transport | 624 | Cheltuieli transport |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Flux Oracle - Salvare Note Contabile
|
||||||
|
|
||||||
|
### 5.1 Proceduri PACK_CONTAFIN
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 1. INIȚIALIZARE SESIUNE
|
||||||
|
PACK_CONTAFIN.INITIALIZEAZA_SCRIERE_ACT_RUL(
|
||||||
|
TNIDUTIL NUMBER, -- ID utilizator
|
||||||
|
TDDATAORA DATE, -- Data și ora
|
||||||
|
TNAN NUMBER, -- An
|
||||||
|
TNLUNA NUMBER, -- Luna
|
||||||
|
TNSUPRASCRIERECOD NUMBER, -- 0/1
|
||||||
|
TNSUPRASCRIEREANLUNA NUMBER, -- 0/1
|
||||||
|
TNSCRIESTERGE NUMBER, -- 0=scrie, 2=șterge
|
||||||
|
TNIDSUCURSALA NUMBER -- ID sucursală
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 2. INSERT ÎN ACT_TEMP (pentru fiecare linie)
|
||||||
|
INSERT INTO ACT_TEMP (
|
||||||
|
LUNA, AN, COD,
|
||||||
|
DATAIREG, DATAACT, DATASCAD,
|
||||||
|
NRACT, SERIE_ACT,
|
||||||
|
EXPLICATIA,
|
||||||
|
SCD, ASCD, -- cont debit, analitic
|
||||||
|
SCC, ASCC, -- cont credit, analitic
|
||||||
|
SUMA,
|
||||||
|
PROC_TVA,
|
||||||
|
ID_FDOC, -- tip document
|
||||||
|
ID_PARTD, ID_PARTC, -- parteneri
|
||||||
|
ID_FACT,
|
||||||
|
ID_VALUTA,
|
||||||
|
ID_SUCURSALA
|
||||||
|
) VALUES (...);
|
||||||
|
|
||||||
|
-- 3. FINALIZARE (transferă în ACT, actualizează BAL, etc.)
|
||||||
|
PACK_CONTAFIN.FINALIZEAZA_SCRIERE_ACT_RUL(
|
||||||
|
TNIDUTIL NUMBER, -- ID utilizator
|
||||||
|
TNCOD NUMBER, -- Cod document
|
||||||
|
TNSCRIESTERGE NUMBER, -- 0=scrie
|
||||||
|
TNMODIFICARENOTA NUMBER, -- 0/1
|
||||||
|
TNSCRIECUMPVANZ NUMBER, -- 0/1
|
||||||
|
TCMESAJREFACERENOTA OUT VARCHAR2 -- Mesaj rezultat
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.2 Ce face FINALIZEAZA intern
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- La linia 8059 în PACK_CONTAFIN:
|
||||||
|
pack_contafin.SCRIE_IN_ACT(user); -- ACT_TEMP → ACT + BAL
|
||||||
|
pack_contafin.SCRIE_IN_RUL(user); -- RUL_TEMP → RUL (dacă există)
|
||||||
|
pack_contafin.SCRIE_IN_RUL_OBINV(user); -- pentru obiecte inventar
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Structura ACT_TEMP
|
||||||
|
|
||||||
|
```sql
|
||||||
|
ACT_TEMP (
|
||||||
|
LUNA NUMBER,
|
||||||
|
AN NUMBER,
|
||||||
|
COD NUMBER,
|
||||||
|
DATAIREG DATE,
|
||||||
|
NRACT NUMBER,
|
||||||
|
DATAACT DATE,
|
||||||
|
EXPLICATIA VARCHAR2(100),
|
||||||
|
SCD VARCHAR2(10), -- Cont DEBIT
|
||||||
|
ASCD VARCHAR2(20), -- Analitic debit
|
||||||
|
SCC VARCHAR2(10), -- Cont CREDIT
|
||||||
|
ASCC VARCHAR2(20), -- Analitic credit
|
||||||
|
SUMA NUMBER,
|
||||||
|
PROC_TVA NUMBER, -- Procent TVA (1.19, 1.09, etc.)
|
||||||
|
ID_FDOC NUMBER, -- Tip document (din NOM_FDOC)
|
||||||
|
ID_PARTD NUMBER, -- Partner debit
|
||||||
|
ID_PARTC NUMBER, -- Partner credit
|
||||||
|
ID_FACT NUMBER, -- ID factură (opțional)
|
||||||
|
ID_VALUTA NUMBER,
|
||||||
|
ID_SUCURSALA NUMBER,
|
||||||
|
SERIE_ACT VARCHAR2(10),
|
||||||
|
TVA_INCASARE NUMBER, -- 0/1 pentru TVA la încasare
|
||||||
|
...
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Optimizări Propuse
|
||||||
|
|
||||||
|
### 7.1 PDF Text Layer Extraction
|
||||||
|
|
||||||
|
Adobe Scan creează PDF-uri cu text layer. Verificăm înainte de OCR:
|
||||||
|
|
||||||
|
```python
|
||||||
|
import pdfplumber
|
||||||
|
|
||||||
|
def extract_text_or_ocr(pdf_path):
|
||||||
|
with pdfplumber.open(pdf_path) as pdf:
|
||||||
|
text = ""
|
||||||
|
for page in pdf.pages:
|
||||||
|
page_text = page.extract_text()
|
||||||
|
if page_text:
|
||||||
|
text += page_text
|
||||||
|
|
||||||
|
if text.strip():
|
||||||
|
# PDF are text layer - folosește-l direct
|
||||||
|
return {"source": "pdf_layer", "text": text}
|
||||||
|
else:
|
||||||
|
# Nu are text - fallback la Doctr OCR
|
||||||
|
return doctr_ocr(pdf_path)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Beneficii:**
|
||||||
|
- Instant vs. 2-5 sec OCR
|
||||||
|
- 100% acuratețe vs. ~95% OCR
|
||||||
|
- Zero procesare GPU
|
||||||
|
|
||||||
|
### 7.2 Mapare CUI → Cont
|
||||||
|
|
||||||
|
Tabel pentru auto-detectare tip cheltuială:
|
||||||
|
|
||||||
|
```python
|
||||||
|
CUI_CONT_MAP = {
|
||||||
|
# Benzinării
|
||||||
|
"RO1234567": "6022", # OMV
|
||||||
|
"RO2345678": "6022", # Petrom
|
||||||
|
"RO3456789": "6022", # Mol
|
||||||
|
|
||||||
|
# Magazine bricolaj
|
||||||
|
"RO10562600": "6021", # Dedeman
|
||||||
|
|
||||||
|
# Default
|
||||||
|
"DEFAULT": "6028" # Alte cheltuieli
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Status Implementare (2026-02-03)
|
||||||
|
|
||||||
|
| Component | Status | Note |
|
||||||
|
|-----------|--------|------|
|
||||||
|
| OCR (Doctr/PaddleOCR) | ✅ Testat | 34 sec per PDF |
|
||||||
|
| Script OCR standalone | ✅ Funcțional | `/workspace/roa2web-telegram-test/` |
|
||||||
|
| Python: INSERT ACT_TEMP | ✅ Testat | 3 linii: cheltuială + TVA + plată |
|
||||||
|
| Mapare CUI → cont | ✅ Bază | 6022=combustibil, 6021=materiale |
|
||||||
|
| Găsire partener | ✅ Funcțional | Caută în NOM_PARTENERI |
|
||||||
|
| Conexiune Oracle | ✅ Testat | Server 10.0.20.121 |
|
||||||
|
| Handler WhatsApp | 🔄 În pregătire | Clawdbot poate primi fișiere |
|
||||||
|
| CALL PACK_CONTAFIN | ⏳ TODO | Finalizare cu procedurile Oracle |
|
||||||
|
| UI aprobare | ⏳ TODO | Opțional |
|
||||||
|
|
||||||
|
### Scripturi create:
|
||||||
|
- `/workspace/roa2web-telegram-test/process_and_save.py` - OCR → Oracle
|
||||||
|
- `/workspace/roa2web-telegram-test/save_to_oracle.py` - test salvare Oracle
|
||||||
|
|
||||||
|
### Test efectuat:
|
||||||
|
```
|
||||||
|
PDF "benzina 07 aug. 2024.pdf" → OCR → Oracle ACT_TEMP
|
||||||
|
CUI: RO11201891
|
||||||
|
Data: 2024-08-01
|
||||||
|
Total: 263.28 RON
|
||||||
|
TVA: 42.04 (19%)
|
||||||
|
Note: 6022=401, 4426=401, 401=5311
|
||||||
|
Status: DRY RUN OK
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Plan Implementare
|
||||||
|
|
||||||
|
### Faza 1: Handler Telegram (3-4h)
|
||||||
|
- [ ] MessageHandler pentru imagini/PDF în bot
|
||||||
|
- [ ] Forward la `/api/data-entry/ocr/extract`
|
||||||
|
- [ ] Răspuns cu datele extrase (preview)
|
||||||
|
|
||||||
|
### Faza 2: PDF Optimization (1-2h)
|
||||||
|
- [ ] Verificare text layer în PDF
|
||||||
|
- [ ] Extracție directă dacă există
|
||||||
|
- [ ] Fallback la Doctr
|
||||||
|
|
||||||
|
### Faza 3: Integrare Oracle (5-7h)
|
||||||
|
- [ ] Conexiune Oracle din roa2web
|
||||||
|
- [ ] Găsire/creare partener după CUI
|
||||||
|
- [ ] INSERT în ACT_TEMP
|
||||||
|
- [ ] Apel PACK_CONTAFIN proceduri
|
||||||
|
- [ ] Salvare atașament (ATAS_ATASAMENTE)
|
||||||
|
|
||||||
|
### Faza 4: WhatsApp Bridge (4-6h)
|
||||||
|
- [ ] Clawdbot handler pentru fișiere
|
||||||
|
- [ ] Forward la roa2web API
|
||||||
|
- [ ] Răspuns pe canal
|
||||||
|
|
||||||
|
### Faza 5: Aprobare & Polish (3-5h)
|
||||||
|
- [ ] Workflow aprobare (opțional)
|
||||||
|
- [ ] Mapare automată CUI → cont
|
||||||
|
- [ ] Logging și error handling
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Credențiale Test
|
||||||
|
|
||||||
|
**Oracle Test Server:**
|
||||||
|
```
|
||||||
|
Host: 10.0.20.121
|
||||||
|
Port: 1521
|
||||||
|
SID: ROA
|
||||||
|
User: CONTAFIN_ORACLE
|
||||||
|
Pass: (în secrets/test.oracle_pass)
|
||||||
|
Schema: MARIUSM_AUTO
|
||||||
|
```
|
||||||
|
|
||||||
|
**roa2web:**
|
||||||
|
```
|
||||||
|
LXC: 171 (claude-agent)
|
||||||
|
Path: /workspace/roa2web
|
||||||
|
Start: ./start.sh test
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. Fișiere Proiect
|
||||||
|
|
||||||
|
```
|
||||||
|
roa2web-telegram-import/
|
||||||
|
├── README.md # Acest fișier
|
||||||
|
├── schema-oracle.md # Detalii schema Oracle
|
||||||
|
├── flux-contabil.md # Formule contabile detaliate
|
||||||
|
├── api-spec.md # Specificații API (de creat)
|
||||||
|
└── implementation-notes.md # Note implementare (de creat)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. Întrebări Deschise
|
||||||
|
|
||||||
|
1. **Workflow aprobare:** E necesar ca un contabil să aprobe înainte de salvare?
|
||||||
|
2. **Tip document (ID_FDOC):** Ce valoare pentru bonuri fiscale achiziție?
|
||||||
|
3. **Analitice:** Sunt necesare analitice pe conturi (ASCD, ASCC)?
|
||||||
|
4. **Sucursală:** Valoare default pentru ID_SUCURSALA?
|
||||||
|
5. **Atașamente:** Unde se face legătura ATAS_REFERINTE (ID_ENTITATE = ID_ACT)?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Analiză generată de Echo • 2026-02-03*
|
||||||
229
memory/kb/projects/roa2web-telegram-import/flux-contabil.md
Normal file
229
memory/kb/projects/roa2web-telegram-import/flux-contabil.md
Normal file
@@ -0,0 +1,229 @@
|
|||||||
|
# Flux Contabil - Import Bonuri Fiscale Achiziție
|
||||||
|
|
||||||
|
**Actualizat:** 2026-02-03
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Formula Contabilă Generală
|
||||||
|
|
||||||
|
### Bon fiscal achiziție (plătit cash)
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ DEBIT │ CREDIT │ SUMA │ DESCRIERE │
|
||||||
|
├──────────────────┼─────────────────┼──────────┼─────────────┤
|
||||||
|
│ 6xx (cheltuială)│ 401 (furnizor) │ fără TVA│ Cheltuială │
|
||||||
|
│ 4426 (TVA ded.) │ 401 (furnizor) │ TVA │ TVA deduct.│
|
||||||
|
│ 401 (furnizor) │ 5311 (casa) │ cu TVA │ Plată cash │
|
||||||
|
└──────────────────┴─────────────────┴──────────┴─────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### Bon fiscal cu TVA la încasare
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ DEBIT │ CREDIT │ SUMA │ DESCRIERE │
|
||||||
|
├──────────────────┼─────────────────┼──────────┼─────────────┤
|
||||||
|
│ 6xx (cheltuială)│ 401 (furnizor) │ fără TVA│ Cheltuială │
|
||||||
|
│ 4428 (TVA neex.)│ 401 (furnizor) │ TVA │ TVA neexig.│
|
||||||
|
│ 401 (furnizor) │ 5311 (casa) │ cu TVA │ Plată cash │
|
||||||
|
│ 4426 (TVA ded.) │ 4428 (TVA neex)│ TVA │ Exigibiliz.│
|
||||||
|
└──────────────────┴─────────────────┴──────────┴─────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Conturi Cheltuieli (6xx)
|
||||||
|
|
||||||
|
| Cont | Denumire | Utilizare |
|
||||||
|
|------|----------|-----------|
|
||||||
|
| 6021 | Cheltuieli cu materialele auxiliare | Rechizite, materiale consumabile |
|
||||||
|
| 6022 | Cheltuieli privind combustibilii | Benzină, motorină, GPL |
|
||||||
|
| 6024 | Cheltuieli privind piesele de schimb | Piese auto, componente |
|
||||||
|
| 6028 | Cheltuieli privind alte materiale consumabile | Diverse materiale |
|
||||||
|
| 604 | Cheltuieli privind materialele nestocate | Materiale directe în consum |
|
||||||
|
| 611 | Cheltuieli cu întreținerea și reparațiile | Service, reparații |
|
||||||
|
| 612 | Cheltuieli cu redevențele | Licențe, abonamente |
|
||||||
|
| 613 | Cheltuieli cu primele de asigurare | Asigurări |
|
||||||
|
| 614 | Cheltuieli cu studiile și cercetările | Consultanță |
|
||||||
|
| 622 | Cheltuieli privind comisioanele | Comisioane bancare |
|
||||||
|
| 623 | Cheltuieli de protocol, reclamă | Protocol, publicitate |
|
||||||
|
| 624 | Cheltuieli cu transportul | Transport bunuri/persoane |
|
||||||
|
| 625 | Cheltuieli cu deplasările | Diurnă, cazare, transport |
|
||||||
|
| 626 | Cheltuieli poștale și telecomunicații | Telefon, internet |
|
||||||
|
| 627 | Cheltuieli cu serviciile bancare | Comisioane bancare |
|
||||||
|
| 628 | Alte cheltuieli cu serviciile | Diverse servicii |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Mapare Automată CUI → Cont
|
||||||
|
|
||||||
|
### Benzinării
|
||||||
|
|
||||||
|
| CUI | Denumire | Cont |
|
||||||
|
|-----|----------|------|
|
||||||
|
| RO1590082 | OMV PETROM | 6022 |
|
||||||
|
| RO1025tried | ROMPETROL | 6022 |
|
||||||
|
| RO14991381 | MOL ROMANIA | 6022 |
|
||||||
|
| RO524186 | LUKOIL | 6022 |
|
||||||
|
|
||||||
|
### Magazine Bricolaj
|
||||||
|
|
||||||
|
| CUI | Denumire | Cont |
|
||||||
|
|-----|----------|------|
|
||||||
|
| RO10562600 | DEDEMAN | 6021/6028 |
|
||||||
|
| RO6323780 | HORNBACH | 6021/6028 |
|
||||||
|
| RO14688790 | LEROY MERLIN | 6021/6028 |
|
||||||
|
|
||||||
|
### Supermarket-uri
|
||||||
|
|
||||||
|
| CUI | Denumire | Cont |
|
||||||
|
|-----|----------|------|
|
||||||
|
| RO15tried | MEGA IMAGE | 6028 |
|
||||||
|
| RO5765323 | KAUFLAND | 6028 |
|
||||||
|
| RO3891449 | CARREFOUR | 6028 |
|
||||||
|
| RO6334171 | LIDL | 6028 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Exemplu Complet - Bon Benzină
|
||||||
|
|
||||||
|
**Bon:**
|
||||||
|
- Furnizor: OMV PETROM (CUI: RO1590082)
|
||||||
|
- Data: 2026-02-03
|
||||||
|
- Număr bon: 12345
|
||||||
|
- Total: 250.00 RON (210.08 + 39.92 TVA 19%)
|
||||||
|
|
||||||
|
### 4.1 Date extrase OCR
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"partner_name": "OMV PETROM MARKETING SRL",
|
||||||
|
"cui": "RO1590082",
|
||||||
|
"receipt_date": "2026-02-03",
|
||||||
|
"receipt_number": "12345",
|
||||||
|
"amount": 250.00,
|
||||||
|
"tva_entries": [
|
||||||
|
{"code": "A", "percent": 19, "amount": 39.92}
|
||||||
|
],
|
||||||
|
"tva_total": 39.92,
|
||||||
|
"payment_methods": [{"method": "NUMERAR", "amount": 250.00}]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 Note Contabile Generate
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- LINIA 1: Cheltuială combustibil
|
||||||
|
INSERT INTO ACT_TEMP (
|
||||||
|
LUNA, AN, COD, DATAIREG, DATAACT, NRACT,
|
||||||
|
EXPLICATIA,
|
||||||
|
SCD, SCC, SUMA,
|
||||||
|
ID_PARTC, ID_FDOC
|
||||||
|
) VALUES (
|
||||||
|
2, 2026, :cod, SYSDATE, TO_DATE('2026-02-03', 'YYYY-MM-DD'), 12345,
|
||||||
|
'Combustibil OMV bon 12345',
|
||||||
|
'6022', '401', 210.08,
|
||||||
|
:id_part_omv, :id_fdoc_bon
|
||||||
|
);
|
||||||
|
|
||||||
|
-- LINIA 2: TVA deductibil
|
||||||
|
INSERT INTO ACT_TEMP (
|
||||||
|
LUNA, AN, COD, DATAIREG, DATAACT, NRACT,
|
||||||
|
EXPLICATIA,
|
||||||
|
SCD, SCC, SUMA, PROC_TVA,
|
||||||
|
ID_PARTC, ID_FDOC
|
||||||
|
) VALUES (
|
||||||
|
2, 2026, :cod, SYSDATE, TO_DATE('2026-02-03', 'YYYY-MM-DD'), 12345,
|
||||||
|
'TVA combustibil OMV bon 12345',
|
||||||
|
'4426', '401', 39.92, 1.19,
|
||||||
|
:id_part_omv, :id_fdoc_bon
|
||||||
|
);
|
||||||
|
|
||||||
|
-- LINIA 3: Plată din casă
|
||||||
|
INSERT INTO ACT_TEMP (
|
||||||
|
LUNA, AN, COD, DATAIREG, DATAACT, NRACT,
|
||||||
|
EXPLICATIA,
|
||||||
|
SCD, SCC, SUMA,
|
||||||
|
ID_PARTD, ID_FDOC
|
||||||
|
) VALUES (
|
||||||
|
2, 2026, :cod, SYSDATE, TO_DATE('2026-02-03', 'YYYY-MM-DD'), 12345,
|
||||||
|
'Plata bon OMV 12345',
|
||||||
|
'401', '5311', 250.00,
|
||||||
|
:id_part_omv, :id_fdoc_bon
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.3 Flux Complet Python
|
||||||
|
|
||||||
|
```python
|
||||||
|
import oracledb
|
||||||
|
|
||||||
|
def import_bon_achizitie(extraction_data, conn):
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
# 1. Găsește sau creează partenerul
|
||||||
|
id_part = find_or_create_partner(cursor, extraction_data['cui'], extraction_data['partner_name'])
|
||||||
|
|
||||||
|
# 2. Determină contul de cheltuială
|
||||||
|
cont_cheltuiala = get_cont_by_cui(extraction_data['cui']) # ex: '6022'
|
||||||
|
|
||||||
|
# 3. Calculează sumele
|
||||||
|
total_cu_tva = extraction_data['amount']
|
||||||
|
tva = extraction_data['tva_total']
|
||||||
|
total_fara_tva = total_cu_tva - tva
|
||||||
|
|
||||||
|
# 4. Inițializare
|
||||||
|
cursor.callproc('MARIUSM_AUTO.PACK_CONTAFIN.INITIALIZEAZA_SCRIERE_ACT_RUL', [
|
||||||
|
id_util, # ID utilizator
|
||||||
|
datetime.now(),
|
||||||
|
an,
|
||||||
|
luna,
|
||||||
|
0, # suprascriere_cod
|
||||||
|
0, # suprascriere_anluna
|
||||||
|
0, # scrie_sterge (0=scrie)
|
||||||
|
id_sucursala
|
||||||
|
])
|
||||||
|
|
||||||
|
# 5. INSERT în ACT_TEMP (cele 3 linii)
|
||||||
|
# ... INSERT-uri ca mai sus ...
|
||||||
|
|
||||||
|
# 6. Finalizare
|
||||||
|
mesaj = cursor.var(oracledb.STRING)
|
||||||
|
cursor.callproc('MARIUSM_AUTO.PACK_CONTAFIN.FINALIZEAZA_SCRIERE_ACT_RUL', [
|
||||||
|
id_util,
|
||||||
|
cod,
|
||||||
|
0, # scrie_sterge
|
||||||
|
0, # modificare_nota
|
||||||
|
0, # scrie_cump_vanz
|
||||||
|
mesaj
|
||||||
|
])
|
||||||
|
|
||||||
|
conn.commit()
|
||||||
|
return mesaj.getvalue()
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Cote TVA România
|
||||||
|
|
||||||
|
| Cotă | Procent | PROC_TVA | Aplicare |
|
||||||
|
|------|---------|----------|----------|
|
||||||
|
| Standard | 19% | 1.19 | Majoritatea bunurilor/serviciilor |
|
||||||
|
| Redus 1 | 9% | 1.09 | Alimente, medicamente, cărți |
|
||||||
|
| Redus 2 | 5% | 1.05 | Locuințe sociale |
|
||||||
|
| Zero | 0% | 1.00 | Export, anumite servicii |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Validări Necesare
|
||||||
|
|
||||||
|
1. **CUI valid** - verificare checksum
|
||||||
|
2. **Data bon** - nu în viitor, nu mai veche de 1 an
|
||||||
|
3. **Sumă** - pozitivă, format corect
|
||||||
|
4. **TVA** - să corespundă cu cotele legale
|
||||||
|
5. **Balanță** - SUMA(debit) = SUMA(credit)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Flux documentat de Echo • 2026-02-03*
|
||||||
275
memory/kb/projects/roa2web-telegram-import/schema-oracle.md
Normal file
275
memory/kb/projects/roa2web-telegram-import/schema-oracle.md
Normal file
@@ -0,0 +1,275 @@
|
|||||||
|
# Schema Oracle - ROA (MARIUSM_AUTO)
|
||||||
|
|
||||||
|
**Actualizat:** 2026-02-03
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Tabele Principale
|
||||||
|
|
||||||
|
### ACT - Note Contabile
|
||||||
|
|
||||||
|
```sql
|
||||||
|
ACT (
|
||||||
|
ID_ACT NUMBER PRIMARY KEY,
|
||||||
|
LUNA NUMBER,
|
||||||
|
AN NUMBER,
|
||||||
|
COD NUMBER, -- Cod document unic
|
||||||
|
DATAIREG DATE, -- Data înregistrare
|
||||||
|
NRACT NUMBER, -- Număr act
|
||||||
|
DATAACT DATE, -- Data act
|
||||||
|
EXPLICATIA VARCHAR2(100), -- Descriere
|
||||||
|
SCD VARCHAR2(10), -- Cont DEBIT
|
||||||
|
ASCD VARCHAR2(20), -- Analitic debit
|
||||||
|
SCC VARCHAR2(10), -- Cont CREDIT
|
||||||
|
ASCC VARCHAR2(20), -- Analitic credit
|
||||||
|
SUMA NUMBER, -- Sumă
|
||||||
|
ID_FACTD NUMBER, -- Factură debit
|
||||||
|
ID_FACTC NUMBER, -- Factură credit
|
||||||
|
PERECHED NUMBER,
|
||||||
|
PERECHEC NUMBER,
|
||||||
|
SUMA_VAL NUMBER, -- Sumă valută
|
||||||
|
CURS NUMBER, -- Curs valutar
|
||||||
|
DATASCAD DATE, -- Data scadență
|
||||||
|
ID_UTIL NUMBER, -- Utilizator creare
|
||||||
|
DATAORA DATE, -- Data/ora creare
|
||||||
|
ID_UTILS NUMBER, -- Utilizator modificare
|
||||||
|
DATAORAS DATE, -- Data/ora modificare
|
||||||
|
TVA_INCASARE NUMBER, -- Flag TVA la încasare
|
||||||
|
SERIE_ACT VARCHAR2(10), -- Serie act
|
||||||
|
ID_PART NUMBER, -- ID partener
|
||||||
|
ID_CTR NUMBER, -- ID contract
|
||||||
|
CONT VARCHAR2(10),
|
||||||
|
ACONT VARCHAR2(20),
|
||||||
|
ID_VALUTA NUMBER,
|
||||||
|
ID_SUCURSALA NUMBER,
|
||||||
|
ID_SET NUMBER,
|
||||||
|
TIP_SAFT NUMBER
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### ACT_TEMP - Staging Note Contabile
|
||||||
|
|
||||||
|
Aceeași structură ca ACT, folosită pentru inserare temporară înainte de transfer.
|
||||||
|
|
||||||
|
### RUL - Rulaje Gestiuni
|
||||||
|
|
||||||
|
```sql
|
||||||
|
RUL (
|
||||||
|
ID_RUL NUMBER PRIMARY KEY,
|
||||||
|
AN NUMBER,
|
||||||
|
LUNA NUMBER,
|
||||||
|
COD NUMBER,
|
||||||
|
NNIR NUMBER, -- Număr NIR
|
||||||
|
ID_ARTICOL NUMBER,
|
||||||
|
ID_GESTIUNE NUMBER,
|
||||||
|
PRET NUMBER,
|
||||||
|
PRETV NUMBER, -- Preț vânzare
|
||||||
|
TVA NUMBER,
|
||||||
|
TVAV NUMBER,
|
||||||
|
CANT NUMBER, -- Cantitate
|
||||||
|
CANTE NUMBER,
|
||||||
|
CONT VARCHAR2(10),
|
||||||
|
ACONT VARCHAR2(20),
|
||||||
|
ADAOS NUMBER,
|
||||||
|
ID_LUCRARE NUMBER,
|
||||||
|
ID_RESPONSABIL NUMBER,
|
||||||
|
DATAACT DATE,
|
||||||
|
PROCENT NUMBER,
|
||||||
|
CURS NUMBER,
|
||||||
|
PRETD NUMBER,
|
||||||
|
ID_UTIL NUMBER,
|
||||||
|
DATAORA DATE,
|
||||||
|
ID_SECTIE NUMBER,
|
||||||
|
PROC_TVA NUMBER,
|
||||||
|
ID_FACT NUMBER,
|
||||||
|
STERS NUMBER,
|
||||||
|
ID_TIP_RULAJ NUMBER, -- 0=intrare/ieșire reală
|
||||||
|
ID_SET NUMBER,
|
||||||
|
ID_SUCURSALA NUMBER,
|
||||||
|
NRACT NUMBER,
|
||||||
|
SERIE_ACT VARCHAR2(10),
|
||||||
|
ID_VALUTA NUMBER,
|
||||||
|
LOT VARCHAR2(50),
|
||||||
|
ADATA_EXPIRARE DATE,
|
||||||
|
...
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### NOM_TIP_RULAJ - Tipuri Rulaj
|
||||||
|
|
||||||
|
```sql
|
||||||
|
(0, 'INTRARE/IESIRE REALA')
|
||||||
|
(1, 'TRANSFER')
|
||||||
|
(2, 'RULAJ CMP')
|
||||||
|
(6, 'INVENTARIERE')
|
||||||
|
```
|
||||||
|
|
||||||
|
### STOC - Stocuri
|
||||||
|
|
||||||
|
```sql
|
||||||
|
STOC (
|
||||||
|
ID_STOC NUMBER PRIMARY KEY,
|
||||||
|
AN NUMBER,
|
||||||
|
LUNA NUMBER,
|
||||||
|
ID_ARTICOL NUMBER,
|
||||||
|
PRET NUMBER,
|
||||||
|
PRETV NUMBER,
|
||||||
|
TVA NUMBER,
|
||||||
|
TVAV NUMBER,
|
||||||
|
CANTS NUMBER, -- Cantitate sold
|
||||||
|
CANT NUMBER,
|
||||||
|
CANTE NUMBER,
|
||||||
|
CONT VARCHAR2(10),
|
||||||
|
ID_GESTIUNE NUMBER,
|
||||||
|
...
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### BAL - Balanță Verificare
|
||||||
|
|
||||||
|
```sql
|
||||||
|
BAL (
|
||||||
|
ID_BAL NUMBER PRIMARY KEY,
|
||||||
|
CONT VARCHAR2(10),
|
||||||
|
PRECDEB1 NUMBER, -- Precedent debit (an)
|
||||||
|
PRECCRED1 NUMBER,
|
||||||
|
PRECDEB NUMBER, -- Precedent debit (lună)
|
||||||
|
PRECCRED NUMBER,
|
||||||
|
RULDEB NUMBER, -- Rulaj debit
|
||||||
|
RULCRED NUMBER, -- Rulaj credit
|
||||||
|
AN NUMBER,
|
||||||
|
LUNA NUMBER,
|
||||||
|
ID_SUCURSALA NUMBER
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### IREG_PARTENERI - Evidență Parteneri
|
||||||
|
|
||||||
|
```sql
|
||||||
|
IREG_PARTENERI (
|
||||||
|
ID_IREG_PART NUMBER PRIMARY KEY,
|
||||||
|
AN NUMBER,
|
||||||
|
LUNA NUMBER,
|
||||||
|
ID_PART NUMBER,
|
||||||
|
CONT VARCHAR2(10),
|
||||||
|
ACONT VARCHAR2(20),
|
||||||
|
DEBIT NUMBER,
|
||||||
|
CREDIT NUMBER,
|
||||||
|
VALDEBIT NUMBER,
|
||||||
|
VALCREDIT NUMBER,
|
||||||
|
CURS NUMBER,
|
||||||
|
ID_FDOC NUMBER, -- Tip document
|
||||||
|
DATAACT DATE,
|
||||||
|
DATAIREG DATE,
|
||||||
|
DATASCAD DATE,
|
||||||
|
NRACT NUMBER,
|
||||||
|
ID_RESPONSABIL NUMBER,
|
||||||
|
PRECDEB NUMBER,
|
||||||
|
PRECCRED NUMBER,
|
||||||
|
...
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### ATAS_ATASAMENTE - Fișiere Atașate
|
||||||
|
|
||||||
|
```sql
|
||||||
|
ATAS_ATASAMENTE (
|
||||||
|
ID_ATAS NUMBER PRIMARY KEY,
|
||||||
|
NUME_FISIER VARCHAR2(200),
|
||||||
|
FISIER BLOB, -- Conținut fișier
|
||||||
|
ID_UTIL NUMBER,
|
||||||
|
DATAORA DATE,
|
||||||
|
STERS NUMBER,
|
||||||
|
ID_UTILS NUMBER,
|
||||||
|
DATAORAS DATE,
|
||||||
|
DESCRIERE VARCHAR2(500),
|
||||||
|
ID_LINK NUMBER,
|
||||||
|
ID_TIP_ENT_PREL NUMBER
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### ATAS_REFERINTE - Legături Atașamente
|
||||||
|
|
||||||
|
```sql
|
||||||
|
ATAS_REFERINTE (
|
||||||
|
ID_REFERINTA NUMBER PRIMARY KEY,
|
||||||
|
ID_ATAS NUMBER, -- FK la ATAS_ATASAMENTE
|
||||||
|
ID_ENTITATE NUMBER, -- ID din tabela referită
|
||||||
|
ID_UTIL NUMBER,
|
||||||
|
DATAORA DATE,
|
||||||
|
STERS NUMBER,
|
||||||
|
ID_UTILS NUMBER,
|
||||||
|
DATAORAS DATE,
|
||||||
|
ID_PROGRAM NUMBER, -- Identificator tabel/modul
|
||||||
|
ORDINE NUMBER
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### NOM_FDOC - Nomenclator Tipuri Documente
|
||||||
|
|
||||||
|
```sql
|
||||||
|
NOM_FDOC (
|
||||||
|
ID_FDOC NUMBER PRIMARY KEY,
|
||||||
|
FEL_DOCUMENT VARCHAR2(50),
|
||||||
|
STERS NUMBER,
|
||||||
|
ID_MOD NUMBER,
|
||||||
|
INACTIV NUMBER,
|
||||||
|
TIP_DOC VARCHAR2(5),
|
||||||
|
PAYMENTCODE VARCHAR2(10)
|
||||||
|
)
|
||||||
|
|
||||||
|
-- Exemple:
|
||||||
|
(1, 'ABONAMENT')
|
||||||
|
(3, 'AVANSURI')
|
||||||
|
(5, 'AVIZ EXP')
|
||||||
|
(6, 'BILET')
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Package-uri Oracle
|
||||||
|
|
||||||
|
### PACK_CONTAFIN (principal)
|
||||||
|
|
||||||
|
**Proceduri principale:**
|
||||||
|
- `INITIALIZEAZA_SCRIERE_ACT_RUL` - Inițializare sesiune
|
||||||
|
- `SCRIE_IN_ACT` - Transfer ACT_TEMP → ACT
|
||||||
|
- `SCRIE_IN_BALANTA` - Actualizare balanță
|
||||||
|
- `SCRIE_IN_RUL` - Transfer RUL_TEMP → RUL
|
||||||
|
- `SCRIE_IN_STOC` - Actualizare stoc
|
||||||
|
- `SCRIE_IN_IREG_PARTENERI` - Actualizare evidență parteneri
|
||||||
|
- `FINALIZEAZA_SCRIERE_ACT_RUL` - Finalizare (apelează toate cele de mai sus)
|
||||||
|
|
||||||
|
### PACK_FACTURARE (referință)
|
||||||
|
|
||||||
|
**Exemple INSERT ACT_TEMP:**
|
||||||
|
- Liniile 1000-1200 în package body
|
||||||
|
- Model pentru structura datelor
|
||||||
|
- NU pentru achiziții, dar util ca referință
|
||||||
|
|
||||||
|
### PACK_PARTENERI
|
||||||
|
|
||||||
|
- Gestiune parteneri (furnizori, clienți)
|
||||||
|
- Căutare/creare partener după CUI
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Conexiune Python
|
||||||
|
|
||||||
|
```python
|
||||||
|
import oracledb
|
||||||
|
|
||||||
|
conn = oracledb.connect(
|
||||||
|
user="CONTAFIN_ORACLE",
|
||||||
|
password="ROMFASTSOFT",
|
||||||
|
dsn="10.0.20.121:1521/ROA"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Toate operațiile folosesc schema MARIUSM_AUTO
|
||||||
|
cursor.execute("SELECT * FROM MARIUSM_AUTO.ACT WHERE ROWNUM <= 1")
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Schema documentată de Echo • 2026-02-03*
|
||||||
588
tools/process_bon.py
Normal file
588
tools/process_bon.py
Normal file
@@ -0,0 +1,588 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Procesare bon fiscal: PDF → OCR API → SQLite API → Oracle
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
python process_bon.py <pdf_path> [--save]
|
||||||
|
|
||||||
|
--save Salvează efectiv în Oracle (altfel dry run)
|
||||||
|
|
||||||
|
Fluxul:
|
||||||
|
1. OCR extract via API (http://10.0.20.171:8000/api/data-entry/ocr/extract)
|
||||||
|
2. Save receipt via API (http://10.0.20.171:8000/api/data-entry/receipts/) - TOATE datele
|
||||||
|
3. Save to Oracle:
|
||||||
|
- Verifică/creează partener
|
||||||
|
- Verifică TVA la încasare (CALENDAR.TVA_INCASARE)
|
||||||
|
- Generează note contabile corecte
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
import argparse
|
||||||
|
from pathlib import Path
|
||||||
|
from datetime import datetime
|
||||||
|
from decimal import Decimal
|
||||||
|
|
||||||
|
import requests
|
||||||
|
import oracledb
|
||||||
|
|
||||||
|
# === CONFIG ===
|
||||||
|
API_BASE = "http://10.0.20.171:8000"
|
||||||
|
API_USER = "MARIUS M"
|
||||||
|
API_PASS = "123"
|
||||||
|
SERVER_ID = "central"
|
||||||
|
COMPANY_ID = 110 # MARIUSM AUTO
|
||||||
|
|
||||||
|
ORACLE_CONFIG = {
|
||||||
|
"user": "MARIUSM_AUTO",
|
||||||
|
"password": "ROMFASTSOFT",
|
||||||
|
"dsn": "10.0.20.121:1521/ROA"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Mapare CUI → cont cheltuială
|
||||||
|
CUI_TO_CONT = {
|
||||||
|
"11201891": "6022", # MOL
|
||||||
|
"1590082": "6022", # OMV Petrom
|
||||||
|
"14991381": "6022", # Rompetrol
|
||||||
|
"10562600": "6021", # Dedeman / Five Holding (Brick)
|
||||||
|
"1879865": "6021", # Five Holding
|
||||||
|
}
|
||||||
|
|
||||||
|
# Mapare cotă TVA → (ID_JTVA baza, ID_JTVA tva, TAXCODE, TAXCODE_TVAI)
|
||||||
|
# Pentru achiziții interne neexigibile (TVA la încasare)
|
||||||
|
JTVA_NEEX = {
|
||||||
|
21: (210, 211, 301104, 301305), # ACH. INT. NEEX. 21%
|
||||||
|
19: (188, 189, 301101, 301301), # ACH. INT. NEEX. 19%
|
||||||
|
11: (214, 215, 301105, 301306), # ACH. INT. NEEX. 11%
|
||||||
|
9: (172, 173, 301102, 301302), # ACH. INT. NEEX. 9%
|
||||||
|
5: (174, 175, 301103, 301303), # ACH. INT. NEEX. 5%
|
||||||
|
}
|
||||||
|
|
||||||
|
# Pentru achiziții interne normale (fără TVA la încasare)
|
||||||
|
JTVA_NORMAL = {
|
||||||
|
21: (208, 209, 301104, 301305), # ACH. INT. 21%
|
||||||
|
19: (None, None, 301101, 301301),
|
||||||
|
9: (None, None, 301102, 301302),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_cont(cui: str) -> str:
|
||||||
|
"""Mapare CUI → cont cheltuială."""
|
||||||
|
cui_clean = (cui or "").upper().replace("RO", "").strip()
|
||||||
|
return CUI_TO_CONT.get(cui_clean, "6028") # 6028 = alte cheltuieli
|
||||||
|
|
||||||
|
|
||||||
|
class APIClient:
|
||||||
|
"""Client pentru roa2web API."""
|
||||||
|
|
||||||
|
def __init__(self, base_url: str):
|
||||||
|
self.base_url = base_url.rstrip("/")
|
||||||
|
self.token = None
|
||||||
|
self.session = requests.Session()
|
||||||
|
|
||||||
|
def login(self, username: str, password: str, server_id: str) -> bool:
|
||||||
|
"""Login și obține token."""
|
||||||
|
r = self.session.post(
|
||||||
|
f"{self.base_url}/api/auth/login",
|
||||||
|
json={"username": username, "password": password, "server_id": server_id}
|
||||||
|
)
|
||||||
|
if r.status_code == 200:
|
||||||
|
data = r.json()
|
||||||
|
self.token = data.get("access_token")
|
||||||
|
self.session.headers["Authorization"] = f"Bearer {self.token}"
|
||||||
|
return True
|
||||||
|
print(f"Login failed: {r.status_code} - {r.text}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def ocr_extract(self, file_path: Path) -> dict:
|
||||||
|
"""Submit OCR job și așteaptă rezultatul."""
|
||||||
|
# Determine mime type
|
||||||
|
suffix = file_path.suffix.lower()
|
||||||
|
if suffix == ".pdf":
|
||||||
|
mime_type = "application/pdf"
|
||||||
|
elif suffix in (".jpg", ".jpeg"):
|
||||||
|
mime_type = "image/jpeg"
|
||||||
|
elif suffix == ".png":
|
||||||
|
mime_type = "image/png"
|
||||||
|
else:
|
||||||
|
# Try to detect from content
|
||||||
|
with open(file_path, "rb") as f:
|
||||||
|
header = f.read(8)
|
||||||
|
if header[:4] == b'%PDF':
|
||||||
|
mime_type = "application/pdf"
|
||||||
|
suffix = ".pdf"
|
||||||
|
elif header[:3] == b'\xff\xd8\xff':
|
||||||
|
mime_type = "image/jpeg"
|
||||||
|
suffix = ".jpg"
|
||||||
|
elif header[:8] == b'\x89PNG\r\n\x1a\n':
|
||||||
|
mime_type = "image/png"
|
||||||
|
suffix = ".png"
|
||||||
|
else:
|
||||||
|
mime_type = "application/pdf" # default
|
||||||
|
suffix = ".pdf"
|
||||||
|
|
||||||
|
# Use proper filename with extension
|
||||||
|
filename = file_path.stem + suffix if not file_path.suffix else file_path.name
|
||||||
|
|
||||||
|
# Submit
|
||||||
|
with open(file_path, "rb") as f:
|
||||||
|
r = self.session.post(
|
||||||
|
f"{self.base_url}/api/data-entry/ocr/extract",
|
||||||
|
files={"file": (filename, f, mime_type)}
|
||||||
|
)
|
||||||
|
if r.status_code != 200:
|
||||||
|
return {"success": False, "error": f"OCR submit failed: {r.text}"}
|
||||||
|
|
||||||
|
job_id = r.json().get("job_id")
|
||||||
|
print(f" OCR job: {job_id}")
|
||||||
|
|
||||||
|
# Wait for result (max 60s per request, retry if pending)
|
||||||
|
for _ in range(4): # Max 4 retries = ~240s total
|
||||||
|
r = self.session.get(
|
||||||
|
f"{self.base_url}/api/data-entry/ocr/jobs/{job_id}/wait",
|
||||||
|
params={"timeout": 60, "wait_for_terminal": "true"},
|
||||||
|
timeout=70
|
||||||
|
)
|
||||||
|
if r.status_code != 200:
|
||||||
|
return {"success": False, "error": f"OCR wait failed: {r.text}"}
|
||||||
|
|
||||||
|
data = r.json()
|
||||||
|
status = data.get("status")
|
||||||
|
|
||||||
|
if status == "completed":
|
||||||
|
return {"success": True, "result": data.get("result"), "time_ms": data.get("processing_time_ms")}
|
||||||
|
elif status == "failed":
|
||||||
|
return {"success": False, "error": data.get("error") or "OCR failed"}
|
||||||
|
# Still pending/processing - will retry
|
||||||
|
|
||||||
|
return {"success": False, "error": "OCR timeout"}
|
||||||
|
|
||||||
|
def create_receipt(self, ocr_result: dict, company_id: int) -> dict:
|
||||||
|
"""Creează receipt în SQLite via API cu TOATE datele."""
|
||||||
|
# Parse date
|
||||||
|
date_str = ocr_result.get("receipt_date")
|
||||||
|
if date_str:
|
||||||
|
receipt_date = date_str[:10] # YYYY-MM-DD
|
||||||
|
else:
|
||||||
|
receipt_date = datetime.now().strftime("%Y-%m-%d")
|
||||||
|
|
||||||
|
# Build TVA breakdown from OCR
|
||||||
|
tva_breakdown = []
|
||||||
|
for tva_entry in (ocr_result.get("tva_entries") or []):
|
||||||
|
tva_breakdown.append({
|
||||||
|
"code": tva_entry.get("code"),
|
||||||
|
"percent": tva_entry.get("percent"),
|
||||||
|
"amount": float(tva_entry.get("amount") or 0)
|
||||||
|
})
|
||||||
|
|
||||||
|
# Build payment methods from OCR
|
||||||
|
payment_methods = []
|
||||||
|
for pm in (ocr_result.get("payment_methods") or []):
|
||||||
|
payment_methods.append({
|
||||||
|
"method": pm.get("method"),
|
||||||
|
"amount": float(pm.get("amount") or 0)
|
||||||
|
})
|
||||||
|
|
||||||
|
# Determine payment mode
|
||||||
|
payment_mode = ocr_result.get("suggested_payment_mode") or "casa"
|
||||||
|
# If has CARD payment, it's "banca"
|
||||||
|
if any(pm.get("method", "").upper() == "CARD" for pm in payment_methods):
|
||||||
|
payment_mode = "banca"
|
||||||
|
elif any(pm.get("method", "").upper() == "NUMERAR" for pm in payment_methods):
|
||||||
|
payment_mode = "casa"
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
"receipt_type": "bon_fiscal",
|
||||||
|
"direction": "cheltuiala",
|
||||||
|
"receipt_number": ocr_result.get("receipt_number"),
|
||||||
|
"receipt_series": ocr_result.get("receipt_series"),
|
||||||
|
"receipt_date": receipt_date,
|
||||||
|
"amount": float(ocr_result.get("amount") or 0),
|
||||||
|
"partner_name": ocr_result.get("partner_name"),
|
||||||
|
"cui": ocr_result.get("cui"),
|
||||||
|
"tva_total": float(ocr_result.get("tva_total") or 0),
|
||||||
|
"tva_breakdown": tva_breakdown if tva_breakdown else None,
|
||||||
|
"payment_methods": payment_methods if payment_methods else None,
|
||||||
|
"payment_mode": payment_mode,
|
||||||
|
"company_id": company_id,
|
||||||
|
"vendor_address": ocr_result.get("address"),
|
||||||
|
"items_count": ocr_result.get("items_count"),
|
||||||
|
"ocr_raw_text": ocr_result.get("raw_text"),
|
||||||
|
}
|
||||||
|
|
||||||
|
# Remove None values
|
||||||
|
payload = {k: v for k, v in payload.items() if v is not None}
|
||||||
|
|
||||||
|
self.session.headers["X-Selected-Company"] = str(company_id)
|
||||||
|
r = self.session.post(
|
||||||
|
f"{self.base_url}/api/data-entry/receipts/",
|
||||||
|
json=payload
|
||||||
|
)
|
||||||
|
|
||||||
|
if r.status_code in (200, 201):
|
||||||
|
return {"success": True, "receipt": r.json()}
|
||||||
|
else:
|
||||||
|
return {"success": False, "error": f"Create receipt failed: {r.text}"}
|
||||||
|
|
||||||
|
|
||||||
|
def get_or_create_partner(cursor, cui: str, name: str, address: str = None) -> int:
|
||||||
|
"""Găsește sau creează partener în Oracle. Returnează ID_PART."""
|
||||||
|
cui_clean = (cui or "").upper().replace("RO", "").strip()
|
||||||
|
|
||||||
|
if not cui_clean:
|
||||||
|
return 0 # No CUI, no partner
|
||||||
|
|
||||||
|
# Try to find existing partner
|
||||||
|
cursor.execute("""
|
||||||
|
SELECT ID_PART FROM NOM_PARTENERI
|
||||||
|
WHERE COD_FISCAL = :cui OR COD_FISCAL = :cui2
|
||||||
|
""", cui=cui_clean, cui2="RO" + cui_clean)
|
||||||
|
row = cursor.fetchone()
|
||||||
|
|
||||||
|
if row:
|
||||||
|
return row[0] # Found existing partner
|
||||||
|
|
||||||
|
# Create new partner
|
||||||
|
cursor.execute("SELECT SEQ_NOM_PARTENERI.NEXTVAL FROM DUAL")
|
||||||
|
new_id = cursor.fetchone()[0]
|
||||||
|
|
||||||
|
# Clean name
|
||||||
|
partner_name = (name or f"PARTENER {cui_clean}")[:100]
|
||||||
|
partner_address = (address or "")[:200]
|
||||||
|
|
||||||
|
cursor.execute("""
|
||||||
|
INSERT INTO NOM_PARTENERI (ID_PART, NUME, COD_FISCAL, ADRESA, STERS, INACTIV)
|
||||||
|
VALUES (:id_part, :nume, :cui, :adresa, 0, 0)
|
||||||
|
""", id_part=new_id, nume=partner_name, cui=cui_clean, adresa=partner_address)
|
||||||
|
|
||||||
|
print(f" ➕ Partener nou creat: ID={new_id}, CUI={cui_clean}, Nume={partner_name}")
|
||||||
|
return new_id
|
||||||
|
|
||||||
|
|
||||||
|
def check_tva_incasare(cursor, an: int, luna: int) -> bool:
|
||||||
|
"""Verifică dacă firma e plătitoare de TVA la încasare în perioada dată."""
|
||||||
|
cursor.execute("""
|
||||||
|
SELECT NVL(TVA_INCASARE, 0) FROM CALENDAR
|
||||||
|
WHERE AN = :an AND LUNA = :luna
|
||||||
|
""", an=an, luna=luna)
|
||||||
|
row = cursor.fetchone()
|
||||||
|
return row[0] == 1 if row else False
|
||||||
|
|
||||||
|
|
||||||
|
def save_to_oracle(ocr_result: dict, do_commit: bool = False) -> dict:
|
||||||
|
"""Salvează nota contabilă în Oracle cu toate regulile."""
|
||||||
|
conn = oracledb.connect(**ORACLE_CONFIG)
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Parse date
|
||||||
|
date_str = ocr_result.get("receipt_date")
|
||||||
|
if date_str:
|
||||||
|
receipt_date = datetime.strptime(date_str[:10], "%Y-%m-%d").date()
|
||||||
|
else:
|
||||||
|
receipt_date = datetime.now().date()
|
||||||
|
|
||||||
|
an, luna = receipt_date.year, receipt_date.month
|
||||||
|
|
||||||
|
# 1. Get or create partner
|
||||||
|
id_part = get_or_create_partner(
|
||||||
|
cursor,
|
||||||
|
ocr_result.get("cui"),
|
||||||
|
ocr_result.get("partner_name"),
|
||||||
|
ocr_result.get("address")
|
||||||
|
)
|
||||||
|
print(f" Partner ID: {id_part}")
|
||||||
|
|
||||||
|
# 2. Check TVA la încasare
|
||||||
|
tva_incasare = check_tva_incasare(cursor, an, luna)
|
||||||
|
cont_tva = "4428" if tva_incasare else "4426"
|
||||||
|
print(f" TVA la încasare: {'DA (4428)' if tva_incasare else 'NU (4426)'}")
|
||||||
|
|
||||||
|
# 3. Determine payment type
|
||||||
|
payment_methods = ocr_result.get("payment_methods") or []
|
||||||
|
has_cash = any(pm.get("method", "").upper() == "NUMERAR" for pm in payment_methods)
|
||||||
|
has_card = any(pm.get("method", "").upper() == "CARD" for pm in payment_methods)
|
||||||
|
|
||||||
|
# If no payment info, assume cash
|
||||||
|
if not payment_methods:
|
||||||
|
has_cash = True
|
||||||
|
|
||||||
|
print(f" Plată: {'NUMERAR' if has_cash else ''}{' + ' if has_cash and has_card else ''}{'CARD' if has_card else ''}")
|
||||||
|
|
||||||
|
# 4. Init PACK_CONTAFIN
|
||||||
|
cursor.callproc('PACK_CONTAFIN.INITIALIZEAZA_SCRIERE_ACT_RUL',
|
||||||
|
[0, datetime.now(), an, luna, 0, 0, 0, 0])
|
||||||
|
|
||||||
|
# 5. Get next COD
|
||||||
|
cursor.execute(
|
||||||
|
"SELECT NVL(MAX(COD), 0) + 1 FROM ACT WHERE AN = :an AND LUNA = :luna",
|
||||||
|
an=an, luna=luna
|
||||||
|
)
|
||||||
|
cod = cursor.fetchone()[0]
|
||||||
|
|
||||||
|
# 6. Calculate amounts
|
||||||
|
total = float(ocr_result.get("amount") or 0)
|
||||||
|
tva = float(ocr_result.get("tva_total") or 0)
|
||||||
|
fara_tva = total - tva
|
||||||
|
|
||||||
|
nract = ocr_result.get("receipt_number", "")
|
||||||
|
nract = int(nract) if str(nract).isdigit() else 0
|
||||||
|
|
||||||
|
cont_cheltuiala = get_cont(ocr_result.get("cui") or "")
|
||||||
|
expl = f"OCR: {ocr_result.get('partner_name') or 'N/A'}"[:100]
|
||||||
|
|
||||||
|
print(f" COD: {cod}, Cont: {cont_cheltuiala}")
|
||||||
|
print(f" Total: {total}, Bază: {fara_tva}, TVA: {tva}")
|
||||||
|
|
||||||
|
# 7. Process TVA entries from OCR (pot fi mai multe cote TVA)
|
||||||
|
tva_entries = ocr_result.get("tva_entries") or []
|
||||||
|
|
||||||
|
# 8. Build accounting lines
|
||||||
|
lines = []
|
||||||
|
|
||||||
|
# Calculate base for each TVA rate
|
||||||
|
if tva_entries:
|
||||||
|
# Process each TVA entry separately
|
||||||
|
for tva_entry in tva_entries:
|
||||||
|
tva_rate = tva_entry.get("percent") or 21
|
||||||
|
tva_amount = float(tva_entry.get("amount") or 0)
|
||||||
|
|
||||||
|
if tva_amount <= 0:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Calculate base for this TVA rate
|
||||||
|
base_amount = tva_amount / (tva_rate / 100)
|
||||||
|
|
||||||
|
# Get ID_JTVA_COLOANA and TAXCODE based on TVA rate and TVA la încasare
|
||||||
|
if tva_incasare:
|
||||||
|
jtva_data = JTVA_NEEX.get(tva_rate, (210, 211, 301104, 301305))
|
||||||
|
else:
|
||||||
|
jtva_data = JTVA_NORMAL.get(tva_rate, (208, 209, 301104, 301305))
|
||||||
|
|
||||||
|
jtva_baza, jtva_tva, taxcode_normal, taxcode_tvai = jtva_data
|
||||||
|
taxcode = taxcode_tvai if tva_incasare else taxcode_normal
|
||||||
|
|
||||||
|
print(f" TVA {tva_rate}%: baza={base_amount:.2f}, tva={tva_amount:.2f}, JTVA=({jtva_baza},{jtva_tva}), TAXCODE={taxcode}")
|
||||||
|
|
||||||
|
# Linia cheltuială pentru această cotă
|
||||||
|
lines.append({
|
||||||
|
"scd": cont_cheltuiala, "scc": "401",
|
||||||
|
"suma": base_amount, "expl": expl,
|
||||||
|
"id_partc": id_part, "id_partd": 0,
|
||||||
|
"id_jtva": jtva_baza,
|
||||||
|
"taxcode": taxcode
|
||||||
|
})
|
||||||
|
|
||||||
|
# Linia TVA pentru această cotă
|
||||||
|
proc_tva = 1 + tva_rate / 100 # 1.21, 1.19, etc.
|
||||||
|
lines.append({
|
||||||
|
"scd": cont_tva, "scc": "401",
|
||||||
|
"suma": tva_amount, "expl": f"TVA {tva_rate}% {expl}"[:100],
|
||||||
|
"id_partc": id_part, "id_partd": 0,
|
||||||
|
"proc_tva": proc_tva,
|
||||||
|
"id_jtva": jtva_tva,
|
||||||
|
"taxcode": taxcode
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
# Fallback: use total amounts if no tva_entries
|
||||||
|
if fara_tva > 0:
|
||||||
|
tva_rate = round(tva / fara_tva * 100) if fara_tva > 0 else 21
|
||||||
|
else:
|
||||||
|
tva_rate = 21
|
||||||
|
|
||||||
|
if tva_incasare:
|
||||||
|
jtva_data = JTVA_NEEX.get(tva_rate, (210, 211, 301104, 301305))
|
||||||
|
else:
|
||||||
|
jtva_data = JTVA_NORMAL.get(tva_rate, (208, 209, 301104, 301305))
|
||||||
|
|
||||||
|
jtva_baza, jtva_tva, taxcode_normal, taxcode_tvai = jtva_data
|
||||||
|
taxcode = taxcode_tvai if tva_incasare else taxcode_normal
|
||||||
|
|
||||||
|
print(f" TVA {tva_rate}% (estimat): JTVA=({jtva_baza},{jtva_tva}), TAXCODE={taxcode}")
|
||||||
|
|
||||||
|
lines.append({
|
||||||
|
"scd": cont_cheltuiala, "scc": "401",
|
||||||
|
"suma": fara_tva, "expl": expl,
|
||||||
|
"id_partc": id_part, "id_partd": 0,
|
||||||
|
"id_jtva": jtva_baza,
|
||||||
|
"taxcode": taxcode
|
||||||
|
})
|
||||||
|
|
||||||
|
if tva > 0:
|
||||||
|
proc_tva = 1 + tva_rate / 100
|
||||||
|
lines.append({
|
||||||
|
"scd": cont_tva, "scc": "401",
|
||||||
|
"suma": tva, "expl": f"TVA {tva_rate}% {expl}"[:100],
|
||||||
|
"id_partc": id_part, "id_partd": 0,
|
||||||
|
"proc_tva": proc_tva,
|
||||||
|
"id_jtva": jtva_tva,
|
||||||
|
"taxcode": taxcode
|
||||||
|
})
|
||||||
|
|
||||||
|
# Linia plată din casă (DOAR dacă plată numerar)
|
||||||
|
if has_cash and not has_card:
|
||||||
|
lines.append({
|
||||||
|
"scd": "401", "scc": "5311",
|
||||||
|
"suma": total, "expl": f"Plata {expl}"[:100],
|
||||||
|
"id_partc": 0, "id_partd": id_part,
|
||||||
|
"id_jtva": None, # Nu are JTVA pentru plată
|
||||||
|
"taxcode": None
|
||||||
|
})
|
||||||
|
# Dacă plată CARD - nu se face nota 401=5311 (se face la extras bancar)
|
||||||
|
|
||||||
|
# ID_FDOC = 17 pentru BON FISCAL
|
||||||
|
id_fdoc = 17
|
||||||
|
|
||||||
|
# 9. Insert lines
|
||||||
|
for line in lines:
|
||||||
|
proc_tva = line.get("proc_tva") or 0 # Default 0 for non-TVA lines
|
||||||
|
id_jtva = line.get("id_jtva") # Poate fi None pentru plăți
|
||||||
|
taxcode = line.get("taxcode") # Poate fi None pentru plăți
|
||||||
|
cursor.execute("""
|
||||||
|
INSERT INTO ACT_TEMP (
|
||||||
|
LUNA, AN, COD, DATAIREG, DATAACT, NRACT,
|
||||||
|
EXPLICATIA, SCD, SCC, SUMA, PROC_TVA,
|
||||||
|
ID_PARTC, ID_PARTD, ID_FDOC, ID_JTVA_COLOANA, TAXCODE, ID_UTIL, DATAORA
|
||||||
|
) VALUES (
|
||||||
|
:luna, :an, :cod, TRUNC(SYSDATE), :dataact, :nract,
|
||||||
|
:expl, :scd, :scc, :suma, :proc_tva,
|
||||||
|
:id_partc, :id_partd, :id_fdoc, :id_jtva, :taxcode, 0, SYSDATE
|
||||||
|
)
|
||||||
|
""",
|
||||||
|
luna=luna, an=an, cod=cod, dataact=receipt_date, nract=nract,
|
||||||
|
expl=line["expl"], scd=line["scd"], scc=line["scc"],
|
||||||
|
suma=line["suma"], proc_tva=proc_tva,
|
||||||
|
id_partc=line["id_partc"], id_partd=line["id_partd"],
|
||||||
|
id_fdoc=id_fdoc, id_jtva=id_jtva, taxcode=taxcode
|
||||||
|
)
|
||||||
|
jtva_info = f" [JTVA={id_jtva}]" if id_jtva else ""
|
||||||
|
taxcode_info = f" [TAX={taxcode}]" if taxcode else ""
|
||||||
|
print(f" {line['scd']} = {line['scc']}: {line['suma']:.2f}{jtva_info}{taxcode_info}")
|
||||||
|
|
||||||
|
# 9. Finalize
|
||||||
|
mesaj = cursor.var(oracledb.STRING, 4000)
|
||||||
|
cursor.callproc('PACK_CONTAFIN.FINALIZEAZA_SCRIERE_ACT_RUL',
|
||||||
|
[0, cod, 0, 0, 0, mesaj])
|
||||||
|
|
||||||
|
if do_commit:
|
||||||
|
conn.commit()
|
||||||
|
return {"success": True, "cod": cod, "luna": luna, "an": an, "saved": True,
|
||||||
|
"id_part": id_part, "tva_incasare": tva_incasare}
|
||||||
|
else:
|
||||||
|
conn.rollback()
|
||||||
|
return {"success": True, "cod": cod, "luna": luna, "an": an, "saved": False,
|
||||||
|
"id_part": id_part, "tva_incasare": tva_incasare}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
conn.rollback()
|
||||||
|
import traceback
|
||||||
|
return {"success": False, "error": str(e), "traceback": traceback.format_exc()}
|
||||||
|
finally:
|
||||||
|
cursor.close()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
|
||||||
|
def process_bon(file_path: Path, do_save: bool = False, company_id: int = COMPANY_ID,
|
||||||
|
api_user: str = API_USER, api_pass: str = API_PASS):
|
||||||
|
"""Procesează un bon fiscal: OCR → SQLite → Oracle."""
|
||||||
|
print("=" * 60)
|
||||||
|
print(f"📄 Procesez: {file_path.name}")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
# 1. Login
|
||||||
|
print("\n🔑 Login API...")
|
||||||
|
client = APIClient(API_BASE)
|
||||||
|
if not client.login(api_user, api_pass, SERVER_ID):
|
||||||
|
print("❌ Login failed!")
|
||||||
|
return None
|
||||||
|
print(" ✅ OK")
|
||||||
|
|
||||||
|
# 2. OCR
|
||||||
|
print("\n🔍 OCR extract...")
|
||||||
|
ocr_result = client.ocr_extract(file_path)
|
||||||
|
if not ocr_result["success"]:
|
||||||
|
print(f" ❌ {ocr_result['error']}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
ocr = ocr_result["result"]
|
||||||
|
print(f" ✅ OK ({ocr_result.get('time_ms', '?')}ms)")
|
||||||
|
print(f" CUI: {ocr.get('cui')}")
|
||||||
|
print(f" Partner: {ocr.get('partner_name')}")
|
||||||
|
print(f" Data: {ocr.get('receipt_date')}")
|
||||||
|
print(f" Total: {ocr.get('amount')} RON")
|
||||||
|
print(f" TVA: {ocr.get('tva_total')} RON")
|
||||||
|
|
||||||
|
# Show payment methods
|
||||||
|
payment_methods = ocr.get("payment_methods") or []
|
||||||
|
if payment_methods:
|
||||||
|
pm_str = ", ".join(f"{pm.get('method')}: {pm.get('amount')}" for pm in payment_methods)
|
||||||
|
print(f" Plăți: {pm_str}")
|
||||||
|
|
||||||
|
# Show TVA breakdown
|
||||||
|
tva_entries = ocr.get("tva_entries") or []
|
||||||
|
if tva_entries:
|
||||||
|
tva_str = ", ".join(f"{t.get('code')}({t.get('percent')}%): {t.get('amount')}" for t in tva_entries)
|
||||||
|
print(f" TVA detaliat: {tva_str}")
|
||||||
|
|
||||||
|
# 3. SQLite (via API) - cu TOATE datele
|
||||||
|
print("\n💾 Save SQLite (via API)...")
|
||||||
|
sqlite_result = client.create_receipt(ocr, company_id)
|
||||||
|
if not sqlite_result["success"]:
|
||||||
|
print(f" ❌ {sqlite_result['error']}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
receipt = sqlite_result["receipt"]
|
||||||
|
print(f" ✅ Receipt ID: {receipt.get('id')}")
|
||||||
|
print(f" Payment mode: {receipt.get('payment_mode')}")
|
||||||
|
|
||||||
|
# 4. Oracle (direct)
|
||||||
|
mode = "SAVE" if do_save else "DRY RUN"
|
||||||
|
print(f"\n🗄️ Save Oracle ({mode})...")
|
||||||
|
oracle_result = save_to_oracle(ocr, do_commit=do_save)
|
||||||
|
|
||||||
|
if oracle_result["success"]:
|
||||||
|
if oracle_result["saved"]:
|
||||||
|
print(f" ✅ SALVAT: COD={oracle_result['cod']}, {oracle_result['luna']:02d}/{oracle_result['an']}")
|
||||||
|
else:
|
||||||
|
print(f" ⚠️ DRY RUN: ar fi COD={oracle_result['cod']}")
|
||||||
|
else:
|
||||||
|
print(f" ❌ {oracle_result.get('error')}")
|
||||||
|
if oracle_result.get("traceback"):
|
||||||
|
print(oracle_result["traceback"])
|
||||||
|
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
return {
|
||||||
|
"ocr": ocr,
|
||||||
|
"sqlite_receipt_id": receipt.get("id"),
|
||||||
|
"oracle": oracle_result
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(description="Procesare bon fiscal: OCR → SQLite → Oracle")
|
||||||
|
parser.add_argument("file", help="Path către PDF sau imagine")
|
||||||
|
parser.add_argument("--save", action="store_true", help="Salvează efectiv în Oracle")
|
||||||
|
parser.add_argument("--company", type=int, default=COMPANY_ID, help="Company ID")
|
||||||
|
parser.add_argument("--user", default=API_USER, help="API username")
|
||||||
|
parser.add_argument("--password", default=API_PASS, help="API password")
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
file_path = Path(args.file)
|
||||||
|
if not file_path.exists():
|
||||||
|
print(f"❌ File not found: {file_path}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
result = process_bon(file_path, do_save=args.save, company_id=args.company,
|
||||||
|
api_user=args.user, api_pass=args.password)
|
||||||
|
|
||||||
|
if result:
|
||||||
|
print("\n✅ Done!")
|
||||||
|
else:
|
||||||
|
print("\n❌ Failed!")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user