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>
276 lines
8.2 KiB
Markdown
276 lines
8.2 KiB
Markdown
# Architecture: Data Entry App
|
|
|
|
## Overview
|
|
|
|
Aplicatie separata pentru introducere date in ERP, cu workflow de aprobare si staging area inainte de sincronizare in Oracle.
|
|
|
|
## Decizii Arhitecturale
|
|
|
|
### 1. SQLModel + Alembic
|
|
|
|
**Alegere**: SQLModel (Pydantic + SQLAlchemy) cu Alembic pentru migrari
|
|
|
|
**Motivatie**:
|
|
- Creat de autorul FastAPI - integrare perfecta
|
|
- Un model = Pydantic + SQLAlchemy - nu duplici definitii
|
|
- Async support nativ
|
|
- Alembic - standard industrial pentru migrari
|
|
- Validare automata - Pydantic valideaza input, SQLAlchemy gestioneaza DB
|
|
|
|
**Alternative considerate**:
|
|
- SQLAlchemy pur: Mai verbose, necesita scheme Pydantic separate
|
|
- Tortoise ORM: Async nativ dar comunitate mai mica
|
|
- Peewee: Simplu dar fara async
|
|
|
|
### 2. Separare de Reports-App
|
|
|
|
**Alegere**: Aplicatie separata in `data-entry-app/`
|
|
|
|
**Motivatie**:
|
|
- Responsabilitati diferite: reports = read-only, data-entry = write
|
|
- Lifecycle diferit: data-entry poate avea releases mai frecvente
|
|
- Risc izolat: bug in data-entry nu afecteaza raportarile
|
|
- Scalare independenta
|
|
|
|
**Shared Components**:
|
|
- `shared/database/oracle_pool.py` - conexiune Oracle pentru nomenclatoare
|
|
- `shared/auth/` - autentificare JWT comuna
|
|
|
|
### 3. Workflow cu Staging Area
|
|
|
|
**Alegere**: SQLite local ca staging, apoi sync in Oracle
|
|
|
|
**Motivatie**:
|
|
- Permite lucru offline (utilizator poate completa bonuri)
|
|
- Review de contabil inainte de date in Oracle
|
|
- Rollback simplu (stergem din SQLite)
|
|
- Audit trail complet
|
|
|
|
**Flow**:
|
|
```
|
|
User Input → SQLite (staging) → Contabil Review → Oracle (final)
|
|
```
|
|
|
|
### 4. Storage Fisiere
|
|
|
|
**Alegere**: Filesystem local cu referinte in DB
|
|
|
|
**Motivatie**:
|
|
- Simplu de implementat si backup
|
|
- Performanta buna pentru imagini
|
|
- Poate migra la S3/Azure Blob daca e nevoie
|
|
|
|
**Structura**:
|
|
```
|
|
data/uploads/
|
|
{year}/
|
|
{month}/
|
|
{uuid}.{ext}
|
|
```
|
|
|
|
## Diagrama Componente
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
│ data-entry-app │
|
|
├─────────────────────────────────────────────────────────────────┤
|
|
│ │
|
|
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
|
│ │ Frontend │────▶│ Backend │────▶│ SQLite │ │
|
|
│ │ Vue.js │ │ FastAPI │ │ (staging) │ │
|
|
│ │ :3010 │ │ :8003 │ │ │ │
|
|
│ └──────────────┘ └──────┬───────┘ └──────────────┘ │
|
|
│ │ │
|
|
│ │ Nomenclatoare │
|
|
│ ▼ │
|
|
│ ┌──────────────┐ │
|
|
│ │ Oracle │ │
|
|
│ │ (read-only) │ │
|
|
│ └──────────────┘ │
|
|
│ │
|
|
└─────────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
## Model de Date
|
|
|
|
### Receipt (Bon Fiscal)
|
|
|
|
```
|
|
receipts
|
|
├── id (PK)
|
|
├── receipt_type: enum(bon_fiscal, chitanta)
|
|
├── direction: enum(cheltuiala, incasare)
|
|
├── receipt_number, receipt_series
|
|
├── receipt_date, amount, description
|
|
├── company_id, partner_id, partner_name
|
|
├── cash_register_id, cash_register_name
|
|
├── expense_type_code
|
|
├── status: enum(draft, pending_review, approved, rejected, synced)
|
|
├── created_by, created_at, updated_at
|
|
├── submitted_at, reviewed_by, reviewed_at
|
|
├── rejection_reason
|
|
└── oracle_synced_at, oracle_act_id, oracle_error
|
|
```
|
|
|
|
### ReceiptAttachment (Atasamente)
|
|
|
|
```
|
|
receipt_attachments
|
|
├── id (PK)
|
|
├── receipt_id (FK)
|
|
├── filename, stored_filename
|
|
├── file_path, file_size, mime_type
|
|
└── uploaded_at
|
|
```
|
|
|
|
### AccountingEntry (Note Contabile)
|
|
|
|
```
|
|
accounting_entries
|
|
├── id (PK)
|
|
├── receipt_id (FK)
|
|
├── entry_type: enum(debit, credit)
|
|
├── account_code, account_name
|
|
├── amount
|
|
├── partner_id, cost_center_id
|
|
├── is_auto_generated
|
|
└── modified_by, modified_at
|
|
```
|
|
|
|
## Workflow States
|
|
|
|
```
|
|
┌─────────┐
|
|
│ DRAFT │◀────────────────────┐
|
|
└────┬────┘ │
|
|
│ submit() │ (edit after reject)
|
|
▼ │
|
|
┌──────────────┐ │
|
|
│PENDING_REVIEW│──────────────────┤
|
|
└──────┬───────┘ │
|
|
│ │
|
|
┌─────┴─────┐ │
|
|
▼ ▼ │
|
|
┌────────┐ ┌────────┐ │
|
|
│APPROVED│ │REJECTED│──────────────┘
|
|
└────┬───┘ └────────┘ resubmit()
|
|
│
|
|
│ (Faza 2)
|
|
▼
|
|
┌──────┐
|
|
│SYNCED│
|
|
└──────┘
|
|
```
|
|
|
|
## Generare Note Contabile
|
|
|
|
### Algoritm
|
|
|
|
```python
|
|
def generate_entries(receipt):
|
|
expense_type = EXPENSE_TYPES[receipt.expense_type_code]
|
|
entries = []
|
|
|
|
if expense_type.has_vat:
|
|
net_amount = receipt.amount / Decimal('1.19')
|
|
vat_amount = receipt.amount - net_amount
|
|
|
|
# Cheltuiala (debit)
|
|
entries.append(Entry(DEBIT, expense_type.account, net_amount))
|
|
# TVA (debit)
|
|
entries.append(Entry(DEBIT, "4426", vat_amount))
|
|
else:
|
|
entries.append(Entry(DEBIT, expense_type.account, receipt.amount))
|
|
|
|
# Credit casa/banca
|
|
entries.append(Entry(CREDIT, receipt.cash_register_account, receipt.amount))
|
|
|
|
return entries
|
|
```
|
|
|
|
### Exemplu: Bon Benzina 200 RON
|
|
|
|
```
|
|
Debit 6022 Cheltuieli combustibil 168.07
|
|
Debit 4426 TVA deductibila 31.93
|
|
Credit 5311 Casa in lei 200.00
|
|
```
|
|
|
|
## Integrare Oracle (Faza 2)
|
|
|
|
### Proceduri Stocate
|
|
|
|
```sql
|
|
-- 1. Initializare
|
|
pack_contafin.init_scriere_act_rul_local()
|
|
|
|
-- 2. Insert linii
|
|
INSERT INTO ACT_TEMP (
|
|
ID_ACT, DATAIREG, DATAACT, SCD, ASCD, SCC, ASCC,
|
|
SUMA, ID_CTR, ID_PARTD, EXPLICATIA, ...
|
|
)
|
|
|
|
-- 3. Finalizare
|
|
pack_contafin.finalizeaza_scriere_act_rul()
|
|
→ SCRIE_IN_ACT()
|
|
→ SCRIE_IN_RUL()
|
|
→ Actualizare situatii (BV, BP, TVA)
|
|
```
|
|
|
|
## Securitate
|
|
|
|
### Autentificare
|
|
- JWT tokens din shared auth
|
|
- Middleware valideaza token si injecteaza user
|
|
|
|
### Autorizare
|
|
- Permisiuni verificate in services
|
|
- Utilizator poate edita doar bonurile proprii in DRAFT
|
|
- Doar contabil poate aproba/respinge
|
|
|
|
### Upload Fisiere
|
|
- Validare MIME type (whitelist)
|
|
- Sanitizare nume fisier
|
|
- Limita dimensiune (10MB)
|
|
- Stocare cu UUID (previne path traversal)
|
|
|
|
## Configuratie
|
|
|
|
### Environment Variables
|
|
|
|
```bash
|
|
# SQLite Database
|
|
SQLITE_DATABASE_PATH=data/receipts.db
|
|
|
|
# File Storage
|
|
UPLOAD_PATH=data/uploads
|
|
MAX_UPLOAD_SIZE=10485760 # 10MB
|
|
|
|
# Oracle (pentru nomenclatoare)
|
|
ORACLE_USER=CONTAFIN_ORACLE
|
|
ORACLE_PASSWORD=***
|
|
ORACLE_HOST=localhost
|
|
ORACLE_PORT=1526
|
|
ORACLE_SID=ROA
|
|
|
|
# JWT (shared)
|
|
JWT_SECRET_KEY=***
|
|
JWT_ALGORITHM=HS256
|
|
```
|
|
|
|
## Testing Strategy
|
|
|
|
### Unit Tests
|
|
- CRUD operations
|
|
- Workflow transitions
|
|
- Entry generation logic
|
|
|
|
### Integration Tests
|
|
- API endpoints
|
|
- File upload/download
|
|
- Oracle nomenclature fetch
|
|
|
|
### E2E Tests
|
|
- Complete workflow: create → submit → approve
|
|
- File upload cu preview
|