# Plan: Introducere Bonuri Fiscale - Faza 1 (MVP SQLite) > **Plan Handover** - Acest document conține planul complet pentru implementare. > Creat: 2025-12-11 | Status: Ready for implementation ## Obiectiv Sistem de introducere bonuri fiscale cu: - **Upload poze** bonuri de la utilizatori - **Generare automată** note contabile (staging area) - **Aprobare de contabil** înainte de finalizare - SQLite + ORM (SQLModel) + Migrări (Alembic) - Pregătit pentru integrare Oracle în Faza 2 --- ## Setup Proiect ### Branch de dezvoltare ```bash git checkout -b feature/data-entry-receipts ``` ### Structură Directoare (SEPARAT de reports-app) ``` . ├── reports-app/ # EXISTENT - Raportări (read-only din Oracle) │ ├── backend/ │ ├── frontend/ │ └── telegram-bot/ │ ├── data-entry-app/ # NOU - Introduceri date (write în SQLite → Oracle) │ ├── backend/ # FastAPI pentru introduceri │ ├── frontend/ # Vue.js pentru introduceri │ └── docs/ # Documentație și cerințe │ ├── shared/ # EXISTENT - Componente partajate │ ├── database/ │ └── auth/ │ └── docs/ # Documentație generală proiect └── data-entry/ # Documentație specifică data-entry ├── REQUIREMENTS.md # Cerințe inițiale (acest plan) └── ARCHITECTURE.md # Decizii arhitecturale ``` ### Documentație Salvată La finalizarea planului, se vor crea: 1. `docs/data-entry/REQUIREMENTS.md` - Cerințe funcționale și tehnice 2. `docs/data-entry/ARCHITECTURE.md` - Decizii arhitecturale (ORM, workflow) 3. `data-entry-app/README.md` - Quick start pentru dezvoltare --- ## Workflow Principal ``` ┌─────────────────────────────────────────────────────────────────┐ │ 1. UTILIZATOR uploadează bon │ │ ├─ Poză bon fiscal / chitanță │ │ ├─ Date de bază: sumă, dată, furnizor │ │ └─ Status: DRAFT │ └──────────────────────┬──────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────────┐ │ 2. SISTEM generează propunere note contabile │ │ ├─ Debit: Cont cheltuială (6022, 6024, etc.) │ │ ├─ Credit: Casă (5311) sau Bancă (5121) │ │ └─ Status: PENDING_REVIEW │ └──────────────────────┬──────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────────┐ │ 3. CONTABIL revizuiește │ │ ├─ Verifică poza + datele │ │ ├─ Ajustează conturi dacă e nevoie │ │ ├─ APROBĂ → Status: APPROVED │ │ └─ RESPINGE → Status: REJECTED (cu motiv) │ └──────────────────────┬──────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────────┐ │ 4. FAZA 2: Sync în Oracle │ │ ├─ INSERT ACT_TEMP │ │ ├─ pack_contafin.finalizeaza_scriere_act_rul() │ │ └─ Status: SYNCED │ └─────────────────────────────────────────────────────────────────┘ ``` --- ## Decizie Tehnică: ORM + Migrări ### Recomandare: **SQLModel + Alembic** **Motivație:** 1. **Creat de autorul FastAPI** (Sebastian Ramirez) - integrare perfectă 2. **Un model = Pydantic + SQLAlchemy** - nu duplici definiții 3. **Async support** nativ 4. **Alembic** - standard industrial pentru migrări 5. **Validare automată** - Pydantic validează input, SQLAlchemy gestionează DB --- ## Arhitectură Propusă ### Backend Structure (`data-entry-app/backend/`) ``` data-entry-app/backend/ ├── app/ │ ├── __init__.py │ ├── main.py # FastAPI app entry point │ ├── config.py # Settings & env vars │ │ │ ├── db/ # Database layer (SQLModel) │ │ ├── __init__.py │ │ ├── database.py # Engine, SessionLocal, init │ │ ├── models/ │ │ │ ├── __init__.py │ │ │ ├── receipt.py # Receipt, ReceiptAttachment │ │ │ └── accounting_entry.py # AccountingEntry │ │ └── crud/ │ │ ├── __init__.py │ │ ├── receipt.py │ │ ├── attachment.py │ │ └── accounting_entry.py │ │ │ ├── schemas/ │ │ └── receipt.py # Request/Response Pydantic schemas │ │ │ ├── services/ │ │ ├── receipt_service.py # Business logic + workflow │ │ └── nomenclature_service.py # Nomenclatoare din Oracle │ │ │ └── routers/ │ └── receipts.py # API endpoints │ ├── migrations/ # Alembic migrations │ ├── env.py │ ├── alembic.ini │ └── versions/ │ └── 001_initial_receipts.py │ ├── data/ │ ├── receipts.db # SQLite database │ └── uploads/ # Poze bonuri │ ├── requirements.txt └── README.md ``` ### Frontend Structure (`data-entry-app/frontend/`) ``` data-entry-app/frontend/ ├── src/ │ ├── views/receipts/ │ │ ├── ReceiptsListView.vue │ │ ├── ReceiptCreateView.vue │ │ ├── ReceiptDetailView.vue │ │ └── ReceiptApprovalView.vue │ │ │ ├── components/receipts/ │ │ ├── ReceiptForm.vue │ │ ├── ReceiptImageUpload.vue │ │ └── AccountingEntriesTable.vue │ │ │ ├── stores/ │ │ └── receiptsStore.js │ │ │ └── router/ │ └── index.js │ ├── package.json └── vite.config.js ``` --- ## Modele de Date ### 1. Receipt (Bon Fiscal / Chitanță) ```python # app/db/models/receipt.py from sqlmodel import SQLModel, Field, Relationship from datetime import datetime, date from decimal import Decimal from enum import Enum from typing import Optional, List class ReceiptType(str, Enum): BON_FISCAL = "bon_fiscal" CHITANTA = "chitanta" class ReceiptDirection(str, Enum): CHELTUIALA = "cheltuiala" # Plată (bon primit de la furnizor) INCASARE = "incasare" # Încasare (bon emis către client) class ReceiptStatus(str, Enum): DRAFT = "draft" # Utilizator completează PENDING_REVIEW = "pending_review" # Așteaptă aprobare contabil APPROVED = "approved" # Aprobat de contabil REJECTED = "rejected" # Respins de contabil SYNCED = "synced" # Sincronizat în Oracle (Faza 2) class Receipt(SQLModel, table=True): """Bon fiscal sau chitanță cu workflow aprobare""" __tablename__ = "receipts" id: Optional[int] = Field(default=None, primary_key=True) # Identificare document receipt_type: ReceiptType direction: ReceiptDirection receipt_number: Optional[str] = None receipt_series: Optional[str] = None # Date principale receipt_date: date amount: Decimal description: Optional[str] = None # Referințe Oracle (nomenclatoare) company_id: int partner_id: Optional[int] = None partner_name: Optional[str] = None # Cache pentru display cash_register_id: Optional[int] = None # ID casă/bancă Oracle cash_register_name: Optional[str] = None # Cache pentru display # Workflow status: ReceiptStatus = Field(default=ReceiptStatus.DRAFT) created_by: str # Username creator created_at: datetime = Field(default_factory=datetime.utcnow) updated_at: datetime = Field(default_factory=datetime.utcnow) submitted_at: Optional[datetime] = None # Când a fost trimis spre aprobare # Aprobare reviewed_by: Optional[str] = None # Username contabil reviewed_at: Optional[datetime] = None rejection_reason: Optional[str] = None # Motiv respingere # Faza 2 - Oracle sync oracle_synced_at: Optional[datetime] = None oracle_act_id: Optional[int] = None oracle_error: Optional[str] = None # Relații attachments: List["ReceiptAttachment"] = Relationship(back_populates="receipt") entries: List["AccountingEntry"] = Relationship(back_populates="receipt") ``` ### 2. ReceiptAttachment (Poze bonuri - OBLIGATORIU) ```python class ReceiptAttachment(SQLModel, table=True): """Poză sau PDF bon fiscal""" __tablename__ = "receipt_attachments" id: Optional[int] = Field(default=None, primary_key=True) receipt_id: int = Field(foreign_key="receipts.id") filename: str # Nume original fișier stored_filename: str # Nume pe disk (UUID) file_path: str # Cale relativă file_size: int # Bytes mime_type: str # image/jpeg, application/pdf uploaded_at: datetime = Field(default_factory=datetime.utcnow) receipt: Optional["Receipt"] = Relationship(back_populates="attachments") ``` ### 3. AccountingEntry (Note Contabile - Staging) ```python class EntryType(str, Enum): DEBIT = "debit" CREDIT = "credit" class AccountingEntry(SQLModel, table=True): """Notă contabilă propusă pentru bon""" __tablename__ = "accounting_entries" id: Optional[int] = Field(default=None, primary_key=True) receipt_id: int = Field(foreign_key="receipts.id") # Cont entry_type: EntryType # Debit sau Credit account_code: str # Ex: 6022, 5311, 4426 account_name: Optional[str] # Cache: "Cheltuieli combustibil" # Valori amount: Decimal # Analitice (opțional) partner_id: Optional[int] = None cost_center_id: Optional[int] = None # Auto-generat sau modificat manual is_auto_generated: bool = True modified_by: Optional[str] = None modified_at: Optional[datetime] = None receipt: Optional["Receipt"] = Relationship(back_populates="entries") ``` ### Exemplu Note Contabile Generate ``` BON FISCAL BENZINĂ - 200 RON: ┌──────────┬────────┬──────────────────────────┬─────────┐ │ Tip │ Cont │ Denumire │ Sumă │ ├──────────┼────────┼──────────────────────────┼─────────┤ │ DEBIT │ 6022 │ Cheltuieli combustibil │ 168.07 │ │ DEBIT │ 4426 │ TVA deductibilă │ 31.93 │ │ CREDIT │ 5311 │ Casă în lei │ 200.00 │ └──────────┴────────┴──────────────────────────┴─────────┘ ``` --- ## API Endpoints ### Bonuri (CRUD + Workflow) ``` POST /api/receipts/ # Creare bon nou (cu upload poză) GET /api/receipts/ # Listă bonuri (filtrare, paginare) GET /api/receipts/{id} # Detalii bon + note contabile PUT /api/receipts/{id} # Modificare bon (doar DRAFT) DELETE /api/receipts/{id} # Ștergere bon (doar DRAFT) # Workflow POST /api/receipts/{id}/submit # Trimite spre aprobare (DRAFT → PENDING) POST /api/receipts/{id}/approve # Aprobă (PENDING → APPROVED) [Contabil] POST /api/receipts/{id}/reject # Respinge (PENDING → REJECTED) [Contabil] POST /api/receipts/{id}/resubmit # Re-trimite după corecții (REJECTED → PENDING) ``` ### Note Contabile ``` GET /api/receipts/{id}/entries # Liste note contabile propuse PUT /api/receipts/{id}/entries # Modificare note (contabil ajustează conturi) POST /api/receipts/{id}/entries/regenerate # Re-generare automată ``` ### Atașamente ``` POST /api/receipts/{id}/attachments # Upload poză/PDF GET /api/receipts/{id}/attachments # Listă atașamente GET /api/attachments/{id}/download # Download fișier DELETE /api/attachments/{id} # Ștergere atașament ``` ### Nomenclatoare (din Oracle - read only) ``` GET /api/receipts/partners # Furnizori/Clienți pentru dropdown GET /api/receipts/accounts # Conturi sintetice (6xxx, 7xxx, etc.) GET /api/receipts/cash-registers # Case și bănci GET /api/receipts/expense-types # Tipuri cheltuieli predefinite (cu cont asociat) ``` --- ## Frontend Views ``` frontend/src/views/receipts/ ├── ReceiptsListView.vue # Listă bonuri cu filtrare pe status ├── ReceiptCreateView.vue # Form creare + upload poză ├── ReceiptDetailView.vue # Vizualizare + editare note contabile └── ReceiptApprovalView.vue # View pentru contabil (aprobare în masă) ``` ### ReceiptCreateView - Form utilizator ``` ┌──────────────────────────────────────────────────────┐ │ UPLOAD POZĂ BON [Drag & Drop sau Click] │ │ ┌────────────────────────────────────────────┐ │ │ │ │ │ │ │ [Previzualizare imagine] │ │ │ │ │ │ │ └────────────────────────────────────────────┘ │ ├──────────────────────────────────────────────────────┤ │ Tip document: ○ Bon fiscal ○ Chitanță │ │ Direcție: ○ Cheltuială ○ Încasare │ ├──────────────────────────────────────────────────────┤ │ Data: [DatePicker] │ │ Suma: [InputNumber] RON │ │ Furnizor: [Dropdown - din Oracle] │ │ Tip cheltuială:[Dropdown - Benzină, Materiale...] │ │ Casă/Bancă: [Dropdown - din Oracle] │ │ Descriere: [Textarea] │ ├──────────────────────────────────────────────────────┤ │ [Salvează Draft] [Trimite spre aprobare]│ └──────────────────────────────────────────────────────┘ ``` ### ReceiptApprovalView - View Contabil ``` ┌────────────────────────────────────────────────────────────────┐ │ BONURI DE APROBAT (3) [Aprobă selectate] │ ├────────────────────────────────────────────────────────────────┤ │ ☑ │ Data │ Furnizor │ Sumă │ Status │ Acțiuni │ ├───┼──────────┼───────────────┼─────────┼─────────┼───────────┤ │ ☑ │ 10.12.24 │ OMV Petrom │ 200 RON │ PENDING │ [👁️][✓][✗]│ │ ☑ │ 09.12.24 │ Dedeman │ 450 RON │ PENDING │ [👁️][✓][✗]│ │ ☐ │ 08.12.24 │ Kaufland │ 85 RON │ PENDING │ [👁️][✓][✗]│ └────────────────────────────────────────────────────────────────┘ [👁️] = Deschide detalii + poză + note contabile editabile [✓] = Aprobă [✗] = Respinge (cu motiv) ``` --- ## Pași Implementare ### Etapa 0: Setup Proiect și Documentație 1. [ ] Creare branch: `git checkout -b feature/data-entry-receipts` 2. [ ] Creare structură directoare: `data-entry-app/{backend,frontend,docs}` 3. [ ] Creare documentație: - `docs/data-entry/REQUIREMENTS.md` - Cerințe funcționale - `docs/data-entry/ARCHITECTURE.md` - Decizii tehnice (ORM, workflow) 4. [ ] Creare `data-entry-app/README.md` - Quick start 5. [ ] Creare `data-entry-app/CLAUDE.md` - Instrucțiuni pentru Claude Code ### Etapa 1: Setup Backend (SQLModel + Alembic) 6. [ ] Creare `data-entry-app/backend/requirements.txt`: - `fastapi`, `uvicorn`, `sqlmodel`, `alembic`, `python-multipart` - `aiosqlite`, `pydantic`, `python-dotenv` 7. [ ] Creare `app/main.py` - FastAPI app cu CORS, lifespan 8. [ ] Creare `app/config.py` - Settings (DB path, upload path) 9. [ ] Creare `app/db/database.py` - engine async, session factory 10. [ ] Setup Alembic: `alembic init migrations` ### Etapa 2: Modele și Migrări 11. [ ] Creare `app/db/models/receipt.py` - Receipt, ReceiptAttachment 12. [ ] Creare `app/db/models/accounting_entry.py` - AccountingEntry 13. [ ] Prima migrare: `001_initial_receipts.py` 14. [ ] Creare folder `data/uploads/` pentru fișiere ### Etapa 3: Backend CRUD + Upload 15. [ ] Creare `app/db/crud/receipt.py` - operații CRUD 16. [ ] Creare `app/db/crud/attachment.py` - upload/download fișiere 17. [ ] Creare `app/db/crud/accounting_entry.py` - note contabile 18. [ ] Creare `app/schemas/receipt.py` - request/response Pydantic ### Etapa 4: Business Logic + Workflow 19. [ ] Creare `app/services/receipt_service.py`: - `create_receipt()` - creare + upload poză - `generate_accounting_entries()` - generare automată note - `submit_for_review()` - DRAFT → PENDING - `approve_receipt()` - PENDING → APPROVED - `reject_receipt()` - PENDING → REJECTED ### Etapa 5: API Endpoints 20. [ ] Creare `app/routers/receipts.py` - toate endpoint-urile 21. [ ] Register router în `main.py` 22. [ ] Middleware pentru upload fișiere ### Etapa 6: Nomenclatoare Oracle 23. [ ] Creare `app/services/nomenclature_service.py`: - `get_partners()` - furnizori/clienți din Oracle - `get_expense_accounts()` - conturi 6xxx - `get_cash_registers()` - case/bănci - `get_expense_types()` - tipuri cheltuieli predefinite ### Etapa 7: Frontend Setup 24. [ ] `npm create vite@latest frontend -- --template vue` 25. [ ] Instalare dependențe: `pinia`, `vue-router`, `primevue`, `axios` 26. [ ] Copy configurație PrimeVue din reports-app 27. [ ] Copy CSS shared din reports-app (design tokens, patterns) ### Etapa 8: Frontend Views 28. [ ] Creare `views/receipts/ReceiptsListView.vue` - listă cu filtre 29. [ ] Creare `views/receipts/ReceiptCreateView.vue` - form + upload 30. [ ] Creare `views/receipts/ReceiptDetailView.vue` - detalii + note 31. [ ] Creare `views/receipts/ReceiptApprovalView.vue` - view contabil 32. [ ] Creare `stores/receiptsStore.js` - Pinia store 33. [ ] Configurare router și layout ### Etapa 9: Testing & Finalizare 34. [ ] Unit tests pentru CRUD 35. [ ] Integration tests pentru API 36. [ ] Manual testing checklist 37. [ ] Actualizare documentație 38. [ ] Commit și push pe branch 39. [ ] Creare PR către main --- ## Fișiere de Creat ### Documentație (Etapa 0): ``` docs/data-entry/ ├── REQUIREMENTS.md # Cerințe funcționale (din acest plan) └── ARCHITECTURE.md # Decizii tehnice (ORM, workflow) data-entry-app/ ├── CLAUDE.md # Instrucțiuni pentru Claude Code └── README.md # Quick start pentru dezvoltare ``` ### Conținut `data-entry-app/CLAUDE.md`: ```markdown # CLAUDE.md - Data Entry App ## Scop Aplicație pentru introducere date în ERP (bonuri fiscale, chitanțe) cu workflow de aprobare. ## Documentație de Referință - **Cerințe**: `docs/data-entry/REQUIREMENTS.md` - **Arhitectură**: `docs/data-entry/ARCHITECTURE.md` - **Quick Start**: `README.md` ## Decizii Tehnice - **ORM**: SQLModel (Pydantic + SQLAlchemy) - **Migrări**: Alembic - **Database**: SQLite (Faza 1) → Oracle (Faza 2) - **Frontend**: Vue.js 3 + PrimeVue (consistent cu reports-app) ## Workflow Bonuri 1. DRAFT → utilizator completează + upload poză 2. PENDING_REVIEW → sistem generează note contabile 3. APPROVED/REJECTED → contabil aprobă sau respinge 4. SYNCED → (Faza 2) date în Oracle ## Structură Directoare - `backend/` - FastAPI API (port 8003) - `frontend/` - Vue.js UI (port 3010) - `docs/` - Documentație specifică ## Componente Partajate - `shared/database/oracle_pool.py` - Conexiune Oracle - `shared/auth/` - JWT authentication ## Comenzi Dezvoltare ```bash # Backend cd backend && pip install -r requirements.txt uvicorn app.main:app --reload --port 8003 # Frontend cd frontend && npm install && npm run dev # Migrări cd backend && alembic upgrade head ``` ## Integrare Oracle (Faza 2) Vezi `docs/PACK_CONTAFIN.pck` pentru procedurile stocate: - `pack_contafin.init_scriere_act_rul_local()` - `INSERT INTO ACT_TEMP (...)` - `pack_contafin.finalizeaza_scriere_act_rul()` ``` --- ## Tipuri Cheltuieli Predefinite Pentru dropdown "Tip cheltuială" - mapare automată la conturi: | Tip Cheltuială | Cont Debit | TVA | Descriere | |----------------|------------|-----|-----------| | Combustibil | 6022 | 4426 (19%) | Benzină, motorină | | Materiale consumabile | 6028 | 4426 (19%) | Diverse materiale | | Rechizite birou | 6024 | 4426 (19%) | Papetărie, toner | | Telefonie | 626 | 4426 (19%) | Telefon, internet | | Parcare | 6022 | 4426 (19%) | Taxe parcare | | Alimentație | 6028 | - | Fără TVA deductibilă | | Transport | 624 | 4426 (19%) | Taxi, transport | | Altele | 628 | 4426 (19%) | Alte cheltuieli | **Logica generare note:** ```python def generate_entries(receipt): expense_type = EXPENSE_TYPES[receipt.expense_type_code] entries = [] if expense_type.has_vat: net_amount = receipt.amount / 1.19 vat_amount = receipt.amount - net_amount entries.append(AccountingEntry( entry_type=EntryType.DEBIT, account_code=expense_type.account_code, # ex: 6022 amount=net_amount )) entries.append(AccountingEntry( entry_type=EntryType.DEBIT, account_code="4426", # TVA deductibilă amount=vat_amount )) else: entries.append(AccountingEntry( entry_type=EntryType.DEBIT, account_code=expense_type.account_code, amount=receipt.amount )) # Credit - casă sau bancă entries.append(AccountingEntry( entry_type=EntryType.CREDIT, account_code=receipt.cash_register_account, # 5311 sau 5121 amount=receipt.amount )) return entries ``` --- ## Faza 2 Preview (Oracle Integration) După ce Faza 1 funcționează, Faza 2 va adăuga: ```python # receipt_service.py - metodă nouă async def sync_to_oracle(receipt_id: int): """ Sincronizează bon APPROVED în Oracle: 1. pack_contafin.init_scriere_act_rul_local() 2. Pentru fiecare AccountingEntry: INSERT INTO ACT_TEMP ( ID_ACT, DATAIREG, DATAACT, SCD, ASCD, SCC, ASCC, SUMA, ID_CTR, ID_PARTD, EXPLICATIA, ... ) 3. pack_contafin.finalizeaza_scriere_act_rul() → SCRIE_IN_ACT() → SCRIE_IN_RUL() → Actualizare situații (BV, BP, TVA, etc.) 4. Update receipt.status = SYNCED, oracle_act_id = ... """ pass ``` --- ## Riscuri și Mitigări | Risc | Impact | Mitigare | |------|--------|----------| | SQLModel e relativ nou | Mediu | Fallback la SQLAlchemy pur dacă e nevoie | | Upload fișiere mari | Mic | Limit 10MB, compresie imagini | | Workflow complex | Mediu | Începem cu workflow simplu, adăugăm features gradual | | Generare note greșite | Mare | Contabilul poate edita înainte de aprobare | --- ## Success Criteria (Faza 1) - [ ] Utilizator poate uploada poză bon + date de bază - [ ] Sistem generează automat note contabile - [ ] Contabil poate vedea, edita și aproba note - [ ] Bonurile aprobate sunt vizibile în listă - [ ] Migrările Alembic funcționează corect - [ ] Poze bonuri se salvează și se afișează corect --- ## Context Handover **Pentru sesiunea următoare:** 1. Citește acest fișier `PLAN_DATA_ENTRY_RECEIPTS.md` 2. Începe cu Etapa 0 - creare branch și structură directoare 3. Referință pentru proceduri Oracle: `docs/PACK_CONTAFIN.pck`, `docs/PACK_FACTURARE.pck` 4. Pattern-uri existente pentru SQLite: `reports-app/telegram-bot/app/db/`