New application for entering fiscal receipts (bonuri fiscale) with: Backend (FastAPI + SQLModel + Alembic): - Receipt, ReceiptAttachment, AccountingEntry models - CRUD operations with async SQLite database - Workflow: DRAFT → PENDING_REVIEW → APPROVED/REJECTED - Auto-generation of accounting entries with VAT calculation - File upload support (images, PDFs) - Predefined expense types (Fuel, Materials, Office, etc.) - Nomenclature service for partners, accounts, cash registers Frontend (Vue.js 3 + PrimeVue + Pinia): - ReceiptsListView with filters and stats - ReceiptCreateView with image upload - ReceiptDetailView with accounting entries - ReceiptApprovalView for accountant approval Documentation: - REQUIREMENTS.md with functional specifications - ARCHITECTURE.md with technical decisions - CLAUDE.md for AI assistant guidance Phase 1 MVP uses SQLite, prepared for Oracle integration in Phase 2. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
26 KiB
26 KiB
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
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:
docs/data-entry/REQUIREMENTS.md- Cerințe funcționale și tehnicedocs/data-entry/ARCHITECTURE.md- Decizii arhitecturale (ORM, workflow)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:
- Creat de autorul FastAPI (Sebastian Ramirez) - integrare perfectă
- Un model = Pydantic + SQLAlchemy - nu duplici definiții
- Async support nativ
- Alembic - standard industrial pentru migrări
- 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ță)
# 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)
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)
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
- Creare branch:
git checkout -b feature/data-entry-receipts - Creare structură directoare:
data-entry-app/{backend,frontend,docs} - Creare documentație:
docs/data-entry/REQUIREMENTS.md- Cerințe funcționaledocs/data-entry/ARCHITECTURE.md- Decizii tehnice (ORM, workflow)
- Creare
data-entry-app/README.md- Quick start - Creare
data-entry-app/CLAUDE.md- Instrucțiuni pentru Claude Code
Etapa 1: Setup Backend (SQLModel + Alembic)
- Creare
data-entry-app/backend/requirements.txt:fastapi,uvicorn,sqlmodel,alembic,python-multipartaiosqlite,pydantic,python-dotenv
- Creare
app/main.py- FastAPI app cu CORS, lifespan - Creare
app/config.py- Settings (DB path, upload path) - Creare
app/db/database.py- engine async, session factory - Setup Alembic:
alembic init migrations
Etapa 2: Modele și Migrări
- Creare
app/db/models/receipt.py- Receipt, ReceiptAttachment - Creare
app/db/models/accounting_entry.py- AccountingEntry - Prima migrare:
001_initial_receipts.py - Creare folder
data/uploads/pentru fișiere
Etapa 3: Backend CRUD + Upload
- Creare
app/db/crud/receipt.py- operații CRUD - Creare
app/db/crud/attachment.py- upload/download fișiere - Creare
app/db/crud/accounting_entry.py- note contabile - Creare
app/schemas/receipt.py- request/response Pydantic
Etapa 4: Business Logic + Workflow
- Creare
app/services/receipt_service.py:create_receipt()- creare + upload pozăgenerate_accounting_entries()- generare automată notesubmit_for_review()- DRAFT → PENDINGapprove_receipt()- PENDING → APPROVEDreject_receipt()- PENDING → REJECTED
Etapa 5: API Endpoints
- Creare
app/routers/receipts.py- toate endpoint-urile - Register router în
main.py - Middleware pentru upload fișiere
Etapa 6: Nomenclatoare Oracle
- Creare
app/services/nomenclature_service.py:get_partners()- furnizori/clienți din Oracleget_expense_accounts()- conturi 6xxxget_cash_registers()- case/bănciget_expense_types()- tipuri cheltuieli predefinite
Etapa 7: Frontend Setup
npm create vite@latest frontend -- --template vue- Instalare dependențe:
pinia,vue-router,primevue,axios - Copy configurație PrimeVue din reports-app
- Copy CSS shared din reports-app (design tokens, patterns)
Etapa 8: Frontend Views
- Creare
views/receipts/ReceiptsListView.vue- listă cu filtre - Creare
views/receipts/ReceiptCreateView.vue- form + upload - Creare
views/receipts/ReceiptDetailView.vue- detalii + note - Creare
views/receipts/ReceiptApprovalView.vue- view contabil - Creare
stores/receiptsStore.js- Pinia store - Configurare router și layout
Etapa 9: Testing & Finalizare
- Unit tests pentru CRUD
- Integration tests pentru API
- Manual testing checklist
- Actualizare documentație
- Commit și push pe branch
- 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:
# 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:
# 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:
- Citește acest fișier
PLAN_DATA_ENTRY_RECEIPTS.md - Începe cu Etapa 0 - creare branch și structură directoare
- Referință pentru proceduri Oracle:
docs/PACK_CONTAFIN.pck,docs/PACK_FACTURARE.pck - Pattern-uri existente pentru SQLite:
reports-app/telegram-bot/app/db/