Files
roa2web-service-auto/data-entry-app/docs/PLAN_DATA_ENTRY_RECEIPTS.md
2025-12-13 01:56:03 +02:00

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:

  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ță)

# 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

  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)

  1. Creare data-entry-app/backend/requirements.txt:
    • fastapi, uvicorn, sqlmodel, alembic, python-multipart
    • aiosqlite, pydantic, python-dotenv
  2. Creare app/main.py - FastAPI app cu CORS, lifespan
  3. Creare app/config.py - Settings (DB path, upload path)
  4. Creare app/db/database.py - engine async, session factory
  5. Setup Alembic: alembic init migrations

Etapa 2: Modele și Migrări

  1. Creare app/db/models/receipt.py - Receipt, ReceiptAttachment
  2. Creare app/db/models/accounting_entry.py - AccountingEntry
  3. Prima migrare: 001_initial_receipts.py
  4. Creare folder data/uploads/ pentru fișiere

Etapa 3: Backend CRUD + Upload

  1. Creare app/db/crud/receipt.py - operații CRUD
  2. Creare app/db/crud/attachment.py - upload/download fișiere
  3. Creare app/db/crud/accounting_entry.py - note contabile
  4. Creare app/schemas/receipt.py - request/response Pydantic

Etapa 4: Business Logic + Workflow

  1. 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

  1. Creare app/routers/receipts.py - toate endpoint-urile
  2. Register router în main.py
  3. Middleware pentru upload fișiere

Etapa 6: Nomenclatoare Oracle

  1. 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

  1. npm create vite@latest frontend -- --template vue
  2. Instalare dependențe: pinia, vue-router, primevue, axios
  3. Copy configurație PrimeVue din reports-app
  4. Copy CSS shared din reports-app (design tokens, patterns)

Etapa 8: Frontend Views

  1. Creare views/receipts/ReceiptsListView.vue - listă cu filtre
  2. Creare views/receipts/ReceiptCreateView.vue - form + upload
  3. Creare views/receipts/ReceiptDetailView.vue - detalii + note
  4. Creare views/receipts/ReceiptApprovalView.vue - view contabil
  5. Creare stores/receiptsStore.js - Pinia store
  6. Configurare router și layout

Etapa 9: Testing & Finalizare

  1. Unit tests pentru CRUD
  2. Integration tests pentru API
  3. Manual testing checklist
  4. Actualizare documentație
  5. Commit și push pe branch
  6. 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:

  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/