Integrate shared JWT authentication into data-entry-app: - Add Oracle pool initialization for auth service - Add AuthenticationMiddleware to protect API routes - Update all receipt endpoints to use CurrentUser from JWT - Add shared auth router (/api/auth/login, /api/auth/refresh) Add nomenclature synchronization feature: - Create SQLite models for synced suppliers, local suppliers, and cash registers - Add nomenclature router with sync triggers and CRUD endpoints - Add sync service for Oracle → SQLite nomenclature data - Update nomenclature_service to use synced SQLite data with fallbacks Create shared frontend components: - Add shared/frontend/ with LoginView.vue, auth store factory, login.css - Integrate shared login and auth into data-entry-app frontend - Add axios-based API service with token refresh interceptor 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
371 lines
13 KiB
Markdown
371 lines
13 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 si autentificare
|
|
- `shared/auth/` - autentificare JWT comuna (middleware, routes factory, auth service)
|
|
- `shared/frontend/components/LoginView.vue` - UI login partajat
|
|
- `shared/frontend/stores/auth.js` - Pinia auth store factory
|
|
- `shared/frontend/styles/login.css` - stiluri login
|
|
|
|
### 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 │ │ │ │
|
|
│ └──────────────┘ └──────┬───────┘ └──────────────┘ │
|
|
│ │ │ │
|
|
│ │ OCR Upload │ Nomenclatoare │
|
|
│ ▼ ▼ │
|
|
│ ┌──────────────┐ ┌──────────────┐ │
|
|
│ │ OCR Service │ │ Oracle │ │
|
|
│ │ PaddleOCR │ │ (read-only) │ │
|
|
│ │ +Tesseract │ └──────────────┘ │
|
|
│ └──────────────┘ │
|
|
│ │
|
|
└─────────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
## 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
|
|
```
|
|
|
|
## OCR Processing Pipeline
|
|
|
|
### 5. OCR Architecture
|
|
|
|
**Alegere**: PaddleOCR (primar) + Tesseract (fallback), procesare 100% locala
|
|
|
|
**Motivatie**:
|
|
- Zero costuri externe (fara API-uri cloud)
|
|
- Procesare on-premise (date sensibile raman locale)
|
|
- PaddleOCR: acuratete ridicata, CPU-friendly
|
|
- Tesseract: suport excelent pentru diacritice romanesti
|
|
|
|
**Stack OCR**:
|
|
```
|
|
┌─────────────────────────────────────────────────────┐
|
|
│ OCR Pipeline │
|
|
├─────────────────────────────────────────────────────┤
|
|
│ │
|
|
│ Image Upload → ImagePreprocessor → OCREngine │
|
|
│ │ │ │ │
|
|
│ │ ▼ ▼ │
|
|
│ │ ┌─────────┐ ┌──────────────┐ │
|
|
│ │ │ OpenCV │ │ PaddleOCR │ │
|
|
│ │ │ Pipeline│ │ (primary) │ │
|
|
│ │ └─────────┘ └──────┬───────┘ │
|
|
│ │ │ │ │
|
|
│ │ │ fallback│ │
|
|
│ │ │ ▼ │
|
|
│ │ │ ┌──────────────┐ │
|
|
│ │ │ │ Tesseract │ │
|
|
│ │ │ │ (ron+eng) │ │
|
|
│ │ │ └──────────────┘ │
|
|
│ │ │ │ │
|
|
│ ▼ ▼ ▼ │
|
|
│ ┌──────────────────────────────────────────────┐ │
|
|
│ │ ReceiptExtractor (Regex) │ │
|
|
│ │ - Amount patterns (TOTAL, DE PLATA) │ │
|
|
│ │ - Date patterns (DD.MM.YYYY) │ │
|
|
│ │ - CUI patterns (C.U.I., C.I.F.) │ │
|
|
│ │ - Vendor extraction (first lines) │ │
|
|
│ └──────────────────────────────────────────────┘ │
|
|
│ │ │
|
|
│ ▼ │
|
|
│ ExtractionResult + Confidence │
|
|
│ │
|
|
└─────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
### Image Preprocessing Pipeline
|
|
|
|
```python
|
|
def preprocess(image):
|
|
1. Convert to grayscale
|
|
2. Resize if width < 1000px (upscale for better OCR)
|
|
3. Deskew using Hough lines (straighten rotated text)
|
|
4. Denoise (Non-local means denoising)
|
|
5. Adaptive thresholding (binarization)
|
|
6. Morphological close (connect broken characters)
|
|
return processed_image
|
|
```
|
|
|
|
### Extraction Patterns (Romanian Receipts)
|
|
|
|
| Pattern Type | Regex Examples | Confidence |
|
|
|--------------|----------------|------------|
|
|
| Amount | `TOTAL\s*:?\s*([\d.,]+)` | 0.95 |
|
|
| Date | `(\d{2}[./]\d{2}[./]\d{4})` | 0.90 |
|
|
| CUI | `C\.?U\.?I\.?\s*:?\s*(\d{6,10})` | 0.95 |
|
|
| Receipt Number | `NR\.?\s*BON\s*:?\s*(\d+)` | 0.95 |
|
|
| Vendor | First 5 non-keyword lines | 0.70 |
|
|
|
|
### OCR API Endpoints
|
|
|
|
```
|
|
GET /api/ocr/status # Check OCR availability
|
|
POST /api/ocr/extract # Extract from uploaded image
|
|
POST /api/ocr/extract-attachment/{id} # Re-process existing attachment
|
|
```
|
|
|
|
### System Dependencies
|
|
|
|
```bash
|
|
# Ubuntu/Debian
|
|
apt-get install -y \
|
|
tesseract-ocr tesseract-ocr-ron tesseract-ocr-eng \
|
|
poppler-utils libgl1-mesa-glx libglib2.0-0
|
|
```
|
|
|
|
## Testing Strategy
|
|
|
|
### Unit Tests
|
|
- CRUD operations
|
|
- Workflow transitions
|
|
- Entry generation logic
|
|
- OCR extraction patterns
|
|
|
|
### Integration Tests
|
|
- API endpoints
|
|
- File upload/download
|
|
- Oracle nomenclature fetch
|
|
- OCR endpoint with sample receipts
|
|
|
|
### E2E Tests
|
|
- Complete workflow: create → submit → approve
|
|
- File upload cu preview
|
|
- OCR extraction → form auto-fill
|