-```
-
-### 4.2 Add CUI Field to Form State
-**File**: `data-entry-app/frontend/src/views/receipts/ReceiptCreateView.vue`
-
-Add to form ref initialization:
-```javascript
-cui: '',
-ocr_raw_text: '',
-```
-
-### 4.3 Add CUI Display Field
-Add after Furnizor dropdown (around line 210):
-```vue
-
-
-
-
-
- CUI negăsit în nomenclator
-
-
-```
-
-### 4.4 Change Supplier Dialog to Warning Banner
-**Current behavior** (lines 555-563): When CUI not found, opens blocking dialog.
-
-**New behavior**: Show non-blocking warning message.
-
-Replace the `else` block in `applyOCRData()`:
-```javascript
-} else {
- // Not found - show warning but allow continuing
- supplierWarning.value = {
- show: true,
- cui: data.cui,
- name: data.partner_name || ''
- }
- // Still set form values from OCR
- form.value.cui = data.cui
- form.value.partner_name = data.partner_name || ''
-
- toast.add({
- severity: 'warn',
- summary: 'Furnizor negăsit',
- detail: `CUI ${data.cui} nu a fost găsit în nomenclator`,
- life: 5000
- })
-}
-```
-
-Add ref for warning state:
-```javascript
-const supplierWarning = ref({ show: false, cui: '', name: '' })
-```
-
-### 4.5 Update `applyOCRData()` to Save Raw Text
-Add to the function:
-```javascript
-if (data.cui) form.value.cui = data.cui
-if (data.raw_text) form.value.ocr_raw_text = data.raw_text
-```
-
-### 4.6 Update `loadReceipt()` for Edit Mode
-Add to existing field mapping:
-```javascript
-cui: receipt.value.cui || '',
-ocr_raw_text: receipt.value.ocr_raw_text || '',
-```
-
----
-
-## Part 5: Backend Approval Validation
-
-**File**: `data-entry-app/backend/app/services/receipt_service.py`
-
-In `approve_receipt()` method, add validation:
-```python
-if not receipt.cui:
- return False, "Trebuie completat codul fiscal (CUI) pentru aprobare", None
-```
-
-**Note**: At approval, only `cui` (fiscal code) is required, NOT `partner_id`.
-The ROA ERP has a stored procedure that searches/creates suppliers based on `cui`.
-The `partner_id` is only populated later during Oracle import phase.
-
----
-
-## Part 6: OCR Payment Methods Extraction
-
-### 6.1 Update ExtractionResult Dataclass
-**File**: `data-entry-app/backend/app/services/ocr_extractor.py`
-
-Add to `ExtractionResult` (after line 24, after `items_count`):
-```python
-payment_methods: List[dict] = field(default_factory=list) # [{"method":"CARD","amount":Decimal}]
-```
-
-### 6.2 Add Payment Method Patterns
-**File**: `data-entry-app/backend/app/services/ocr_extractor.py`
-
-Add new patterns (after TVA_PATTERNS ~line 184):
-```python
-# Payment method patterns - appears after TOTAL LEI, before TOTAL TVA
-# Format: "CARD: 50.00" or "NUMERAR 100.00" or "PLATA CARD: 50.00"
-PAYMENT_METHOD_PATTERNS = [
- # CARD with amount
- (r'(?:PLATA\s+)?CARD\s*:?\s*([\d\s.,]+)', 'CARD', 0.95),
- # NUMERAR (cash) with amount
- (r'NUMERAR\s*:?\s*([\d\s.,]+)', 'NUMERAR', 0.95),
- # CASH alternative spelling
- (r'CASH\s*:?\s*([\d\s.,]+)', 'NUMERAR', 0.90),
-]
-```
-
-### 6.3 Add Extraction Method
-**File**: `data-entry-app/backend/app/services/ocr_extractor.py`
-
-Add new method `_extract_payment_methods()` (after `_extract_address` ~line 996):
-```python
-def _extract_payment_methods(self, text: str) -> List[dict]:
- """
- Extract payment methods (CARD/NUMERAR) from receipt.
- These appear after TOTAL LEI and before TOTAL TVA section.
-
- Returns list of: {'method': 'CARD'/'NUMERAR', 'amount': Decimal}
- """
- payment_methods = []
- seen_methods = set()
-
- # Normalize spaces in numbers
- normalized_text = re.sub(r'(\d+)[.,]\s+(\d{2})', r'\1.\2', text)
-
- # Find the region between TOTAL LEI and TOTAL TVA
- total_lei_match = re.search(r'TOTAL\s+LEI\s*([\d\s.,]+)', normalized_text, re.IGNORECASE)
- total_tva_match = re.search(r'TOTAL\s+T[VU][AR]', normalized_text, re.IGNORECASE)
-
- # Define search region (after TOTAL LEI, before TOTAL TVA if exists)
- if total_lei_match:
- start_pos = total_lei_match.end()
- end_pos = total_tva_match.start() if total_tva_match else len(normalized_text)
- search_region = normalized_text[start_pos:end_pos]
- else:
- search_region = normalized_text # Fallback to full text
-
- for pattern, method, confidence in self.PAYMENT_METHOD_PATTERNS:
- for match in re.finditer(pattern, search_region, re.IGNORECASE):
- try:
- amount_str = match.group(1).replace(' ', '')
- amount_str = self._normalize_number(re.sub(r'[^\d.,]', '', amount_str))
- amount = Decimal(amount_str)
- if amount > 0 and method not in seen_methods:
- payment_methods.append({
- 'method': method,
- 'amount': amount
- })
- seen_methods.add(method)
- except (InvalidOperation, ValueError):
- continue
-
- return payment_methods
-```
-
-### 6.4 Call Extraction in `extract()` Method
-**File**: `data-entry-app/backend/app/services/ocr_extractor.py`
-
-Add to `extract()` method (after line 255, after `result.address = ...`):
-```python
-result.payment_methods = self._extract_payment_methods(text_upper)
-```
-
-### 6.5 Frontend - Add Payment Methods Display
-**File**: `data-entry-app/frontend/src/views/receipts/ReceiptCreateView.vue`
-
-Add to form ref:
-```javascript
-payment_methods: [],
-```
-
-Add to `applyOCRData()`:
-```javascript
-if (data.payment_methods) form.value.payment_methods = data.payment_methods
-```
-
-Add UI display (after TVA breakdown section):
-```vue
-
-
-
-
-
-
-
-```
-
----
-
-## Implementation Order
-
-| Step | Task | Files |
-|------|------|-------|
-| 1 | Add `cui`, `ocr_raw_text`, `payment_methods` to model | `models/receipt.py` |
-| 2 | Create migration | `migrations/versions/...` |
-| 3 | Update schemas | `schemas/receipt.py` |
-| 4 | Fix image resize | `services/image_preprocessor.py` |
-| 5 | Add payment methods extraction to OCR | `services/ocr_extractor.py` |
-| 6 | Unify frontend form + add new fields | `views/receipts/ReceiptCreateView.vue` |
-| 7 | Add approval validation | `services/receipt_service.py` |
-| 8 | Test full workflow | Manual testing |
-
----
-
-## Files to Modify
-
-### Backend
-- `data-entry-app/backend/app/db/models/receipt.py` - Add cui, ocr_raw_text, payment_methods fields
-- `data-entry-app/backend/app/schemas/receipt.py` - Add PaymentMethodSchema, update schemas
-- `data-entry-app/backend/app/services/image_preprocessor.py` - Fix resize bug (cap at 4000px)
-- `data-entry-app/backend/app/services/ocr_extractor.py` - Add payment methods extraction
-- `data-entry-app/backend/app/services/receipt_service.py` - Add approval validation
-- `data-entry-app/backend/migrations/versions/` - New migration
-
-### Frontend
-- `data-entry-app/frontend/src/views/receipts/ReceiptCreateView.vue` - Unify form, add CUI + payment methods fields, change dialog to warning
-
----
-
-## Expected Behavior After Implementation
-
-1. **OCR Scan**: Extracts supplier name, CUI, raw text, payment methods → all saved to draft
-2. **Payment Methods**: CARD/NUMERAR amounts extracted (after TOTAL LEI, before TOTAL TVA)
-3. **CUI Match**: Auto-fills supplier name from ROA, user can edit
-4. **CUI No Match**: Shows warning toast, allows saving draft with OCR data
-5. **Edit Mode**: Can re-scan OCR to update extracted data
-6. **Approval**: Requires valid `cui` (fiscal code) - NOT partner_id
-7. **Oracle Import** (later): Uses `cui` to find/create supplier via ROA stored procedure
-8. **Large Images**: Automatically resized to max 4000px before OCR
-
----
-
-## Romanian Receipt Structure Reference
-```
-NUME FIRMA S.R.L.
-CIF: RO12345678
-STR. EXEMPLU NR. 1
-
-[Product lines...]
-
-TOTAL LEI 150.00 ← Total amount
-CARD 50.00 ← Payment method 1 (NEW)
-NUMERAR 100.00 ← Payment method 2 (NEW)
-TOTAL TVA A-19% 23.95 ← TVA breakdown
-```
diff --git a/IMPLEMENTATION_PLAN_UNIFIED_APP.md b/IMPLEMENTATION_PLAN_UNIFIED_APP.md
deleted file mode 100644
index f7d589c..0000000
--- a/IMPLEMENTATION_PLAN_UNIFIED_APP.md
+++ /dev/null
@@ -1,285 +0,0 @@
-# Plan: Consolidare ROA2WEB - Pragmatic Monolith
-
-> **Branch**: `feature/unified-app-pragmatic-monolith`
-> **Status**: APROBAT - Ready for Implementation
-> **Efort estimat**: ~2.5 zile
-
----
-
-## Context și Problema
-
-### Situația Curentă
-- 2 aplicații frontend separate în IIS: `/roa2web/` și `/data-entry/`
-- Deploy greoi (2 build-uri, 2 configurații IIS separate)
-- Componentele shared cauzează probleme CSS cross-app (text alb pe fundal alb)
-- Nu există meniu unificat între aplicații
-
-### Obiectiv
-- Un singur meniu cu ambele aplicații (Reports + Data Entry)
-- Deploy simplificat (un build, un site IIS)
-- Izolare între module (bug în Reports să nu afecteze DataEntry)
-- URL-uri pe root: `/reports/*`, `/data-entry/*`
-
----
-
-## Decizie Arhitecturală: Pragmatic Monolith
-
-### De ce NU Micro-frontends?
-| Criteriu pentru MFE | ROA2WEB | Necesită MFE? |
-|---------------------|---------|---------------|
-| 20+ dezvoltatori | 1 dev | ❌ Nu |
-| Deploy de multe ori/zi | Săptămânal | ❌ Nu |
-| Milioane de utilizatori | 1-5 concurenți | ❌ Nu |
-| Framework-uri diferite | Vue only | ❌ Nu |
-
-**Verdict**: Module Federation / Single-SPA = OVERKILL
-
-### Abordare Aleasă: Monolith cu Mecanisme de Izolare
-- **Error Boundaries** per modul (bug în Reports nu strică DataEntry)
-- **Lazy Loading** (bundle-uri separate, încărcate la nevoie)
-- **Stores izolate** per modul
-- **Feature flags** pentru control
-
-**Blast radius cu protecții: 50-70%** (aproape ca 2 apps separate!)
-
----
-
-## Arhitectura Finală
-
-```
-┌─────────────────────────────────────────────────────────────┐
-│ ROA2WEB Unified SPA │
-│ │
-│ ┌─────────────────┐ ┌─────────────────┐ ┌──────────────┐ │
-│ │ Reports Module │ │ DataEntry Module │ │ Shared │ │
-│ │ /reports/* │ │ /data-entry/* │ │ - Auth │ │
-│ │ (lazy loaded) │ │ (lazy loaded) │ │ - Company │ │
-│ │ │ │ │ │ - Period │ │
-│ │ ErrorBoundary │ │ ErrorBoundary │ │ - Header │ │
-│ └────────┬────────┘ └────────┬─────────┘ └──────────────┘ │
-│ │ │ │
-│ ┌────────┴────────────────────┴─────────┐ │
-│ │ Vue Router + Global Error Handler │ │
-│ └────────────────────────────────────────┘ │
-└─────────────────────────────────────────────────────────────┘
- │
- ┌─────────┴─────────┐
- │ IIS Proxy │
- └─────────┬─────────┘
- ┌───────────────┴───────────────┐
- │ │
- ┌────────▼────────┐ ┌──────────▼────────┐
- │ Reports Backend │ │ DataEntry Backend │
- │ port 8001 │ │ port 8003 │
- │ (Oracle RO) │ │ (SQLite + Oracle) │
- └─────────────────┘ └───────────────────┘
-```
-
----
-
-## Structura Proiect Unified
-
-```
-roa2web/
-├── src/
-│ ├── main.js
-│ ├── App.vue # Meniu unificat
-│ ├── router/index.js # Rute unificate cu lazy loading
-│ │
-│ ├── modules/
-│ │ ├── reports/ # MODUL IZOLAT
-│ │ │ ├── ReportsLayout.vue # Error boundary pentru modul
-│ │ │ ├── views/
-│ │ │ │ ├── DashboardView.vue
-│ │ │ │ ├── InvoicesView.vue
-│ │ │ │ ├── BankCashRegisterView.vue
-│ │ │ │ ├── TrialBalanceView.vue
-│ │ │ │ ├── TelegramView.vue
-│ │ │ │ └── CacheStatsView.vue
-│ │ │ ├── stores/
-│ │ │ └── services/
-│ │ │
-│ │ └── data-entry/ # MODUL IZOLAT
-│ │ ├── DataEntryLayout.vue
-│ │ ├── views/
-│ │ │ ├── ReceiptsListView.vue
-│ │ │ └── ReceiptCreateView.vue
-│ │ ├── stores/
-│ │ └── services/
-│ │
-│ ├── shared/ # Shared între module
-│ │ ├── components/ # AppHeader, SlideMenu, CompanySelector, etc.
-│ │ ├── stores/ # Auth, Company, Period
-│ │ └── styles/
-│ │
-│ └── config/
-│ ├── menu.js # Configurație meniu unificat
-│ └── features.js # Feature flags
-│
-├── vite.config.js
-└── package.json
-```
-
----
-
-## Pași Implementare
-
-### Faza 1: Setup Proiect (0.5 zile)
-1. Creează directorul `roa2web/`
-2. Setup `package.json` cu dependencies combinate din ambele apps
-3. Setup `vite.config.js` cu:
- - Dual proxy: `/api/reports/*` → `:8001`, `/api/data-entry/*` → `:8003`
- - Lazy loading chunks configuration
- - Base path `/`
-4. Setup `main.js` cu Pinia, PrimeVue, Router
-
-### Faza 2: Migrare Module (1 zi)
-1. Copiază views din `reports-app/frontend/src/views/` → `modules/reports/views/`
-2. Copiază views din `data-entry-app/frontend/src/views/` → `modules/data-entry/views/`
-3. Copiază stores specifice fiecărui modul
-4. Creează `ReportsLayout.vue` și `DataEntryLayout.vue` cu error boundaries
-5. Setup router unificat:
- ```javascript
- const routes = [
- { path: '/login', component: LoginView },
- {
- path: '/reports',
- component: () => import('./modules/reports/ReportsLayout.vue'),
- children: [
- { path: 'dashboard', component: () => import('./modules/reports/views/DashboardView.vue') },
- { path: 'invoices', component: () => import('./modules/reports/views/InvoicesView.vue') },
- // ...
- ]
- },
- {
- path: '/data-entry',
- component: () => import('./modules/data-entry/DataEntryLayout.vue'),
- children: [
- { path: '', component: () => import('./modules/data-entry/views/ReceiptsListView.vue') },
- { path: 'create', component: () => import('./modules/data-entry/views/ReceiptCreateView.vue') },
- ]
- },
- { path: '/', redirect: '/reports/dashboard' }
- ]
- ```
-
-### Faza 3: Izolare și Resilience (0.5 zile)
-1. Implementează `ErrorBoundary.vue`:
- ```vue
-
-
-
⚠️ {{ moduleName }} a întâmpinat o eroare
-
{{ error.message }}
-
-
-
-
- ```
-2. Separă stores per modul (nu global)
-3. Adaugă feature flags în `config/features.js`
-4. Testează izolarea: introduce bug în Reports, verifică că DataEntry funcționează
-
-### Faza 4: Build & Deploy (0.5 zile)
-1. Verifică bundle splitting cu `npm run build`:
- ```
- dist/assets/
- ├── index-[hash].js # Shell + shared (~150KB)
- ├── reports-[hash].js # Reports module (~200KB)
- └── data-entry-[hash].js # DataEntry module (~100KB)
- ```
-2. Update IIS web.config pentru SPA routing (toate rutele → index.html)
-3. Update deployment scripts pentru single app
-4. Test end-to-end pe server
-5. Deploy în producție
-
----
-
-## Meniu Unificat
-
-```javascript
-// config/menu.js
-export const menuSections = [
- {
- title: 'Rapoarte',
- items: [
- { to: '/reports/dashboard', icon: 'pi pi-home', label: 'Dashboard' },
- { to: '/reports/invoices', icon: 'pi pi-file', label: 'Facturi' },
- { to: '/reports/bank-cash', icon: 'pi pi-money-bill', label: 'Casa și Banca' },
- { to: '/reports/trial-balance', icon: 'pi pi-calculator', label: 'Balanță' },
- ]
- },
- {
- title: 'Introduceri Date',
- items: [
- { to: '/data-entry', icon: 'pi pi-list', label: 'Lista Bonuri' },
- { to: '/data-entry/create', icon: 'pi pi-plus', label: 'Bon Nou' },
- ]
- },
- {
- title: 'Sistem',
- items: [
- { to: '/reports/telegram', icon: 'pi pi-telegram', label: 'Telegram' },
- { to: '/reports/cache-stats', icon: 'pi pi-chart-bar', label: 'Cache Stats' },
- ]
- }
-];
-```
-
----
-
-## Fișiere de Referință
-
-### Fișiere Existente (sursă pentru copiere)
-- `reports-app/frontend/src/views/*.vue` - Views Reports
-- `reports-app/frontend/src/stores/*.js` - Stores Reports
-- `reports-app/frontend/src/services/api.js` - API service Reports
-- `data-entry-app/frontend/src/views/*.vue` - Views DataEntry
-- `data-entry-app/frontend/src/stores/*.js` - Stores DataEntry
-- `data-entry-app/frontend/src/services/api.js` - API service DataEntry
-- `shared/frontend/components/` - Componente shared (AppHeader, SlideMenu, etc.)
-- `shared/frontend/stores/` - Stores shared (auth, companies, accountingPeriod)
-
-### Configurații de Referință
-- `reports-app/frontend/vite.config.js` - Pentru proxy și build config
-- `reports-app/frontend/package.json` - Pentru dependencies
-- `data-entry-app/frontend/vite.config.js` - Pentru proxy config
-- `data-entry-app/frontend/package.json` - Pentru dependencies
-
----
-
-## URL-uri Finale
-
-| Path | Descriere |
-|------|-----------|
-| `/` | Redirect la `/reports/dashboard` |
-| `/login` | Pagină login |
-| `/reports/dashboard` | Dashboard principal |
-| `/reports/invoices` | Facturi |
-| `/reports/bank-cash` | Casa și Banca |
-| `/reports/trial-balance` | Balanță de Verificare |
-| `/reports/telegram` | Telegram Bot |
-| `/reports/cache-stats` | Statistici Cache |
-| `/data-entry` | Lista Bonuri |
-| `/data-entry/create` | Bon Nou |
-| `/data-entry/:id` | Detalii Bon |
-| `/data-entry/:id/edit` | Editare Bon |
-
----
-
-## Note Importante
-
-1. **Backend-urile rămân separate** - port 8001 (Reports) și port 8003 (DataEntry)
-2. **IIS proxy routing** - trebuie configurat pentru a ruta API calls corect
-3. **Error Boundaries** - critice pentru izolarea modulelor
-4. **Lazy Loading** - asigură bundle splitting corect
-5. **Feature flags** - permit dezactivarea unui modul fără redeploy
-
----
-
-## Handover Notes
-
-Această implementare va înlocui cele 2 aplicații separate cu o aplicație unificată.
-După implementare, directoarele `reports-app/frontend/` și `data-entry-app/frontend/`
-pot fi arhivate/șterse, păstrând doar `unified-app/`.
-
-Backend-urile rămân neschimbate în `reports-app/backend/` și `data-entry-app/backend/`.
diff --git a/QUICK-START.md b/QUICK-START.md
new file mode 100644
index 0000000..4d71587
--- /dev/null
+++ b/QUICK-START.md
@@ -0,0 +1,140 @@
+# ROA2WEB Ultrathin Monolith - Quick Start 🚀
+
+## Pornire Rapidă
+
+### PROD Environment (server PRODUCȚIE 10.0.20.36)
+```bash
+./start-prod.sh # Pornește tot: SSH tunnel + backend + frontend
+./start-prod.sh stop # Oprește toate serviciile
+```
+
+### TEST Environment (server TEST 10.0.20.121)
+```bash
+./start-test.sh # Pornește tot: SSH tunnel + backend + frontend
+./start-test.sh stop # Oprește toate serviciile
+```
+
+## Verificare Status
+
+```bash
+./status.sh # Arată status-ul tuturor serviciilor
+```
+
+## Ce s-a Schimbat?
+
+### Înainte (3 Backend-uri Separate)
+```
+Reports Backend → Port 8001
+Data Entry Backend → Port 8003
+Telegram Bot → Port 8002
+```
+
+### Acum (1 Backend Unificat)
+```
+Unified Backend → Port 8000
+ ├── Reports API: /api/reports/*
+ ├── Data Entry: /api/data-entry/*
+ ├── Telegram: /api/telegram/*
+ └── Bot: Running ca background task
+```
+
+## Scripturi Disponibile
+
+| Script | Descriere |
+|--------|-----------|
+| `./start-prod.sh` | Pornește tot pentru PROD (Oracle PROD: 10.0.20.36) |
+| `./start-test.sh` | Pornește tot pentru TEST (Oracle TEST: 10.0.20.121) |
+| `./status.sh` | Verifică status-ul serviciilor |
+| `./start-backend.sh start/stop/restart` | Control granular backend |
+| `./start-frontend.sh restart` | Restart rapid frontend (~7s) |
+| `./test-unified-backend.sh` | Rulează testele comprehensive |
+
+## API Endpoints
+
+**Backend Unificat**: http://localhost:8000
+
+- **API Docs**: http://localhost:8000/docs
+- **Health Check**: http://localhost:8000/health
+- **Reports**: http://localhost:8000/api/reports/*
+- **Data Entry**: http://localhost:8000/api/data-entry/*
+- **Telegram**: http://localhost:8000/api/telegram/*
+
+**Frontend**: http://localhost:3000
+
+## Log Files
+
+```bash
+# Backend logs
+tail -f /tmp/unified_backend_dev.log # DEV
+tail -f /tmp/unified_backend_test.log # TEST
+
+# Frontend logs
+tail -f /tmp/unified_frontend_dev.log # DEV
+tail -f /tmp/unified_frontend_test.log # TEST
+```
+
+## Troubleshooting
+
+### Backend nu pornește
+```bash
+# Verifică logurile
+tail -n 50 /tmp/unified_backend_dev.log
+
+# Verifică dacă portul este ocupat
+lsof -i :8000
+
+# Oprește procesul vechi
+./start-prod.sh stop
+```
+
+### Frontend nu pornește
+```bash
+# Verifică logurile
+tail -n 50 /tmp/unified_frontend_dev.log
+
+# Reinstalează dependențele
+rm -rf node_modules package-lock.json
+npm install
+```
+
+### SSH Tunnel nu se conectează
+```bash
+# DEV (PRODUCȚIE)
+./ssh-tunnel-prod.sh stop
+./ssh-tunnel-prod.sh start
+
+# TEST
+./ssh-tunnel-test.sh stop
+./ssh-tunnel-test.sh start
+```
+
+## Configurare Inițială
+
+1. **Backend**: Creează `backend/.env` din `backend/.env.example`
+2. **Configurează variabilele** pentru mediul dorit (DEV/TEST)
+3. **Pornește serviciile**: `./start-prod.sh` sau `./start-test.sh`
+
+## Diferențe DEV vs TEST
+
+| Aspect | DEV | TEST |
+|--------|-----|------|
+| SSH Tunnel | `./ssh-tunnel-prod.sh` | `./ssh-tunnel-test.sh` |
+| Server Oracle | 10.0.20.36 (PROD) | 10.0.20.121 (TEST) |
+| Schema Test | ROMFAST (id=114) | MARIUSM_AUTO (id=110) |
+| .env File | `backend/.env` | `backend/.env.test` → `backend/.env` |
+| Hot Reload | DA (--reload) | NU (pentru stabilitate) |
+
+## Avantaje
+
+✅ **1 comandă** în loc de 3 pentru pornire
+✅ **1 proces** în loc de 3 de monitorizat
+✅ **Loguri unificate** într-un singur fișier
+✅ **Debugging mai ușor** - tot în același proces
+✅ **Resurse optimizate** - Oracle pool partajat
+✅ **Pornire mai rapidă** - inițializare paralelă
+
+## Documentație Completă
+
+- **Implementare**: `ULTRATHIN-MONOLITH-IMPLEMENTATION.md`
+- **Teste**: `./test-unified-backend.sh`
+- **Plan Detaliat**: `.auto-build/specs/ultrathin-monolith/plan.md`
diff --git a/README.md b/README.md
index 6092ac9..8681eb4 100644
--- a/README.md
+++ b/README.md
@@ -1,20 +1,20 @@
-# ROA2WEB - Modern ERP Reports Application
+# ROA2WEB - Modern ERP Application
**FastAPI Backend + Vue.js 3 Frontend + Telegram Bot**
-Modern microservices-based ERP reporting application for managing invoices, payments, and financial data with Oracle database integration.
+Modern ultrathin monolith ERP application for managing reports, data entry, and financial data with Oracle database integration.
---
## Project Overview
-ROA2WEB is a comprehensive financial reporting platform built with modern technologies:
+ROA2WEB is a comprehensive financial platform built with modern technologies:
-- **Backend**: FastAPI (Python) - High-performance async API
-- **Frontend**: Vue.js 3 + PrimeVue - Rich, responsive web interface
-- **Telegram Bot**: Alternative command-based interface
-- **Database**: Oracle Database with connection pooling
-- **Architecture**: Microservices with shared components
+- **Backend**: FastAPI (Python) - Unified async API with modular architecture
+- **Frontend**: Vue.js 3 + PrimeVue - Single-page application with lazy-loaded modules
+- **Telegram Bot**: Alternative command-based interface (integrated module)
+- **Database**: Oracle Database + SQLite (hybrid approach)
+- **Architecture**: Ultrathin monolith with clear module boundaries
---
@@ -35,16 +35,16 @@ git clone
cd roa2web
# Start all services with one command
-./start-dev.sh
+./start-prod.sh
```
-This starts SSH tunnel, backend (port 8001), and frontend (port 3000-3005).
+This starts SSH tunnel, unified backend (port 8001), and frontend (port 3000).
-**For individual service setup or troubleshooting**: See "Development & Testing" section below or component-specific READMEs.
+**For individual service setup or troubleshooting**: See "Development & Testing" section below.
### Access the Application
-- **Frontend**: http://localhost:3000 (or 3001-3005 if 3000 is busy)
+- **Frontend**: http://localhost:3000
- **Backend API Docs**: http://localhost:8001/docs (Swagger UI)
- **Backend ReDoc**: http://localhost:8001/redoc
- **Health Check**: http://localhost:8001/health
@@ -53,29 +53,39 @@ This starts SSH tunnel, backend (port 8001), and frontend (port 3000-3005).
## Architecture
-### Directory Structure
+### Ultrathin Monolith Structure
```
-
-├── shared/ # Shared components
-│ ├── database/ # Oracle connection pool (singleton)
-│ ├── auth/ # JWT authentication & middleware
-│ └── utils/ # Common utilities
+.
+├── backend/ # Unified FastAPI backend (port 8001)
+│ ├── modules/ # Business logic modules
+│ │ ├── reports/ # Reports module (Oracle read-only)
+│ │ ├── data_entry/ # Data entry module (SQLite + workflow)
+│ │ └── telegram/ # Telegram bot module
+│ ├── config.py # Centralized configuration
+│ └── main.py # FastAPI app entry point
│
-├── reports-app/ # Main reports application
-│ ├── backend/ # FastAPI backend (port 8001)
-│ ├── frontend/ # Vue.js 3 frontend (port 3000-3005)
-│ └── telegram-bot/ # Telegram bot (port 8002)
+├── src/ # Unified Vue.js 3 frontend
+│ ├── modules/ # Feature modules
+│ │ ├── reports/ # Reports frontend
+│ │ └── data-entry/ # Data entry frontend
+│ ├── shared/ # Shared frontend components
+│ ├── assets/ # Global CSS, images
+│ └── router/ # Vue Router
│
-├── nginx/ # Nginx reverse proxy config
-├── ssh-tunnel/ # SSH tunnel for Oracle DB
-├── deployment/ # Deployment scripts (Linux & Windows)
-└── scripts/ # Utility scripts
+├── shared/ # Shared backend components
+│ ├── database/ # Oracle connection pool
+│ ├── auth/ # JWT authentication
+│ └── frontend/ # Shared frontend assets
+│
+├── docs/ # Documentation
+├── deployment/ # Deployment scripts
+└── ssh-tunnel/ # SSH tunnel for Oracle DB
```
### Key Features
-- **Shared Database Pool**: Singleton Oracle connection pool shared across microservices
+- **Shared Database Pool**: Singleton Oracle connection pool shared across all modules
- **Two-Tier Cache System**: Hybrid L1 (Memory) + L2 (SQLite) for optimal performance
- **JWT Authentication**: Secure token-based auth with middleware
- **Microservices**: Independent services with clear separation of concerns
@@ -98,21 +108,21 @@ This starts SSH tunnel, backend (port 8001), and frontend (port 3000-3005).
## Development & Testing
-**Quick Start**: Use `./start-dev.sh` to start all services (SSH tunnel + Backend + Frontend).
+**Quick Start**: Use `./start-prod.sh` to start all services (SSH tunnel + Backend + Frontend).
**For detailed development commands, testing procedures, and troubleshooting**: See `CLAUDE.md` and component-specific READMEs:
-- Backend: `reports-app/backend/README.md`
-- Frontend: `reports-app/frontend/README.md` & `reports-app/frontend/tests/README.md`
+- Backend: `backend/ modules and CLAUDE.md`
+- Frontend: `src/ and docs/MONOLITH_ARCHITECTURE.md` & `E2E testing guide in docs/`
- Telegram Bot: `reports-app/telegram-bot/README.md`
**Key Commands**:
```bash
# Start All Services (FAST with parallel backend startup - ~11s dev, ~33s test)
-./start-dev.sh # Start all (SSH tunnel + Backends + Bot + Frontend)
+./start-prod.sh # Start all (SSH tunnel + Backends + Bot + Frontend)
./start-test.sh # Start all (TEST environment)
# Individual Service Control (NEW - for quick restarts!)
-./frontend.sh start|stop|restart|status # Frontend only (~7s restart!)
+./start-frontend.sh start|stop|restart|status # Frontend only (~7s restart!)
./backend-reports.sh start|stop|status # Reports backend only
./backend-data-entry.sh start|stop|status # Data Entry backend only
./bot.sh start|stop|status # Telegram bot only
@@ -121,53 +131,53 @@ This starts SSH tunnel, backend (port 8001), and frontend (port 3000-3005).
./status.sh # Show all services status + health checks
# Infrastructure Only
-./ssh_tunnel.sh start|stop|status # Oracle DB tunnel (production)
+./ssh-tunnel-prod.sh start|stop|status # Oracle DB tunnel (production)
./ssh-tunnel-test.sh start|stop|status # Oracle TEST tunnel
```
**💡 Pro Tips**:
-- **Frontend changes?** Use `./frontend.sh restart` instead of restarting everything (87% faster!)
+- **Frontend changes?** Use `./start-frontend.sh restart` instead of restarting everything (87% faster!)
- **Check what's running:** `./status.sh` shows everything at a glance
-- **Backend-uri pornesc în paralel** în start-dev.sh și start-test.sh pentru pornire mai rapidă
+- **Backend-uri pornesc în paralel** în start-prod.sh și start-test.sh pentru pornire mai rapidă
### 📖 Usage Flow
-**Individual scripts (`frontend.sh`, `backend-*.sh`, `bot.sh`) are environment-neutral:**
+**Individual scripts (`start-frontend.sh`, `start-backend.sh`, `backend-*.sh`, `bot.sh`) are environment-neutral:**
- They DON'T change `.env` files
- They use whatever `.env` is already present
- Use them for **quick restarts** when working on a specific service
-**Master scripts (`start-dev.sh`, `start-test.sh`) set the environment:**
-- `start-dev.sh` → uses existing `.env` files (DEV mode)
+**Master scripts (`start-prod.sh`, `start-test.sh`) set the environment:**
+- `start-prod.sh` → uses existing `.env` files (DEV mode)
- `start-test.sh` → copies `.env.test` → `.env` (TEST mode)
**Recommended workflow:**
```bash
# Morning: Start full stack with environment selection
-./start-dev.sh # DEV mode - sets up .env files
+./start-prod.sh # DEV mode - sets up .env files
# During development: Quick service restarts
-./frontend.sh restart # Frontend only (~7s)
+./start-frontend.sh restart # Frontend only (~7s)
./backend-reports.sh restart # Reports backend only (~30s)
-# ⚠️ Individual scripts inherit the environment set by start-dev.sh
+# ⚠️ Individual scripts inherit the environment set by start-prod.sh
# End of day: Stop everything
-./start-dev.sh stop
+./start-prod.sh stop
```
**Common scenarios:**
```bash
# Scenario 1: Working on frontend only
-./start-dev.sh # Start everything once
-./frontend.sh restart # Restart frontend multiple times (fast!)
+./start-prod.sh # Start everything once
+./start-frontend.sh restart # Restart frontend multiple times (fast!)
# Scenario 2: Debugging a single backend
-./start-dev.sh stop # Stop all
-./ssh_tunnel.sh start # Infrastructure only
+./start-prod.sh stop # Stop all
+./ssh-tunnel-prod.sh start # Infrastructure only
./backend-reports.sh start # Just the backend you need
-./frontend.sh start # Just the frontend
+./start-frontend.sh start # Just the frontend
# Scenario 3: Testing mode
./start-test.sh # Starts everything in TEST mode
@@ -295,9 +305,9 @@ BACKEND_API_URL=http://localhost:8001
### Component-Specific
- `README.md` - Main application README
-- `reports-app/backend/README.md` - Backend specifics
-- `reports-app/frontend/README.md` - Frontend guide
-- `reports-app/frontend/tests/README.md` - Frontend testing
+- `backend/ modules and CLAUDE.md` - Backend specifics
+- `src/ and docs/MONOLITH_ARCHITECTURE.md` - Frontend guide
+- `E2E testing guide in docs/` - Frontend testing
- `reports-app/telegram-bot/README.md` - Telegram bot guide
- `reports-app/telegram-bot/TELEGRAM_COMMANDS.md` - Bot commands
diff --git a/TESTING_CHECKLIST.md b/TESTING_CHECKLIST.md
deleted file mode 100644
index d195353..0000000
--- a/TESTING_CHECKLIST.md
+++ /dev/null
@@ -1,214 +0,0 @@
-# ROA2WEB Unified App - Integration Testing Checklist
-
-## Pre-Test Setup
-
-- [ ] Stop any previously running services: `./start-test.sh stop`
-- [ ] Verify SSH tunnel is configured: `./ssh-tunnel-test.sh status`
-- [ ] Start all services: `./start-test.sh`
-- [ ] Wait for all services to start (check logs if needed)
-
-## Service Health Checks
-
-### Backend Services
-- [ ] Reports Backend (8001): http://localhost:8001/health
-- [ ] Reports API Docs: http://localhost:8001/docs
-- [ ] Data Entry Backend (8003): http://localhost:8003/health
-- [ ] Data Entry API Docs: http://localhost:8003/docs
-- [ ] Telegram Bot Internal API (8002): Should be running (check logs: `/tmp/telegram_bot.log`)
-
-### Frontend
-- [ ] Unified Frontend loads: http://localhost:3000
-- [ ] No console errors in browser DevTools
-- [ ] Login page displays correctly
-
-## Authentication Flow
-
-### Login
-- [ ] Navigate to http://localhost:3000
-- [ ] Should redirect to `/login` automatically
-- [ ] Enter valid test credentials
-- [ ] Login succeeds and redirects to dashboard
-- [ ] User info displays in header (username, company selector)
-- [ ] Access token stored in localStorage
-- [ ] JWT contains correct user info and companies
-
-### Session Persistence
-- [ ] Refresh page - user remains logged in
-- [ ] Close tab and reopen - user remains logged in
-- [ ] Open in new tab - user is already logged in
-
-## Reports Module (http://localhost:3000/reports)
-
-### Navigation
-- [ ] Click "Rapoarte" in menu
-- [ ] Dashboard loads at `/reports/dashboard`
-- [ ] No console errors
-- [ ] Company selector works (change company)
-- [ ] Period selector works (change accounting period)
-
-### Dashboard Widgets
-- [ ] Metrics cards display (Sales, Purchases, etc.)
-- [ ] Charts render correctly
-- [ ] Data loads from Reports API (8001)
-- [ ] Check Network tab: requests go to `/api/reports/*`
-
-### Reports Pages
-- [ ] Navigate to "Facturi Clienți" (`/reports/invoices/sales`)
-- [ ] Table loads with data
-- [ ] Filters work (date range, company, status)
-- [ ] Pagination works
-- [ ] Export buttons work (Excel, PDF)
-- [ ] Invoice details modal opens
-- [ ] Navigate to "Facturi Furnizori" (`/reports/invoices/purchases`)
-- [ ] Verify same functionality as sales invoices
-
-### Treasury Reports
-- [ ] Navigate to "Trezorerie" (`/reports/treasury`)
-- [ ] Cash flow data loads
-- [ ] Charts display correctly
-- [ ] Date filters work
-
-### Error Boundary Testing
-- [ ] Manually trigger an error in Reports module (e.g., bad API call)
-- [ ] ErrorBoundary catches the error
-- [ ] Error message displays: "A apărut o eroare în modulul Rapoarte"
-- [ ] Other modules (Data Entry) remain functional
-
-## Data Entry Module (http://localhost:3000/data-entry)
-
-### Navigation
-- [ ] Click "Introduceri" in menu
-- [ ] Receipts list loads at `/data-entry/receipts`
-- [ ] No console errors
-- [ ] Check Network tab: requests go to `/api/data-entry/*`
-
-### Receipts List
-- [ ] Table displays receipts
-- [ ] Status badges display correctly (DRAFT, PENDING, APPROVED)
-- [ ] Filters work (date range, status, user)
-- [ ] Create new receipt button visible
-
-### Create Receipt
-- [ ] Click "Adaugă Bon Fiscal"
-- [ ] Form displays at `/data-entry/receipts/new`
-- [ ] Partner/Supplier dropdown loads from Data Entry API
-- [ ] Expense type dropdown works
-- [ ] Date picker works
-- [ ] Amount fields accept input
-- [ ] File upload works (image/PDF)
-- [ ] Save as DRAFT works
-- [ ] Receipt appears in list with DRAFT status
-
-### Edit Receipt
-- [ ] Click edit on a DRAFT receipt
-- [ ] Form loads with existing data
-- [ ] Modify fields
-- [ ] Save changes - updates successfully
-- [ ] Delete receipt - removes from list
-
-### Submit for Review
-- [ ] Open a DRAFT receipt
-- [ ] Click "Trimite spre aprobare"
-- [ ] Status changes to PENDING_REVIEW
-- [ ] Accounting entries auto-generated
-- [ ] Receipt is read-only in PENDING state
-
-### Approval Workflow (Accountant Role)
-- [ ] Login as accountant user
-- [ ] See pending receipts
-- [ ] Open PENDING receipt
-- [ ] Review accounting entries
-- [ ] Approve receipt - status changes to APPROVED
-- [ ] Receipt becomes fully read-only
-
-### Error Boundary Testing
-- [ ] Manually trigger an error in Data Entry module
-- [ ] ErrorBoundary catches the error
-- [ ] Error message displays: "A apărut o eroare în modulul Introduceri"
-- [ ] Other modules (Reports) remain functional
-
-## Cross-Module Testing
-
-### Module Switching
-- [ ] Start in Reports module
-- [ ] Navigate to Data Entry module
-- [ ] Return to Reports module
-- [ ] Company selection persists across modules
-- [ ] No console errors during switching
-- [ ] No memory leaks (check DevTools Memory tab)
-
-### Shared State
-- [ ] Login state shared (logout in one module logs out everywhere)
-- [ ] Company selection shared (change company affects both modules)
-- [ ] Period selection shared (for modules that use it)
-
-### API Isolation
-- [ ] Reports module only calls `/api/reports/*`
-- [ ] Data Entry module only calls `/api/data-entry/*`
-- [ ] No cross-contamination of API calls
-- [ ] Auth headers included in all requests
-
-## Logout Flow
-
-- [ ] Click logout button in header
-- [ ] User redirected to `/login`
-- [ ] Access token removed from localStorage
-- [ ] Cannot access protected routes without re-login
-- [ ] Attempting to access `/reports` or `/data-entry` redirects to login
-
-## Browser Compatibility
-
-- [ ] Chrome/Edge (latest)
-- [ ] Firefox (latest)
-- [ ] Safari (if available)
-
-## Responsive Design
-
-- [ ] Desktop (1920x1080)
-- [ ] Laptop (1366x768)
-- [ ] Tablet (768x1024)
-- [ ] Mobile (375x667)
-
-## Performance
-
-- [ ] Initial page load < 3s
-- [ ] Navigation between modules smooth
-- [ ] No unnecessary re-renders (check React DevTools)
-- [ ] API responses cached appropriately
-- [ ] Bundle sizes reasonable (check Network tab)
-
-## Build & Production
-
-- [ ] Run `npm run build`
-- [ ] Build completes without errors
-- [ ] Dist folder created with proper structure
-- [ ] index.html exists
-- [ ] Assets folder has JS/CSS bundles
-- [ ] Chunk splitting works (separate bundles for modules)
-- [ ] Source maps generated (for debugging)
-
-## Cleanup
-
-- [ ] Stop all services: `./start-test.sh stop`
-- [ ] Verify all ports released (8001, 8003, 8002, 3000)
-- [ ] SSH tunnel stopped
-- [ ] No lingering processes
-
-## Issues Found
-
-Document any issues discovered during testing:
-
-| Issue | Module | Severity | Description | Status |
-|-------|--------|----------|-------------|--------|
-| | | | | |
-
-## Sign-Off
-
-- **Tester**: _______________
-- **Date**: _______________
-- **Environment**: TEST / DEV / PROD
-- **Status**: PASS / FAIL / NEEDS FIXES
-
-## Notes
-
-Additional observations or comments:
diff --git a/backend-data-entry.sh b/backend-data-entry.sh
deleted file mode 100644
index 4aed10e..0000000
--- a/backend-data-entry.sh
+++ /dev/null
@@ -1,168 +0,0 @@
-#!/bin/bash
-# Backend Data Entry Service Control Script for ROA2WEB Unified App
-# Manages the FastAPI Data Entry backend on port 8003
-
-# Script directory
-SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
-ROOT_DIR="$SCRIPT_DIR"
-
-# Source helper functions
-source "$SCRIPT_DIR/scripts/service-helpers.sh"
-
-# Service configuration
-SERVICE_NAME="Data Entry Backend"
-PORT=8003
-LOG_FILE="/tmp/data-entry-backend.log"
-BACKEND_DIR="$ROOT_DIR/data-entry-app/backend"
-VENV_DIR="$BACKEND_DIR/venv"
-ENV_FILE="$BACKEND_DIR/.env"
-
-# Function to start backend
-start_backend() {
- print_header "Starting $SERVICE_NAME"
-
- # Check if port is already in use
- if ! check_port_available $PORT "$SERVICE_NAME"; then
- print_warning "$SERVICE_NAME may already be running"
- print_info "Use './backend-data-entry.sh status' to check or './backend-data-entry.sh stop' to stop it"
- return 1
- fi
-
- # Check backend directory
- if [ ! -d "$BACKEND_DIR" ]; then
- print_error "Backend directory not found: $BACKEND_DIR"
- return 1
- fi
-
- # Check virtual environment
- if [ ! -d "$VENV_DIR" ]; then
- print_error "Virtual environment not found: $VENV_DIR"
- print_info "Please run setup first: cd data-entry-app/backend && python3 -m venv venv && source venv/bin/activate && pip install -r requirements.txt"
- return 1
- fi
-
- # Check .env file
- if [ ! -f "$ENV_FILE" ]; then
- print_warning ".env file not found: $ENV_FILE"
- print_info "Database connection may fail"
- fi
-
- # Start backend
- print_info "Starting FastAPI server..."
- cd "$BACKEND_DIR"
-
- # Activate venv and start uvicorn in background
- (
- source venv/bin/activate
- nohup uvicorn app.main:app --host 0.0.0.0 --port $PORT > "$LOG_FILE" 2>&1 &
- echo $! > /tmp/data-entry-backend.pid
- )
-
- local pid=$(cat /tmp/data-entry-backend.pid 2>/dev/null)
- print_info "Started with PID: $pid"
- print_info "Log file: $LOG_FILE"
-
- # Wait for port to be ready (Data Entry takes longer due to SQLite migrations)
- if wait_for_port $PORT "$SERVICE_NAME" 35; then
- # Check health endpoint
- print_info "Checking health endpoint..."
- sleep 2
- local health_status=$(curl -s http://localhost:$PORT/health 2>&1 | head -1)
-
- if echo "$health_status" | grep -q "ok"; then
- print_success "$SERVICE_NAME started successfully and is healthy!"
- else
- print_success "$SERVICE_NAME started (health check inconclusive)"
- fi
-
- echo ""
- print_info "🌐 API URLs:"
- echo " • API Docs: http://localhost:$PORT/docs"
- echo " • Health: http://localhost:$PORT/health"
- echo ""
- print_info "📄 View logs: tail -f $LOG_FILE"
- return 0
- else
- print_error "Failed to start $SERVICE_NAME (timeout waiting for port $PORT)"
- print_info "Check logs: tail -20 $LOG_FILE"
- rm -f /tmp/data-entry-backend.pid
- return 1
- fi
-}
-
-# Function to stop backend
-stop_backend() {
- print_header "Stopping $SERVICE_NAME"
-
- kill_port $PORT "$SERVICE_NAME"
-
- # Clean up PID file
- if [ -f "/tmp/data-entry-backend.pid" ]; then
- rm /tmp/data-entry-backend.pid
- fi
-
- return 0
-}
-
-# Function to show backend status
-status_backend() {
- print_header "$SERVICE_NAME Status"
-
- if check_service_status $PORT "$SERVICE_NAME"; then
- echo ""
-
- # Check health endpoint
- print_info "Health check:"
- local health=$(curl -s http://localhost:$PORT/health 2>&1)
- if echo "$health" | grep -q "ok"; then
- print_success "Health endpoint: OK"
- else
- print_warning "Health endpoint: Not responding"
- fi
-
- # Show recent logs
- if [ -f "$LOG_FILE" ]; then
- echo ""
- tail_logs "$LOG_FILE" 10
- fi
- return 0
- else
- echo ""
- print_warning "Service is not running"
-
- # Show last logs if available
- if [ -f "$LOG_FILE" ]; then
- echo ""
- print_info "Last logs before shutdown:"
- tail_logs "$LOG_FILE" 10
- fi
- return 1
- fi
-}
-
-# Main script logic
-case "${1:-}" in
- start)
- start_backend
- ;;
- stop)
- stop_backend
- ;;
- status)
- status_backend
- ;;
- *)
- echo "Usage: $0 {start|stop|status}"
- echo ""
- echo "Commands:"
- echo " start - Start the Data Entry backend API server"
- echo " stop - Stop the Data Entry backend API server"
- echo " status - Show backend status and health check"
- echo ""
- echo "Examples:"
- echo " ./backend-data-entry.sh start # Start backend"
- echo " ./backend-data-entry.sh status # Check if running"
- echo " ./backend-data-entry.sh stop # Stop backend"
- exit 1
- ;;
-esac
diff --git a/backend-reports.sh b/backend-reports.sh
deleted file mode 100644
index cbd40f3..0000000
--- a/backend-reports.sh
+++ /dev/null
@@ -1,168 +0,0 @@
-#!/bin/bash
-# Backend Reports Service Control Script for ROA2WEB Unified App
-# Manages the FastAPI Reports backend on port 8001
-
-# Script directory
-SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
-ROOT_DIR="$SCRIPT_DIR"
-
-# Source helper functions
-source "$SCRIPT_DIR/scripts/service-helpers.sh"
-
-# Service configuration
-SERVICE_NAME="Reports Backend"
-PORT=8001
-LOG_FILE="/tmp/reports-backend.log"
-BACKEND_DIR="$ROOT_DIR/reports-app/backend"
-VENV_DIR="$BACKEND_DIR/venv"
-ENV_FILE="$BACKEND_DIR/.env"
-
-# Function to start backend
-start_backend() {
- print_header "Starting $SERVICE_NAME"
-
- # Check if port is already in use
- if ! check_port_available $PORT "$SERVICE_NAME"; then
- print_warning "$SERVICE_NAME may already be running"
- print_info "Use './backend-reports.sh status' to check or './backend-reports.sh stop' to stop it"
- return 1
- fi
-
- # Check backend directory
- if [ ! -d "$BACKEND_DIR" ]; then
- print_error "Backend directory not found: $BACKEND_DIR"
- return 1
- fi
-
- # Check virtual environment
- if [ ! -d "$VENV_DIR" ]; then
- print_error "Virtual environment not found: $VENV_DIR"
- print_info "Please run setup first: cd reports-app/backend && python3 -m venv venv && source venv/bin/activate && pip install -r requirements.txt"
- return 1
- fi
-
- # Check .env file
- if [ ! -f "$ENV_FILE" ]; then
- print_warning ".env file not found: $ENV_FILE"
- print_info "Database connection may fail"
- fi
-
- # Start backend
- print_info "Starting FastAPI server..."
- cd "$BACKEND_DIR"
-
- # Activate venv and start uvicorn in background
- (
- source venv/bin/activate
- nohup uvicorn app.main:app --host 0.0.0.0 --port $PORT > "$LOG_FILE" 2>&1 &
- echo $! > /tmp/reports-backend.pid
- )
-
- local pid=$(cat /tmp/reports-backend.pid 2>/dev/null)
- print_info "Started with PID: $pid"
- print_info "Log file: $LOG_FILE"
-
- # Wait for port to be ready
- if wait_for_port $PORT "$SERVICE_NAME" 30; then
- # Check health endpoint
- print_info "Checking health endpoint..."
- sleep 2
- local health_status=$(curl -s http://localhost:$PORT/health 2>&1 | head -1)
-
- if echo "$health_status" | grep -q "ok"; then
- print_success "$SERVICE_NAME started successfully and is healthy!"
- else
- print_success "$SERVICE_NAME started (health check inconclusive)"
- fi
-
- echo ""
- print_info "🌐 API URLs:"
- echo " • API Docs: http://localhost:$PORT/docs"
- echo " • Health: http://localhost:$PORT/health"
- echo ""
- print_info "📄 View logs: tail -f $LOG_FILE"
- return 0
- else
- print_error "Failed to start $SERVICE_NAME (timeout waiting for port $PORT)"
- print_info "Check logs: tail -20 $LOG_FILE"
- rm -f /tmp/reports-backend.pid
- return 1
- fi
-}
-
-# Function to stop backend
-stop_backend() {
- print_header "Stopping $SERVICE_NAME"
-
- kill_port $PORT "$SERVICE_NAME"
-
- # Clean up PID file
- if [ -f "/tmp/reports-backend.pid" ]; then
- rm /tmp/reports-backend.pid
- fi
-
- return 0
-}
-
-# Function to show backend status
-status_backend() {
- print_header "$SERVICE_NAME Status"
-
- if check_service_status $PORT "$SERVICE_NAME"; then
- echo ""
-
- # Check health endpoint
- print_info "Health check:"
- local health=$(curl -s http://localhost:$PORT/health 2>&1)
- if echo "$health" | grep -q "ok"; then
- print_success "Health endpoint: OK"
- else
- print_warning "Health endpoint: Not responding"
- fi
-
- # Show recent logs
- if [ -f "$LOG_FILE" ]; then
- echo ""
- tail_logs "$LOG_FILE" 10
- fi
- return 0
- else
- echo ""
- print_warning "Service is not running"
-
- # Show last logs if available
- if [ -f "$LOG_FILE" ]; then
- echo ""
- print_info "Last logs before shutdown:"
- tail_logs "$LOG_FILE" 10
- fi
- return 1
- fi
-}
-
-# Main script logic
-case "${1:-}" in
- start)
- start_backend
- ;;
- stop)
- stop_backend
- ;;
- status)
- status_backend
- ;;
- *)
- echo "Usage: $0 {start|stop|status}"
- echo ""
- echo "Commands:"
- echo " start - Start the Reports backend API server"
- echo " stop - Stop the Reports backend API server"
- echo " status - Show backend status and health check"
- echo ""
- echo "Examples:"
- echo " ./backend-reports.sh start # Start backend"
- echo " ./backend-reports.sh status # Check if running"
- echo " ./backend-reports.sh stop # Stop backend"
- exit 1
- ;;
-esac
diff --git a/backend/.env.dev.example b/backend/.env.dev.example
new file mode 100644
index 0000000..13923d5
--- /dev/null
+++ b/backend/.env.dev.example
@@ -0,0 +1,147 @@
+# ============================================================================
+# ROA2WEB Unified Backend - Environment Configuration (Development)
+# ============================================================================
+# Single backend process serving Reports, Data Entry, and Telegram modules
+# IMPORTANT: Never commit this file to git!
+
+# ============================================================================
+# ORACLE DATABASE CONFIGURATION (REQUIRED - Shared by all modules)
+# ============================================================================
+# Connection to CONTAFIN_ORACLE schema for authentication and user management
+# Each company is a separate schema in Oracle Database
+# Development: Through SSH tunnel (localhost:1526)
+
+ORACLE_USER=CONTAFIN_ORACLE
+ORACLE_PASSWORD=your_oracle_password_here
+ORACLE_HOST=localhost
+ORACLE_PORT=1526
+ORACLE_SID=ROA
+
+# Development: Start SSH tunnel before running backend
+# ./ssh_tunnel.sh start (production) or ./ssh-tunnel-test.sh start (test)
+
+# ============================================================================
+# JWT AUTHENTICATION (REQUIRED - Shared by all modules)
+# ============================================================================
+# Used for JWT token generation and validation (shared/auth/jwt_handler.py)
+
+JWT_SECRET_KEY=generate_with_secrets_token_urlsafe_32
+JWT_ALGORITHM=HS256
+
+# Token expiration settings (used by shared/auth/jwt_handler.py)
+ACCESS_TOKEN_EXPIRE_MINUTES=30
+REFRESH_TOKEN_EXPIRE_DAYS=7
+
+# ============================================================================
+# SESSION SECURITY - EMAIL 2FA (REQUIRED for Telegram email login)
+# ============================================================================
+# Used by Telegram module for session token validation
+# Must match between backend and Telegram bot
+
+AUTH_SESSION_SECRET=generate_with_secrets_token_urlsafe_32
+
+# ============================================================================
+# SERVER CONFIGURATION
+# ============================================================================
+# Unified backend server settings
+
+API_HOST=0.0.0.0
+API_PORT=8000
+DEBUG=true
+
+# CORS Origins (comma-separated, includes both old and new frontend ports)
+CORS_ORIGINS=http://localhost:3000,http://localhost:3010,http://localhost:5173
+
+# ============================================================================
+# REPORTS MODULE - CACHE CONFIGURATION (OPTIONAL - defaults provided)
+# ============================================================================
+# Two-tier hybrid cache system (L1: in-memory LRU, L2: SQLite persistent)
+# Used by backend/modules/reports/cache/config.py
+
+# Core Settings
+CACHE_ENABLED=True
+CACHE_TYPE=hybrid
+CACHE_SQLITE_PATH=./data/cache/roa2web_cache.db
+CACHE_MEMORY_MAX_SIZE=1000
+CACHE_DEFAULT_TTL=900
+
+# TTL per Cache Type (seconds)
+CACHE_TTL_SCHEMA=86400
+CACHE_TTL_COMPANIES=1800
+CACHE_TTL_DASHBOARD_SUMMARY=1800
+CACHE_TTL_DASHBOARD_TRENDS=1800
+CACHE_TTL_INVOICES=600
+CACHE_TTL_INVOICES_SUMMARY=900
+CACHE_TTL_TREASURY=600
+
+# Maintenance
+CACHE_CLEANUP_INTERVAL=3600
+
+# Event-Based Invalidation (experimental)
+CACHE_AUTO_INVALIDATE=False
+CACHE_CHECK_INTERVAL=300
+
+# Performance Tracking
+CACHE_TRACK_PERFORMANCE=True
+CACHE_BENCHMARK_ON_STARTUP=False
+
+# ============================================================================
+# DATA ENTRY MODULE - CONFIGURATION
+# ============================================================================
+# Data Entry module settings (receipts, OCR, etc.)
+
+# Environment identifier (dev/test/prod)
+ORACLE_ENV=dev
+
+# SQLite Database (development)
+SQLITE_DATABASE_PATH=data/receipts/receipts_dev.db
+
+# File uploads
+UPLOAD_PATH=data/receipts/uploads
+MAX_UPLOAD_SIZE_MB=10
+
+# Test company (for development testing)
+TEST_COMPANY_ID=110
+TEST_COMPANY_SCHEMA=MARIUSM_AUTO
+
+# ============================================================================
+# TELEGRAM MODULE - BOT CONFIGURATION (REQUIRED for Telegram features)
+# ============================================================================
+# Obtain bot token from @BotFather on Telegram
+
+TELEGRAM_BOT_TOKEN=your_bot_token_from_botfather
+
+# Backend URL for bot to communicate with API
+BACKEND_URL=http://localhost:8000
+
+# Internal API port (bot's internal API for backend callbacks)
+INTERNAL_API_PORT=8002
+
+# Enable internal API documentation (development only)
+ENABLE_DOCS=false
+
+# ============================================================================
+# TELEGRAM MODULE - EMAIL AUTHENTICATION (SMTP) (REQUIRED for email 2FA)
+# ============================================================================
+# Required for email-based 2FA authentication flow
+# Users can login with email + password instead of web app linking
+
+# SMTP Server Configuration
+SMTP_HOST=mail.romfast.ro
+SMTP_PORT=587
+SMTP_USER=ups@romfast.ro
+SMTP_PASSWORD=your_smtp_password_here
+SMTP_FROM_EMAIL=ups@romfast.ro
+SMTP_FROM_NAME=ROA2WEB
+SMTP_USE_TLS=true
+
+# Email Retry Settings
+EMAIL_MAX_RETRIES=3
+EMAIL_RETRY_DELAY=2.0
+
+# ============================================================================
+# TELEGRAM MODULE - DATABASE (SQLite for bot data)
+# ============================================================================
+# Separate SQLite database for Telegram bot auth codes and sessions
+
+TELEGRAM_SQLITE_DATABASE_PATH=data/telegram/telegram.db
diff --git a/backend/.env.example b/backend/.env.example
new file mode 100644
index 0000000..ef4916e
--- /dev/null
+++ b/backend/.env.example
@@ -0,0 +1,146 @@
+# ============================================================================
+# ROA2WEB Unified Backend - Environment Configuration Template
+# ============================================================================
+# Single backend process serving Reports, Data Entry, and Telegram modules
+#
+# SETUP INSTRUCTIONS:
+# 1. Copy this template: cp .env.example .env.dev
+# 2. Fill in your actual values in .env.dev
+# 3. Run: ./start-dev.sh (auto-copies .env.dev to .env)
+#
+# ENVIRONMENT FILES:
+# - .env.dev → Development config (committed to git with real values)
+# - .env.test → Test config (committed to git)
+# - .env.prod → Production config template (committed, use placeholders!)
+# - .env → Active config (auto-generated, NOT committed)
+#
+# IMPORTANT: Never manually edit .env - edit .env.dev instead!
+
+# ============================================================================
+# ORACLE DATABASE CONFIGURATION (REQUIRED - Shared by all modules)
+# ============================================================================
+# Connection to CONTAFIN_ORACLE schema for authentication and user management
+# Each company is a separate schema in Oracle Database
+# Development: Through SSH tunnel (localhost:1526)
+# Windows Production: Direct connection to Oracle server
+
+ORACLE_USER=CONTAFIN_ORACLE
+ORACLE_PASSWORD=SET_IN_PRODUCTION_ENV
+ORACLE_HOST=localhost
+ORACLE_PORT=1526
+ORACLE_SID=ROA
+
+# Development Only: Start SSH tunnel before running backend
+# ./ssh_tunnel.sh start
+# ./ssh_tunnel.sh status
+
+# ============================================================================
+# JWT AUTHENTICATION (REQUIRED - Shared by all modules)
+# ============================================================================
+# Used for JWT token generation and validation (shared/auth/jwt_handler.py)
+# Generate strong secret: python3 -c "import secrets; print(secrets.token_urlsafe(32))"
+
+JWT_SECRET_KEY=GENERATE_STRONG_SECRET_IN_PRODUCTION
+JWT_ALGORITHM=HS256
+
+# Token expiration settings (used by shared/auth/jwt_handler.py)
+ACCESS_TOKEN_EXPIRE_MINUTES=30
+REFRESH_TOKEN_EXPIRE_DAYS=7
+
+# ============================================================================
+# SESSION SECURITY - EMAIL 2FA (REQUIRED for Telegram email login)
+# ============================================================================
+# Used by Telegram module for session token validation
+# Generate with: python3 -c "import secrets; print(secrets.token_urlsafe(32))"
+
+AUTH_SESSION_SECRET=your-secure-random-secret-here-min-32-chars
+
+# ============================================================================
+# SERVER CONFIGURATION
+# ============================================================================
+# Unified backend server settings
+
+API_HOST=0.0.0.0
+API_PORT=8000
+DEBUG=false
+
+# CORS Origins (comma-separated)
+CORS_ORIGINS=http://localhost:3000,http://localhost:5173
+
+# ============================================================================
+# REPORTS MODULE - CACHE CONFIGURATION (OPTIONAL - defaults provided)
+# ============================================================================
+# Two-tier hybrid cache system (L1: in-memory LRU, L2: SQLite persistent)
+# Used by backend/modules/reports/cache/config.py
+
+# Core Settings
+REPORTS_CACHE_ENABLED=True
+REPORTS_CACHE_TYPE=hybrid
+REPORTS_CACHE_SQLITE_PATH=./data/cache/roa2web_cache.db
+REPORTS_CACHE_MEMORY_MAX_SIZE=1000
+REPORTS_CACHE_DEFAULT_TTL=900
+
+# TTL per Cache Type (seconds)
+REPORTS_CACHE_TTL_SCHEMA=86400
+REPORTS_CACHE_TTL_COMPANIES=1800
+REPORTS_CACHE_TTL_DASHBOARD_SUMMARY=1800
+REPORTS_CACHE_TTL_DASHBOARD_TRENDS=1800
+REPORTS_CACHE_TTL_INVOICES=600
+REPORTS_CACHE_TTL_INVOICES_SUMMARY=900
+REPORTS_CACHE_TTL_TREASURY=600
+
+# Maintenance
+REPORTS_CACHE_CLEANUP_INTERVAL=3600
+
+# Event-Based Invalidation (experimental)
+REPORTS_CACHE_AUTO_INVALIDATE=False
+REPORTS_CACHE_CHECK_INTERVAL=300
+
+# Performance Tracking
+REPORTS_CACHE_TRACK_PERFORMANCE=True
+REPORTS_CACHE_BENCHMARK_ON_STARTUP=False
+
+# ============================================================================
+# DATA ENTRY MODULE - CONFIGURATION
+# ============================================================================
+# Data Entry module settings (receipts, OCR, etc.)
+
+# SQLite Database
+DATA_ENTRY_SQLITE_DATABASE_PATH=data/receipts/receipts.db
+
+# File uploads
+DATA_ENTRY_UPLOAD_PATH=data/receipts/uploads
+DATA_ENTRY_MAX_UPLOAD_SIZE_MB=10
+
+# ============================================================================
+# TELEGRAM MODULE - BOT CONFIGURATION (REQUIRED for Telegram features)
+# ============================================================================
+# Obtain bot token from @BotFather on Telegram
+
+TELEGRAM_BOT_TOKEN=your_bot_token_here
+
+# ============================================================================
+# TELEGRAM MODULE - EMAIL AUTHENTICATION (SMTP) (REQUIRED for email 2FA)
+# ============================================================================
+# Required for email-based 2FA authentication flow
+# Users can login with email + password instead of web app linking
+
+# SMTP Server Configuration
+TELEGRAM_SMTP_HOST=mail.romfast.ro
+TELEGRAM_SMTP_PORT=587
+TELEGRAM_SMTP_USER=ups@romfast.ro
+TELEGRAM_SMTP_PASSWORD=your_smtp_password_here
+TELEGRAM_SMTP_FROM_EMAIL=ups@romfast.ro
+TELEGRAM_SMTP_FROM_NAME=ROA2WEB
+TELEGRAM_SMTP_USE_TLS=true
+
+# Email Retry Settings
+TELEGRAM_EMAIL_MAX_RETRIES=3
+TELEGRAM_EMAIL_RETRY_DELAY=2.0
+
+# ============================================================================
+# TELEGRAM MODULE - DATABASE (SQLite for bot data)
+# ============================================================================
+# Separate SQLite database for Telegram bot auth codes and sessions
+
+TELEGRAM_SQLITE_DATABASE_PATH=data/telegram/telegram.db
diff --git a/backend/.env.prod.example b/backend/.env.prod.example
new file mode 100644
index 0000000..a4561c0
--- /dev/null
+++ b/backend/.env.prod.example
@@ -0,0 +1,139 @@
+# ============================================================================
+# ROA2WEB Unified Backend - Environment Configuration (PRODUCTION)
+# ============================================================================
+# Single backend process serving Reports, Data Entry, and Telegram modules
+# IMPORTANT: This is a TEMPLATE - fill in production values before deploying!
+
+# ============================================================================
+# ORACLE DATABASE CONFIGURATION (REQUIRED - Shared by all modules)
+# ============================================================================
+# Connection to CONTAFIN_ORACLE schema for authentication and user management
+# PRODUCTION: Direct connection to Oracle server (no SSH tunnel)
+
+ORACLE_USER=CONTAFIN_ORACLE
+ORACLE_PASSWORD=CHANGE_IN_PRODUCTION
+ORACLE_HOST=your_oracle_server_ip_or_hostname
+ORACLE_PORT=1521
+ORACLE_SID=ROA
+
+# ============================================================================
+# JWT AUTHENTICATION (REQUIRED - Shared by all modules)
+# ============================================================================
+# CRITICAL: Generate new secrets for production!
+# python3 -c "import secrets; print(secrets.token_urlsafe(32))"
+
+JWT_SECRET_KEY=GENERATE_NEW_SECRET_FOR_PRODUCTION
+JWT_ALGORITHM=HS256
+
+# Token expiration settings
+ACCESS_TOKEN_EXPIRE_MINUTES=30
+REFRESH_TOKEN_EXPIRE_DAYS=7
+
+# ============================================================================
+# SESSION SECURITY - EMAIL 2FA (REQUIRED for Telegram email login)
+# ============================================================================
+# CRITICAL: Generate new secret for production!
+# python3 -c "import secrets; print(secrets.token_urlsafe(32))"
+
+AUTH_SESSION_SECRET=GENERATE_NEW_SECRET_FOR_PRODUCTION
+
+# ============================================================================
+# SERVER CONFIGURATION
+# ============================================================================
+# Unified backend server settings
+
+API_HOST=0.0.0.0
+API_PORT=8000
+DEBUG=false
+
+# CORS Origins (comma-separated) - Update with production frontend URL
+CORS_ORIGINS=https://your-production-domain.com,http://localhost:3000
+
+# ============================================================================
+# REPORTS MODULE - CACHE CONFIGURATION (OPTIONAL - defaults provided)
+# ============================================================================
+# Two-tier hybrid cache system (L1: in-memory LRU, L2: SQLite persistent)
+
+# Core Settings
+CACHE_ENABLED=True
+CACHE_TYPE=hybrid
+CACHE_SQLITE_PATH=./data/cache/roa2web_cache_prod.db
+CACHE_MEMORY_MAX_SIZE=1000
+CACHE_DEFAULT_TTL=900
+
+# TTL per Cache Type (seconds)
+CACHE_TTL_SCHEMA=86400
+CACHE_TTL_COMPANIES=1800
+CACHE_TTL_DASHBOARD_SUMMARY=1800
+CACHE_TTL_DASHBOARD_TRENDS=1800
+CACHE_TTL_INVOICES=600
+CACHE_TTL_INVOICES_SUMMARY=900
+CACHE_TTL_TREASURY=600
+
+# Maintenance
+CACHE_CLEANUP_INTERVAL=3600
+
+# Event-Based Invalidation (experimental)
+CACHE_AUTO_INVALIDATE=False
+CACHE_CHECK_INTERVAL=300
+
+# Performance Tracking
+CACHE_TRACK_PERFORMANCE=True
+CACHE_BENCHMARK_ON_STARTUP=False
+
+# ============================================================================
+# DATA ENTRY MODULE - CONFIGURATION
+# ============================================================================
+# Data Entry module settings (receipts, OCR, etc.)
+
+# Environment identifier
+ORACLE_ENV=prod
+
+# SQLite Database (production)
+SQLITE_DATABASE_PATH=data/receipts/receipts_prod.db
+
+# File uploads
+UPLOAD_PATH=data/receipts/uploads
+MAX_UPLOAD_SIZE_MB=10
+
+# ============================================================================
+# TELEGRAM MODULE - BOT CONFIGURATION (REQUIRED for Telegram features)
+# ============================================================================
+# Obtain bot token from @BotFather on Telegram
+# CRITICAL: Use production bot token, not development!
+
+TELEGRAM_BOT_TOKEN=your_bot_token_from_botfather
+
+# Backend URL for bot to communicate with API
+BACKEND_URL=http://localhost:8000
+
+# Internal API port (bot's internal API for backend callbacks)
+INTERNAL_API_PORT=8002
+
+# Enable internal API documentation (DISABLE in production!)
+ENABLE_DOCS=false
+
+# ============================================================================
+# TELEGRAM MODULE - EMAIL AUTHENTICATION (SMTP) (REQUIRED for email 2FA)
+# ============================================================================
+# CRITICAL: Update with production SMTP credentials
+
+# SMTP Server Configuration
+SMTP_HOST=mail.romfast.ro
+SMTP_PORT=587
+SMTP_USER=ups@romfast.ro
+SMTP_PASSWORD=CHANGE_IN_PRODUCTION
+SMTP_FROM_EMAIL=ups@romfast.ro
+SMTP_FROM_NAME=ROA2WEB
+SMTP_USE_TLS=true
+
+# Email Retry Settings
+EMAIL_MAX_RETRIES=3
+EMAIL_RETRY_DELAY=2.0
+
+# ============================================================================
+# TELEGRAM MODULE - DATABASE (SQLite for bot data)
+# ============================================================================
+# Separate SQLite database for Telegram bot auth codes and sessions
+
+TELEGRAM_SQLITE_DATABASE_PATH=data/telegram/telegram_prod.db
diff --git a/backend/.env.test.example b/backend/.env.test.example
new file mode 100644
index 0000000..f5f04a0
--- /dev/null
+++ b/backend/.env.test.example
@@ -0,0 +1,147 @@
+# ============================================================================
+# ROA2WEB Unified Backend - Environment Configuration (TEST)
+# ============================================================================
+# TEST environment using Oracle TEST server (10.0.20.121)
+# Single backend process serving Reports, Data Entry, and Telegram modules
+# IMPORTANT: Never commit this file to git!
+
+# ============================================================================
+# ORACLE DATABASE CONFIGURATION (REQUIRED - Shared by all modules)
+# ============================================================================
+# Connection to CONTAFIN_ORACLE schema for authentication and user management
+# TEST: Through SSH tunnel to 10.0.20.121 (localhost:1526)
+
+ORACLE_USER=CONTAFIN_ORACLE
+ORACLE_PASSWORD=your_oracle_password_here
+ORACLE_HOST=localhost
+ORACLE_PORT=1526
+ORACLE_SID=roa
+
+# TEST: Start SSH tunnel before running backend
+# ./ssh-tunnel-test.sh start
+
+# ============================================================================
+# JWT AUTHENTICATION (REQUIRED - Shared by all modules)
+# ============================================================================
+# Used for JWT token generation and validation (shared/auth/jwt_handler.py)
+
+JWT_SECRET_KEY=generate_with_secrets_token_urlsafe_32
+JWT_ALGORITHM=HS256
+
+# Token expiration settings (used by shared/auth/jwt_handler.py)
+ACCESS_TOKEN_EXPIRE_MINUTES=480
+REFRESH_TOKEN_EXPIRE_DAYS=7
+
+# ============================================================================
+# SESSION SECURITY - EMAIL 2FA (REQUIRED for Telegram email login)
+# ============================================================================
+# Used by Telegram module for session token validation
+# Must match between backend and Telegram bot
+
+AUTH_SESSION_SECRET=generate_with_secrets_token_urlsafe_32
+
+# ============================================================================
+# SERVER CONFIGURATION
+# ============================================================================
+# Unified backend server settings
+
+API_HOST=0.0.0.0
+API_PORT=8000
+DEBUG=true
+
+# CORS Origins (comma-separated, includes both old and new frontend ports)
+CORS_ORIGINS=http://localhost:3000,http://localhost:3010,http://localhost:5173
+
+# ============================================================================
+# REPORTS MODULE - CACHE CONFIGURATION (OPTIONAL - defaults provided)
+# ============================================================================
+# Two-tier hybrid cache system (L1: in-memory LRU, L2: SQLite persistent)
+# Used by backend/modules/reports/cache/config.py
+
+# Core Settings
+CACHE_ENABLED=True
+CACHE_TYPE=hybrid
+CACHE_SQLITE_PATH=./data/cache/roa2web_cache_test.db
+CACHE_MEMORY_MAX_SIZE=1000
+CACHE_DEFAULT_TTL=900
+
+# TTL per Cache Type (seconds)
+CACHE_TTL_SCHEMA=86400
+CACHE_TTL_COMPANIES=1800
+CACHE_TTL_DASHBOARD_SUMMARY=1800
+CACHE_TTL_DASHBOARD_TRENDS=1800
+CACHE_TTL_INVOICES=600
+CACHE_TTL_INVOICES_SUMMARY=900
+CACHE_TTL_TREASURY=600
+
+# Maintenance
+CACHE_CLEANUP_INTERVAL=3600
+
+# Event-Based Invalidation (experimental)
+CACHE_AUTO_INVALIDATE=False
+CACHE_CHECK_INTERVAL=300
+
+# Performance Tracking
+CACHE_TRACK_PERFORMANCE=True
+CACHE_BENCHMARK_ON_STARTUP=False
+
+# ============================================================================
+# DATA ENTRY MODULE - CONFIGURATION
+# ============================================================================
+# Data Entry module settings (receipts, OCR, etc.)
+
+# Environment identifier (dev/test/prod)
+ORACLE_ENV=test
+
+# SQLite Database (test)
+SQLITE_DATABASE_PATH=data/receipts/receipts_test.db
+
+# File uploads
+UPLOAD_PATH=data/receipts/uploads
+MAX_UPLOAD_SIZE_MB=10
+
+# Test company (for testing)
+TEST_COMPANY_ID=110
+TEST_COMPANY_SCHEMA=MARIUSM_AUTO
+
+# ============================================================================
+# TELEGRAM MODULE - BOT CONFIGURATION (REQUIRED for Telegram features)
+# ============================================================================
+# Obtain bot token from @BotFather on Telegram
+
+TELEGRAM_BOT_TOKEN=your_bot_token_from_botfather
+
+# Backend URL for bot to communicate with API
+BACKEND_URL=http://localhost:8000
+
+# Internal API port (bot's internal API for backend callbacks)
+INTERNAL_API_PORT=8002
+
+# Enable internal API documentation (development only)
+ENABLE_DOCS=false
+
+# ============================================================================
+# TELEGRAM MODULE - EMAIL AUTHENTICATION (SMTP) (REQUIRED for email 2FA)
+# ============================================================================
+# Required for email-based 2FA authentication flow
+# Users can login with email + password instead of web app linking
+
+# SMTP Server Configuration
+SMTP_HOST=mail.romfast.ro
+SMTP_PORT=587
+SMTP_USER=ups@romfast.ro
+SMTP_PASSWORD=your_smtp_password_here
+SMTP_FROM_EMAIL=ups@romfast.ro
+SMTP_FROM_NAME=ROA2WEB
+SMTP_USE_TLS=true
+
+# Email Retry Settings
+EMAIL_MAX_RETRIES=3
+EMAIL_RETRY_DELAY=2.0
+
+# ============================================================================
+# TELEGRAM MODULE - DATABASE (SQLite for bot data)
+# ============================================================================
+# Separate SQLite database for Telegram bot auth codes and sessions
+
+TELEGRAM_SQLITE_DATABASE_PATH=data/telegram/telegram_test.db
diff --git a/backend/ENV-SETUP.md b/backend/ENV-SETUP.md
new file mode 100644
index 0000000..83a7c8c
--- /dev/null
+++ b/backend/ENV-SETUP.md
@@ -0,0 +1,212 @@
+# Environment Configuration Guide
+
+## Overview
+
+The unified backend uses environment-specific configuration files that are automatically loaded by startup scripts.
+
+**SECURITY**: All `.env*` files (except `.env*.example`) contain real credentials and are **NEVER committed to git**.
+
+## File Structure
+
+```
+backend/
+├── .env.prod.example # Production template (COMMITTED - no credentials)
+├── .env.test.example # Test template (COMMITTED - no credentials)
+├── .env.prod.example # Production template (COMMITTED - no credentials)
+├── .env.example # Generic template (COMMITTED)
+├── .env.prod # Production config (IGNORED - real credentials)
+├── .env.test # Test config (IGNORED - real credentials)
+├── .env.prod # Production config (IGNORED - real credentials)
+└── .env # Active config (IGNORED - auto-generated)
+```
+
+## First-Time Setup
+
+### Production
+```bash
+# 1. Copy template
+cp backend/.env.prod.example backend/.env.prod
+
+# 2. Edit with your credentials
+vim backend/.env.prod
+
+# 3. Fill in:
+# - ORACLE_PASSWORD
+# - JWT_SECRET_KEY (generate with: python3 -c "import secrets; print(secrets.token_urlsafe(32))")
+# - AUTH_SESSION_SECRET (generate with: python3 -c "import secrets; print(secrets.token_urlsafe(32))")
+# - TELEGRAM_BOT_TOKEN (from @BotFather)
+# - SMTP_PASSWORD
+
+# 4. Start
+./start-prod.sh
+```
+
+### Test
+```bash
+# Same process with .env.test
+cp backend/.env.test.example backend/.env.test
+vim backend/.env.test
+# Fill in TEST credentials (separate from dev!)
+./start-test.sh
+```
+
+### Production
+```bash
+# Same process with .env.prod
+cp backend/.env.prod.example backend/.env.prod
+vim backend/.env.prod
+# Fill in PRODUCTION credentials (generate NEW secrets!)
+./start-backend.sh start
+```
+
+## How It Works
+
+### Production
+```bash
+./start-prod.sh # Checks for .env.prod → copies to .env → starts backend
+```
+
+### Test
+```bash
+./start-test.sh # Checks for .env.test → copies to .env → starts backend
+```
+
+### Production
+```bash
+# Manual setup (one-time)
+cp .env.prod.example .env.prod
+vim .env.prod # Fill in credentials
+# Then start
+./start-backend.sh start
+```
+
+## Important Rules
+
+### ✅ DO
+- Copy `.env.*.example` to `.env.*` and fill in real credentials
+- Edit `.env.prod` for production changes
+- Edit `.env.test` for test environment changes
+- Edit `.env.prod` for production
+- Generate **new** secrets for each environment
+- Keep `.env.prod`, `.env.test`, `.env.prod` **local only** (never commit!)
+
+### ❌ DON'T
+- Don't commit `.env`, `.env.prod`, `.env.test`, or `.env.prod` (they're in .gitignore)
+- Don't manually edit `.env` (it's auto-generated!)
+- Don't use same secrets across environments
+- Don't share credentials via git (use secure channels)
+- Don't put real credentials in `.env*.example` files
+
+## Environment Differences
+
+| Setting | .env.prod | .env.test | .env.prod |
+|---------|----------|-----------|-----------|
+| Oracle SID | `ROA` | `roa` | `ROA` |
+| JWT Expire | 30 min | 480 min | 30 min |
+| DEBUG | `true` | `true` | `false` |
+| Cache DB | `roa2web_cache.db` | `roa2web_cache_test.db` | `roa2web_cache_prod.db` |
+| Receipts DB | `receipts_dev.db` | `receipts_test.db` | `receipts_prod.db` |
+| Telegram DB | `telegram.db` | `telegram_test.db` | `telegram_prod.db` |
+
+## Security Notes
+
+### Template Files (.env.*.example)
+These contain **placeholders only**:
+- ✅ Safe to commit to git
+- ✅ Shared across team
+- ✅ No real credentials
+- 📖 Used as reference for first-time setup
+
+### Actual Config Files (.env.prod, .env.test, .env.prod)
+These contain **real credentials**:
+- ❌ **NEVER commit to git** (in .gitignore)
+- ❌ Never share via email/chat
+- ✅ Keep local only
+- ✅ Generate unique secrets per environment
+- 🔐 Share securely if needed (encrypted vault, 1Password, etc.)
+
+### Active Config (.env)
+This is **auto-generated** and **ignored by git**:
+- ❌ Never commit to git
+- 🔄 Auto-overwritten by startup scripts
+- 📝 Edit source files (.env.prod, .env.test) instead
+
+## Generating Secrets
+
+For `JWT_SECRET_KEY` and `AUTH_SESSION_SECRET`:
+```bash
+python3 -c "import secrets; print(secrets.token_urlsafe(32))"
+```
+
+Generate **different** secrets for dev, test, and production!
+
+## Quick Reference
+
+### First Time Setup
+```bash
+# 1. Copy template
+cp backend/.env.prod.example backend/.env.prod
+
+# 2. Fill credentials
+vim backend/.env.prod
+
+# 3. Start
+./start-prod.sh
+```
+
+### Changing Configuration
+```bash
+# 1. Edit source file
+vim backend/.env.prod
+
+# 2. Restart to apply
+./start-prod.sh
+```
+
+### Production Deployment
+```bash
+# 1. Copy template
+cp backend/.env.prod.example backend/.env.prod
+
+# 2. Fill in PRODUCTION values
+vim backend/.env.prod
+
+# 3. Generate NEW secrets
+python3 -c "import secrets; print(secrets.token_urlsafe(32))"
+
+# 4. Start backend
+./start-backend.sh start
+```
+
+## Troubleshooting
+
+### "Wrong database" error
+Check that you're using the correct startup script:
+- Production: `./start-prod.sh` (uses `.env.prod`)
+- Test: `./start-test.sh` (uses `.env.test`)
+
+### ".env.prod not found" error
+First-time setup required:
+```bash
+cp backend/.env.prod.example backend/.env.prod
+vim backend/.env.prod # Fill in your credentials
+```
+
+### Changes not taking effect
+The `.env` file is regenerated on each start. Edit the source file (`.env.prod` or `.env.test`) instead.
+
+### Checking what will be committed
+```bash
+git status backend/.env*
+# Should show:
+# modified: .env.prod.example (if you changed template)
+# nothing else!
+```
+
+## Team Sharing
+
+**Templates only** are committed to git:
+- Share configuration structure via `.env*.example`
+- Each developer creates their own `.env.prod` from template
+- Never commit actual credentials
+- Use secure channels for sharing sensitive values (1Password, encrypted vault, etc.)
diff --git a/backend/QUICK-ENV-REFERENCE.md b/backend/QUICK-ENV-REFERENCE.md
new file mode 100644
index 0000000..8b4becd
--- /dev/null
+++ b/backend/QUICK-ENV-REFERENCE.md
@@ -0,0 +1,102 @@
+# Quick Environment Reference
+
+## 🔒 SECURITY FIRST
+
+**All `.env*` files (except `.env*.example`) contain real credentials and are NEVER committed to git!**
+
+## 🚀 First-Time Setup
+
+```bash
+# 1. Copy template with real credentials
+cp backend/.env.prod.example backend/.env.prod
+
+# 2. Edit with YOUR credentials
+vim backend/.env.prod
+
+# 3. Fill in the placeholders:
+# - ORACLE_PASSWORD
+# - JWT_SECRET_KEY
+# - AUTH_SESSION_SECRET
+# - TELEGRAM_BOT_TOKEN
+# - SMTP_PASSWORD
+
+# 4. Start production
+./start-prod.sh
+```
+
+## 📋 Daily Usage
+
+```bash
+# Production (uses .env.prod automatically)
+./start-prod.sh
+
+# Test Environment (uses .env.test automatically)
+./start-test.sh
+
+# Quick Restart (uses existing .env)
+./start-backend.sh restart
+```
+
+## ✏️ Changing Configuration
+
+```bash
+# 1. Edit the source file (NOT .env!)
+vim backend/.env.prod # Production
+vim backend/.env.test # Test
+
+# 2. Restart to apply changes
+./start-prod.sh
+```
+
+## 📁 Which File to Edit?
+
+| You Want To... | Edit This File |
+|----------------|----------------|
+| Change dev database password | `backend/.env.prod` |
+| Update test server settings | `backend/.env.test` |
+| Add new environment variable | Templates: `.env*.example` + your `.env.prod`/`.env.test` |
+| Create production config | Copy `.env.prod.example` to `.env.prod` and fill secrets |
+
+## 🔑 Generating Secrets
+
+```bash
+# For JWT_SECRET_KEY and AUTH_SESSION_SECRET
+python3 -c "import secrets; print(secrets.token_urlsafe(32))"
+```
+
+**Generate DIFFERENT secrets for each environment (dev, test, prod)!**
+
+## ⚠️ Important
+
+- **Never edit** `backend/.env` directly (it's auto-generated!)
+- **Always edit** `backend/.env.prod` or `.env.test`
+- **Never commit** `.env`, `.env.prod`, `.env.test`, `.env.prod`
+- **Only commit** `.env*.example` (templates with placeholders)
+- Restart after changes for them to take effect
+
+## 🛡️ Git Behavior
+
+| File | Git Status | Contains |
+|------|-----------|----------|
+| `.env.prod.example` | ✅ Committed | Template (placeholders) |
+| `.env.test.example` | ✅ Committed | Template (placeholders) |
+| `.env.prod.example` | ✅ Committed | Template (placeholders) |
+| `.env.example` | ✅ Committed | Generic template |
+| `.env.prod` | ❌ Ignored | **Real dev credentials** |
+| `.env.test` | ❌ Ignored | **Real test credentials** |
+| `.env.prod` | ❌ Ignored | **Real prod credentials** |
+| `.env` | ❌ Ignored | Auto-generated (current) |
+
+## ✅ Quick Check
+
+```bash
+# See what git will commit
+git status backend/.env*
+
+# Should show ONLY .env*.example files
+# If .env.prod or .env.test appear, they're NOT properly ignored!
+```
+
+## 📖 More Info
+
+See `backend/ENV-SETUP.md` for complete documentation.
diff --git a/reports-app/backend/app/__init__.py b/backend/__init__.py
similarity index 100%
rename from reports-app/backend/app/__init__.py
rename to backend/__init__.py
diff --git a/backend/config.py b/backend/config.py
new file mode 100644
index 0000000..f97cae2
--- /dev/null
+++ b/backend/config.py
@@ -0,0 +1,173 @@
+"""
+Unified Configuration for ROA2WEB Backend
+Consolidates settings from Reports, Data Entry, and Telegram modules
+"""
+
+import os
+from pathlib import Path
+from typing import List
+from pydantic_settings import BaseSettings
+from functools import lru_cache
+
+
+class UnifiedSettings(BaseSettings):
+ """Unified application settings for all modules."""
+
+ # ============================================================================
+ # GENERAL APPLICATION SETTINGS
+ # ============================================================================
+ app_name: str = "ROA2WEB Unified Backend"
+ app_version: str = "1.0.0"
+ debug: bool = False
+ api_host: str = "0.0.0.0"
+ api_port: int = 8000
+
+ # ============================================================================
+ # ORACLE DATABASE (Shared by all modules)
+ # ============================================================================
+ oracle_user: str = ""
+ oracle_password: str = ""
+ oracle_host: str = "localhost"
+ oracle_port: int = 1526
+ oracle_sid: str = "ROA"
+
+ # ============================================================================
+ # JWT AUTHENTICATION (Shared by all modules)
+ # ============================================================================
+ jwt_secret_key: str = "change-me-in-production"
+ jwt_algorithm: str = "HS256"
+ access_token_expire_minutes: int = 30
+ refresh_token_expire_days: int = 7
+
+ # ============================================================================
+ # SESSION SECURITY - EMAIL 2FA (Telegram module)
+ # ============================================================================
+ auth_session_secret: str = "change-me-in-production"
+
+ # ============================================================================
+ # CORS
+ # ============================================================================
+ cors_origins: str = "http://localhost:3000,http://localhost:5173"
+
+ # ============================================================================
+ # REPORTS MODULE - CACHE CONFIGURATION
+ # ============================================================================
+ reports_cache_enabled: bool = True
+ reports_cache_type: str = "hybrid"
+ reports_cache_sqlite_path: str = "./data/cache/roa2web_cache.db"
+ reports_cache_memory_max_size: int = 1000
+ reports_cache_default_ttl: int = 900
+
+ # Cache TTL per type (seconds)
+ reports_cache_ttl_schema: int = 86400
+ reports_cache_ttl_companies: int = 1800
+ reports_cache_ttl_dashboard_summary: int = 1800
+ reports_cache_ttl_dashboard_trends: int = 1800
+ reports_cache_ttl_invoices: int = 600
+ reports_cache_ttl_invoices_summary: int = 900
+ reports_cache_ttl_treasury: int = 600
+
+ # Cache maintenance
+ reports_cache_cleanup_interval: int = 3600
+ reports_cache_auto_invalidate: bool = False
+ reports_cache_check_interval: int = 300
+ reports_cache_track_performance: bool = True
+ reports_cache_benchmark_on_startup: bool = False
+
+ # ============================================================================
+ # DATA ENTRY MODULE - CONFIGURATION
+ # ============================================================================
+ data_entry_sqlite_database_path: str = "data/receipts/receipts.db"
+ data_entry_upload_path: str = "data/receipts/uploads"
+ data_entry_max_upload_size_mb: int = 10
+ data_entry_allowed_mime_types: List[str] = [
+ "image/jpeg",
+ "image/png",
+ "image/gif",
+ "image/webp",
+ "application/pdf",
+ ]
+
+ # ============================================================================
+ # TELEGRAM MODULE - BOT CONFIGURATION
+ # ============================================================================
+ telegram_bot_token: str = ""
+ telegram_smtp_host: str = ""
+ telegram_smtp_port: int = 587
+ telegram_smtp_user: str = ""
+ telegram_smtp_password: str = ""
+ telegram_smtp_from_email: str = ""
+ telegram_smtp_from_name: str = "ROA2WEB"
+ telegram_smtp_use_tls: bool = True
+ telegram_email_max_retries: int = 3
+ telegram_email_retry_delay: float = 2.0
+ telegram_sqlite_database_path: str = "data/telegram/telegram.db"
+
+ class Config:
+ env_file = ".env"
+ env_file_encoding = "utf-8"
+ extra = "ignore"
+ case_sensitive = False
+
+ # ============================================================================
+ # COMPUTED PROPERTIES
+ # ============================================================================
+
+ @property
+ def oracle_dsn(self) -> str:
+ """Get Oracle DSN string."""
+ return f"{self.oracle_host}:{self.oracle_port}/{self.oracle_sid}"
+
+ @property
+ def cors_origins_list(self) -> List[str]:
+ """Get CORS origins as list."""
+ return [origin.strip() for origin in self.cors_origins.split(",")]
+
+ # Data Entry properties
+ @property
+ def data_entry_database_url(self) -> str:
+ """Get SQLite database URL for async (Data Entry)."""
+ return f"sqlite+aiosqlite:///{self.data_entry_sqlite_database_path}"
+
+ @property
+ def data_entry_sync_database_url(self) -> str:
+ """Get SQLite database URL for sync operations (Alembic)."""
+ return f"sqlite:///{self.data_entry_sqlite_database_path}"
+
+ @property
+ def data_entry_upload_path_resolved(self) -> Path:
+ """Get resolved upload path."""
+ path = Path(self.data_entry_upload_path)
+ path.mkdir(parents=True, exist_ok=True)
+ return path
+
+ @property
+ def data_entry_max_upload_size_bytes(self) -> int:
+ """Get max upload size in bytes."""
+ return self.data_entry_max_upload_size_mb * 1024 * 1024
+
+ # Reports cache properties
+ @property
+ def reports_cache_sqlite_path_resolved(self) -> Path:
+ """Get resolved cache SQLite path."""
+ path = Path(self.reports_cache_sqlite_path)
+ path.parent.mkdir(parents=True, exist_ok=True)
+ return path
+
+ # Telegram properties
+ @property
+ def telegram_sqlite_path_resolved(self) -> Path:
+ """Get resolved Telegram SQLite path."""
+ path = Path(self.telegram_sqlite_database_path)
+ path.parent.mkdir(parents=True, exist_ok=True)
+ return path
+
+
+@lru_cache()
+def get_settings() -> UnifiedSettings:
+ """Get cached settings instance."""
+ return UnifiedSettings()
+
+
+# Convenience instance
+settings = get_settings()
diff --git a/backend/data/README.md b/backend/data/README.md
new file mode 100644
index 0000000..83249c8
--- /dev/null
+++ b/backend/data/README.md
@@ -0,0 +1,45 @@
+# Backend Runtime Data
+
+This directory contains runtime data generated by the unified backend.
+
+## Directory Structure
+
+```
+data/
+├── cache/ # Reports module cache (hybrid L1+L2)
+│ └── *.db # SQLite L2 cache database
+├── receipts/ # Data Entry module data
+│ ├── *.db # SQLite receipts database
+│ └── uploads/ # User-uploaded files (receipts, attachments)
+└── telegram/ # Telegram bot data
+ └── *.db # SQLite bot auth/session database
+```
+
+## Git Behavior
+
+- **Ignored**: All `*.db` files and `uploads/` contents
+- **Committed**: Only `.gitkeep` files to preserve directory structure
+
+## Environment-Specific Databases
+
+Different environments use separate databases:
+
+- **Development** (`.env.prod`):
+ - Cache: `roa2web_cache.db`
+ - Receipts: `receipts_dev.db`
+ - Telegram: `telegram.db`
+
+- **Test** (`.env.test`):
+ - Cache: `roa2web_cache_test.db`
+ - Receipts: `receipts_test.db`
+ - Telegram: `telegram_test.db`
+
+- **Production** (`.env.prod`):
+ - Cache: `roa2web_cache_prod.db`
+ - Receipts: `receipts_prod.db`
+ - Telegram: `telegram_prod.db`
+
+## Auto-Created
+
+All databases and directories are created automatically on first run.
+No manual setup required.
diff --git a/reports-app/telegram-bot/data/.gitkeep b/backend/data/cache/.gitkeep
similarity index 100%
rename from reports-app/telegram-bot/data/.gitkeep
rename to backend/data/cache/.gitkeep
diff --git a/reports-app/backend/app/models/__init__.py b/backend/data/receipts/.gitkeep
similarity index 100%
rename from reports-app/backend/app/models/__init__.py
rename to backend/data/receipts/.gitkeep
diff --git a/reports-app/backend/app/routers/__init__.py b/backend/data/telegram/.gitkeep
similarity index 100%
rename from reports-app/backend/app/routers/__init__.py
rename to backend/data/telegram/.gitkeep
diff --git a/backend/main.py b/backend/main.py
new file mode 100644
index 0000000..839cc11
--- /dev/null
+++ b/backend/main.py
@@ -0,0 +1,428 @@
+"""
+ROA2WEB Unified Backend - Single FastAPI Application
+Consolidates Reports, Data Entry, and Telegram modules into one process
+"""
+
+import asyncio
+import logging
+import os
+import sys
+from datetime import datetime
+from pathlib import Path
+from contextlib import asynccontextmanager
+
+from dotenv import load_dotenv
+from fastapi import FastAPI
+from fastapi.middleware.cors import CORSMiddleware
+
+# Load environment variables
+load_dotenv()
+
+# Add project root and shared modules to path
+project_root = Path(__file__).parent.parent
+sys.path.insert(0, str(project_root)) # Enable 'from backend.xxx import yyy'
+sys.path.insert(0, str(project_root / "shared")) # Enable 'from shared.xxx import yyy'
+
+# Import configuration
+from backend.config import settings
+
+# Import shared infrastructure
+from shared.database.oracle_pool import oracle_pool
+from shared.auth.middleware import AuthenticationMiddleware
+from shared.auth.routes import create_auth_router
+from shared.routes.companies import create_companies_router
+from shared.routes.calendar import create_calendar_router
+
+# Import module router factories
+from backend.modules.reports.routers import create_reports_router
+from backend.modules.data_entry.routers import create_data_entry_router
+from backend.modules.telegram.routers import create_telegram_router
+
+# Configure logging
+logging.basicConfig(
+ level=logging.INFO,
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
+ datefmt='%H:%M:%S'
+)
+logger = logging.getLogger(__name__)
+
+# Global variables for background tasks
+telegram_bot_task = None
+
+
+# ============================================================================
+# INITIALIZATION FUNCTIONS
+# ============================================================================
+
+async def init_oracle_pool():
+ """Initialize Oracle connection pool (shared by all modules)."""
+ logger.info("[ORACLE] Initializing connection pool...")
+ await oracle_pool.initialize()
+ logger.info("[ORACLE] ✅ Pool initialized successfully")
+
+
+async def init_reports_cache():
+ """Initialize Reports cache system."""
+ logger.info("[REPORTS] Initializing cache system...")
+ try:
+ from backend.modules.reports.cache import init_cache, init_event_monitor, get_cache
+ from backend.modules.reports.cache.config import CacheConfig
+
+ cache_config = CacheConfig.from_env()
+ await init_cache(cache_config)
+ logger.info(f"[REPORTS] ✅ Cache initialized: type={cache_config.cache_type}, enabled={cache_config.enabled}")
+
+ # Initialize event monitor
+ cache = get_cache()
+ await init_event_monitor(cache, cache_config)
+ if cache_config.auto_invalidate_enabled:
+ logger.info("[REPORTS] Event-based auto-invalidation ENABLED")
+ else:
+ logger.info("[REPORTS] Event-based auto-invalidation DISABLED")
+
+ except Exception as e:
+ logger.error(f"[REPORTS] ⚠️ Cache initialization error: {e}", exc_info=True)
+ logger.warning("[REPORTS] Continuing without cache")
+
+
+async def init_data_entry_db():
+ """Initialize Data Entry SQLite database."""
+ logger.info("[DATA-ENTRY] Initializing SQLite database...")
+ try:
+ from backend.modules.data_entry.db.database import init_db
+ await init_db()
+ logger.info(f"[DATA-ENTRY] ✅ Database initialized: {settings.data_entry_sqlite_database_path}")
+
+ # Ensure upload directory exists
+ settings.data_entry_upload_path_resolved
+ logger.info(f"[DATA-ENTRY] Upload path: {settings.data_entry_upload_path_resolved}")
+
+ except Exception as e:
+ logger.error(f"[DATA-ENTRY] ❌ Database initialization error: {e}", exc_info=True)
+ raise
+
+
+async def init_telegram_db():
+ """Initialize Telegram SQLite database."""
+ logger.info("[TELEGRAM] Initializing SQLite database...")
+ try:
+ from backend.modules.telegram.db import init_database, cleanup_expired_codes, cleanup_expired_sessions, cleanup_expired_email_codes
+
+ await init_database()
+ logger.info(f"[TELEGRAM] ✅ Database initialized: {settings.telegram_sqlite_database_path}")
+
+ # Cleanup expired data
+ expired_codes = await cleanup_expired_codes()
+ expired_sessions = await cleanup_expired_sessions()
+ expired_email_codes = await cleanup_expired_email_codes()
+ logger.info(f"[TELEGRAM] Cleanup: {expired_codes} codes, {expired_sessions} sessions, {expired_email_codes} email codes removed")
+
+ except Exception as e:
+ logger.error(f"[TELEGRAM] ❌ Database initialization error: {e}", exc_info=True)
+ raise
+
+
+def init_paddle_ocr_background():
+ """Initialize PaddleOCR in background thread (takes 15-20s)."""
+ try:
+ logger.info("[DATA-ENTRY] Pre-loading OCR engine (background)...")
+ from backend.modules.data_entry.services.ocr_service import ocr_service
+ ocr_service.ocr_engine._init_paddle_lazy()
+ logger.info("[DATA-ENTRY] ✅ OCR engine ready")
+ except Exception as e:
+ logger.warning(f"[DATA-ENTRY] ⚠️ OCR engine pre-load failed: {e}")
+
+
+async def run_telegram_bot():
+ """Run Telegram bot as background task."""
+ logger.info("[TELEGRAM] Starting bot...")
+ try:
+ from telegram.ext import Application, CommandHandler, CallbackQueryHandler, MessageHandler, filters
+ from backend.modules.telegram.bot.handlers import (
+ start_command, help_command, clear_command, companies_command,
+ unlink_command, selectcompany_command, dashboard_command, sold_command,
+ facturi_command, trezorerie_command, menu_command, trezorerie_casa_command,
+ trezorerie_banca_command, clienti_command, furnizori_command, evolutie_command,
+ clearcache_command, togglecache_command, handle_text_message, button_callback,
+ error_handler
+ )
+ from backend.modules.telegram.bot.email_handlers import email_login_handler
+
+ # Create Telegram application
+ application = Application.builder().token(settings.telegram_bot_token).build()
+
+ # Register handlers
+ application.add_handler(email_login_handler)
+ application.add_handler(CommandHandler("start", start_command))
+ application.add_handler(CommandHandler("menu", menu_command))
+ application.add_handler(CommandHandler("help", help_command))
+ application.add_handler(CommandHandler("unlink", unlink_command))
+ application.add_handler(CommandHandler("clear", clear_command))
+ application.add_handler(CommandHandler("companies", companies_command))
+ application.add_handler(CommandHandler("selectcompany", selectcompany_command))
+ application.add_handler(CommandHandler("dashboard", dashboard_command))
+ application.add_handler(CommandHandler("sold", sold_command))
+ application.add_handler(CommandHandler("facturi", facturi_command))
+ application.add_handler(CommandHandler("trezorerie", trezorerie_command))
+ application.add_handler(CommandHandler("trezorerie_casa", trezorerie_casa_command))
+ application.add_handler(CommandHandler("trezorerie_banca", trezorerie_banca_command))
+ application.add_handler(CommandHandler("clienti", clienti_command))
+ application.add_handler(CommandHandler("furnizori", furnizori_command))
+ application.add_handler(CommandHandler("evolutie", evolutie_command))
+ application.add_handler(CommandHandler("clearcache", clearcache_command))
+ application.add_handler(CommandHandler("togglecache", togglecache_command))
+ application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handle_text_message))
+ application.add_handler(CallbackQueryHandler(button_callback))
+ application.add_error_handler(error_handler)
+
+ # Initialize and start
+ await application.initialize()
+ await application.start()
+ await application.updater.start_polling(drop_pending_updates=True)
+
+ bot_info = await application.bot.get_me()
+ logger.info(f"[TELEGRAM] ✅ Bot running: @{bot_info.username}")
+
+ # Keep bot running
+ while True:
+ await asyncio.sleep(1)
+
+ except asyncio.CancelledError:
+ logger.info("[TELEGRAM] Bot task cancelled, stopping...")
+ if 'application' in locals():
+ await application.updater.stop()
+ await application.stop()
+ await application.shutdown()
+ logger.info("[TELEGRAM] ✅ Bot stopped")
+ raise
+ except Exception as e:
+ logger.error(f"[TELEGRAM] ❌ Bot error: {e}", exc_info=True)
+ raise
+
+
+# ============================================================================
+# FASTAPI APPLICATION
+# ============================================================================
+
+app = FastAPI(
+ title="ROA2WEB Unified Backend",
+ description="Unified FastAPI backend for Reports, Data Entry, and Telegram modules",
+ version="1.0.0"
+)
+
+
+# ============================================================================
+# STARTUP/SHUTDOWN EVENT HANDLERS
+# ============================================================================
+
+@app.on_event("startup")
+async def startup_event():
+ """Application startup - Initialize all resources."""
+ global telegram_bot_task
+
+ logger.info("=" * 80)
+ logger.info("[STARTUP] ROA2WEB Unified Backend")
+ logger.info("=" * 80)
+
+ try:
+ # Step 1: Initialize Oracle pool (shared by all modules)
+ await init_oracle_pool()
+
+ # Step 2: Parallel initialization of module-specific resources
+ logger.info("[STARTUP] Initializing module resources in parallel...")
+ await asyncio.gather(
+ init_reports_cache(),
+ init_data_entry_db(),
+ init_telegram_db(),
+ )
+
+ # Step 3: Start PaddleOCR initialization in background thread
+ import threading
+ threading.Thread(target=init_paddle_ocr_background, daemon=True).start()
+
+ # Step 4: Start Telegram bot as background task
+ if settings.telegram_bot_token:
+ telegram_bot_task = asyncio.create_task(run_telegram_bot())
+ logger.info("[STARTUP] ✅ Telegram bot task created")
+ else:
+ logger.warning("[STARTUP] ⚠️ TELEGRAM_BOT_TOKEN not set, bot disabled")
+
+ logger.info("=" * 80)
+ logger.info("[STARTUP] ✅ All modules initialized successfully")
+ logger.info(f"[STARTUP] ✅ Server running on http://{settings.api_host}:{settings.api_port}")
+ logger.info("=" * 80)
+
+ except Exception as e:
+ logger.error(f"[STARTUP] ❌ Initialization failed: {e}", exc_info=True)
+ raise
+
+
+@app.on_event("shutdown")
+async def shutdown_event():
+ """Application shutdown - Cleanup resources."""
+ global telegram_bot_task
+
+ logger.info("=" * 80)
+ logger.info("[SHUTDOWN] Stopping ROA2WEB Unified Backend...")
+ logger.info("=" * 80)
+
+ try:
+ # Stop Telegram bot
+ if telegram_bot_task and not telegram_bot_task.done():
+ logger.info("[SHUTDOWN] Stopping Telegram bot...")
+ telegram_bot_task.cancel()
+ try:
+ await telegram_bot_task
+ except asyncio.CancelledError:
+ pass
+
+ # Stop Reports cache event monitor
+ try:
+ from backend.modules.reports.cache import close_cache, get_event_monitor
+ monitor = get_event_monitor()
+ if monitor:
+ await monitor.stop()
+ logger.info("[SHUTDOWN] Reports cache monitor stopped")
+
+ await close_cache()
+ logger.info("[SHUTDOWN] Reports cache closed")
+ except Exception as e:
+ logger.error(f"[SHUTDOWN] Cache error: {e}")
+
+ # Close Oracle pool
+ await oracle_pool.close_pool()
+ logger.info("[SHUTDOWN] Oracle pool closed")
+
+ logger.info("=" * 80)
+ logger.info("[SHUTDOWN] ✅ Shutdown complete")
+ logger.info("=" * 80)
+
+ except Exception as e:
+ logger.error(f"[SHUTDOWN] Error during shutdown: {e}", exc_info=True)
+
+
+# ============================================================================
+# MIDDLEWARE
+# ============================================================================
+
+# CORS middleware
+app.add_middleware(
+ CORSMiddleware,
+ allow_origins=["*"], # Allow all origins for production deployment
+ allow_credentials=True,
+ allow_methods=["*"],
+ allow_headers=["*"],
+)
+
+# Authentication middleware
+app.add_middleware(
+ AuthenticationMiddleware,
+ excluded_paths=[
+ "/", "/docs", "/health", "/redoc", "/openapi.json",
+ "/api/auth/login", "/api/auth/refresh",
+ "/api/telegram/auth/verify-user",
+ "/api/telegram/auth/verify-email",
+ "/api/telegram/auth/login-with-email",
+ "/api/telegram/auth/refresh-token",
+ "/api/telegram/health"
+ ]
+)
+
+
+# ============================================================================
+# ROUTER REGISTRATION
+# ============================================================================
+
+# Module routers with prefixes
+app.include_router(create_reports_router(), prefix="/api/reports", tags=["reports"])
+app.include_router(create_data_entry_router(), prefix="/api/data-entry", tags=["data-entry"])
+app.include_router(create_telegram_router(), prefix="/api/telegram", tags=["telegram"])
+
+# Shared routers
+auth_router = create_auth_router(prefix="", tags=["authentication"])
+app.include_router(auth_router, prefix="/api/auth")
+
+companies_router = create_companies_router(oracle_pool, tags=["companies"])
+app.include_router(companies_router, prefix="/api/companies")
+
+calendar_router = create_calendar_router(oracle_pool, tags=["calendar"])
+app.include_router(calendar_router, prefix="/api/calendar")
+
+
+# ============================================================================
+# ROOT & HEALTH ENDPOINTS
+# ============================================================================
+
+@app.get("/")
+async def root():
+ """Root endpoint - API information."""
+ return {
+ "name": settings.app_name,
+ "version": settings.app_version,
+ "status": "running",
+ "modules": ["reports", "data-entry", "telegram"],
+ "docs": "/docs",
+ "health": "/health"
+ }
+
+
+@app.get("/health")
+async def health_check():
+ """Health check endpoint with module status."""
+ health_status = {
+ "api": "healthy",
+ "timestamp": datetime.utcnow().isoformat(),
+ "modules": {}
+ }
+
+ # Check Oracle connection
+ try:
+ async with oracle_pool.get_connection() as conn:
+ with conn.cursor() as cursor:
+ cursor.execute("SELECT 1 FROM DUAL")
+ health_status["modules"]["oracle"] = "connected"
+ except Exception as e:
+ health_status["modules"]["oracle"] = f"error: {str(e)}"
+
+ # Check Reports cache
+ try:
+ from backend.modules.reports.cache import get_cache
+ cache = get_cache()
+ health_status["modules"]["reports_cache"] = "initialized" if cache else "disabled"
+ except Exception as e:
+ health_status["modules"]["reports_cache"] = f"error: {str(e)}"
+
+ # Check Data Entry DB
+ try:
+ db_path = Path(settings.data_entry_sqlite_database_path)
+ health_status["modules"]["data_entry_db"] = "exists" if db_path.exists() else "missing"
+ except Exception as e:
+ health_status["modules"]["data_entry_db"] = f"error: {str(e)}"
+
+ # Check Telegram bot
+ global telegram_bot_task
+ if telegram_bot_task:
+ if telegram_bot_task.done():
+ health_status["modules"]["telegram_bot"] = "stopped"
+ else:
+ health_status["modules"]["telegram_bot"] = "running"
+ else:
+ health_status["modules"]["telegram_bot"] = "disabled"
+
+ return health_status
+
+
+# ============================================================================
+# MAIN ENTRY POINT
+# ============================================================================
+
+if __name__ == "__main__":
+ import uvicorn
+
+ uvicorn.run(
+ "backend.main:app",
+ host=settings.api_host,
+ port=settings.api_port,
+ reload=False,
+ log_level="info"
+ )
diff --git a/reports-app/backend/app/schemas/__init__.py b/backend/modules/__init__.py
similarity index 100%
rename from reports-app/backend/app/schemas/__init__.py
rename to backend/modules/__init__.py
diff --git a/reports-app/backend/app/services/__init__.py b/backend/modules/data_entry/__init__.py
similarity index 100%
rename from reports-app/backend/app/services/__init__.py
rename to backend/modules/data_entry/__init__.py
diff --git a/data-entry-app/backend/app/config.py b/backend/modules/data_entry/config.py
similarity index 97%
rename from data-entry-app/backend/app/config.py
rename to backend/modules/data_entry/config.py
index b6c2a18..15514cc 100644
--- a/data-entry-app/backend/app/config.py
+++ b/backend/modules/data_entry/config.py
@@ -20,7 +20,7 @@ class Settings(BaseSettings):
api_port: int = 8003
# SQLite Database
- sqlite_database_path: str = "data/receipts.db"
+ sqlite_database_path: str = "data/receipts/receipts.db"
# File uploads
upload_path: str = "data/uploads"
diff --git a/data-entry-app/backend/app/db/__init__.py b/backend/modules/data_entry/db/__init__.py
similarity index 100%
rename from data-entry-app/backend/app/db/__init__.py
rename to backend/modules/data_entry/db/__init__.py
diff --git a/data-entry-app/backend/app/db/crud/__init__.py b/backend/modules/data_entry/db/crud/__init__.py
similarity index 100%
rename from data-entry-app/backend/app/db/crud/__init__.py
rename to backend/modules/data_entry/db/crud/__init__.py
diff --git a/data-entry-app/backend/app/db/crud/accounting_entry.py b/backend/modules/data_entry/db/crud/accounting_entry.py
similarity index 97%
rename from data-entry-app/backend/app/db/crud/accounting_entry.py
rename to backend/modules/data_entry/db/crud/accounting_entry.py
index 8051f1e..f321295 100644
--- a/data-entry-app/backend/app/db/crud/accounting_entry.py
+++ b/backend/modules/data_entry/db/crud/accounting_entry.py
@@ -6,8 +6,8 @@ from typing import Optional, List
from sqlalchemy import select, delete
from sqlalchemy.ext.asyncio import AsyncSession
-from app.db.models.accounting_entry import AccountingEntry, EntryType
-from app.schemas.receipt import AccountingEntryCreate, AccountingEntryUpdate
+from backend.modules.data_entry.db.models.accounting_entry import AccountingEntry, EntryType
+from backend.modules.data_entry.schemas.receipt import AccountingEntryCreate, AccountingEntryUpdate
class AccountingEntryCRUD:
diff --git a/data-entry-app/backend/app/db/crud/attachment.py b/backend/modules/data_entry/db/crud/attachment.py
similarity index 97%
rename from data-entry-app/backend/app/db/crud/attachment.py
rename to backend/modules/data_entry/db/crud/attachment.py
index f746550..82d57e9 100644
--- a/data-entry-app/backend/app/db/crud/attachment.py
+++ b/backend/modules/data_entry/db/crud/attachment.py
@@ -11,8 +11,8 @@ from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from fastapi import UploadFile
-from app.db.models.receipt import ReceiptAttachment
-from app.config import settings
+from backend.modules.data_entry.db.models.receipt import ReceiptAttachment
+from backend.modules.data_entry.config import settings
class AttachmentCRUD:
diff --git a/data-entry-app/backend/app/db/crud/receipt.py b/backend/modules/data_entry/db/crud/receipt.py
similarity index 98%
rename from data-entry-app/backend/app/db/crud/receipt.py
rename to backend/modules/data_entry/db/crud/receipt.py
index e4e33f7..054a8a4 100644
--- a/data-entry-app/backend/app/db/crud/receipt.py
+++ b/backend/modules/data_entry/db/crud/receipt.py
@@ -8,8 +8,8 @@ from sqlalchemy import select, func, or_
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload
-from app.db.models.receipt import Receipt, ReceiptStatus
-from app.schemas.receipt import ReceiptCreate, ReceiptUpdate, ReceiptFilter
+from backend.modules.data_entry.db.models.receipt import Receipt, ReceiptStatus
+from backend.modules.data_entry.schemas.receipt import ReceiptCreate, ReceiptUpdate, ReceiptFilter
def _serialize_tva_breakdown(tva_breakdown: Optional[List[Any]]) -> Optional[str]:
diff --git a/data-entry-app/backend/app/db/database.py b/backend/modules/data_entry/db/database.py
similarity index 95%
rename from data-entry-app/backend/app/db/database.py
rename to backend/modules/data_entry/db/database.py
index d502019..3d02315 100644
--- a/data-entry-app/backend/app/db/database.py
+++ b/backend/modules/data_entry/db/database.py
@@ -6,7 +6,7 @@ from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy.orm import sessionmaker
from sqlmodel import SQLModel
-from app.config import settings
+from backend.modules.data_entry.config import settings
# Create async engine
diff --git a/data-entry-app/backend/app/db/models/__init__.py b/backend/modules/data_entry/db/models/__init__.py
similarity index 100%
rename from data-entry-app/backend/app/db/models/__init__.py
rename to backend/modules/data_entry/db/models/__init__.py
diff --git a/data-entry-app/backend/app/db/models/accounting_entry.py b/backend/modules/data_entry/db/models/accounting_entry.py
similarity index 100%
rename from data-entry-app/backend/app/db/models/accounting_entry.py
rename to backend/modules/data_entry/db/models/accounting_entry.py
diff --git a/data-entry-app/backend/app/db/models/nomenclature.py b/backend/modules/data_entry/db/models/nomenclature.py
similarity index 100%
rename from data-entry-app/backend/app/db/models/nomenclature.py
rename to backend/modules/data_entry/db/models/nomenclature.py
diff --git a/data-entry-app/backend/app/db/models/receipt.py b/backend/modules/data_entry/db/models/receipt.py
similarity index 100%
rename from data-entry-app/backend/app/db/models/receipt.py
rename to backend/modules/data_entry/db/models/receipt.py
diff --git a/data-entry-app/backend/migrations/env.py b/backend/modules/data_entry/migrations/env.py
similarity index 87%
rename from data-entry-app/backend/migrations/env.py
rename to backend/modules/data_entry/migrations/env.py
index 8f8d2d0..063cb99 100644
--- a/data-entry-app/backend/migrations/env.py
+++ b/backend/modules/data_entry/migrations/env.py
@@ -14,16 +14,16 @@ from sqlmodel import SQLModel
load_dotenv()
# Import all models to ensure they're registered with SQLModel
-from app.db.models.receipt import Receipt, ReceiptAttachment
-from app.db.models.accounting_entry import AccountingEntry
-from app.db.models.nomenclature import SyncedSupplier, LocalSupplier, SyncedCashRegister
+from backend.modules.data_entry.db.models.receipt import Receipt, ReceiptAttachment
+from backend.modules.data_entry.db.models.accounting_entry import AccountingEntry
+from backend.modules.data_entry.db.models.nomenclature import SyncedSupplier, LocalSupplier, SyncedCashRegister
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config
# Override sqlalchemy.url from environment variable if set
-db_path = os.getenv("SQLITE_DATABASE_PATH", "data/receipts.db")
+db_path = os.getenv("SQLITE_DATABASE_PATH", "data/receipts/receipts.db")
config.set_main_option("sqlalchemy.url", f"sqlite:///{db_path}")
# Interpret the config file for Python logging.
diff --git a/data-entry-app/backend/migrations/script.py.mako b/backend/modules/data_entry/migrations/script.py.mako
similarity index 100%
rename from data-entry-app/backend/migrations/script.py.mako
rename to backend/modules/data_entry/migrations/script.py.mako
diff --git a/data-entry-app/backend/migrations/versions/001_initial_receipts.py b/backend/modules/data_entry/migrations/versions/001_initial_receipts.py
similarity index 100%
rename from data-entry-app/backend/migrations/versions/001_initial_receipts.py
rename to backend/modules/data_entry/migrations/versions/001_initial_receipts.py
diff --git a/data-entry-app/backend/migrations/versions/20251212_140422_add_tva_breakdown_to_receipt.py b/backend/modules/data_entry/migrations/versions/20251212_140422_add_tva_breakdown_to_receipt.py
similarity index 100%
rename from data-entry-app/backend/migrations/versions/20251212_140422_add_tva_breakdown_to_receipt.py
rename to backend/modules/data_entry/migrations/versions/20251212_140422_add_tva_breakdown_to_receipt.py
diff --git a/data-entry-app/backend/migrations/versions/20251213_002805_add_nomenclature_tables.py b/backend/modules/data_entry/migrations/versions/20251213_002805_add_nomenclature_tables.py
similarity index 100%
rename from data-entry-app/backend/migrations/versions/20251213_002805_add_nomenclature_tables.py
rename to backend/modules/data_entry/migrations/versions/20251213_002805_add_nomenclature_tables.py
diff --git a/data-entry-app/backend/migrations/versions/20251215_add_ocr_fields_to_receipt.py b/backend/modules/data_entry/migrations/versions/20251215_add_ocr_fields_to_receipt.py
similarity index 100%
rename from data-entry-app/backend/migrations/versions/20251215_add_ocr_fields_to_receipt.py
rename to backend/modules/data_entry/migrations/versions/20251215_add_ocr_fields_to_receipt.py
diff --git a/data-entry-app/backend/migrations/versions/20251215_remove_partner_id.py b/backend/modules/data_entry/migrations/versions/20251215_remove_partner_id.py
similarity index 100%
rename from data-entry-app/backend/migrations/versions/20251215_remove_partner_id.py
rename to backend/modules/data_entry/migrations/versions/20251215_remove_partner_id.py
diff --git a/data-entry-app/backend/migrations/versions/20251216_add_payment_mode.py b/backend/modules/data_entry/migrations/versions/20251216_add_payment_mode.py
similarity index 100%
rename from data-entry-app/backend/migrations/versions/20251216_add_payment_mode.py
rename to backend/modules/data_entry/migrations/versions/20251216_add_payment_mode.py
diff --git a/backend/modules/data_entry/routers/__init__.py b/backend/modules/data_entry/routers/__init__.py
new file mode 100644
index 0000000..fab0364
--- /dev/null
+++ b/backend/modules/data_entry/routers/__init__.py
@@ -0,0 +1,30 @@
+"""Data Entry module router factory."""
+
+from fastapi import APIRouter
+
+
+def create_data_entry_router() -> APIRouter:
+ """
+ Create and configure Data Entry module router.
+
+ Includes all data entry endpoints:
+ - /receipts - Receipt CRUD and workflow
+ - /ocr - OCR processing for receipts
+ - /nomenclature - Nomenclature syncing from Oracle
+
+ Returns:
+ APIRouter: Configured router for data entry module
+ """
+ router = APIRouter()
+
+ # Import routers here to avoid circular imports
+ from .receipts import router as receipts_router
+ from .ocr import router as ocr_router
+ from .nomenclature import router as nomenclature_router
+
+ # Include all sub-routers (no prefix - already prefixed in main.py with /api/data-entry)
+ router.include_router(receipts_router, prefix="/receipts", tags=["data-entry-receipts"])
+ router.include_router(ocr_router, prefix="/ocr", tags=["data-entry-ocr"])
+ router.include_router(nomenclature_router, prefix="/nomenclature", tags=["data-entry-nomenclature"])
+
+ return router
diff --git a/data-entry-app/backend/app/routers/nomenclature.py b/backend/modules/data_entry/routers/nomenclature.py
similarity index 94%
rename from data-entry-app/backend/app/routers/nomenclature.py
rename to backend/modules/data_entry/routers/nomenclature.py
index 1971453..84b6821 100644
--- a/data-entry-app/backend/app/routers/nomenclature.py
+++ b/backend/modules/data_entry/routers/nomenclature.py
@@ -5,17 +5,18 @@ from fastapi import APIRouter, Depends, HTTPException, Header
from sqlalchemy.ext.asyncio import AsyncSession
from pydantic import BaseModel
-from app.db.database import get_session
-from app.services.sync_service import SyncService
+from backend.modules.data_entry.db.database import get_session
+from backend.modules.data_entry.services.sync_service import SyncService
# Import auth dependencies
import sys
from pathlib import Path
-project_root = Path(__file__).parent.parent.parent.parent.parent
-sys.path.insert(0, str(project_root / "shared"))
+# Path setup handled by main.py - this is redundant
+# project_root = Path(__file__).parent.parent.parent.parent.parent
+# sys.path.insert(0, str(project_root / "shared"))
-from auth.dependencies import get_current_user
-from auth.models import CurrentUser
+from shared.auth.dependencies import get_current_user
+from shared.auth.models import CurrentUser
router = APIRouter()
diff --git a/data-entry-app/backend/app/routers/ocr.py b/backend/modules/data_entry/routers/ocr.py
similarity index 93%
rename from data-entry-app/backend/app/routers/ocr.py
rename to backend/modules/data_entry/routers/ocr.py
index ca30406..d1ad183 100644
--- a/data-entry-app/backend/app/routers/ocr.py
+++ b/backend/modules/data_entry/routers/ocr.py
@@ -7,15 +7,15 @@ from pathlib import Path
from fastapi import APIRouter, HTTPException, UploadFile, File, Depends
from sqlalchemy.ext.asyncio import AsyncSession
-from app.db.database import get_session
-from app.db.crud.attachment import AttachmentCRUD
-from app.services.ocr_service import ocr_service
-from app.services.ocr_engine import OCREngine
-from app.schemas.ocr import OCRResponse, OCRStatusResponse, ExtractionData, TvaEntry, PaymentMethod
+from backend.modules.data_entry.db.database import get_session
+from backend.modules.data_entry.db.crud.attachment import AttachmentCRUD
+from backend.modules.data_entry.services.ocr_service import ocr_service
+from backend.modules.data_entry.services.ocr_engine import OCREngine
+from backend.modules.data_entry.schemas.ocr import OCRResponse, OCRStatusResponse, ExtractionData, TvaEntry, PaymentMethod
# Auth integration (will be protected by middleware)
-from auth.dependencies import get_current_user
-from auth.models import CurrentUser
+from shared.auth.dependencies import get_current_user
+from shared.auth.models import CurrentUser
router = APIRouter()
diff --git a/data-entry-app/backend/app/routers/receipts.py b/backend/modules/data_entry/routers/receipts.py
similarity index 95%
rename from data-entry-app/backend/app/routers/receipts.py
rename to backend/modules/data_entry/routers/receipts.py
index 5e2fd1b..492165d 100644
--- a/data-entry-app/backend/app/routers/receipts.py
+++ b/backend/modules/data_entry/routers/receipts.py
@@ -7,13 +7,13 @@ from fastapi import APIRouter, Depends, HTTPException, UploadFile, File, Query,
from fastapi.responses import FileResponse
from sqlalchemy.ext.asyncio import AsyncSession
-from app.db.database import get_session
-from app.db.crud.receipt import ReceiptCRUD
-from app.db.crud.attachment import AttachmentCRUD
-from app.db.crud.accounting_entry import AccountingEntryCRUD
-from app.services.receipt_service import ReceiptService
-from app.services.nomenclature_service import NomenclatureService
-from app.schemas.receipt import (
+from backend.modules.data_entry.db.database import get_session
+from backend.modules.data_entry.db.crud.receipt import ReceiptCRUD
+from backend.modules.data_entry.db.crud.attachment import AttachmentCRUD
+from backend.modules.data_entry.db.crud.accounting_entry import AccountingEntryCRUD
+from backend.modules.data_entry.services.receipt_service import ReceiptService
+from backend.modules.data_entry.services.nomenclature_service import NomenclatureService
+from backend.modules.data_entry.schemas.receipt import (
ReceiptCreate,
ReceiptUpdate,
ReceiptResponse,
@@ -29,11 +29,11 @@ from app.schemas.receipt import (
CashRegisterOption,
ExpenseTypeOption,
)
-from app.db.models.receipt import ReceiptStatus, ReceiptDirection
+from backend.modules.data_entry.db.models.receipt import ReceiptStatus, ReceiptDirection
# Auth integration
-from auth.dependencies import get_current_user
-from auth.models import CurrentUser
+from shared.auth.dependencies import get_current_user
+from shared.auth.models import CurrentUser
router = APIRouter()
diff --git a/data-entry-app/backend/app/schemas/__init__.py b/backend/modules/data_entry/schemas/__init__.py
similarity index 100%
rename from data-entry-app/backend/app/schemas/__init__.py
rename to backend/modules/data_entry/schemas/__init__.py
diff --git a/data-entry-app/backend/app/schemas/ocr.py b/backend/modules/data_entry/schemas/ocr.py
similarity index 100%
rename from data-entry-app/backend/app/schemas/ocr.py
rename to backend/modules/data_entry/schemas/ocr.py
diff --git a/data-entry-app/backend/app/schemas/receipt.py b/backend/modules/data_entry/schemas/receipt.py
similarity index 98%
rename from data-entry-app/backend/app/schemas/receipt.py
rename to backend/modules/data_entry/schemas/receipt.py
index 6c96426..11b542c 100644
--- a/data-entry-app/backend/app/schemas/receipt.py
+++ b/backend/modules/data_entry/schemas/receipt.py
@@ -6,8 +6,8 @@ from decimal import Decimal
from typing import Optional, List, Any, Union
from pydantic import BaseModel, Field, ConfigDict, field_validator
-from app.db.models.receipt import ReceiptType, ReceiptDirection, ReceiptStatus
-from app.db.models.accounting_entry import EntryType
+from backend.modules.data_entry.db.models.receipt import ReceiptType, ReceiptDirection, ReceiptStatus
+from backend.modules.data_entry.db.models.accounting_entry import EntryType
# ============ Accounting Entry Schemas ============
diff --git a/data-entry-app/backend/app/services/__init__.py b/backend/modules/data_entry/services/__init__.py
similarity index 100%
rename from data-entry-app/backend/app/services/__init__.py
rename to backend/modules/data_entry/services/__init__.py
diff --git a/data-entry-app/backend/app/services/expense_types.py b/backend/modules/data_entry/services/expense_types.py
similarity index 100%
rename from data-entry-app/backend/app/services/expense_types.py
rename to backend/modules/data_entry/services/expense_types.py
diff --git a/data-entry-app/backend/app/services/image_preprocessor.py b/backend/modules/data_entry/services/image_preprocessor.py
similarity index 100%
rename from data-entry-app/backend/app/services/image_preprocessor.py
rename to backend/modules/data_entry/services/image_preprocessor.py
diff --git a/data-entry-app/backend/app/services/nomenclature_service.py b/backend/modules/data_entry/services/nomenclature_service.py
similarity index 97%
rename from data-entry-app/backend/app/services/nomenclature_service.py
rename to backend/modules/data_entry/services/nomenclature_service.py
index d25546b..16d3af3 100644
--- a/data-entry-app/backend/app/services/nomenclature_service.py
+++ b/backend/modules/data_entry/services/nomenclature_service.py
@@ -6,14 +6,14 @@ from decimal import Decimal
from sqlmodel import select
from sqlalchemy.ext.asyncio import AsyncSession
-from app.schemas.receipt import (
+from backend.modules.data_entry.schemas.receipt import (
PartnerOption,
AccountOption,
CashRegisterOption,
ExpenseTypeOption,
)
-from app.services.expense_types import EXPENSE_TYPES
-from app.db.models.nomenclature import SyncedSupplier, LocalSupplier, SyncedCashRegister
+from backend.modules.data_entry.services.expense_types import EXPENSE_TYPES
+from backend.modules.data_entry.db.models.nomenclature import SyncedSupplier, LocalSupplier, SyncedCashRegister
class NomenclatureService:
diff --git a/data-entry-app/backend/app/services/ocr_engine.py b/backend/modules/data_entry/services/ocr_engine.py
similarity index 100%
rename from data-entry-app/backend/app/services/ocr_engine.py
rename to backend/modules/data_entry/services/ocr_engine.py
diff --git a/data-entry-app/backend/app/services/ocr_extractor.py b/backend/modules/data_entry/services/ocr_extractor.py
similarity index 100%
rename from data-entry-app/backend/app/services/ocr_extractor.py
rename to backend/modules/data_entry/services/ocr_extractor.py
diff --git a/data-entry-app/backend/app/services/ocr_service.py b/backend/modules/data_entry/services/ocr_service.py
similarity index 99%
rename from data-entry-app/backend/app/services/ocr_service.py
rename to backend/modules/data_entry/services/ocr_service.py
index f771915..0a983a1 100644
--- a/data-entry-app/backend/app/services/ocr_service.py
+++ b/backend/modules/data_entry/services/ocr_service.py
@@ -14,9 +14,9 @@ from decimal import Decimal
from pathlib import Path
from typing import Optional, Tuple
-from app.services.ocr_engine import OCREngine
-from app.services.ocr_extractor import ReceiptExtractor, ExtractionResult
-from app.services.image_preprocessor import ImagePreprocessor
+from backend.modules.data_entry.services.ocr_engine import OCREngine
+from backend.modules.data_entry.services.ocr_extractor import ReceiptExtractor, ExtractionResult
+from backend.modules.data_entry.services.image_preprocessor import ImagePreprocessor
# Setup logging
logger = logging.getLogger(__name__)
diff --git a/data-entry-app/backend/app/services/receipt_service.py b/backend/modules/data_entry/services/receipt_service.py
similarity index 96%
rename from data-entry-app/backend/app/services/receipt_service.py
rename to backend/modules/data_entry/services/receipt_service.py
index 278eed4..8f85191 100644
--- a/data-entry-app/backend/app/services/receipt_service.py
+++ b/backend/modules/data_entry/services/receipt_service.py
@@ -5,11 +5,11 @@ from typing import List, Optional, Tuple
from sqlalchemy.ext.asyncio import AsyncSession
-from app.db.models.receipt import Receipt, ReceiptStatus, ReceiptDirection
-from app.db.models.accounting_entry import EntryType
-from app.db.crud.receipt import ReceiptCRUD
-from app.db.crud.accounting_entry import AccountingEntryCRUD
-from app.schemas.receipt import (
+from backend.modules.data_entry.db.models.receipt import Receipt, ReceiptStatus, ReceiptDirection
+from backend.modules.data_entry.db.models.accounting_entry import EntryType
+from backend.modules.data_entry.db.crud.receipt import ReceiptCRUD
+from backend.modules.data_entry.db.crud.accounting_entry import AccountingEntryCRUD
+from backend.modules.data_entry.schemas.receipt import (
ReceiptCreate,
ReceiptUpdate,
ReceiptFilter,
@@ -17,7 +17,7 @@ from app.schemas.receipt import (
ReceiptListResponse,
AccountingEntryCreate,
)
-from app.services.expense_types import EXPENSE_TYPES, get_expense_type
+from backend.modules.data_entry.services.expense_types import EXPENSE_TYPES, get_expense_type
# Payment mode to accounting account mapping
diff --git a/data-entry-app/backend/app/services/sync_service.py b/backend/modules/data_entry/services/sync_service.py
similarity index 97%
rename from data-entry-app/backend/app/services/sync_service.py
rename to backend/modules/data_entry/services/sync_service.py
index a3599c5..3f59694 100644
--- a/data-entry-app/backend/app/services/sync_service.py
+++ b/backend/modules/data_entry/services/sync_service.py
@@ -9,12 +9,12 @@ import logging
from sqlmodel import select
from sqlalchemy.ext.asyncio import AsyncSession
-# Add shared modules path
-project_root = Path(__file__).parent.parent.parent.parent.parent
-sys.path.insert(0, str(project_root / "shared"))
+# Path setup handled by main.py - this is redundant
+# project_root = Path(__file__).parent.parent.parent.parent.parent
+# sys.path.insert(0, str(project_root / "shared"))
-from database.oracle_pool import oracle_pool
-from app.db.models.nomenclature import SyncedSupplier, LocalSupplier, SyncedCashRegister
+from shared.database.oracle_pool import oracle_pool
+from backend.modules.data_entry.db.models.nomenclature import SyncedSupplier, LocalSupplier, SyncedCashRegister
logger = logging.getLogger(__name__)
diff --git a/reports-app/frontend/src/services/__init__.py b/backend/modules/reports/__init__.py
similarity index 100%
rename from reports-app/frontend/src/services/__init__.py
rename to backend/modules/reports/__init__.py
diff --git a/reports-app/backend/app/cache/__init__.py b/backend/modules/reports/cache/__init__.py
similarity index 100%
rename from reports-app/backend/app/cache/__init__.py
rename to backend/modules/reports/cache/__init__.py
diff --git a/reports-app/backend/app/cache/benchmarks.py b/backend/modules/reports/cache/benchmarks.py
similarity index 100%
rename from reports-app/backend/app/cache/benchmarks.py
rename to backend/modules/reports/cache/benchmarks.py
diff --git a/reports-app/backend/app/cache/cache_manager.py b/backend/modules/reports/cache/cache_manager.py
similarity index 100%
rename from reports-app/backend/app/cache/cache_manager.py
rename to backend/modules/reports/cache/cache_manager.py
diff --git a/reports-app/backend/app/cache/config.py b/backend/modules/reports/cache/config.py
similarity index 97%
rename from reports-app/backend/app/cache/config.py
rename to backend/modules/reports/cache/config.py
index 08e6257..0ba07c9 100644
--- a/reports-app/backend/app/cache/config.py
+++ b/backend/modules/reports/cache/config.py
@@ -46,7 +46,7 @@ class CacheConfig:
# Core Settings
enabled=os.getenv('CACHE_ENABLED', 'True').lower() == 'true',
cache_type=os.getenv('CACHE_TYPE', 'hybrid'),
- sqlite_path=os.getenv('CACHE_SQLITE_PATH', './cache_data/roa2web_cache.db'),
+ sqlite_path=os.getenv('CACHE_SQLITE_PATH', './data/cache/roa2web_cache.db'),
memory_max_size=int(os.getenv('CACHE_MEMORY_MAX_SIZE', '1000')),
default_ttl=int(os.getenv('CACHE_DEFAULT_TTL', '900')),
diff --git a/reports-app/backend/app/cache/decorators.py b/backend/modules/reports/cache/decorators.py
similarity index 100%
rename from reports-app/backend/app/cache/decorators.py
rename to backend/modules/reports/cache/decorators.py
diff --git a/reports-app/backend/app/cache/event_monitor.py b/backend/modules/reports/cache/event_monitor.py
similarity index 98%
rename from reports-app/backend/app/cache/event_monitor.py
rename to backend/modules/reports/cache/event_monitor.py
index 1dbc4a4..c7306e9 100644
--- a/reports-app/backend/app/cache/event_monitor.py
+++ b/backend/modules/reports/cache/event_monitor.py
@@ -9,8 +9,8 @@ import sys
import os
from typing import Optional
-# Add shared to path for Oracle pool access
-sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../..')))
+# Path setup handled by main.py - this is redundant but kept for module isolation
+# sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../..')))
logger = logging.getLogger(__name__)
diff --git a/reports-app/backend/app/cache/keys.py b/backend/modules/reports/cache/keys.py
similarity index 100%
rename from reports-app/backend/app/cache/keys.py
rename to backend/modules/reports/cache/keys.py
diff --git a/reports-app/backend/app/cache/memory_cache.py b/backend/modules/reports/cache/memory_cache.py
similarity index 100%
rename from reports-app/backend/app/cache/memory_cache.py
rename to backend/modules/reports/cache/memory_cache.py
diff --git a/reports-app/backend/app/cache/sqlite_cache.py b/backend/modules/reports/cache/sqlite_cache.py
similarity index 100%
rename from reports-app/backend/app/cache/sqlite_cache.py
rename to backend/modules/reports/cache/sqlite_cache.py
diff --git a/reports-app/frontend/src/utils/__init__.py b/backend/modules/reports/models/__init__.py
similarity index 100%
rename from reports-app/frontend/src/utils/__init__.py
rename to backend/modules/reports/models/__init__.py
diff --git a/reports-app/backend/app/models/calendar.py b/backend/modules/reports/models/calendar.py
similarity index 100%
rename from reports-app/backend/app/models/calendar.py
rename to backend/modules/reports/models/calendar.py
diff --git a/reports-app/backend/app/models/dashboard.py b/backend/modules/reports/models/dashboard.py
similarity index 100%
rename from reports-app/backend/app/models/dashboard.py
rename to backend/modules/reports/models/dashboard.py
diff --git a/reports-app/backend/app/models/invoice.py b/backend/modules/reports/models/invoice.py
similarity index 100%
rename from reports-app/backend/app/models/invoice.py
rename to backend/modules/reports/models/invoice.py
diff --git a/reports-app/backend/app/models/treasury.py b/backend/modules/reports/models/treasury.py
similarity index 100%
rename from reports-app/backend/app/models/treasury.py
rename to backend/modules/reports/models/treasury.py
diff --git a/reports-app/backend/app/models/trial_balance.py b/backend/modules/reports/models/trial_balance.py
similarity index 99%
rename from reports-app/backend/app/models/trial_balance.py
rename to backend/modules/reports/models/trial_balance.py
index c370411..23741df 100644
--- a/reports-app/backend/app/models/trial_balance.py
+++ b/backend/modules/reports/models/trial_balance.py
@@ -69,7 +69,7 @@ class TrialBalanceResponse(BaseModel):
data: dict = Field(description="Trial balance data with items, pagination, and filters")
class Config:
- schema_extra = {
+ json_schema_extra = {
"example": {
"success": True,
"data": {
diff --git a/backend/modules/reports/routers/__init__.py b/backend/modules/reports/routers/__init__.py
new file mode 100644
index 0000000..1df06cb
--- /dev/null
+++ b/backend/modules/reports/routers/__init__.py
@@ -0,0 +1,36 @@
+"""Reports module router factory."""
+
+from fastapi import APIRouter
+
+
+def create_reports_router() -> APIRouter:
+ """
+ Create and configure Reports module router.
+
+ Includes all report-related endpoints:
+ - /invoices - Invoice management
+ - /dashboard - Dashboard and metrics
+ - /treasury - Treasury operations
+ - /trial-balance - Trial balance reports
+ - /cache - Cache management
+
+ Returns:
+ APIRouter: Configured router for reports module
+ """
+ router = APIRouter()
+
+ # Import routers here to avoid circular imports
+ from .invoices import router as invoices_router
+ from .dashboard import router as dashboard_router
+ from .treasury import router as treasury_router
+ from .trial_balance import router as trial_balance_router
+ from .cache import router as cache_router
+
+ # Include all sub-routers (no prefix - already prefixed in main.py with /api/reports)
+ router.include_router(invoices_router, prefix="/invoices", tags=["reports-invoices"])
+ router.include_router(dashboard_router, prefix="/dashboard", tags=["reports-dashboard"])
+ router.include_router(treasury_router, prefix="/treasury", tags=["reports-treasury"])
+ router.include_router(trial_balance_router, prefix="/trial-balance", tags=["reports-trial-balance"])
+ router.include_router(cache_router, prefix="/cache", tags=["reports-cache"])
+
+ return router
diff --git a/reports-app/backend/app/routers/cache.py b/backend/modules/reports/routers/cache.py
similarity index 98%
rename from reports-app/backend/app/routers/cache.py
rename to backend/modules/reports/routers/cache.py
index 2b1b588..f33f395 100644
--- a/reports-app/backend/app/routers/cache.py
+++ b/backend/modules/reports/routers/cache.py
@@ -4,15 +4,14 @@ API Router pentru managementul cache-ului
from fastapi import APIRouter, Depends, HTTPException, Request
from pydantic import BaseModel
from typing import Optional, Dict, Any
-import sys
+# import sys # Removed - no longer needed
import os
import time
from datetime import datetime, timedelta
-sys.path.append(os.path.join(os.path.dirname(__file__), '../../../../shared'))
-from auth.dependencies import get_current_user
-from auth.models import CurrentUser
+from shared.auth.dependencies import get_current_user
+from shared.auth.models import CurrentUser
from ..cache import get_cache, get_event_monitor, toggle_event_monitor
router = APIRouter(prefix="/cache", tags=["cache"])
diff --git a/reports-app/backend/app/routers/dashboard.py b/backend/modules/reports/routers/dashboard.py
similarity index 99%
rename from reports-app/backend/app/routers/dashboard.py
rename to backend/modules/reports/routers/dashboard.py
index 8a55f7c..7be4b7b 100644
--- a/reports-app/backend/app/routers/dashboard.py
+++ b/backend/modules/reports/routers/dashboard.py
@@ -1,11 +1,10 @@
from fastapi import APIRouter, Depends, HTTPException, Query, Request
from typing import Optional
-import sys
+# import sys # Removed - no longer needed
import os
-sys.path.append(os.path.join(os.path.dirname(__file__), '../../../../shared'))
-from auth.dependencies import get_current_user
-from auth.models import CurrentUser
+from shared.auth.dependencies import get_current_user
+from shared.auth.models import CurrentUser
import logging
logger = logging.getLogger(__name__)
diff --git a/reports-app/backend/app/routers/invoices.py b/backend/modules/reports/routers/invoices.py
similarity index 96%
rename from reports-app/backend/app/routers/invoices.py
rename to backend/modules/reports/routers/invoices.py
index 54d581b..d91056e 100644
--- a/reports-app/backend/app/routers/invoices.py
+++ b/backend/modules/reports/routers/invoices.py
@@ -4,12 +4,11 @@ API Router pentru facturi
from fastapi import APIRouter, Depends, HTTPException, Query
from typing import List, Optional
from datetime import date
-import sys
+# import sys # Removed - no longer needed
import os
-sys.path.append(os.path.join(os.path.dirname(__file__), '../../../../shared'))
-from auth.dependencies import get_current_user, require_company_access
-from auth.models import CurrentUser
+from shared.auth.dependencies import get_current_user, require_company_access
+from shared.auth.models import CurrentUser
from ..models.invoice import InvoiceFilter, InvoiceListResponse, InvoiceSummary
from ..services.invoice_service import InvoiceService
diff --git a/reports-app/backend/app/routers/treasury.py b/backend/modules/reports/routers/treasury.py
similarity index 96%
rename from reports-app/backend/app/routers/treasury.py
rename to backend/modules/reports/routers/treasury.py
index f39cd1e..e41f3b9 100644
--- a/reports-app/backend/app/routers/treasury.py
+++ b/backend/modules/reports/routers/treasury.py
@@ -1,12 +1,11 @@
from fastapi import APIRouter, Depends, HTTPException, Query
from typing import Optional, List
from datetime import date
-import sys
+# import sys # Removed - no longer needed
import os
-sys.path.append(os.path.join(os.path.dirname(__file__), '../../../../shared'))
-from auth.dependencies import get_current_user
-from auth.models import CurrentUser
+from shared.auth.dependencies import get_current_user
+from shared.auth.models import CurrentUser
from ..models.treasury import RegisterFilter, RegisterListResponse
from ..services.treasury_service import TreasuryService
diff --git a/reports-app/backend/app/routers/trial_balance.py b/backend/modules/reports/routers/trial_balance.py
similarity index 95%
rename from reports-app/backend/app/routers/trial_balance.py
rename to backend/modules/reports/routers/trial_balance.py
index 9f0b792..91445df 100644
--- a/reports-app/backend/app/routers/trial_balance.py
+++ b/backend/modules/reports/routers/trial_balance.py
@@ -5,12 +5,11 @@ Refactored to use service layer with caching
from fastapi import APIRouter, Depends, HTTPException, Query
from typing import Optional
from datetime import date
-import sys
+# import sys # Removed - no longer needed
import os
-sys.path.append(os.path.join(os.path.dirname(__file__), '../../../../shared'))
-from auth.dependencies import get_current_user
-from auth.models import CurrentUser
+from shared.auth.dependencies import get_current_user
+from shared.auth.models import CurrentUser
from ..models.trial_balance import TrialBalanceResponse
from ..services.trial_balance_service import TrialBalanceService
import logging
diff --git a/reports-app/telegram-bot/app/__init__.py b/backend/modules/reports/schemas/__init__.py
similarity index 100%
rename from reports-app/telegram-bot/app/__init__.py
rename to backend/modules/reports/schemas/__init__.py
diff --git a/reports-app/telegram-bot/app/agent/__init__.py b/backend/modules/reports/services/__init__.py
similarity index 100%
rename from reports-app/telegram-bot/app/agent/__init__.py
rename to backend/modules/reports/services/__init__.py
diff --git a/reports-app/backend/app/services/calendar_service.py b/backend/modules/reports/services/calendar_service.py
similarity index 95%
rename from reports-app/backend/app/services/calendar_service.py
rename to backend/modules/reports/services/calendar_service.py
index e1dd318..db4a72f 100644
--- a/reports-app/backend/app/services/calendar_service.py
+++ b/backend/modules/reports/services/calendar_service.py
@@ -1,11 +1,10 @@
"""
Calendar service for fetching available accounting periods
"""
-import sys
+# import sys # Removed - no longer needed
import os
-sys.path.append(os.path.join(os.path.dirname(__file__), '../../../../shared'))
-from database.oracle_pool import oracle_pool
+from shared.database.oracle_pool import oracle_pool
from ..models.calendar import CalendarPeriod, CalendarPeriodsResponse
from ..cache.decorators import cached
import logging
diff --git a/reports-app/backend/app/services/dashboard_service.py b/backend/modules/reports/services/dashboard_service.py
similarity index 99%
rename from reports-app/backend/app/services/dashboard_service.py
rename to backend/modules/reports/services/dashboard_service.py
index 91c9c29..00973bd 100644
--- a/reports-app/backend/app/services/dashboard_service.py
+++ b/backend/modules/reports/services/dashboard_service.py
@@ -1,8 +1,7 @@
-import sys
+# import sys # Removed - no longer needed
import os
-sys.path.append(os.path.join(os.path.dirname(__file__), '../../../../shared'))
-from database.oracle_pool import oracle_pool
+from shared.database.oracle_pool import oracle_pool
from ..models.dashboard import DashboardSummary, TreasuryAccount, TrendData
from ..cache.decorators import cached
from decimal import Decimal
diff --git a/reports-app/backend/app/services/invoice_service.py b/backend/modules/reports/services/invoice_service.py
similarity index 99%
rename from reports-app/backend/app/services/invoice_service.py
rename to backend/modules/reports/services/invoice_service.py
index e5b44f6..7912cae 100644
--- a/reports-app/backend/app/services/invoice_service.py
+++ b/backend/modules/reports/services/invoice_service.py
@@ -1,11 +1,10 @@
"""
Service pentru logica facturi - Portează query-urile din aplicația Flask
"""
-import sys
+# import sys # Removed - no longer needed
import os
-sys.path.append(os.path.join(os.path.dirname(__file__), '../../../../shared'))
-from database.oracle_pool import oracle_pool
+from shared.database.oracle_pool import oracle_pool
from typing import List, Tuple
from ..models.invoice import Invoice, InvoiceFilter, InvoiceListResponse, InvoiceSummary
from ..cache.decorators import cached
diff --git a/reports-app/backend/app/services/treasury_service.py b/backend/modules/reports/services/treasury_service.py
similarity index 99%
rename from reports-app/backend/app/services/treasury_service.py
rename to backend/modules/reports/services/treasury_service.py
index 9f46cc9..0d936b0 100644
--- a/reports-app/backend/app/services/treasury_service.py
+++ b/backend/modules/reports/services/treasury_service.py
@@ -1,9 +1,8 @@
-import sys
+# import sys # Removed - no longer needed
import os
-sys.path.append(os.path.join(os.path.dirname(__file__), '../../../../shared'))
import oracledb
-from database.oracle_pool import oracle_pool
+from shared.database.oracle_pool import oracle_pool
from ..models.treasury import BankCashRegister, RegisterFilter, RegisterListResponse, AccountingPeriod
from ..cache.decorators import cached
from decimal import Decimal
diff --git a/reports-app/backend/app/services/trial_balance_service.py b/backend/modules/reports/services/trial_balance_service.py
similarity index 98%
rename from reports-app/backend/app/services/trial_balance_service.py
rename to backend/modules/reports/services/trial_balance_service.py
index 7fd8132..23b19f4 100644
--- a/reports-app/backend/app/services/trial_balance_service.py
+++ b/backend/modules/reports/services/trial_balance_service.py
@@ -2,11 +2,10 @@
Service pentru Trial Balance (Balanță de Verificare) - Query VBAL VIEW
Refactored to use caching system for optimal performance
"""
-import sys
+# import sys # Removed - no longer needed
import os
-sys.path.append(os.path.join(os.path.dirname(__file__), '../../../../shared'))
-from database.oracle_pool import oracle_pool
+from shared.database.oracle_pool import oracle_pool
from typing import Dict, Any
from ..models.trial_balance import (
TrialBalanceItem,
diff --git a/reports-app/telegram-bot/app/api/__init__.py b/backend/modules/telegram/__init__.py
similarity index 100%
rename from reports-app/telegram-bot/app/api/__init__.py
rename to backend/modules/telegram/__init__.py
diff --git a/reports-app/telegram-bot/app/auth/__init__.py b/backend/modules/telegram/agent/__init__.py
similarity index 100%
rename from reports-app/telegram-bot/app/auth/__init__.py
rename to backend/modules/telegram/agent/__init__.py
diff --git a/reports-app/telegram-bot/app/agent/session.py b/backend/modules/telegram/agent/session.py
similarity index 99%
rename from reports-app/telegram-bot/app/agent/session.py
rename to backend/modules/telegram/agent/session.py
index 250eed7..211faec 100644
--- a/reports-app/telegram-bot/app/agent/session.py
+++ b/backend/modules/telegram/agent/session.py
@@ -10,7 +10,7 @@ import json
from typing import Dict, Any, Optional
from datetime import datetime
-from app.db.operations import (
+from backend.modules.telegram.db.operations import (
create_session,
get_user_active_session,
update_session_state,
diff --git a/reports-app/telegram-bot/app/bot/__init__.py b/backend/modules/telegram/api/__init__.py
similarity index 100%
rename from reports-app/telegram-bot/app/bot/__init__.py
rename to backend/modules/telegram/api/__init__.py
diff --git a/reports-app/telegram-bot/app/api/client.py b/backend/modules/telegram/api/client.py
similarity index 97%
rename from reports-app/telegram-bot/app/api/client.py
rename to backend/modules/telegram/api/client.py
index ed37c33..77f10e0 100644
--- a/reports-app/telegram-bot/app/api/client.py
+++ b/backend/modules/telegram/api/client.py
@@ -358,7 +358,7 @@ class BackendAPIClient:
headers['X-Include-Cache-Metadata'] = 'true'
response = await self.client.get(
- "/api/dashboard/summary",
+ "/api/reports/dashboard/summary",
params={"company": str(company_id)},
headers=headers
)
@@ -393,7 +393,7 @@ class BackendAPIClient:
headers['X-Include-Cache-Metadata'] = 'true'
response = await self.client.get(
- f"/api/dashboard/treasury-breakdown?company={company_id}",
+ f"/api/reports/dashboard/treasury-breakdown?company={company_id}",
headers=headers
)
@@ -429,7 +429,7 @@ class BackendAPIClient:
headers['X-Include-Cache-Metadata'] = 'true'
response = await self.client.get(
- f"/api/dashboard/detailed-data?company={company_id}&data_type={data_type}",
+ f"/api/reports/dashboard/detailed-data?company={company_id}&data_type={data_type}",
headers=headers
)
@@ -465,7 +465,7 @@ class BackendAPIClient:
headers['X-Include-Cache-Metadata'] = 'true'
response = await self.client.get(
- f"/api/dashboard/maturity?company={company_id}&period={period}",
+ f"/api/reports/dashboard/maturity?company={company_id}&period={period}",
headers=headers
)
@@ -499,7 +499,7 @@ class BackendAPIClient:
headers['X-Include-Cache-Metadata'] = 'true'
response = await self.client.get(
- f"/api/dashboard/performance?company={company_id}",
+ f"/api/reports/dashboard/performance?company={company_id}",
headers=headers
)
@@ -535,7 +535,7 @@ class BackendAPIClient:
headers['X-Include-Cache-Metadata'] = 'true'
response = await self.client.get(
- f"/api/dashboard/monthly-flows?company={company_id}&months={months}",
+ f"/api/reports/dashboard/monthly-flows?company={company_id}&months={months}",
headers=headers
)
@@ -571,7 +571,7 @@ class BackendAPIClient:
headers['X-Include-Cache-Metadata'] = 'true'
response = await self.client.get(
- f"/api/dashboard/trends?company={company_id}&period={period}",
+ f"/api/reports/dashboard/trends?company={company_id}&period={period}",
headers=headers
)
@@ -619,7 +619,7 @@ class BackendAPIClient:
params.update(filters)
response = await self.client.get(
- "/api/invoices/",
+ "/api/reports/invoices/",
params=params,
headers=self._get_auth_headers(jwt_token)
)
@@ -660,7 +660,7 @@ class BackendAPIClient:
self.client = AsyncClient(base_url=self.base_url, timeout=REQUEST_TIMEOUT)
response = await self.client.get(
- "/api/invoices/summary",
+ "/api/reports/invoices/summary",
params={
"company": str(company_id),
"partner_type": partner_type
@@ -698,7 +698,7 @@ class BackendAPIClient:
self.client = AsyncClient(base_url=self.base_url, timeout=REQUEST_TIMEOUT)
response = await self.client.get(
- "/api/treasury/bank-cash-register",
+ "/api/reports/treasury/bank-cash-register",
params={
"company": str(company_id),
"page": 1,
@@ -794,7 +794,7 @@ class BackendAPIClient:
request_data['cache_type'] = cache_type
response = await self.client.post(
- "/api/cache/invalidate",
+ "/api/reports/cache/invalidate",
json=request_data,
headers=self._get_auth_headers(jwt_token)
)
@@ -827,7 +827,7 @@ class BackendAPIClient:
self.client = AsyncClient(base_url=self.base_url, timeout=REQUEST_TIMEOUT)
response = await self.client.post(
- "/api/cache/toggle-user",
+ "/api/reports/cache/toggle-user",
json={"enabled": enabled},
headers=self._get_auth_headers(jwt_token)
)
@@ -858,7 +858,7 @@ class BackendAPIClient:
self.client = AsyncClient(base_url=self.base_url, timeout=REQUEST_TIMEOUT)
response = await self.client.get(
- "/api/cache/stats",
+ "/api/reports/cache/stats",
headers=self._get_auth_headers(jwt_token)
)
diff --git a/reports-app/telegram-bot/app/utils/__init__.py b/backend/modules/telegram/auth/__init__.py
similarity index 100%
rename from reports-app/telegram-bot/app/utils/__init__.py
rename to backend/modules/telegram/auth/__init__.py
diff --git a/reports-app/telegram-bot/app/auth/email_auth.py b/backend/modules/telegram/auth/email_auth.py
similarity index 98%
rename from reports-app/telegram-bot/app/auth/email_auth.py
rename to backend/modules/telegram/auth/email_auth.py
index 0070c7b..bc31a26 100644
--- a/reports-app/telegram-bot/app/auth/email_auth.py
+++ b/backend/modules/telegram/auth/email_auth.py
@@ -114,7 +114,7 @@ async def verify_email_in_oracle(email: str) -> Optional[str]:
NOTE: Uses backend API endpoint /api/telegram/auth/verify-email
"""
try:
- from app.api.client import get_backend_client
+ from backend.modules.telegram.api.client import get_backend_client
backend_client = get_backend_client()
diff --git a/reports-app/telegram-bot/app/auth/linking.py b/backend/modules/telegram/auth/linking.py
similarity index 98%
rename from reports-app/telegram-bot/app/auth/linking.py
rename to backend/modules/telegram/auth/linking.py
index 8a8f050..c958341 100644
--- a/reports-app/telegram-bot/app/auth/linking.py
+++ b/backend/modules/telegram/auth/linking.py
@@ -12,7 +12,7 @@ from datetime import datetime, timedelta
from telegram import User as TelegramUser
-from app.db.operations import (
+from backend.modules.telegram.db.operations import (
get_user,
create_or_update_user,
link_user_to_oracle,
@@ -20,7 +20,7 @@ from app.db.operations import (
verify_and_use_auth_code,
is_user_linked
)
-from app.api.client import get_backend_client
+from backend.modules.telegram.api.client import get_backend_client
logger = logging.getLogger(__name__)
@@ -315,7 +315,7 @@ async def unlink_user(telegram_user_id: int) -> bool:
"""
try:
# Set Oracle username and tokens to NULL
- from app.db.database import DB_PATH
+ from backend.modules.telegram.db.database import DB_PATH
import aiosqlite
async with aiosqlite.connect(DB_PATH) as db:
diff --git a/reports-app/frontend/src/utils/index.js b/backend/modules/telegram/bot/__init__.py
similarity index 100%
rename from reports-app/frontend/src/utils/index.js
rename to backend/modules/telegram/bot/__init__.py
diff --git a/reports-app/telegram-bot/app/bot/email_handlers.py b/backend/modules/telegram/bot/email_handlers.py
similarity index 98%
rename from reports-app/telegram-bot/app/bot/email_handlers.py
rename to backend/modules/telegram/bot/email_handlers.py
index 98e2210..bb11db1 100644
--- a/reports-app/telegram-bot/app/bot/email_handlers.py
+++ b/backend/modules/telegram/bot/email_handlers.py
@@ -14,7 +14,7 @@ import logging
from datetime import datetime
import asyncio
-from app.auth.email_auth import (
+from backend.modules.telegram.auth.email_auth import (
is_valid_email_format,
verify_email_in_oracle,
generate_email_code,
@@ -22,8 +22,8 @@ from app.auth.email_auth import (
check_rate_limit,
clear_rate_limit
)
-from app.utils.email_service import get_email_service
-from app.db.operations import (
+from backend.modules.telegram.utils.email_service import get_email_service
+from backend.modules.telegram.db.operations import (
create_email_auth_code,
get_email_auth_code,
get_pending_email_code,
@@ -34,7 +34,7 @@ from app.db.operations import (
link_user_to_oracle,
create_or_update_user
)
-from app.api.client import get_backend_client
+from backend.modules.telegram.api.client import get_backend_client
logger = logging.getLogger(__name__)
@@ -635,8 +635,8 @@ async def receive_password(update: Update, context: ContextTypes.DEFAULT_TYPE):
clear_rate_limit(f"resend_{user_id}")
# Get session and active company BEFORE editing message
- from app.agent.session import get_session_manager
- from app.bot.menus import create_main_menu, pad_message_for_wide_buttons
+ from backend.modules.telegram.agent.session import get_session_manager
+ from backend.modules.telegram.bot.menus import create_main_menu, pad_message_for_wide_buttons
session_manager = get_session_manager()
session = await session_manager.get_or_create_session(user_id)
diff --git a/reports-app/telegram-bot/app/bot/formatters.py b/backend/modules/telegram/bot/formatters.py
similarity index 100%
rename from reports-app/telegram-bot/app/bot/formatters.py
rename to backend/modules/telegram/bot/formatters.py
diff --git a/reports-app/telegram-bot/app/bot/handlers.py b/backend/modules/telegram/bot/handlers.py
similarity index 91%
rename from reports-app/telegram-bot/app/bot/handlers.py
rename to backend/modules/telegram/bot/handlers.py
index a514f87..a391abc 100644
--- a/reports-app/telegram-bot/app/bot/handlers.py
+++ b/backend/modules/telegram/bot/handlers.py
@@ -13,15 +13,15 @@ from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
from telegram.ext import ContextTypes
from telegram.constants import ParseMode
-from app.auth.linking import (
+from backend.modules.telegram.auth.linking import (
link_telegram_account,
check_user_linked,
get_user_auth_data,
get_user_companies
)
-from app.agent.session import get_session_manager
-from app.db.operations import update_user_last_active
-from app.api.client import get_backend_client
+from backend.modules.telegram.agent.session import get_session_manager
+from backend.modules.telegram.db.operations import update_user_last_active
+from backend.modules.telegram.api.client import get_backend_client
logger = logging.getLogger(__name__)
@@ -107,7 +107,7 @@ async def start_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
# Get cache status
cache_enabled = None
try:
- from app.api.client import get_backend_client
+ from backend.modules.telegram.api.client import get_backend_client
client = get_backend_client()
async with client:
cache_stats = await client.get_cache_stats(jwt_token=jwt_token)
@@ -115,7 +115,7 @@ async def start_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
except Exception as e:
logger.warning(f"Could not get cache status in /start: {e}")
- from app.bot.menus import create_main_menu, pad_message_for_wide_buttons
+ from backend.modules.telegram.bot.menus import create_main_menu, pad_message_for_wide_buttons
keyboard = create_main_menu(company_name, company_cui, is_authenticated=True, cache_enabled=cache_enabled)
# EDIT message to show menu with company
@@ -169,7 +169,7 @@ async def start_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
company_cui = company.get('cui') if company else None
# Create main menu
- from app.bot.menus import create_main_menu, get_menu_message
+ from backend.modules.telegram.bot.menus import create_main_menu, get_menu_message
keyboard = create_main_menu(company_name, company_cui, is_authenticated=True)
menu_text = get_menu_message(company_name, company_cui)
@@ -181,7 +181,7 @@ async def start_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
else:
# User not linked - show main menu with Login button
- from app.bot.menus import create_main_menu, pad_message_for_wide_buttons
+ from backend.modules.telegram.bot.menus import create_main_menu, pad_message_for_wide_buttons
keyboard = create_main_menu(
company_name=None,
@@ -364,7 +364,7 @@ async def clearcache_command(update: Update, context: ContextTypes.DEFAULT_TYPE)
else:
# Get active company
session_manager = get_session_manager()
- from app.bot.helpers import get_active_company_or_prompt
+ from backend.modules.telegram.bot.helpers import get_active_company_or_prompt
company = await get_active_company_or_prompt(update, session_manager, telegram_user_id)
if not company:
@@ -636,7 +636,7 @@ async def dashboard_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
# Get active company
session_manager = get_session_manager()
- from app.bot.helpers import get_active_company_or_prompt
+ from backend.modules.telegram.bot.helpers import get_active_company_or_prompt
company = await get_active_company_or_prompt(update, session_manager, telegram_user_id)
if not company:
@@ -683,7 +683,7 @@ async def facturi_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
# Get active company
session_manager = get_session_manager()
- from app.bot.helpers import get_active_company_or_prompt
+ from backend.modules.telegram.bot.helpers import get_active_company_or_prompt
company = await get_active_company_or_prompt(update, session_manager, telegram_user_id)
if not company:
@@ -712,11 +712,11 @@ async def facturi_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
)
# Format response
- from app.bot.formatters import format_invoices_response
+ from backend.modules.telegram.bot.formatters import format_invoices_response
response = format_invoices_response(invoices, company['name'])
# FAZA 3: Add action buttons
- from app.bot.menus import create_action_buttons
+ from backend.modules.telegram.bot.menus import create_action_buttons
keyboard = create_action_buttons("facturi", show_export=True)
await update.message.reply_text(
@@ -752,7 +752,7 @@ async def trezorerie_command(update: Update, context: ContextTypes.DEFAULT_TYPE)
# Get active company
session_manager = get_session_manager()
- from app.bot.helpers import get_active_company_or_prompt
+ from backend.modules.telegram.bot.helpers import get_active_company_or_prompt
company = await get_active_company_or_prompt(update, session_manager, telegram_user_id)
if not company:
@@ -763,7 +763,7 @@ async def trezorerie_command(update: Update, context: ContextTypes.DEFAULT_TYPE)
jwt_token = auth_data['jwt_token']
# ✅ MODIFICARE: Folosim treasury_breakdown_split ca în Casa/Banca
- from app.bot.helpers import get_treasury_breakdown_split
+ from backend.modules.telegram.bot.helpers import get_treasury_breakdown_split
treasury_data = await get_treasury_breakdown_split(
company_id=company['id'],
jwt_token=jwt_token
@@ -790,12 +790,12 @@ async def trezorerie_command(update: Update, context: ContextTypes.DEFAULT_TYPE)
content += "Foloseste butoanele pentru detalii:"
# Apply company header formatting
- from app.bot.menus import format_response_with_company
+ from backend.modules.telegram.bot.menus import format_response_with_company
text = format_response_with_company(content, company['name'])
# Add performance footer
if response_time_ms > 0:
- from app.bot.formatters import add_performance_footer
+ from backend.modules.telegram.bot.formatters import add_performance_footer
text = add_performance_footer(text, cache_hit, response_time_ms, cache_source)
# Add buttons to view details
@@ -857,7 +857,7 @@ async def menu_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
# Get cache status for user
cache_enabled = None
try:
- from app.api.client import get_backend_client
+ from backend.modules.telegram.api.client import get_backend_client
client = get_backend_client()
async with client:
cache_stats = await client.get_cache_stats(jwt_token=auth_data['jwt_token'])
@@ -866,7 +866,7 @@ async def menu_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
logger.warning(f"Could not get cache status: {e}")
# Create main menu (user is authenticated if they passed the is_linked check)
- from app.bot.menus import create_main_menu, get_menu_message
+ from backend.modules.telegram.bot.menus import create_main_menu, get_menu_message
keyboard = create_main_menu(company_name, company_cui, is_authenticated=True, cache_enabled=cache_enabled)
menu_text = get_menu_message(company_name, company_cui)
@@ -906,7 +906,7 @@ async def trezorerie_casa_command(update: Update, context: ContextTypes.DEFAULT_
# Get active company
session_manager = get_session_manager()
- from app.bot.helpers import get_active_company_or_prompt
+ from backend.modules.telegram.bot.helpers import get_active_company_or_prompt
company = await get_active_company_or_prompt(update, session_manager, telegram_user_id)
if not company:
@@ -917,7 +917,7 @@ async def trezorerie_casa_command(update: Update, context: ContextTypes.DEFAULT_
jwt_token = auth_data['jwt_token']
# Get treasury breakdown split
- from app.bot.helpers import get_treasury_breakdown_split
+ from backend.modules.telegram.bot.helpers import get_treasury_breakdown_split
treasury_data = await get_treasury_breakdown_split(
company_id=company['id'],
jwt_token=jwt_token
@@ -928,8 +928,8 @@ async def trezorerie_casa_command(update: Update, context: ContextTypes.DEFAULT_
return
# Format response
- from app.bot.formatters import format_treasury_casa_response, add_performance_footer
- from app.bot.menus import create_action_buttons, format_response_with_company
+ from backend.modules.telegram.bot.formatters import format_treasury_casa_response, add_performance_footer
+ from backend.modules.telegram.bot.menus import create_action_buttons, format_response_with_company
content = format_treasury_casa_response(treasury_data['casa'])
response = format_response_with_company(content, company['name'])
@@ -979,7 +979,7 @@ async def trezorerie_banca_command(update: Update, context: ContextTypes.DEFAULT
# Get active company
session_manager = get_session_manager()
- from app.bot.helpers import get_active_company_or_prompt
+ from backend.modules.telegram.bot.helpers import get_active_company_or_prompt
company = await get_active_company_or_prompt(update, session_manager, telegram_user_id)
if not company:
@@ -990,7 +990,7 @@ async def trezorerie_banca_command(update: Update, context: ContextTypes.DEFAULT
jwt_token = auth_data['jwt_token']
# Get treasury breakdown split
- from app.bot.helpers import get_treasury_breakdown_split
+ from backend.modules.telegram.bot.helpers import get_treasury_breakdown_split
treasury_data = await get_treasury_breakdown_split(
company_id=company['id'],
jwt_token=jwt_token
@@ -1001,8 +1001,8 @@ async def trezorerie_banca_command(update: Update, context: ContextTypes.DEFAULT
return
# Format response
- from app.bot.formatters import format_treasury_banca_response, add_performance_footer
- from app.bot.menus import create_action_buttons, format_response_with_company
+ from backend.modules.telegram.bot.formatters import format_treasury_banca_response, add_performance_footer
+ from backend.modules.telegram.bot.menus import create_action_buttons, format_response_with_company
content = format_treasury_banca_response(treasury_data['banca'])
response = format_response_with_company(content, company['name'])
@@ -1053,7 +1053,7 @@ async def clienti_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
# Get active company
session_manager = get_session_manager()
- from app.bot.helpers import get_active_company_or_prompt
+ from backend.modules.telegram.bot.helpers import get_active_company_or_prompt
company = await get_active_company_or_prompt(update, session_manager, telegram_user_id)
if not company:
@@ -1064,7 +1064,7 @@ async def clienti_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
jwt_token = auth_data['jwt_token']
# Get clients with maturity data
- from app.bot.helpers import get_clients_with_maturity
+ from backend.modules.telegram.bot.helpers import get_clients_with_maturity
clients_data = await get_clients_with_maturity(
company_id=company['id'],
jwt_token=jwt_token
@@ -1080,8 +1080,8 @@ async def clienti_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
cache_source = clients_data.get('cache_source', None)
# Format response
- from app.bot.formatters import format_clients_balance_response, add_performance_footer
- from app.bot.menus import create_client_list_keyboard, format_response_with_company
+ from backend.modules.telegram.bot.formatters import format_clients_balance_response, add_performance_footer
+ from backend.modules.telegram.bot.menus import create_client_list_keyboard, format_response_with_company
content = format_clients_balance_response(
clients_data['clients'],
@@ -1132,7 +1132,7 @@ async def furnizori_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
# Get active company
session_manager = get_session_manager()
- from app.bot.helpers import get_active_company_or_prompt
+ from backend.modules.telegram.bot.helpers import get_active_company_or_prompt
company = await get_active_company_or_prompt(update, session_manager, telegram_user_id)
if not company:
@@ -1143,7 +1143,7 @@ async def furnizori_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
jwt_token = auth_data['jwt_token']
# Get suppliers with maturity data
- from app.bot.helpers import get_suppliers_with_maturity
+ from backend.modules.telegram.bot.helpers import get_suppliers_with_maturity
suppliers_data = await get_suppliers_with_maturity(
company_id=company['id'],
jwt_token=jwt_token
@@ -1159,8 +1159,8 @@ async def furnizori_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
cache_source = suppliers_data.get('cache_source', None)
# Format response
- from app.bot.formatters import format_suppliers_balance_response, add_performance_footer
- from app.bot.menus import create_supplier_list_keyboard, format_response_with_company
+ from backend.modules.telegram.bot.formatters import format_suppliers_balance_response, add_performance_footer
+ from backend.modules.telegram.bot.menus import create_supplier_list_keyboard, format_response_with_company
content = format_suppliers_balance_response(
suppliers_data['suppliers'],
@@ -1210,7 +1210,7 @@ async def evolutie_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
# Get active company
session_manager = get_session_manager()
- from app.bot.helpers import get_active_company_or_prompt
+ from backend.modules.telegram.bot.helpers import get_active_company_or_prompt
company = await get_active_company_or_prompt(update, session_manager, telegram_user_id)
if not company:
@@ -1221,7 +1221,7 @@ async def evolutie_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
jwt_token = auth_data['jwt_token']
# Get cash flow evolution data
- from app.bot.helpers import get_cashflow_evolution_data
+ from backend.modules.telegram.bot.helpers import get_cashflow_evolution_data
evolution_data = await get_cashflow_evolution_data(
company_id=company['id'],
jwt_token=jwt_token
@@ -1232,8 +1232,8 @@ async def evolutie_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
return
# Format response
- from app.bot.formatters import format_cashflow_evolution_response, add_performance_footer
- from app.bot.menus import create_action_buttons, format_response_with_company
+ from backend.modules.telegram.bot.formatters import format_cashflow_evolution_response, add_performance_footer
+ from backend.modules.telegram.bot.menus import create_action_buttons, format_response_with_company
content = format_cashflow_evolution_response(
evolution_data['performance'],
@@ -1340,7 +1340,7 @@ async def handle_text_message(update: Update, context: ContextTypes.DEFAULT_TYPE
company_name = company['name'] if company else None
company_cui = company.get('cui') if company else None
- from app.bot.menus import create_main_menu, pad_message_for_wide_buttons
+ from backend.modules.telegram.bot.menus import create_main_menu, pad_message_for_wide_buttons
keyboard = create_main_menu(company_name, company_cui, is_authenticated=True)
# Create menu text
@@ -1420,7 +1420,7 @@ async def handle_menu_callback(query, telegram_user_id: int, callback_data: str)
# If user is not authenticated and trying to access financial data, show auth required message
if auth_data is None and action != "select_company":
- from app.bot.menus import create_main_menu, pad_message_for_wide_buttons
+ from backend.modules.telegram.bot.menus import create_main_menu, pad_message_for_wide_buttons
keyboard = create_main_menu(company_name=None, company_cui=None, is_authenticated=False)
menu_text = pad_message_for_wide_buttons(
"⚠️ **Autentificare necesară**\n\n"
@@ -1461,7 +1461,7 @@ async def handle_menu_callback(query, telegram_user_id: int, callback_data: str)
)
return
- from app.bot.helpers import create_company_selection_keyboard_paginated
+ from backend.modules.telegram.bot.helpers import create_company_selection_keyboard_paginated
keyboard = create_company_selection_keyboard_paginated(companies, page=0)
await query.edit_message_text(
@@ -1485,11 +1485,11 @@ async def handle_menu_callback(query, telegram_user_id: int, callback_data: str)
elif action == "casa":
# Trezorerie casa
- from app.bot.helpers import get_treasury_breakdown_split
+ from backend.modules.telegram.bot.helpers import get_treasury_breakdown_split
treasury_data = await get_treasury_breakdown_split(company['id'], jwt_token)
- from app.bot.formatters import format_treasury_casa_response, add_performance_footer
- from app.bot.menus import create_action_buttons, format_response_with_company
+ from backend.modules.telegram.bot.formatters import format_treasury_casa_response, add_performance_footer
+ from backend.modules.telegram.bot.menus import create_action_buttons, format_response_with_company
content = format_treasury_casa_response(treasury_data['casa'])
response = format_response_with_company(content, company['name'])
@@ -1516,11 +1516,11 @@ async def handle_menu_callback(query, telegram_user_id: int, callback_data: str)
elif action == "banca":
# Trezorerie banca
- from app.bot.helpers import get_treasury_breakdown_split
+ from backend.modules.telegram.bot.helpers import get_treasury_breakdown_split
treasury_data = await get_treasury_breakdown_split(company['id'], jwt_token)
- from app.bot.formatters import format_treasury_banca_response, add_performance_footer
- from app.bot.menus import create_action_buttons, format_response_with_company
+ from backend.modules.telegram.bot.formatters import format_treasury_banca_response, add_performance_footer
+ from backend.modules.telegram.bot.menus import create_action_buttons, format_response_with_company
content = format_treasury_banca_response(treasury_data['banca'])
response = format_response_with_company(content, company['name'])
@@ -1547,11 +1547,11 @@ async def handle_menu_callback(query, telegram_user_id: int, callback_data: str)
elif action == "clienti":
# Sold clienți + listă cu paginare
- from app.bot.helpers import get_clients_with_maturity
+ from backend.modules.telegram.bot.helpers import get_clients_with_maturity
clients_data = await get_clients_with_maturity(company['id'], jwt_token)
- from app.bot.formatters import format_clients_balance_response, add_performance_footer
- from app.bot.menus import create_client_list_keyboard, format_response_with_company
+ from backend.modules.telegram.bot.formatters import format_clients_balance_response, add_performance_footer
+ from backend.modules.telegram.bot.menus import create_client_list_keyboard, format_response_with_company
content = format_clients_balance_response(
clients_data['clients'],
@@ -1576,11 +1576,11 @@ async def handle_menu_callback(query, telegram_user_id: int, callback_data: str)
elif action == "furnizori":
# Sold furnizori + listă cu paginare
- from app.bot.helpers import get_suppliers_with_maturity
+ from backend.modules.telegram.bot.helpers import get_suppliers_with_maturity
suppliers_data = await get_suppliers_with_maturity(company['id'], jwt_token)
- from app.bot.formatters import format_suppliers_balance_response, add_performance_footer
- from app.bot.menus import create_supplier_list_keyboard, format_response_with_company
+ from backend.modules.telegram.bot.formatters import format_suppliers_balance_response, add_performance_footer
+ from backend.modules.telegram.bot.menus import create_supplier_list_keyboard, format_response_with_company
content = format_suppliers_balance_response(
suppliers_data['suppliers'],
@@ -1605,11 +1605,11 @@ async def handle_menu_callback(query, telegram_user_id: int, callback_data: str)
elif action == "evolutie":
# Evoluție cash flow
- from app.bot.helpers import get_cashflow_evolution_data
+ from backend.modules.telegram.bot.helpers import get_cashflow_evolution_data
evolution_data = await get_cashflow_evolution_data(company['id'], jwt_token)
- from app.bot.formatters import format_cashflow_evolution_response, add_performance_footer
- from app.bot.menus import create_action_buttons, format_response_with_company
+ from backend.modules.telegram.bot.formatters import format_cashflow_evolution_response, add_performance_footer
+ from backend.modules.telegram.bot.menus import create_action_buttons, format_response_with_company
content = format_cashflow_evolution_response(
evolution_data['performance'],
@@ -1739,7 +1739,7 @@ async def handle_action_callback(query, telegram_user_id: int, callback_data: st
cache_enabled = None
if is_authenticated:
try:
- from app.api.client import get_backend_client
+ from backend.modules.telegram.api.client import get_backend_client
client = get_backend_client()
async with client:
cache_stats = await client.get_cache_stats(jwt_token=auth_data['jwt_token'])
@@ -1747,7 +1747,7 @@ async def handle_action_callback(query, telegram_user_id: int, callback_data: st
except Exception as e:
logger.warning(f"Could not get cache status: {e}")
- from app.bot.menus import create_main_menu, get_menu_message
+ from backend.modules.telegram.bot.menus import create_main_menu, get_menu_message
company_name = company['name'] if company else None
company_cui = company.get('cui') if company else None
keyboard = create_main_menu(company_name, company_cui, is_authenticated, cache_enabled)
@@ -1784,7 +1784,7 @@ async def handle_action_callback(query, telegram_user_id: int, callback_data: st
elif action_type == "help":
# Show help message above menu (edit current message)
- from app.bot.menus import pad_message_for_wide_buttons, create_main_menu
+ from backend.modules.telegram.bot.menus import pad_message_for_wide_buttons, create_main_menu
# Get auth status and company info
session_manager = get_session_manager()
@@ -1820,7 +1820,7 @@ async def handle_action_callback(query, telegram_user_id: int, callback_data: st
elif action_type == "logout":
# Show logout confirmation
- from app.bot.menus import pad_message_for_wide_buttons
+ from backend.modules.telegram.bot.menus import pad_message_for_wide_buttons
confirmation_text = pad_message_for_wide_buttons(
"**Confirmare Deconectare**\n\n"
"Ești sigur că vrei să deconectezi contul?\n\n"
@@ -1873,7 +1873,7 @@ async def handle_details_callback(query, telegram_user_id: int, callback_data: s
if detail_type == "client":
# Get client details (from clients list)
# entity_name might be truncated to 40 chars, so search by startswith
- from app.bot.helpers import get_clients_with_maturity
+ from backend.modules.telegram.bot.helpers import get_clients_with_maturity
clients_data = await get_clients_with_maturity(company['id'], jwt_token)
# Find client by full or partial name match
@@ -1887,12 +1887,12 @@ async def handle_details_callback(query, telegram_user_id: int, callback_data: s
full_client_name = client['name']
# Get client invoices by FULL name
- from app.bot.helpers import get_client_invoices
+ from backend.modules.telegram.bot.helpers import get_client_invoices
invoices = await get_client_invoices(company['id'], full_client_name, jwt_token)
# Format response
- from app.bot.formatters import format_client_detail_response
- from app.bot.menus import create_action_buttons, format_response_with_company
+ from backend.modules.telegram.bot.formatters import format_client_detail_response
+ from backend.modules.telegram.bot.menus import create_action_buttons, format_response_with_company
content = format_client_detail_response(client, invoices)
response = format_response_with_company(content, company['name'])
@@ -1910,7 +1910,7 @@ async def handle_details_callback(query, telegram_user_id: int, callback_data: s
elif detail_type == "supplier":
# Get supplier details (from suppliers list)
# entity_name might be truncated to 40 chars, so search by startswith
- from app.bot.helpers import get_suppliers_with_maturity
+ from backend.modules.telegram.bot.helpers import get_suppliers_with_maturity
suppliers_data = await get_suppliers_with_maturity(company['id'], jwt_token)
# Find supplier by full or partial name match
@@ -1924,12 +1924,12 @@ async def handle_details_callback(query, telegram_user_id: int, callback_data: s
full_supplier_name = supplier['name']
# Get supplier invoices by FULL name
- from app.bot.helpers import get_supplier_invoices
+ from backend.modules.telegram.bot.helpers import get_supplier_invoices
invoices = await get_supplier_invoices(company['id'], full_supplier_name, jwt_token)
# Format response
- from app.bot.formatters import format_supplier_detail_response
- from app.bot.menus import create_action_buttons, format_response_with_company
+ from backend.modules.telegram.bot.formatters import format_supplier_detail_response
+ from backend.modules.telegram.bot.menus import create_action_buttons, format_response_with_company
content = format_supplier_detail_response(supplier, invoices)
response = format_response_with_company(content, company['name'])
@@ -2058,7 +2058,7 @@ async def button_callback(update: Update, context: ContextTypes.DEFAULT_TYPE):
companies = await client.get_user_companies(jwt_token=jwt_token)
# Create paginated keyboard for requested page
- from app.bot.helpers import create_company_selection_keyboard_paginated
+ from backend.modules.telegram.bot.helpers import create_company_selection_keyboard_paginated
keyboard = create_company_selection_keyboard_paginated(companies, page=page)
await query.edit_message_text(
@@ -2103,7 +2103,7 @@ async def button_callback(update: Update, context: ContextTypes.DEFAULT_TYPE):
await session_manager.save_session(telegram_user_id)
# Show main menu directly (no confirmation message)
- from app.bot.menus import create_main_menu, get_menu_message
+ from backend.modules.telegram.bot.menus import create_main_menu, get_menu_message
keyboard = create_main_menu(
company_name=company_name,
company_cui=company_cui
@@ -2124,7 +2124,7 @@ async def button_callback(update: Update, context: ContextTypes.DEFAULT_TYPE):
elif callback_data == "logout_confirm":
# Logout user (same as unlink but shows menu after)
- from app.auth.linking import unlink_user
+ from backend.modules.telegram.auth.linking import unlink_user
success = await unlink_user(telegram_user_id)
@@ -2134,7 +2134,7 @@ async def button_callback(update: Update, context: ContextTypes.DEFAULT_TYPE):
await session_manager.delete_session(telegram_user_id)
# Show login menu (non-authenticated)
- from app.bot.menus import create_main_menu, get_menu_message, pad_message_for_wide_buttons
+ from backend.modules.telegram.bot.menus import create_main_menu, get_menu_message, pad_message_for_wide_buttons
keyboard = create_main_menu(company_name=None, company_cui=None, is_authenticated=False)
menu_text = pad_message_for_wide_buttons(
"**Deconectat cu succes**\n\n"
@@ -2163,7 +2163,7 @@ async def button_callback(update: Update, context: ContextTypes.DEFAULT_TYPE):
auth_data = await get_user_auth_data(telegram_user_id)
is_authenticated = auth_data is not None
- from app.bot.menus import create_main_menu, get_menu_message
+ from backend.modules.telegram.bot.menus import create_main_menu, get_menu_message
company_name = company['name'] if company else None
company_cui = company.get('cui') if company else None
keyboard = create_main_menu(company_name, company_cui, is_authenticated)
@@ -2179,7 +2179,7 @@ async def button_callback(update: Update, context: ContextTypes.DEFAULT_TYPE):
elif callback_data == "unlink_confirm":
# Unlink user
- from app.auth.linking import unlink_user
+ from backend.modules.telegram.auth.linking import unlink_user
success = await unlink_user(telegram_user_id)
@@ -2383,11 +2383,11 @@ async def button_callback(update: Update, context: ContextTypes.DEFAULT_TYPE):
company = session.get_active_company()
# Get clients with maturity
- from app.bot.helpers import get_clients_with_maturity
+ from backend.modules.telegram.bot.helpers import get_clients_with_maturity
clients_data = await get_clients_with_maturity(company['id'], jwt_token)
- from app.bot.formatters import format_clients_balance_response
- from app.bot.menus import create_client_list_keyboard, format_response_with_company
+ from backend.modules.telegram.bot.formatters import format_clients_balance_response
+ from backend.modules.telegram.bot.menus import create_client_list_keyboard, format_response_with_company
content = format_clients_balance_response(
clients_data['clients'],
@@ -2415,11 +2415,11 @@ async def button_callback(update: Update, context: ContextTypes.DEFAULT_TYPE):
company = session.get_active_company()
# Get suppliers with maturity
- from app.bot.helpers import get_suppliers_with_maturity
+ from backend.modules.telegram.bot.helpers import get_suppliers_with_maturity
suppliers_data = await get_suppliers_with_maturity(company['id'], jwt_token)
- from app.bot.formatters import format_suppliers_balance_response
- from app.bot.menus import create_supplier_list_keyboard, format_response_with_company
+ from backend.modules.telegram.bot.formatters import format_suppliers_balance_response
+ from backend.modules.telegram.bot.menus import create_supplier_list_keyboard, format_response_with_company
content = format_suppliers_balance_response(
suppliers_data['suppliers'],
@@ -2452,27 +2452,27 @@ async def button_callback(update: Update, context: ContextTypes.DEFAULT_TYPE):
# Get invoices for this partner
if partner_type == "CLIENTI":
- from app.bot.helpers import get_client_invoices, get_clients_with_maturity
+ from backend.modules.telegram.bot.helpers import get_client_invoices, get_clients_with_maturity
invoices = await get_client_invoices(company['id'], partner_name, jwt_token)
# Get client details
clients_data = await get_clients_with_maturity(company['id'], jwt_token)
partner = next((c for c in clients_data['clients'] if c['name'] == partner_name), None)
- from app.bot.formatters import format_client_detail_response
+ from backend.modules.telegram.bot.formatters import format_client_detail_response
content = format_client_detail_response(partner, invoices)
else:
- from app.bot.helpers import get_supplier_invoices, get_suppliers_with_maturity
+ from backend.modules.telegram.bot.helpers import get_supplier_invoices, get_suppliers_with_maturity
invoices = await get_supplier_invoices(company['id'], partner_name, jwt_token)
# Get supplier details
suppliers_data = await get_suppliers_with_maturity(company['id'], jwt_token)
partner = next((s for s in suppliers_data['suppliers'] if s['name'] == partner_name), None)
- from app.bot.formatters import format_supplier_detail_response
+ from backend.modules.telegram.bot.formatters import format_supplier_detail_response
content = format_supplier_detail_response(partner, invoices)
- from app.bot.menus import create_invoice_list_keyboard, format_response_with_company
+ from backend.modules.telegram.bot.menus import create_invoice_list_keyboard, format_response_with_company
response = format_response_with_company(content, company['name'])
keyboard = create_invoice_list_keyboard(invoices, partner_type, partner_name, page=page)
@@ -2555,7 +2555,7 @@ async def _handle_expired_auth(query_or_update, telegram_user_id: int, auth_data
await query_or_update.answer("Sesiunea a expirat. Te rog să te reconectezi.", show_alert=True)
# Transform the current message (menu) to Login menu
- from app.bot.menus import create_main_menu, pad_message_for_wide_buttons
+ from backend.modules.telegram.bot.menus import create_main_menu, pad_message_for_wide_buttons
keyboard = create_main_menu(company_name=None, company_cui=None, is_authenticated=False)
menu_text = pad_message_for_wide_buttons(
"⚠️ **Sesiunea a expirat**\n\n"
@@ -2615,8 +2615,8 @@ async def _handle_sold_view(
await query_or_update.message.reply_text(error_msg)
return
- from app.bot.formatters import format_dashboard_response, add_performance_footer
- from app.bot.menus import create_action_buttons, format_response_with_company
+ from backend.modules.telegram.bot.formatters import format_dashboard_response, add_performance_footer
+ from backend.modules.telegram.bot.menus import create_action_buttons, format_response_with_company
# Extract cache metadata
cache_hit = data.get('cache_hit', False)
@@ -2684,7 +2684,7 @@ async def _handle_selectcompany_view(
# Apply search filter if provided
if search_term:
- from app.bot.helpers import search_companies_by_name
+ from backend.modules.telegram.bot.helpers import search_companies_by_name
companies = await search_companies_by_name(search_term, jwt_token)
if not companies:
@@ -2710,7 +2710,7 @@ async def _handle_selectcompany_view(
)
return
- from app.bot.helpers import create_company_selection_keyboard_paginated
+ from backend.modules.telegram.bot.helpers import create_company_selection_keyboard_paginated
keyboard = create_company_selection_keyboard_paginated(companies, page=page)
message = f"**Selecteaza Compania**\n\n"
diff --git a/reports-app/telegram-bot/app/bot/helpers.py b/backend/modules/telegram/bot/helpers.py
similarity index 98%
rename from reports-app/telegram-bot/app/bot/helpers.py
rename to backend/modules/telegram/bot/helpers.py
index 3137ccc..e52196a 100644
--- a/reports-app/telegram-bot/app/bot/helpers.py
+++ b/backend/modules/telegram/bot/helpers.py
@@ -7,9 +7,9 @@ import logging
from typing import Optional, Dict, List, Any
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
-from app.api.client import get_backend_client
-from app.agent.session import SessionManager
-from app.bot.menus import pad_message_for_wide_buttons
+from backend.modules.telegram.api.client import get_backend_client
+from backend.modules.telegram.agent.session import SessionManager
+from backend.modules.telegram.bot.menus import pad_message_for_wide_buttons
logger = logging.getLogger(__name__)
@@ -44,7 +44,7 @@ async def get_active_company_or_prompt(
if not company:
# Get auth data and companies
- from app.auth.linking import get_user_auth_data
+ from backend.modules.telegram.auth.linking import get_user_auth_data
auth_data = await get_user_auth_data(telegram_user_id)
jwt_token = auth_data['jwt_token']
diff --git a/reports-app/telegram-bot/app/bot/keyboards.py b/backend/modules/telegram/bot/keyboards.py
similarity index 100%
rename from reports-app/telegram-bot/app/bot/keyboards.py
rename to backend/modules/telegram/bot/keyboards.py
diff --git a/reports-app/telegram-bot/app/bot/menus.py b/backend/modules/telegram/bot/menus.py
similarity index 100%
rename from reports-app/telegram-bot/app/bot/menus.py
rename to backend/modules/telegram/bot/menus.py
diff --git a/reports-app/telegram-bot/app/main.py b/backend/modules/telegram/bot_main.py
similarity index 97%
rename from reports-app/telegram-bot/app/main.py
rename to backend/modules/telegram/bot_main.py
index 3a36630..17dd327 100644
--- a/reports-app/telegram-bot/app/main.py
+++ b/backend/modules/telegram/bot_main.py
@@ -30,7 +30,7 @@ from telegram.ext import (
)
# Import database initialization
-from app.db import (
+from backend.modules.telegram.db import (
init_database,
cleanup_expired_codes,
cleanup_expired_sessions,
@@ -38,7 +38,7 @@ from app.db import (
)
# Import bot handlers
-from app.bot.handlers import (
+from backend.modules.telegram.bot.handlers import (
start_command,
help_command,
clear_command,
@@ -67,10 +67,10 @@ from app.bot.handlers import (
)
# Import email authentication handler
-from app.bot.email_handlers import email_login_handler
+from backend.modules.telegram.bot.email_handlers import email_login_handler
# Import internal API
-from app.internal_api import internal_api
+from backend.modules.telegram.internal_api import internal_api
# Configure logging
logging.basicConfig(
diff --git a/reports-app/telegram-bot/app/db/__init__.py b/backend/modules/telegram/db/__init__.py
similarity index 100%
rename from reports-app/telegram-bot/app/db/__init__.py
rename to backend/modules/telegram/db/__init__.py
diff --git a/reports-app/telegram-bot/app/db/database.py b/backend/modules/telegram/db/database.py
similarity index 100%
rename from reports-app/telegram-bot/app/db/database.py
rename to backend/modules/telegram/db/database.py
diff --git a/reports-app/telegram-bot/app/db/operations.py b/backend/modules/telegram/db/operations.py
similarity index 100%
rename from reports-app/telegram-bot/app/db/operations.py
rename to backend/modules/telegram/db/operations.py
diff --git a/backend/modules/telegram/routers/__init__.py b/backend/modules/telegram/routers/__init__.py
new file mode 100644
index 0000000..153af18
--- /dev/null
+++ b/backend/modules/telegram/routers/__init__.py
@@ -0,0 +1,32 @@
+"""Telegram module router factory."""
+
+from fastapi import APIRouter
+
+
+def create_telegram_router() -> APIRouter:
+ """
+ Create and configure Telegram module router.
+
+ Includes all Telegram bot internal API endpoints:
+ - /auth/verify-user - Verify Telegram user authentication
+ - /auth/generate-code - Generate auth code for linking
+ - /auth/verify-code - Verify auth code
+ - /stats - Bot database statistics
+
+ Returns:
+ APIRouter: Configured router for Telegram module
+ """
+ router = APIRouter()
+
+ # Import routers here to avoid circular imports
+ from .auth_codes import router as auth_codes_router
+ from .internal_api import internal_api as internal_api_router
+
+ # Include all sub-routers (no prefix - already prefixed in main.py with /api/telegram)
+ # Auth codes router provides /auth/* endpoints
+ router.include_router(auth_codes_router, tags=["telegram-auth"])
+
+ # Internal API router provides additional endpoints like /stats
+ router.include_router(internal_api_router, tags=["telegram-internal"])
+
+ return router
diff --git a/reports-app/backend/app/routers/telegram.py b/backend/modules/telegram/routers/auth_codes.py
similarity index 99%
rename from reports-app/backend/app/routers/telegram.py
rename to backend/modules/telegram/routers/auth_codes.py
index e00ef06..b60ab20 100644
--- a/reports-app/backend/app/routers/telegram.py
+++ b/backend/modules/telegram/routers/auth_codes.py
@@ -4,7 +4,7 @@ Furnizează endpoint-uri pentru autentificare, linking și export rapoarte pentr
"""
from fastapi import APIRouter, Depends, HTTPException, Request
from typing import List, Optional, Dict, Any
-import sys
+# import sys # Removed - no longer needed
import os
import secrets
import string
@@ -12,12 +12,11 @@ import httpx
from datetime import datetime, timedelta
from pydantic import BaseModel, Field
-sys.path.append(os.path.join(os.path.dirname(__file__), '../../../../shared'))
-from auth.dependencies import get_current_user
-from auth.models import CurrentUser
-from auth.jwt_handler import jwt_handler
-from database.oracle_pool import oracle_pool
+from shared.auth.dependencies import get_current_user
+from shared.auth.models import CurrentUser
+from shared.auth.jwt_handler import jwt_handler
+from shared.database.oracle_pool import oracle_pool
# Telegram bot internal API URL (running on same server)
TELEGRAM_BOT_INTERNAL_API = os.getenv("TELEGRAM_BOT_INTERNAL_API", "http://localhost:8002")
diff --git a/reports-app/telegram-bot/app/internal_api.py b/backend/modules/telegram/routers/internal_api.py
similarity index 87%
rename from reports-app/telegram-bot/app/internal_api.py
rename to backend/modules/telegram/routers/internal_api.py
index a49c7be..c67c5c7 100644
--- a/reports-app/telegram-bot/app/internal_api.py
+++ b/backend/modules/telegram/routers/internal_api.py
@@ -14,23 +14,17 @@ import os
from datetime import datetime
from typing import Optional
-from fastapi import FastAPI, HTTPException, status
+from fastapi import APIRouter, HTTPException, status
from fastapi.responses import JSONResponse
from pydantic import BaseModel, Field
-from app.db.operations import create_auth_code, get_auth_code
-from app.db.database import get_database_stats
+from backend.modules.telegram.db.operations import create_auth_code, get_auth_code
+from backend.modules.telegram.db.database import get_database_stats
logger = logging.getLogger(__name__)
-# Initialize FastAPI app
-internal_api = FastAPI(
- title="ROA2WEB Telegram Bot - Internal API",
- description="Internal API for backend communication (auth code management)",
- version="1.0.0",
- docs_url="/internal/docs" if os.getenv("ENABLE_DOCS", "false") == "true" else None,
- redoc_url=None
-)
+# Initialize APIRouter (converted from FastAPI app for unified backend)
+internal_api = APIRouter()
# ============================================================================
@@ -333,43 +327,27 @@ async def get_stats():
# EXCEPTION HANDLERS
# ============================================================================
-@internal_api.exception_handler(Exception)
-async def global_exception_handler(request, exc):
- """
- Global exception handler for uncaught exceptions.
- """
- logger.error(f"Unhandled exception: {exc}", exc_info=True)
-
- return JSONResponse(
- status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
- content={
- "success": False,
- "error": "Internal server error",
- "detail": str(exc) if os.getenv("DEBUG", "false") == "true" else "An error occurred"
- }
- )
-
-
# ============================================================================
-# STARTUP/SHUTDOWN EVENTS
+# NOTE: Exception handlers and startup/shutdown events removed
+# These are FastAPI-specific and don't work with APIRouter
+# The unified backend (main.py) handles these at the app level
# ============================================================================
-@internal_api.on_event("startup")
-async def startup_event():
- """
- Startup event handler.
- """
- logger.info("Internal API starting up...")
- logger.info(f"Internal API ready on port {os.getenv('INTERNAL_API_PORT', '8002')}")
+# @internal_api.exception_handler(Exception) - Not supported by APIRouter
+# async def global_exception_handler(request, exc):
+# """Global exception handler - moved to main.py"""
+# pass
+
+# @internal_api.on_event("startup") - Not supported by APIRouter
+# async def startup_event():
+# """Startup event - handled by main.py lifespan"""
+# pass
+
+# @internal_api.on_event("shutdown") - Not supported by APIRouter
+# async def shutdown_event():
+# """Shutdown event - handled by main.py lifespan"""
+# pass
-@internal_api.on_event("shutdown")
-async def shutdown_event():
- """
- Shutdown event handler.
- """
- logger.info("Internal API shutting down...")
-
-
-# Export the FastAPI app
+# Export the APIRouter
__all__ = ['internal_api']
diff --git a/backend/modules/telegram/utils/__init__.py b/backend/modules/telegram/utils/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/reports-app/telegram-bot/app/utils/email_service.py b/backend/modules/telegram/utils/email_service.py
similarity index 100%
rename from reports-app/telegram-bot/app/utils/email_service.py
rename to backend/modules/telegram/utils/email_service.py
diff --git a/backend/requirements.txt b/backend/requirements.txt
new file mode 100644
index 0000000..423a58d
--- /dev/null
+++ b/backend/requirements.txt
@@ -0,0 +1,93 @@
+# ============================================================================
+# ROA2WEB Unified Backend - Dependencies
+# ============================================================================
+# Merged from reports-app, data-entry-app, and telegram-bot
+# All three modules now run in a single backend process
+
+# ============================================================================
+# FASTAPI CORE (Common to all modules)
+# ============================================================================
+fastapi>=0.109.0
+uvicorn[standard]>=0.27.0
+python-multipart>=0.0.6
+pydantic>=2.5.3
+pydantic-settings>=2.1.0
+email-validator>=2.1.0
+
+# ============================================================================
+# AUTHENTICATION (Shared across all modules)
+# ============================================================================
+PyJWT>=2.8.0
+python-jose[cryptography]>=3.3.0
+
+# ============================================================================
+# DATABASE - ORACLE (Shared: Reports + Data Entry nomenclatures + Auth)
+# ============================================================================
+oracledb>=2.0.1
+
+# ============================================================================
+# DATABASE - SQLITE (Data Entry + Telegram + Reports Cache)
+# ============================================================================
+aiosqlite>=0.19.0
+
+# ============================================================================
+# DATABASE - SQLMODEL + ALEMBIC (Data Entry only)
+# ============================================================================
+sqlmodel>=0.0.14
+sqlalchemy[asyncio]>=2.0.25
+alembic>=1.13.1
+
+# ============================================================================
+# HTTP CLIENT (Shared)
+# ============================================================================
+httpx>=0.27.0
+
+# ============================================================================
+# UTILITIES (Shared)
+# ============================================================================
+python-dotenv>=1.0.0
+python-dateutil>=2.8.2
+
+# ============================================================================
+# FILE HANDLING (Data Entry)
+# ============================================================================
+aiofiles>=23.2.1
+Pillow>=10.2.0
+
+# ============================================================================
+# REPORTS MODULE - Exports (Excel, PDF)
+# ============================================================================
+openpyxl>=3.1.0
+fpdf2>=2.7.0
+
+# ============================================================================
+# DATA ENTRY MODULE - OCR Dependencies
+# ============================================================================
+# PaddleOCR for receipt text extraction
+paddleocr>=2.7.0
+paddlepaddle>=2.5.0
+opencv-python>=4.8.0
+pytesseract>=0.3.10
+pdf2image>=1.16.0
+numpy>=1.24.0
+
+# ============================================================================
+# TELEGRAM MODULE - Bot SDK
+# ============================================================================
+python-telegram-bot>=20.7
+
+# ============================================================================
+# TELEGRAM MODULE - Email (SMTP for 2FA)
+# ============================================================================
+aiosmtplib>=3.0.0
+
+# ============================================================================
+# MONITORING (Optional - Telegram module)
+# ============================================================================
+# sentry-sdk>=1.40.0 # Uncomment if needed
+
+# ============================================================================
+# TESTING
+# ============================================================================
+pytest>=8.0.0
+pytest-asyncio>=0.23.3
diff --git a/bot.sh b/bot.sh
deleted file mode 100644
index 8acb10c..0000000
--- a/bot.sh
+++ /dev/null
@@ -1,147 +0,0 @@
-#!/bin/bash
-# Telegram Bot Service Control Script for ROA2WEB Unified App
-# Manages the Telegram Bot on port 8002
-
-# Script directory
-SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
-ROOT_DIR="$SCRIPT_DIR"
-
-# Source helper functions
-source "$SCRIPT_DIR/scripts/service-helpers.sh"
-
-# Service configuration
-SERVICE_NAME="Telegram Bot"
-PORT=8002
-LOG_FILE="/tmp/telegram-bot.log"
-BOT_DIR="$ROOT_DIR/reports-app/telegram-bot"
-VENV_DIR="$BOT_DIR/venv"
-ENV_FILE="$BOT_DIR/.env"
-
-# Function to start bot
-start_bot() {
- print_header "Starting $SERVICE_NAME"
-
- # Check if port is already in use
- if ! check_port_available $PORT "$SERVICE_NAME"; then
- print_warning "$SERVICE_NAME may already be running"
- print_info "Use './bot.sh status' to check or './bot.sh stop' to stop it"
- return 1
- fi
-
- # Check bot directory
- if [ ! -d "$BOT_DIR" ]; then
- print_error "Bot directory not found: $BOT_DIR"
- return 1
- fi
-
- # Check virtual environment
- if [ ! -d "$VENV_DIR" ]; then
- print_error "Virtual environment not found: $VENV_DIR"
- print_info "Please run setup first: cd reports-app/telegram-bot && python3 -m venv venv && source venv/bin/activate && pip install -r requirements.txt"
- return 1
- fi
-
- # Check .env file
- if [ ! -f "$ENV_FILE" ]; then
- print_warning ".env file not found: $ENV_FILE"
- print_info "Bot may not start without TELEGRAM_BOT_TOKEN"
- fi
-
- # Start bot
- print_info "Starting Telegram bot..."
- cd "$BOT_DIR"
-
- # Activate venv and start bot in background
- (
- source venv/bin/activate
- nohup python -m app.main > "$LOG_FILE" 2>&1 &
- echo $! > /tmp/telegram-bot.pid
- )
-
- local pid=$(cat /tmp/telegram-bot.pid 2>/dev/null)
- print_info "Started with PID: $pid"
- print_info "Log file: $LOG_FILE"
-
- # Wait for port to be ready
- if wait_for_port $PORT "$SERVICE_NAME" 5; then
- print_success "$SERVICE_NAME started successfully!"
- echo ""
- print_info "🤖 Bot is running and listening for commands"
- print_info "📄 View logs: tail -f $LOG_FILE"
- return 0
- else
- print_error "Failed to start $SERVICE_NAME (timeout waiting for port $PORT)"
- print_info "Check logs: tail -20 $LOG_FILE"
- rm -f /tmp/telegram-bot.pid
- return 1
- fi
-}
-
-# Function to stop bot
-stop_bot() {
- print_header "Stopping $SERVICE_NAME"
-
- kill_port $PORT "$SERVICE_NAME"
-
- # Clean up PID file
- if [ -f "/tmp/telegram-bot.pid" ]; then
- rm /tmp/telegram-bot.pid
- fi
-
- return 0
-}
-
-# Function to show bot status
-status_bot() {
- print_header "$SERVICE_NAME Status"
-
- if check_service_status $PORT "$SERVICE_NAME"; then
- echo ""
- print_info "Service is running"
-
- # Show recent logs
- if [ -f "$LOG_FILE" ]; then
- echo ""
- tail_logs "$LOG_FILE" 10
- fi
- return 0
- else
- echo ""
- print_warning "Service is not running"
-
- # Show last logs if available
- if [ -f "$LOG_FILE" ]; then
- echo ""
- print_info "Last logs before shutdown:"
- tail_logs "$LOG_FILE" 10
- fi
- return 1
- fi
-}
-
-# Main script logic
-case "${1:-}" in
- start)
- start_bot
- ;;
- stop)
- stop_bot
- ;;
- status)
- status_bot
- ;;
- *)
- echo "Usage: $0 {start|stop|status}"
- echo ""
- echo "Commands:"
- echo " start - Start the Telegram bot"
- echo " stop - Stop the Telegram bot"
- echo " status - Show bot status"
- echo ""
- echo "Examples:"
- echo " ./bot.sh start # Start bot"
- echo " ./bot.sh status # Check if running"
- echo " ./bot.sh stop # Stop bot"
- exit 1
- ;;
-esac
diff --git a/data-entry-app/CLAUDE.md b/data-entry-app/CLAUDE.md
deleted file mode 100644
index 336a3ae..0000000
--- a/data-entry-app/CLAUDE.md
+++ /dev/null
@@ -1,244 +0,0 @@
-# CLAUDE.md - Data Entry App
-
-## IMPORTANT - Reguli pentru Claude
-
-- **Pentru teste folosește DOAR `./start-data-entry-test.sh`** (server TEST 10.0.20.121)
-- **NU folosi `./start-data-entry-dev.sh`** decât la instrucțiuni explicite de la utilizator (server PRODUCȚIE 10.0.20.36)
-
-## Scop
-
-Aplicatie pentru introducere date in ERP (bonuri fiscale, chitante) cu workflow de aprobare.
-
-## Documentatie de Referinta
-
-- **Cerinte**: `docs/data-entry/REQUIREMENTS.md`
-- **Arhitectura**: `docs/data-entry/ARCHITECTURE.md`
-- **Quick Start**: `README.md`
-
-## Decizii Tehnice
-
-- **ORM**: SQLModel (Pydantic + SQLAlchemy)
-- **Migrari**: Alembic
-- **Database**: SQLite (Faza 1) → Oracle (Faza 2)
-- **Frontend**: Vue.js 3 + PrimeVue (consistent cu reports-app)
-
-## Workflow Bonuri
-
-```
-1. DRAFT → utilizator completeaza + upload poza
-2. PENDING_REVIEW → sistem genereaza note contabile
-3. APPROVED/REJECTED → contabil aproba sau respinge
-4. SYNCED → (Faza 2) date in Oracle
-```
-
-## Structura Directoare
-
-```
-data-entry-app/
-├── backend/ # FastAPI API (port 8003)
-│ ├── app/
-│ │ ├── db/ # SQLModel models + CRUD
-│ │ ├── schemas/ # Pydantic schemas
-│ │ ├── services/ # Business logic
-│ │ └── routers/ # API endpoints
-│ ├── migrations/ # Alembic migrations
-│ └── data/ # SQLite DB + uploads
-├── frontend/ # Vue.js UI (port 3010)
-│ └── src/
-│ ├── views/ # Page components
-│ ├── components/ # Reusable components
-│ └── stores/ # Pinia stores
-└── docs/ # Documentatie specifica
-```
-
-## Componente Partajate
-
-### Backend
-- `shared/database/oracle_pool.py` - Conexiune Oracle pentru nomenclatoare si autentificare
-- `shared/auth/` - JWT authentication (middleware, routes, service)
-
-### Frontend
-- `shared/frontend/components/LoginView.vue` - Componenta login partajata
-- `shared/frontend/stores/auth.js` - Pinia auth store factory
-- `shared/frontend/styles/login.css` - Stiluri login
-
-## Servere Oracle (Producție vs Test)
-
-**IMPORTANT**: Există două servere Oracle separate. Verifică întotdeauna la care ești conectat!
-
-| Server | IP Oracle | Tunel SSH | Schema Verificare | Company ID |
-|--------|-----------|-----------|-------------------|------------|
-| **PRODUCȚIE** | `10.0.20.36` | `./ssh_tunnel.sh` | `ROMFAST` | 114 |
-| **TEST** | `10.0.20.121` | `./ssh-tunnel-test.sh` | `MARIUSM_AUTO` | 110 |
-
-### Scripturi de Pornire
-
-**IMPORTANT**: Folosește scriptul corespunzător mediului dorit!
-
-```bash
-# Pentru PRODUCȚIE (10.0.20.36)
-./start-data-entry-dev.sh # Pornește tot (tunel + backend + frontend)
-./start-data-entry-dev.sh stop # Oprește tot
-./start-data-entry-dev.sh status # Verifică status
-
-# Pentru TEST (10.0.20.121)
-./start-data-entry-test.sh # Pornește tot (tunel + backend + frontend)
-./start-data-entry-test.sh stop # Oprește tot
-./start-data-entry-test.sh status # Verifică status
-```
-
-Scripturile fac automat:
-1. Opresc tunelul celuilalt mediu (dacă rulează)
-2. Pornesc tunelul corect
-3. Copiază `.env.prod` sau `.env.test` în `.env`
-4. Rulează migrările pe baza de date corectă
-5. Pornesc frontend și backend
-
-### Fișiere .env
-
-| Fișier | Server | Database | Schema Test |
-|--------|--------|----------|-------------|
-| `.env.prod` | PRODUCȚIE (10.0.20.36) | `receipts_prod.db` | ROMFAST (id=114) |
-| `.env.test` | TEST (10.0.20.121) | `receipts_test.db` | MARIUSM_AUTO (id=110) |
-
-### Verificare conexiune
-
-```bash
-# Verifică la ce server ești conectat
-./ssh_tunnel.sh status # Dacă rulează = PRODUCȚIE
-./ssh-tunnel-test.sh status # Dacă rulează = TEST
-
-# Verifică schema disponibilă (din backend/)
-python3 -c "
-import asyncio
-from dotenv import load_dotenv
-load_dotenv()
-import sys; sys.path.insert(0, '../../shared')
-from database.oracle_pool import oracle_pool
-
-async def check():
- await oracle_pool.initialize()
- async with oracle_pool.get_connection() as conn:
- with conn.cursor() as cur:
- cur.execute('SELECT SCHEMA, NUME FROM CONTAFIN_ORACLE.V_NOM_FIRME ORDER BY NUME')
- for row in cur.fetchall()[:10]:
- print(f'{row[0]}: {row[1]}')
-asyncio.run(check())
-"
-```
-
-### Sincronizare Nomenclatoare
-
-Query-ul pentru furnizori folosește `CORESP_TIP_PART`:
-- `id_tip_part = 17` → Furnizori
-- `id_tip_part = 22` → Casa LEI
-- `id_tip_part = 23` → Casa Valută
-- `id_tip_part = 24` → Bancă LEI
-- `id_tip_part = 25` → Bancă Valută
-
-```sql
--- Exemplu query furnizori pentru schema MARIUSM_AUTO (TEST)
-SELECT B.ID_PART, B.DENUMIRE, B.COD_FISCAL, B.ADRESA
-FROM MARIUSM_AUTO.CORESP_TIP_PART A
-INNER JOIN MARIUSM_AUTO.VNOM_PARTENERI B ON A.ID_PART = B.ID_PART
-WHERE A.ID_TIP_PART = 17 AND (B.INACTIV = 0 OR B.INACTIV IS NULL)
-ORDER BY B.DENUMIRE;
-```
-
-## Comenzi Dezvoltare
-
-```bash
-# Backend
-cd data-entry-app/backend
-pip install -r requirements.txt
-alembic upgrade head
-uvicorn app.main:app --reload --port 8003
-
-# Frontend
-cd data-entry-app/frontend
-npm install
-npm run dev -- --port 3010
-
-# Migrari
-cd data-entry-app/backend
-alembic revision --autogenerate -m "description"
-alembic upgrade head
-```
-
-## Tipuri Cheltuieli (hardcoded in Faza 1)
-
-| Cod | Tip | Cont | TVA |
-|-----|-----|------|-----|
-| FUEL | Combustibil | 6022 | 19% |
-| MATERIALS | Materiale | 6028 | 19% |
-| OFFICE | Rechizite | 6024 | 19% |
-| PHONE | Telefonie | 626 | 19% |
-| PARKING | Parcare | 6022 | 19% |
-| FOOD | Alimentatie | 6028 | 0% |
-| TRANSPORT | Transport | 624 | 19% |
-| OTHER | Altele | 628 | 19% |
-
-## 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()`
-
-## API Endpoints Summary
-
-### Receipts CRUD
-- `POST /api/receipts/` - Create
-- `GET /api/receipts/` - List (filterable)
-- `GET /api/receipts/{id}` - Detail
-- `PUT /api/receipts/{id}` - Update (DRAFT only)
-- `DELETE /api/receipts/{id}` - Delete (DRAFT only)
-
-### Workflow
-- `POST /api/receipts/{id}/submit` - Send for review
-- `POST /api/receipts/{id}/approve` - Approve
-- `POST /api/receipts/{id}/reject` - Reject
-- `POST /api/receipts/{id}/resubmit` - Resubmit after rejection
-
-### Attachments
-- `POST /api/receipts/{id}/attachments` - Upload
-- `GET /api/attachments/{id}/download` - Download
-- `DELETE /api/attachments/{id}` - Delete
-
-### Nomenclatures
-- `GET /api/receipts/partners` - Partners from Oracle
-- `GET /api/receipts/accounts` - Accounts from Oracle
-- `GET /api/receipts/cash-registers` - Cash registers from Oracle
-- `GET /api/receipts/expense-types` - Expense types (hardcoded)
-
-## Testing
-
-```bash
-# Backend tests
-cd backend && pytest
-
-# Frontend tests
-cd frontend && npm run test
-```
-
-## Common Issues
-
-### Nomenclatoare goale / furnizori lipsă
-- Verifică la ce server Oracle ești conectat: `./ssh_tunnel.sh status`
-- Verifică dacă schema firmei selectate există pe acel server
-- Sincronizează manual: `POST /api/nomenclature/sync/all`
-
-### Conectat la serverul greșit
-- PRODUCȚIE are schema `ROMFAST`, TEST are schema `MARIUSM_AUTO`
-- Oprește tunelul curent și pornește cel corect (vezi secțiunea "Servere Oracle")
-
-### SQLite locked
-- Asigura-te ca nu ai multiple procese care acceseaza DB-ul
-
-### Upload fails
-- Verifica permisiuni pe `data/uploads/`
-- Verifica MIME type (doar image/*, application/pdf)
-
-### Migration errors
-- `alembic downgrade -1` pentru rollback
-- Sterge migration file si regenereaza
diff --git a/data-entry-app/backend/.env.example b/data-entry-app/backend/.env.example
deleted file mode 100644
index c2f2bfb..0000000
--- a/data-entry-app/backend/.env.example
+++ /dev/null
@@ -1,26 +0,0 @@
-# SQLite Database
-SQLITE_DATABASE_PATH=data/receipts.db
-
-# File uploads
-UPLOAD_PATH=data/uploads
-MAX_UPLOAD_SIZE_MB=10
-
-# Oracle Database (for nomenclatures - through SSH tunnel)
-ORACLE_USER=CONTAFIN_ORACLE
-ORACLE_PASSWORD=your_password
-ORACLE_HOST=localhost
-ORACLE_PORT=1526
-ORACLE_SID=ROA
-
-# JWT Authentication (shared with reports-app)
-JWT_SECRET_KEY=your_secret_key_here
-JWT_ALGORITHM=HS256
-JWT_EXPIRE_MINUTES=480
-
-# API Settings
-API_HOST=0.0.0.0
-API_PORT=8003
-DEBUG=true
-
-# CORS
-CORS_ORIGINS=http://localhost:3010,http://localhost:3000
diff --git a/data-entry-app/backend/alembic.ini b/data-entry-app/backend/alembic.ini
deleted file mode 100644
index 4af7b4b..0000000
--- a/data-entry-app/backend/alembic.ini
+++ /dev/null
@@ -1,106 +0,0 @@
-# Alembic Configuration for Data Entry App
-
-[alembic]
-# path to migration scripts
-script_location = migrations
-
-# template used to generate migration file names
-file_template = %%(year)d%%(month).2d%%(day).2d_%%(hour).2d%%(minute).2d%%(second).2d_%%(slug)s
-
-# sys.path path, will be prepended to sys.path if present.
-prepend_sys_path = .
-
-# timezone to use when rendering the date within the migration file
-# as well as the filename.
-timezone = UTC
-
-# max length of characters to apply to the "slug" field
-truncate_slug_length = 40
-
-# set to 'true' to run the environment during
-# the 'revision' command, regardless of autogenerate
-# revision_environment = false
-
-# set to 'true' to allow .pyc and .pyo files without
-# a source .py file to be detected as revisions in the
-# versions/ directory
-# sourceless = false
-
-# version location specification; This defaults
-# to migrations/versions. When using multiple version
-# directories, initial revisions must be specified with --version-path.
-# The path separator used here should be the separator specified by "version_path_separator" below.
-# version_locations = %(here)s/bar:%(here)s/bat:migrations/versions
-
-# version path separator; As mentioned above, this is the character used to split
-# version_locations. The default within new alembic.ini files is "os", which uses os.pathsep.
-# If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas.
-# Valid values for version_path_separator are:
-#
-# version_path_separator = :
-# version_path_separator = ;
-# version_path_separator = space
-version_path_separator = os # Use os.pathsep. Default configuration used for new projects.
-
-# set to 'true' to search source files recursively
-# in each "version_locations" directory
-# recursive_version_locations = false
-
-# the output encoding used when revision files
-# are written from script.py.mako
-output_encoding = utf-8
-
-sqlalchemy.url = sqlite:///data/receipts.db
-
-
-[post_write_hooks]
-# post_write_hooks defines scripts or Python functions that are run
-# on newly generated revision scripts. See the documentation for further
-# detail and examples
-
-# format using "black" - use the console_scripts runner, against the "black" entrypoint
-# hooks = black
-# black.type = console_scripts
-# black.entrypoint = black
-# black.options = -l 79 REVISION_SCRIPT_FILENAME
-
-# lint with attempts to fix using "ruff" - use the exec runner, execute a binary
-# hooks = ruff
-# ruff.type = exec
-# ruff.executable = %(here)s/.venv/bin/ruff
-# ruff.options = --fix REVISION_SCRIPT_FILENAME
-
-# Logging configuration
-[loggers]
-keys = root,sqlalchemy,alembic
-
-[handlers]
-keys = console
-
-[formatters]
-keys = generic
-
-[logger_root]
-level = WARN
-handlers = console
-qualname =
-
-[logger_sqlalchemy]
-level = WARN
-handlers =
-qualname = sqlalchemy.engine
-
-[logger_alembic]
-level = INFO
-handlers =
-qualname = alembic
-
-[handler_console]
-class = StreamHandler
-args = (sys.stderr,)
-level = NOTSET
-formatter = generic
-
-[formatter_generic]
-format = %(levelname)-5.5s [%(name)s] %(message)s
-datefmt = %H:%M:%S
diff --git a/data-entry-app/backend/app/__init__.py b/data-entry-app/backend/app/__init__.py
deleted file mode 100644
index 2b808a5..0000000
--- a/data-entry-app/backend/app/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-# Data Entry App - Backend
diff --git a/data-entry-app/backend/app/main.py b/data-entry-app/backend/app/main.py
deleted file mode 100644
index 19191e1..0000000
--- a/data-entry-app/backend/app/main.py
+++ /dev/null
@@ -1,173 +0,0 @@
-"""FastAPI application entry point for Data Entry App."""
-
-import sys
-import logging
-import threading
-from pathlib import Path
-from contextlib import asynccontextmanager
-
-# Load .env file BEFORE any imports that use os.getenv()
-from dotenv import load_dotenv
-load_dotenv()
-
-from fastapi import FastAPI
-
-# Configure logging to show INFO level messages
-logging.basicConfig(
- level=logging.INFO,
- format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
- datefmt='%H:%M:%S'
-)
-from fastapi.middleware.cors import CORSMiddleware
-from fastapi.staticfiles import StaticFiles
-
-# Add shared modules to path
-# Development: data-entry-app/backend/app/main.py -> 4 parents to project root
-# Production: data-entry-backend/app/main.py -> 3 parents to roa2web root
-def find_shared_path():
- """Find shared folder - works in both dev and production."""
- current = Path(__file__).parent # app/
-
- # Try different parent levels to find shared folder
- for levels in range(1, 6):
- candidate = current
- for _ in range(levels):
- candidate = candidate.parent
- shared_path = candidate / "shared"
- if shared_path.exists() and (shared_path / "auth").exists():
- return shared_path
-
- # Fallback to original logic
- return Path(__file__).parent.parent.parent.parent / "shared"
-
-shared_path = find_shared_path()
-sys.path.insert(0, str(shared_path))
-
-from app.config import settings
-from app.db.database import init_db
-
-# Import Oracle pool for auth service
-from database.oracle_pool import oracle_pool
-
-
-@asynccontextmanager
-async def lifespan(app: FastAPI):
- """Application lifespan - startup and shutdown events."""
- # Startup
- print(f"Starting {settings.app_name} v{settings.app_version}")
-
- # Initialize Oracle pool (required for authentication)
- try:
- await oracle_pool.initialize()
- print("Oracle pool initialized")
- except Exception as e:
- print(f"Warning: Oracle pool initialization failed: {e}")
- print("Authentication will not work without Oracle connection")
-
- # Initialize SQLite database
- await init_db()
- print("Database initialized")
-
- # Ensure upload directory exists
- settings.upload_path_resolved
- print(f"Upload path: {settings.upload_path_resolved}")
-
- # Pre-initialize OCR engine in background (PaddleOCR takes 15-20s)
- def init_ocr_background():
- try:
- from app.services.ocr_service import ocr_service
- ocr_service.ocr_engine._init_paddle_lazy()
- print("OCR engine ready")
- except Exception as e:
- print(f"Warning: OCR engine pre-load failed: {e}")
-
- print("Starting OCR engine pre-load (background)...")
- threading.Thread(target=init_ocr_background, daemon=True).start()
-
- yield
-
- # Shutdown
- print("Shutting down...")
- try:
- await oracle_pool.close()
- print("Oracle pool closed")
- except Exception as e:
- print(f"Warning: Oracle pool close failed: {e}")
-
-
-# Create FastAPI app
-app = FastAPI(
- title=settings.app_name,
- version=settings.app_version,
- description="API pentru introducere bonuri fiscale cu workflow de aprobare",
- lifespan=lifespan,
-)
-
-# CORS middleware
-app.add_middleware(
- CORSMiddleware,
- allow_origins=settings.cors_origins_list,
- allow_credentials=True,
- allow_methods=["*"],
- allow_headers=["*"],
-)
-
-# Authentication middleware
-from auth.middleware import AuthenticationMiddleware
-
-app.add_middleware(
- AuthenticationMiddleware,
- excluded_paths=["/docs", "/redoc", "/openapi.json", "/health", "/", "/api/auth/login", "/api/auth/refresh"]
-)
-
-# Mount static files for uploads (optional - can serve through nginx in prod)
-uploads_path = Path(settings.upload_path)
-if uploads_path.exists():
- app.mount("/uploads", StaticFiles(directory=str(uploads_path)), name="uploads")
-
-
-# Health check endpoint
-@app.get("/health")
-async def health_check():
- """Health check endpoint."""
- return {
- "status": "healthy",
- "app": settings.app_name,
- "version": settings.app_version,
- }
-
-
-# Import and include routers
-from app.routers import receipts, ocr, nomenclature
-
-app.include_router(receipts.router, prefix="/api/receipts", tags=["receipts"])
-app.include_router(ocr.router, prefix="/api/ocr", tags=["ocr"])
-app.include_router(nomenclature.router, prefix="/api/nomenclature", tags=["nomenclature"])
-
-# Auth router
-from auth.routes import create_auth_router
-
-auth_router = create_auth_router(prefix="") # No prefix - we set it in include_router
-app.include_router(auth_router, prefix="/api/auth", tags=["auth"])
-
-# Shared routes (companies, calendar)
-from routes.companies import create_companies_router
-from routes.calendar import create_calendar_router
-
-companies_router = create_companies_router(oracle_pool) # No cache for data-entry
-calendar_router = create_calendar_router(oracle_pool)
-
-app.include_router(companies_router, prefix="/api/companies", tags=["companies"])
-app.include_router(calendar_router, prefix="/api/calendar", tags=["calendar"])
-
-
-# Root endpoint
-@app.get("/")
-async def root():
- """Root endpoint - API information."""
- return {
- "name": settings.app_name,
- "version": settings.app_version,
- "docs": "/docs",
- "health": "/health",
- }
diff --git a/data-entry-app/backend/app/routers/__init__.py b/data-entry-app/backend/app/routers/__init__.py
deleted file mode 100644
index 6a8f0e9..0000000
--- a/data-entry-app/backend/app/routers/__init__.py
+++ /dev/null
@@ -1,4 +0,0 @@
-# API routers
-from . import receipts, nomenclature
-
-__all__ = ["receipts", "nomenclature"]
diff --git a/data-entry-app/backend/requirements.txt b/data-entry-app/backend/requirements.txt
deleted file mode 100644
index e201548..0000000
--- a/data-entry-app/backend/requirements.txt
+++ /dev/null
@@ -1,42 +0,0 @@
-# FastAPI
-fastapi>=0.109.0
-uvicorn[standard]>=0.27.0
-
-# Database - SQLModel + Alembic
-sqlmodel>=0.0.14
-sqlalchemy[asyncio]>=2.0.25
-aiosqlite>=0.19.0
-alembic>=1.13.1
-
-# Pydantic
-pydantic>=2.5.3
-pydantic-settings>=2.1.0
-email-validator>=2.1.0
-
-# File handling
-python-multipart>=0.0.6
-aiofiles>=23.2.1
-Pillow>=10.2.0
-
-# Authentication (shared)
-PyJWT>=2.8.0
-python-jose[cryptography]>=3.3.0
-
-# Oracle (for nomenclatures)
-oracledb>=2.0.1
-
-# Utils
-python-dotenv>=1.0.0
-httpx>=0.26.0
-
-# Testing
-pytest>=8.0.0
-pytest-asyncio>=0.23.3
-
-# OCR Dependencies
-paddleocr>=2.7.0
-paddlepaddle>=2.5.0
-opencv-python>=4.8.0
-pytesseract>=0.3.10
-pdf2image>=1.16.0
-numpy>=1.24.0
diff --git a/data-entry-app/docs/PLAN_DATA_ENTRY_RECEIPTS.md b/data-entry-app/docs/PLAN_DATA_ENTRY_RECEIPTS.md
deleted file mode 100644
index c57b85e..0000000
--- a/data-entry-app/docs/PLAN_DATA_ENTRY_RECEIPTS.md
+++ /dev/null
@@ -1,667 +0,0 @@
-# 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/`
diff --git a/data-entry-app/docs/abonament kineterra.pdf b/data-entry-app/docs/abonament kineterra.pdf
deleted file mode 100644
index 76fa159..0000000
Binary files a/data-entry-app/docs/abonament kineterra.pdf and /dev/null differ
diff --git a/data-entry-app/docs/benzina 14 august.pdf b/data-entry-app/docs/benzina 14 august.pdf
deleted file mode 100644
index 9ae5fd8..0000000
Binary files a/data-entry-app/docs/benzina 14 august.pdf and /dev/null differ
diff --git a/data-entry-app/docs/benzina 27 octombrie .pdf b/data-entry-app/docs/benzina 27 octombrie .pdf
deleted file mode 100644
index 3143458..0000000
Binary files a/data-entry-app/docs/benzina 27 octombrie .pdf and /dev/null differ
diff --git a/data-entry-app/docs/igiena 11 octombrie .pdf b/data-entry-app/docs/igiena 11 octombrie .pdf
deleted file mode 100644
index ed8e621..0000000
--- a/data-entry-app/docs/igiena 11 octombrie .pdf
+++ /dev/null
@@ -1,2086 +0,0 @@
-%PDF-1.3
-%âãÏÓ
-1 0 obj
-<>
-endobj
-2 0 obj
-<>
-endobj
-3 0 obj
-<>/Font<>>>>>/Rotate 0/AF[6 0 R]/Type/Page>>
-endobj
-4 0 obj<>stream
-q 195.5755 0.0000 0.0000 792.0000 0.0000 0.0000 cm /Im0 Do Q q 0.2020 0.0000 0.0000 0.2020 0.0000 0.0000 cm BT
-3 Tr
-/Ft0 1 Tf
--0.035 Tc
-17.4086 0 0 40 9.07 3497 Tm
-(NIVS: ) Tj
-28.5839 0 0 40 60 3497 Tm
-(1360760 ) Tj
-21.8954 0 0 41.25 9.77 3449 Tm
-(OPERATOR:FLORENT ) Tj
-20.682 0 0 41.25 227.85 3449 Tm
-(INA ) Tj
-22.0059 0 0 41.25 278.77 3449 Tm
-(CUSMIR ) Tj
-13.3033 0 0 48.75 10.06 3400 Tm
-(NR ) Tj
-24.6151 0 0 48.75 42.88 3400 Tm
-(POS: ) Tj
-26.7823 0 0 48.75 97.71 3400 Tm
-(C3POS-) Tj
-24.7714 0 0 48.75 181.74 3400 Tm
-(C12 ) Tj
-21.9939 0 0 47.5 2.77 3311 Tm
-(QLIENT ) Tj
-27.2511 0 0 47.5 94.71 3311 Tm
-(C.U. ) Tj
-45.1441 0 0 47.5 154.67 3311 Tm
-(I./ ) Tj
-36.9724 0 0 47.5 217.6 3311 Tm
-(C.IE. ) Tj
-27.5038 0 0 47.5 292.3 3311 Tm
-(:RO1879855 ) Tj
-21.1264 0 0 43.75 -1.11 3219 Tm
-(DETERGENT ) Tj
-20.5004 0 0 43.75 135.79 3219 Tm
-(GEAMURI ) Tj
-16.3336 0 0 43.75 246.77 3219 Tm
-(5L ) Tj
-23.6223 0 0 43.75 287.89 3219 Tm
-(PH ) Tj
-21.3702 0 0 43.75 329.9 3219 Tm
-(MISAUON ) Tj
-27.2716 0 0 50 0.96 3167 Tm
-(U79005130 ) Tj
-32.6134 0 0 50 136.58 3167 Tm
-(6422 ) Tj
-27.1727 0 0 50 198.7 3167 Tm
-(768027152 ) Tj
-22.2987 0 0 41.25 -3.12 3128 Tm
-(PROSOP ) Tj
-22.7218 0 0 41.25 92.89 3128 Tm
-(HARTIE ) Tj
-0 Tc
-41.25 0 0 41.25 189.74 3128 Tm
-(2 ) Tj
--0.035 Tc
-22.5766 0 0 41.25 217.58 3128 Tm
-(STR ) Tj
-25.232 0 0 41.25 272.68 3128 Tm
-(640G/ROLA ) Tj
-20.5545 0 0 41.25 412.9 3128 Tm
-(MLI ) Tj
-28.1421 0 0 36.25 2.79 3081 Tm
-(IZ4441011047 ) Tj
-29.5604 0 0 36.25 174.62 3081 Tm
-(6422465006429 ) Tj
-24.0428 0 0 50 196.89 3634 Tm
-(BINE ) Tj
-30.2041 0 0 50 269.93 3634 Tm
-(ATI ) Tj
-22.4532 0 0 50 320.95 3634 Tm
-(VENIT ) Tj
-20.8894 0 0 50 406.85 3634 Tm
-(IN ) Tj
-17.926 0 0 50 450.92 3634 Tm
-(MAGAZ ) Tj
-18.5243 0 0 50 517.87 3634 Tm
-(INUL ) Tj
-23.1722 0 0 50 580.89 3634 Tm
-(BRICK ) Tj
-29.905 0 0 40 345.68 3595 Tm
-(CIF: ) Tj
-26.6236 0 0 40 400.87 3595 Tm
-(RO10562600 ) Tj
-21.106 0 0 40 9.9 3032 Tm
-(ROSOP ) Tj
-23.5846 0 0 40 90.88 3032 Tm
-(HORTIE ) Tj
-0 Tc
-40 0 0 40 188.75 3032 Tm
-(2 ) Tj
--0.035 Tc
-23.1555 0 0 40 216.57 3032 Tm
-(STR ) Tj
-26.3735 0 0 40 273.88 3032 Tm
-(4506/ROLA ) Tj
-20.8502 0 0 40 413.9 3032 Tm
-(ML6 ) Tj
-28.4894 0 0 47.5 10.9 2980 Tm
-(Z677101186146 ) Tj
-29.8907 0 0 47.5 202.62 2980 Tm
-(6422465006719 ) Tj
-25.7316 0 0 56.25 -0.49 2926 Tm
-(SACI ) Tj
-21.374 0 0 56.25 64.9 2926 Tm
-(MENAJ ) Tj
-23.7919 0 0 56.25 147.86 2926 Tm
-(EXIRA ) Tj
-24.2523 0 0 56.25 232.71 2926 Tm
-(351. ) Tj
-26.2895 0 0 56.25 296.63 2926 Tm
-(50BUC/SET ) Tj
-27.6479 0 0 56.25 427.65 2926 Tm
-(60 ) Tj
-28.0802 0 0 45 10.8 2876 Tm
-(IZ1022100021038102 ) Tj
-29.7256 0 0 45 260.62 2876 Tm
-(6422768020146 ) Tj
-27.1352 0 0 57.5 4.5 2822 Tm
-(SACI ) Tj
-21.374 0 0 57.5 66.9 2822 Tm
-(MENAJ ) Tj
-26.6117 0 0 57.5 150.68 2822 Tm
-(35L ) Tj
-24.7164 0 0 57.5 213.1 2822 Tm
-(NEGRI ) Tj
-23.4361 0 0 57.5 289.75 2822 Tm
-(C2 ) Tj
-21.4677 0 0 57.5 331.7 2822 Tm
-(50BLUC/ROL ) Tj
-30.27 0 0 47.5 1.89 2776 Tm
-(Z974101075372 ) Tj
-29.2302 0 0 47.5 206.63 2776 Tm
-(6425385000492 ) Tj
-17.0747 0 0 42.5 10.68 2725 Tm
-(SOLUT ) Tj
-23.5085 0 0 42.5 72.83 2725 Tm
-(IE ) Tj
-21.2999 0 0 42.5 109.77 2725 Tm
-(CURATARE ) Tj
-21.9856 0 0 42.5 234.59 2725 Tm
-(SPRAY ) Tj
-22.3616 0 0 42.5 317.89 2725 Tm
-(MOBILA ) Tj
-28.713 0 0 42.5 414.66 2725 Tm
-(300 ) Tj
-26.3394 0 0 43.75 10.96 2675 Tm
-(UZ102010108824 ) Tj
-32.0192 0 0 43.75 206.59 2675 Tm
-(642276802669 ) Tj
-18.2461 0 0 52.5 9.66 2625 Tm
-(SACI ) Tj
-21.0596 0 0 52.5 68.9 2625 Tm
-(MENAJ ) Tj
-21.4029 0 0 52.5 151.87 2625 Tm
-(EXTRA ) Tj
-25.9464 0 0 52.5 234.69 2625 Tm
-(35L ) Tj
-22.2452 0 0 52.5 296.69 2625 Tm
-(5OBUC ) Tj
-27.7155 0 0 52.5 365.99 2625 Tm
-(/SET ) Tj
-27.6479 0 0 52.5 428.65 2625 Tm
-(60 ) Tj
-27.1421 0 0 42.5 9.96 2577 Tm
-(UZ1022100021038102 ) Tj
-28.3557 0 0 42.5 262.64 2577 Tm
-(6422768O20146 ) Tj
-18.2461 0 0 45 12.66 2526 Tm
-(SACI ) Tj
-21.6883 0 0 45 67.9 2526 Tm
-(MENAJ ) Tj
-25.9464 0 0 45 151.69 2526 Tm
-(35L ) Tj
-21.9701 0 0 45 207.09 2526 Tm
-(NEGRI ) Tj
-23.4361 0 0 45 289.75 2526 Tm
-(C2 ) Tj
-23.9292 0 0 45 331.66 2526 Tm
-(50BUC/ROL ) Tj
-27.8066 0 0 46.25 10.8 2477 Tm
-(IZ974101075372 ) Tj
-29.3953 0 0 46.25 206.63 2477 Tm
-(6425385000492 ) Tj
-0 Tc
-55 0 0 55 3.71 2424 Tm
-(+ ) Tj
--0.035 Tc
-22.1574 0 0 55 25.89 2424 Tm
-(BEC ) Tj
-21.5083 0 0 55 81.88 2424 Tm
-(LED ) Tj
-20.8861 0 0 55 137.88 2424 Tm
-(ECO ) Tj
-24.2986 0 0 55 192.95 2424 Tm
-(A50 ) Tj
-26.1926 0 0 55 248.84 2424 Tm
-(E27 ) Tj
-23.2279 0 0 55 303.75 2424 Tm
-(7N ) Tj
-21.2557 0 0 55 345.88 2424 Tm
-(LUMINA ) Tj
-21.3938 0 0 55 442.9 2424 Tm
-(REC ) Tj
-28.6299 0 0 46.25 11.83 2377 Tm
-(EL13801101103 ) Tj
-29.6074 0 0 46.25 206.58 2377 Tm
-(5949054916415 ) Tj
-23.8603 0 0 46.25 9.88 2327 Tm
-(+BEC ) Tj
-21.5083 0 0 46.25 81.88 2327 Tm
-(LED ) Tj
-26.1926 0 0 46.25 137.84 2327 Tm
-(E27 ) Tj
-22.2026 0 0 46.25 193.22 2327 Tm
-(15W ) Tj
-28.8879 0 0 46.25 248.99 2327 Tm
-(175-250V ) Tj
-21.2557 0 0 46.25 373.88 2327 Tm
-(LUMINA ) Tj
-0 Tc
-46.25 0 0 46.25 470.77 2327 Tm
-(R ) Tj
--0.035 Tc
-26.3974 0 0 48.75 -0.17 2280 Tm
-(ELO9081109 ) Tj
-29.2809 0 0 48.75 151.47 2280 Tm
-(8680985523118 ) Tj
-24.0125 0 0 42.5 320.89 3775 Tm
-(FIVE-HOLDING ) Tj
-26.8276 0 0 42.5 497.5 3775 Tm
-(S.0. ) Tj
-25.8903 0 0 50 246.85 3725 Tm
-(JUD. ) Tj
-22.8536 0 0 50 307.76 3725 Tm
-(CONSIANTA, ) Tj
-18.6428 0 0 50 457.91 3725 Tm
-(MUN, ) Tj
-21.216 0 0 50 525.78 3725 Tm
-(CONSTANTA ) Tj
-26.4193 0 0 42.5 306.51 3686 Tm
-(SIR. ) Tj
-20.0911 0 0 42.5 378.85 3686 Tm
-(ION ) Tj
-20.0019 0 0 42.5 428.9 3686 Tm
-(RONTA ) Tj
-20.694 0 0 42.5 511.09 3686 Tm
-(NR ) Tj
-0 Tc
-42.5 0 0 42.5 566.49 3686 Tm
-(3 ) Tj
-47.5 0 0 47.5 9.75 2228 Tm
-(+ ) Tj
--0.035 Tc
-21.617 0 0 47.5 26.9 2228 Tm
-(BEC ) Tj
-22.046 0 0 47.5 81.87 2228 Tm
-(LED ) Tj
-24.2986 0 0 47.5 137.95 2228 Tm
-(A70 ) Tj
-26.1926 0 0 47.5 193.84 2228 Tm
-(E27 ) Tj
-22.2026 0 0 47.5 249.22 2228 Tm
-(18W ) Tj
-21.5181 0 0 47.5 304.88 2228 Tm
-(LUMINA ) Tj
-21.6796 0 0 47.5 401.89 2228 Tm
-(RECE ) Tj
-0 Tc
-47.5 0 0 47.5 470.39 2228 Tm
-(6 ) Tj
--0.035 Tc
-25.5244 0 0 45 9.85 2180 Tm
-(EL9001612 ) Tj
-29.6074 0 0 45 137.58 2180 Tm
-(5849054922850 ) Tj
-19.501 0 0 45 10.91 1555 Tm
-(BTOTAL ) Tj
-0 Tc
-47.5 0 0 47.5 9.75 2129 Tm
-(+ ) Tj
--0.035 Tc
-21.617 0 0 47.5 27.9 2129 Tm
-(BEC ) Tj
-22.046 0 0 47.5 82.87 2129 Tm
-(LED ) Tj
-20.8861 0 0 47.5 138.88 2129 Tm
-(ECO ) Tj
-22.0301 0 0 47.5 194.89 2129 Tm
-(R5O ) Tj
-26.1926 0 0 47.5 249.84 2129 Tm
-(E27 ) Tj
-27.648 0 0 47.5 305.7 2129 Tm
-(74 ) Tj
-21.2557 0 0 47.5 346.88 2129 Tm
-(LUMINA ) Tj
-21.3938 0 0 47.5 442.9 2129 Tm
-(REC ) Tj
-16.4921 0 0 46.25 9.9 2080 Tm
-(EL1 ) Tj
-29.532 0 0 46.25 42.65 2080 Tm
-(38011001103 ) Tj
-29.2766 0 0 46.25 208.58 2080 Tm
-(5949054916415 ) Tj
-21.2837 0 0 46.25 10.9 2031 Tm
-(HARTIE ) Tj
-25.351 0 0 46.25 102.82 2031 Tm
-(IGIENICA ) Tj
-23.8639 0 0 46.25 223.89 2031 Tm
-(BRICK ) Tj
-0 Tc
-46.25 0 0 46.25 306.44 2031 Tm
-(3 ) Tj
--0.035 Tc
-22.582 0 0 46.25 333.58 2031 Tm
-(STRATURI ) Tj
-28.6635 0 0 45 10.96 1984 Tm
-(UZ905335 ) Tj
-29.2302 0 0 45 142.63 1984 Tm
-(6425667001247 ) Tj
-21.5713 0 0 50 8.89 1938 Tm
-(HARTIE ) Tj
-29.6861 0 0 50 103.78 1938 Tm
-(IGIENI ) Tj
-19.5283 0 0 50 187.79 1938 Tm
-(CA ) Tj
-23.8639 0 0 50 222.89 1938 Tm
-(BRICK ) Tj
-0 Tc
-50 0 0 50 305.4 1938 Tm
-(3 ) Tj
--0.035 Tc
-22.7991 0 0 50 332.58 1938 Tm
-(STRATURI ) Tj
-26.3919 0 0 43.75 -0.05 1898 Tm
-(UZ9005335 ) Tj
-28.8999 0 0 43.75 134.63 1898 Tm
-(6425667001247 ) Tj
-21.2837 0 0 45 0.9 1853 Tm
-(HARTIE ) Tj
-25.5902 0 0 45 94.81 1853 Tm
-(IGIENICA ) Tj
-23.518 0 0 45 216.89 1853 Tm
-(BRICK ) Tj
-0 Tc
-45 0 0 45 299.46 1853 Tm
-(3 ) Tj
--0.035 Tc
-22.7991 0 0 45 328.58 1853 Tm
-(STRATURI ) Tj
-30.9941 0 0 41.25 10.89 1805 Tm
-(Z900535 ) Tj
-29.8907 0 0 41.25 130.62 1805 Tm
-(6425667001247 ) Tj
-22.181 0 0 45 10.95 1755 Tm
-(ARTIE ) Tj
-25.8294 0 0 45 89.81 1755 Tm
-(IGIENICA ) Tj
-24.2098 0 0 45 212.88 1755 Tm
-(BRICK ) Tj
-0 Tc
-45 0 0 45 296.46 1755 Tm
-(3 ) Tj
--0.035 Tc
-23.2334 0 0 45 325.57 1755 Tm
-(STRATURI ) Tj
-25.9555 0 0 42.5 10.91 1707 Tm
-(Z9005335 ) Tj
-29.5604 0 0 42.5 125.62 1707 Tm
-(6425667001247 ) Tj
-26.5189 0 0 45 10.87 1656 Tm
-(RTIE ) Tj
-26.0685 0 0 45 85.81 1656 Tm
-(IGIENICA ) Tj
-24.5556 0 0 45 209.88 1656 Tm
-(BRICK ) Tj
-0 Tc
-45 0 0 45 294.46 1656 Tm
-(3 ) Tj
--0.035 Tc
-23.4505 0 0 45 322.57 1656 Tm
-(STRATURI ) Tj
-29.6537 0 0 45 10.81 1607 Tm
-(2905335 ) Tj
-30.3861 0 0 45 122.61 1607 Tm
-(6425667001247 ) Tj
-23.8736 0 0 100 11.83 1404 Tm
-(IA ) Tj
-20.4648 0 0 100 60.88 1404 Tm
-(LE ) Tj
-20.081 0 0 38.75 -0.13 1256 Tm
-(EST ) Tj
-18.5277 0 0 41.25 9.81 1160 Tm
-(OTAL ) Tj
-21.3464 0 0 41.25 71.81 1160 Tm
-(TUA ) Tj
-0 Tc
-41.25 0 0 41.25 127.91 1160 Tm
-(A ) Tj
-41.25 0 0 41.25 156.5 1160 Tm
-(-) Tj
--0.035 Tc
-23.5079 0 0 41.25 183.85 1160 Tm
-(21% ) Tj
-23.6523 0 0 38.75 -2.25 1120 Tm
-(OTAL ) Tj
-20.3299 0 0 38.75 74.82 1120 Tm
-(TUA ) Tj
-20.3131 0 0 38.75 129.9 1120 Tm
-(BON ) Tj
-14.0302 0 0 33.75 9.06 1079 Tm
-(NR. ) Tj
-24.2857 0 0 33.75 51.88 1079 Tm
-(POZ. ) Tj
-22.9273 0 0 33.75 120.95 1079 Tm
-(ART. ) Tj
-23.8736 0 0 33.75 193.83 1079 Tm
-(IN ) Tj
-22.5624 0 0 33.75 231.89 1079 Tm
-(BON: ) Tj
-29.6324 0 0 40 5.89 508 Tm
-(Z:0146 ) Tj
-27.8509 0 0 40 101.87 508 Tm
-(BF:0171 ) Tj
-21.8841 0 0 43.75 11.84 454 Tm
-(ID ) Tj
-24.8686 0 0 43.75 48.88 454 Tm
-(BF: ) Tj
-31.0124 0 0 36.25 7.42 362 Tm
-(S/N:D8470001107 ) Tj
-23.8351 0 0 43.75 10.75 307 Tm
-(CASIER ) Tj
-31.3154 0 0 43.75 105.9 307 Tm
-(1: ) Tj
-27.937 0 0 38.75 322.7 759 Tm
-(C3POS-CT2:1360760 ) Tj
-0 Tc
-52.5 0 0 52.5 594.16 3168 Tm
-(1 ) Tj
--0.035 Tc
-20.3832 0 0 52.5 609.9 3168 Tm
-(BUC ) Tj
-0 Tc
-52.5 0 0 52.5 665.89 3168 Tm
-(X ) Tj
--0.035 Tc
-27.5601 0 0 52.5 693.83 3168 Tm
-(27. ) Tj
-25.8839 0 0 52.5 734.7 3168 Tm
-(93= ) Tj
-32.5188 0 0 52.5 790.8 3168 Tm
-(27.93 ) Tj
-0 Tc
-52.5 0 0 52.5 873.88 3168 Tm
-(A ) Tj
-52.5 0 0 52.5 578.16 3076 Tm
-(1 ) Tj
--0.035 Tc
-20.2856 0 0 52.5 595.9 3076 Tm
-(ROLA ) Tj
-0 Tc
-52.5 0 0 52.5 666.89 3076 Tm
-(X ) Tj
--0.035 Tc
-27.0613 0 0 52.5 695.05 3076 Tm
-(13. ) Tj
-22.26 0 0 52.5 741.76 3076 Tm
-(00 ) Tj
-31.3942 0 0 52.5 792.9 3076 Tm
-(13.00 ) Tj
-0 Tc
-52.5 0 0 52.5 874.88 3076 Tm
-(A ) Tj
-58.75 0 0 58.75 618.94 2970 Tm
-(1 ) Tj
--0.035 Tc
-20.8928 0 0 58.75 637.9 2970 Tm
-(BUC ) Tj
-0 Tc
-58.75 0 0 58.75 693.88 2970 Tm
-(X ) Tj
--0.035 Tc
-27.5324 0 0 58.75 721.68 2970 Tm
-(9. ) Tj
-30.6923 0 0 58.75 749.64 2970 Tm
-(96-) Tj
-33.3314 0 0 58.75 805.61 2970 Tm
-(9,95 ) Tj
-0 Tc
-58.75 0 0 58.75 874.87 2970 Tm
-(A ) Tj
--0.035 Tc
-19.8177 0 0 36.25 381.91 279 Tm
-(BON ) Tj
-23.0709 0 0 36.25 435.89 279 Tm
-(FISCAL ) Tj
-28.9694 0 0 36.25 378.66 231 Tm
-(9000221498 ) Tj
-0 Tc
-55 0 0 55 621.07 2872 Tm
-(1 ) Tj
--0.035 Tc
-20.8928 0 0 55 636.9 2872 Tm
-(BUC ) Tj
-0 Tc
-55 0 0 55 692.88 2872 Tm
-(X ) Tj
--0.035 Tc
-27.9084 0 0 55 720.6 2872 Tm
-(5. ) Tj
-29.6546 0 0 55 748.87 2872 Tm
-(49-) Tj
-27.9084 0 0 55 803.6 2872 Tm
-(5, ) Tj
-27.3771 0 0 55 831.88 2872 Tm
-(49 ) Tj
-0 Tc
-55 0 0 55 873.88 2872 Tm
-(A ) Tj
-48.75 0 0 48.75 606.29 2778 Tm
-(1 ) Tj
--0.035 Tc
-21.0511 0 0 48.75 620.9 2778 Tm
-(ROLA ) Tj
-0 Tc
-48.75 0 0 48.75 689.9 2778 Tm
-(X ) Tj
--0.035 Tc
-40.3527 0 0 48.75 717.43 2778 Tm
-(5.81 ) Tj
-35.3862 0 0 48.75 805.5 2778 Tm
-(5.81 ) Tj
-0 Tc
-48.75 0 0 48.75 870.89 2778 Tm
-(A ) Tj
-47.5 0 0 47.5 590.34 2679 Tm
-(1 ) Tj
--0.035 Tc
-20.3832 0 0 47.5 606.9 2679 Tm
-(BUC ) Tj
-0 Tc
-47.5 0 0 47.5 662.9 2679 Tm
-(X ) Tj
--0.035 Tc
-27.9633 0 0 47.5 691.02 2679 Tm
-(10, ) Tj
-24.3622 0 0 47.5 731.89 2679 Tm
-(49= ) Tj
-32.3752 0 0 47.5 786.87 2679 Tm
-(10.49 ) Tj
-0 Tc
-47.5 0 0 47.5 869.89 2679 Tm
-(A ) Tj
-51.25 0 0 51.25 622.2 2576 Tm
-(1 ) Tj
--0.035 Tc
-19.8736 0 0 51.25 634.91 2576 Tm
-(BUC ) Tj
-0 Tc
-51.25 0 0 51.25 689.89 2576 Tm
-(X ) Tj
--0.035 Tc
-35.0821 0 0 51.25 716.5 2576 Tm
-(5.49: ) Tj
-33.5238 0 0 51.25 798.52 2576 Tm
-(5.49 ) Tj
-0 Tc
-51.25 0 0 51.25 868.88 2576 Tm
-(A ) Tj
-48.75 0 0 48.75 608.29 2481 Tm
-(1 ) Tj
--0.035 Tc
-20.6683 0 0 48.75 620.9 2481 Tm
-(ROLA ) Tj
-0 Tc
-48.75 0 0 48.75 688.9 2481 Tm
-(X ) Tj
--0.035 Tc
-27.9084 0 0 48.75 716.6 2481 Tm
-(5. ) Tj
-26.065 0 0 48.75 743.53 2481 Tm
-(81= ) Tj
-31.6613 0 0 48.75 797.55 2481 Tm
-(5,81 ) Tj
-0 Tc
-48.75 0 0 48.75 866.89 2481 Tm
-(A ) Tj
-46.25 0 0 46.25 618.38 2380 Tm
-(1 ) Tj
--0.035 Tc
-20.3832 0 0 46.25 633.9 2380 Tm
-(BUC ) Tj
-0 Tc
-46.25 0 0 46.25 688.9 2380 Tm
-(X ) Tj
--0.035 Tc
-35.0821 0 0 46.25 715.5 2380 Tm
-(5.54: ) Tj
-34.1446 0 0 46.25 797.51 2380 Tm
-(5.54 ) Tj
-0 Tc
-46.25 0 0 46.25 866.9 2380 Tm
-(A ) Tj
-51.25 0 0 51.25 622.2 2279 Tm
-(1 ) Tj
--0.035 Tc
-19.8736 0 0 51.25 633.91 2279 Tm
-(BUC ) Tj
-0 Tc
-51.25 0 0 51.25 687.89 2279 Tm
-(X ) Tj
--0.035 Tc
-18.838 0 0 51.25 721.78 2279 Tm
-(9. ) Tj
-26.7509 0 0 51.25 745.52 2279 Tm
-(80= ) Tj
-33.3314 0 0 51.25 796.61 2279 Tm
-(9.80 ) Tj
-0 Tc
-51.25 0 0 51.25 865.88 2279 Tm
-(A ) Tj
-50 0 0 50 592.25 2175 Tm
-(1 ) Tj
--0.035 Tc
-20.3832 0 0 50 607.9 2175 Tm
-(BUC ) Tj
-0 Tc
-50 0 0 50 662.9 2175 Tm
-(X ) Tj
--0.035 Tc
-30.3905 0 0 50 688.94 2175 Tm
-(13.61= ) Tj
-30.9036 0 0 50 783.92 2175 Tm
-(13.61 ) Tj
-0 Tc
-50 0 0 50 867.89 2175 Tm
-(A ) Tj
-51.25 0 0 51.25 616.2 2078 Tm
-(1 ) Tj
--0.035 Tc
-20.3832 0 0 51.25 632.9 2078 Tm
-(BUC ) Tj
-0 Tc
-51.25 0 0 51.25 687.89 2078 Tm
-(X ) Tj
--0.035 Tc
-29.9234 0 0 51.25 714.58 2078 Tm
-(5.54+ ) Tj
-32.9029 0 0 51.25 796.53 2078 Tm
-(5,54 ) Tj
-0 Tc
-51.25 0 0 51.25 865.88 2078 Tm
-(A ) Tj
-51.25 0 0 51.25 594.2 1978 Tm
-(1 ) Tj
--0.035 Tc
-20.3832 0 0 51.25 606.9 1978 Tm
-(BUC ) Tj
-0 Tc
-51.25 0 0 51.25 661.89 1978 Tm
-(X ) Tj
--0.035 Tc
-30.3905 0 0 51.25 688.94 1978 Tm
-(13.54= ) Tj
-32.8658 0 0 51.25 783.85 1978 Tm
-(13.54 ) Tj
-0 Tc
-51.25 0 0 51.25 866.88 1978 Tm
-(A ) Tj
-45 0 0 45 589.42 1894 Tm
-(1 ) Tj
--0.035 Tc
-20.3832 0 0 45 606.9 1894 Tm
-(BUC ) Tj
-0 Tc
-45 0 0 45 661.91 1894 Tm
-(X ) Tj
--0.035 Tc
-30.7801 0 0 45 688.92 1894 Tm
-(13.54= ) Tj
-32.8658 0 0 45 786.85 1894 Tm
-(13.54 ) Tj
-0 Tc
-45 0 0 45 869.9 1894 Tm
-(A ) Tj
-45 0 0 45 589.42 1804 Tm
-(1 ) Tj
--0.035 Tc
-20.3832 0 0 45 606.9 1804 Tm
-(BUC ) Tj
-0 Tc
-45 0 0 45 662.91 1804 Tm
-(X ) Tj
--0.035 Tc
-31.1697 0 0 45 689.91 1804 Tm
-(13.54= ) Tj
-33.8468 0 0 45 787.82 1804 Tm
-(13.54 ) Tj
-0 Tc
-45 0 0 45 872.9 1804 Tm
-(A ) Tj
--0.035 Tc
-22.3236 0 0 48.75 3.89 663 Tm
-(PRETUL ) Tj
-22.7923 0 0 48.75 100.95 663 Tm
-(ARTICOLELOR ) Tj
-20.1525 0 0 48.75 266.9 663 Tm
-(MARCATE ) Tj
-19.5283 0 0 48.75 377.79 663 Tm
-(CU ) Tj
-21.4767 0 0 48.75 450.84 663 Tm
-(INCLUD ) Tj
-23.3562 0 0 48.75 541.79 663 Tm
-(TAKA ) Tj
-19.4762 0 0 48.75 617.95 663 Tm
-(VERDE ) Tj
-19.9516 0 0 48.75 4.79 614 Tm
-(CONFORM ) Tj
-23.3952 0 0 48.75 120.95 614 Tm
-(ART. ) Tj
-26.9368 0 0 48.75 178.68 614 Tm
-(34 ) Tj
-23.6624 0 0 48.75 211.88 614 Tm
-(DIN ) Tj
-18.8065 0 0 48.75 267.81 614 Tm
-(OUG ) Tj
-35.6967 0 0 48.75 322.82 614 Tm
-(R.5/2015 ) Tj
-20.8894 0 0 48.75 465.85 614 Tm
-(IN ) Tj
-20.4065 0 0 48.75 502.95 614 Tm
-(VALOARE ) Tj
-20.3055 0 0 48.75 611.9 614 Tm
-(DE ) Tj
-31.9784 0 0 48.75 653.8 614 Tm
-(2.45 ) Tj
-0 Tc
-52.5 0 0 52.5 588.16 1704 Tm
-(1 ) Tj
--0.035 Tc
-20.8928 0 0 52.5 605.9 1704 Tm
-(BUC ) Tj
-0 Tc
-52.5 0 0 52.5 662.89 1704 Tm
-(X ) Tj
--0.035 Tc
-35.0938 0 0 52.5 689.77 1704 Tm
-(13.54-) Tj
-33.8468 0 0 52.5 789.82 1704 Tm
-(13.54 ) Tj
-0 Tc
-52.5 0 0 52.5 875.88 1704 Tm
-(A ) Tj
-57.5 0 0 57.5 589.98 1601 Tm
-(1 ) Tj
--0.035 Tc
-20.8928 0 0 57.5 605.9 1601 Tm
-(BUC ) Tj
-0 Tc
-57.5 0 0 57.5 662.88 1601 Tm
-(X ) Tj
--0.035 Tc
-30.6694 0 0 57.5 690.93 1601 Tm
-(13. ) Tj
-31.7383 0 0 57.5 734.55 1601 Tm
-(54-) Tj
-34.3374 0 0 57.5 790.8 1601 Tm
-(13.54 ) Tj
-0 Tc
-57.5 0 0 57.5 877.87 1601 Tm
-(A ) Tj
--0.035 Tc
-21.4056 0 0 41.25 242.9 422 Tm
-(DATA: ) Tj
-31.7787 0 0 41.25 324.89 422 Tm
-(11-10-2025 ) Tj
-21.6838 0 0 41.25 476.78 422 Tm
-(ORA: ) Tj
-31.5049 0 0 41.25 545.9 422 Tm
-(12:51:01 ) Tj
-33.9512 0 0 45 805.81 1555 Tm
-(186.16 ) Tj
-15.6712 0 0 45 210.98 181 Tm
-(UA ) Tj
-21.5162 0 0 45 245.89 181 Tm
-(RUGAM ) Tj
-20.6877 0 0 45 327.62 181 Tm
-(SA ) Tj
-25.0296 0 0 45 373.88 181 Tm
-(PASTRATI ) Tj
-20.1195 0 0 45 492.9 181 Tm
-(BONUL ) Tj
-35.5044 0 0 45 573.83 181 Tm
-(FISCAL! ) Tj
-20.3315 0 0 43.75 162.95 132 Tm
-(VOCEA ) Tj
-25.3923 0 0 43.75 244.73 132 Tm
-(CLIENTULU|: ) Tj
-28.2245 0 0 43.75 410.7 132 Tm
-(08008 ) Tj
-24.2548 0 0 43.75 492.88 132 Tm
-(BRICK: ) Tj
-28.9489 0 0 43.75 588.69 132 Tm
-(0800827425 ) Tj
-25.808 0 0 47.5 178.77 82 Tm
-(TIPARIT ) Tj
-23.6624 0 0 47.5 286.88 82 Tm
-(DIN ) Tj
-22.6358 0 0 47.5 341.76 82 Tm
-(COGITO ) Tj
-21.7249 0 0 47.5 438.87 82 Tm
-(ERP ) Tj
-22.3374 0 0 47.5 493.92 82 Tm
-(WW. ) Tj
-20.7218 0 0 47.5 547.78 82 Tm
-(COGI ) Tj
-24.7121 0 0 47.5 603.78 82 Tm
-(TO-ERP. ) Tj
-18.5089 0 0 47.5 698.91 82 Tm
-(RO ) Tj
-34.3506 0 0 42.5 807.8 1300 Tm
-(186.16 ) Tj
-34.4956 0 0 41.25 836.63 1251 Tm
-(0.00 ) Tj
-30.3893 0 0 37.5 821.64 1159 Tm
-(32.31 ) Tj
-29.907 0 0 35 820.64 1117 Tm
-(32.31 ) Tj
-29.0126 0 0 52.5 450.66 463 Tm
-(90002214982025101112510101460171 ) Tj
-24.6196 0 0 30 863.14 1080 Tm
-(17 ) Tj
-19.7698 0 0 42.5 725.08 516 Tm
-(NR. ) Tj
-22.618 0 0 42.5 765.95 516 Tm
-(ANEF: ) Tj
-25.0882 0 0 42.5 835.73 516 Tm
-(0001 ) Tj
-26.5139 0 0 55 741.76 363 Tm
-(TD:00029241 ) Tj
-23.8351 0 0 43.75 781.75 319 Tm
-(CASIER ) Tj
-0 Tc
-43.75 0 0 43.75 878.47 319 Tm
-(1 ) Tj
-ET
- Q
-endstream
-endobj
-5 0 obj
-<>stream
- JFIF
-
-
++&.%#%.&D5//5DNB>BN_UU_wqw
-
-
++&.%#%.&D5//5DNB>BN_UU_wqw P" 1 (l@8@&n.e&S0R@ @&Ra@38N ` # @`&ȃ"N4441b&ԲDd@"biĀ E"U8ܵ&2#F,I
-`BRTQi $1 0)(q Bh b`$H@ !)*8@LӠj `r&ЁPf͠粨]vN:Èh llJ4CB`&
-I DBj:L$I Tc04@*I@2`& (l@ "Lb A
-i(Hhd6 @dY&j44K HDi&n#IH%@*i,TD` 2Qp !( h@Mi D 0lpbY!<dZ*dg*i{OP_B
-]Xb 4
0HE (8j
S!0 @FB$8@dR4,L %$ # dIC4
1Ȕ *Lch+vjƾU-z$ 0bQŃi
B-0@@ Q`6Dh ` C "ڠ BpІb h000p LW%؍}#c5
R@
-L@ %$
iSp8F 4M@!)DI: a89D &%$` Fi +&c_[12L J#d !I&
` 4k=iWzOжkh Ӷ}k$(v=K.ƥaCwnUפao5[X7١\EU m p,(H%(`@&FI,$@) `&