Files
roa2web-service-auto/docs/data-entry/DATA-ENTRY-MODULE.md
Claude Agent b137e80b71 feat: multi-Oracle server support with runtime switching
Complete implementation of multi-server Oracle database support:

Backend:
- Multi-pool Oracle with lazy loading per server
- Email-to-server cache for automatic server discovery
- JWT tokens include server_id claim
- /auth/check-identity and /auth/check-email endpoints
- /auth/my-servers endpoint for listing user's accessible servers
- Server switch with password re-authentication

Frontend:
- New ServerSelector component for header dropdown
- Multi-step login flow (identity → server → password)
- Server switching from header with password modal
- Mobile drawer menu with server selection
- Dark mode support for all new components
- URL bookmark support with ?server= query param

Scripts:
- Unified start.sh replacing start-prod.sh/start-test.sh
- Unified ssh-tunnel.sh with multi-server support
- Updated status.sh for new architecture

Tests:
- E2E tests for multi-server and single-server login flows
- Backend unit tests for all new endpoints
- Oracle multi-pool integration tests

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-26 22:39:06 +00:00

595 lines
12 KiB
Markdown

# Data Entry Module - Technical Reference
> **Part of ROA2WEB Ultrathin Monolith** - See `CLAUDE.md` for general architecture
## Module Overview
**Purpose:** Introducere date în ERP (bonuri fiscale, chitanțe) cu workflow de aprobare
**Location:**
- Backend: `backend/modules/data_entry/`
- Frontend: `src/modules/data-entry/`
- Database: `backend/modules/data/receipts/receipts.db` (SQLite)
- Uploads: `backend/modules/data/receipts/uploads/`
**API Base Path:** `/api/data-entry/`
---
## Technical Stack
**Backend:**
- **ORM:** SQLModel (Pydantic + SQLAlchemy)
- **Migrations:** Alembic
- **Database:** SQLite (Phase 1) → Oracle integration (Phase 2)
- **Validation:** Pydantic v2
**Frontend:**
- **Framework:** Vue.js 3 Composition API
- **UI Library:** PrimeVue
- **State:** Pinia stores
- **Routing:** Vue Router (integrated with main app)
---
## Workflow States
```
1. DRAFT
↓ User completes form + uploads receipt photo
2. PENDING_REVIEW
↓ System auto-generates accounting entries
3. APPROVED / REJECTED
↓ Accountant approves or rejects
↓ (if rejected, user can resubmit)
4. SYNCED
└→ (Phase 2) Data synced to Oracle ERP
```
### State Transitions
| From | To | Allowed Action | Who |
|------|----|----|-----|
| DRAFT | PENDING_REVIEW | Submit for review | User |
| DRAFT | DRAFT | Update/Delete | User |
| PENDING_REVIEW | APPROVED | Approve | Accountant |
| PENDING_REVIEW | REJECTED | Reject | Accountant |
| REJECTED | PENDING_REVIEW | Resubmit | User |
| APPROVED | SYNCED | Sync to Oracle | System (Phase 2) |
---
## Database Schema (SQLite)
### Main Tables
**receipts** - Receipt header
- `id` (PK)
- `receipt_number` - Document number
- `receipt_date` - Document date
- `partner_id` - Supplier ID (from Oracle)
- `partner_name` - Supplier name
- `partner_cui` - Supplier CUI/CIF
- `receipt_type` - BON/CHITANTA/FACTURA
- `operation_type` - EXPENSE/INCOME
- `total_amount` - Total with VAT
- `vat_amount` - VAT amount
- `payment_method` - CASH/CARD/BANK
- `payment_amount` - Amount paid
- `expense_type_code` - Expense type (FUEL, MATERIALS, etc.)
- `description` - Notes
- `status` - DRAFT/PENDING/APPROVED/REJECTED/SYNCED
- `rejection_reason` - If rejected
- `created_by` - Username
- `approved_by` - Accountant username
- `company_id` - Company ID from Oracle
**attachments** - Receipt photos/PDFs
- `id` (PK)
- `receipt_id` (FK)
- `filename` - Original filename
- `file_path` - Storage path
- `mime_type` - image/*, application/pdf
- `file_size` - Bytes
- `uploaded_at`
**accounting_entries** - Auto-generated entries
- `id` (PK)
- `receipt_id` (FK)
- `account_code` - Cont contabil (e.g., "6022")
- `debit_amount` - Debit
- `credit_amount` - Credit
- `description` - Entry description
- `entry_order` - Sequence number
### Migrations
**Location:** `backend/modules/data_entry/migrations/`
```bash
# Create new migration
cd backend/modules/data_entry
alembic revision --autogenerate -m "description"
# Apply migrations
alembic upgrade head
# Rollback
alembic downgrade -1
```
**Important:** Migrations run automatically on module startup via `lifespan` event in `main.py`.
---
## Expense Types (Hardcoded - Phase 1)
| Code | Type | Account | VAT | Description |
|------|------|---------|-----|-------------|
| `FUEL` | Combustibil | 6022 | 19% | Combustibil auto |
| `MATERIALS` | Materiale | 6028 | 19% | Materiale consumabile |
| `OFFICE` | Rechizite | 6024 | 19% | Rechizite birou |
| `PHONE` | Telefonie | 626 | 19% | Servicii telefonie |
| `PARKING` | Parcare | 6022 | 19% | Taxe parcare |
| `FOOD` | Alimentație | 6028 | 0% | Alimente (fără TVA) |
| `TRANSPORT` | Transport | 624 | 19% | Transport marfă |
| `OTHER` | Altele | 628 | 19% | Alte cheltuieli |
**Auto-generation Logic:**
When user submits receipt, system automatically generates:
```python
# Entry 1: Expense account (Debit)
account_code = expense_type.account # e.g., "6022"
debit_amount = total_amount - vat_amount
credit_amount = 0
# Entry 2: VAT account (Debit) - if VAT > 0
account_code = "4426" # TVA deductibilă
debit_amount = vat_amount
credit_amount = 0
# Entry 3: Payment account (Credit)
account_code = cash_register.account # e.g., "5311"
debit_amount = 0
credit_amount = total_amount
```
---
## Oracle Nomenclatures (Read-Only)
Module uses shared Oracle connection for nomenclatures:
### Partners (Suppliers)
**Query:**
```sql
SELECT B.ID_PART, B.DENUMIRE, B.COD_FISCAL, B.ADRESA
FROM {schema}.CORESP_TIP_PART A
INNER JOIN {schema}.VNOM_PARTENERI B ON A.ID_PART = B.ID_PART
WHERE A.ID_TIP_PART = 17 -- Furnizori
AND (B.INACTIV = 0 OR B.INACTIV IS NULL)
ORDER BY B.DENUMIRE
```
**Partner Types** (`id_tip_part`):
- `17` - Furnizori (Suppliers)
- `22` - Casa LEI (Cash LEI)
- `23` - Casa Valută (Cash FX)
- `24` - Bancă LEI (Bank LEI)
- `25` - Bancă Valută (Bank FX)
### Cash Registers & Bank Accounts
**Query:**
```sql
SELECT B.ID_PART, B.DENUMIRE, B.COD_FISCAL, B.CONT
FROM {schema}.CORESP_TIP_PART A
INNER JOIN {schema}.VNOM_PARTENERI B ON A.ID_PART = B.ID_PART
WHERE A.ID_TIP_PART IN (22, 23, 24, 25)
AND (B.INACTIV = 0 OR B.INACTIV IS NULL)
ORDER BY B.DENUMIRE
```
**Account Mapping:**
- Casa LEI → Account `5311`
- Casa Valută → Account `5314`
- Bancă LEI → Account `5121`
- Bancă Valută → Account `5124`
---
## Oracle Integration (Phase 2)
### Sync Process
When receipt is APPROVED, sync to Oracle using stored procedures:
```sql
-- 1. Initialize
EXEC {schema}.pack_contafin.init_scriere_act_rul_local(
p_id_utilizator => :user_id,
p_id_baza => :company_id,
p_id_luna => :month_id
);
-- 2. Insert temp document
INSERT INTO {schema}.ACT_TEMP (
ID_ACT, DATA_ACT, NR_DOCUMENT, ID_PART,
TIP_ACT, STARE_ACT, ...
) VALUES (
:act_id, :receipt_date, :receipt_number, :partner_id,
'CUMPARARE', 'TEMP', ...
);
-- 3. Insert temp entries
INSERT INTO {schema}.ACT_TEMP_RUL (
ID_ACT, CONT, SUMA_D, SUMA_C, EXPLICATIE
) VALUES (
:act_id, :account_code, :debit, :credit, :description
);
-- 4. Finalize
EXEC {schema}.pack_contafin.finalizeaza_scriere_act_rul(
p_id_utilizator => :user_id,
p_id_act => :act_id
);
```
**Stored Procedures:**
- `pack_contafin.init_scriere_act_rul_local()` - Initialize transaction
- `pack_contafin.finalizeaza_scriere_act_rul()` - Commit to permanent tables
- `pack_contafin.get_next_act_id()` - Get next document ID
**Reference:** See `docs/PACK_CONTAFIN.pck` for full stored procedure definitions.
---
## API Endpoints
### Receipts CRUD
**List Receipts**
```
GET /api/data-entry/receipts/
Query params: page, page_size, status, search, date_from, date_to
```
**Get Receipt**
```
GET /api/data-entry/receipts/{id}
```
**Create Receipt**
```
POST /api/data-entry/receipts/
Body: ReceiptCreate schema
```
**Update Receipt** (DRAFT only)
```
PUT /api/data-entry/receipts/{id}
Body: ReceiptUpdate schema
```
**Delete Receipt** (DRAFT only)
```
DELETE /api/data-entry/receipts/{id}
```
### Workflow Actions
**Submit for Review**
```
POST /api/data-entry/receipts/{id}/submit
Status: DRAFT → PENDING_REVIEW
Auto-generates accounting entries
```
**Approve**
```
POST /api/data-entry/receipts/{id}/approve
Body: { "notes": "Optional approval notes" }
Status: PENDING_REVIEW → APPROVED
```
**Reject**
```
POST /api/data-entry/receipts/{id}/reject
Body: { "reason": "Rejection reason (required)" }
Status: PENDING_REVIEW → REJECTED
```
**Resubmit** (after rejection)
```
POST /api/data-entry/receipts/{id}/resubmit
Status: REJECTED → PENDING_REVIEW
```
### Attachments
**Upload**
```
POST /api/data-entry/receipts/{id}/attachments
Content-Type: multipart/form-data
Field: file (image/*, application/pdf, max 10MB)
```
**Download**
```
GET /api/data-entry/attachments/{id}/download
Response: File stream
```
**Delete**
```
DELETE /api/data-entry/attachments/{id}
```
### Nomenclatures
**Partners** (from Oracle)
```
GET /api/data-entry/receipts/nomenclature/partners
Query: search (optional)
```
**Cash Registers** (from Oracle)
```
GET /api/data-entry/receipts/nomenclature/cash-registers
```
**Expense Types** (hardcoded)
```
GET /api/data-entry/receipts/nomenclature/expense-types
```
**Statistics**
```
GET /api/data-entry/receipts/stats
Response: { total, by_status: { DRAFT: N, ... } }
```
---
## Frontend Components
### Views
**Location:** `src/modules/data-entry/views/`
- `ReceiptListView.vue` - Main list with filters and status tabs
- `ReceiptCreateView.vue` - Create/edit form
- `ReceiptDetailView.vue` - View receipt details (future)
### Key Components
**Location:** `src/modules/data-entry/components/`
- `ReceiptForm.vue` - Main form component
- `AttachmentUpload.vue` - Drag & drop file upload
- `AccountingEntriesTable.vue` - Preview generated entries
- `WorkflowActions.vue` - Approve/Reject buttons
### Stores
**Location:** `src/modules/data-entry/stores/`
- `receiptsStore.js` - Main receipts CRUD
- `nomenclatureStore.js` - Partners, cash registers, expense types
---
## Common Issues
### 1. Nomenclatoare goale / furnizori lipsă
**Symptom:** Partner dropdown is empty
**Causes:**
- Wrong Oracle server (PROD vs TEST)
- Selected company doesn't exist on current server
- No suppliers configured for company
**Solution:**
```bash
# Check SSH tunnel status (for servers requiring SSH)
./ssh-tunnel.sh status
# Restart with correct environment
./start.sh prod # or ./start.sh test (test uses direct connection)
```
### 2. SQLite locked errors
**Symptom:** `database is locked`
**Causes:**
- Multiple backend instances accessing same DB
- Alembic migration in progress
**Solution:**
```bash
# Check for multiple processes
ps aux | grep uvicorn
# Kill duplicates, restart
./start.sh prod restart
```
### 3. Upload fails
**Symptom:** 500 error on file upload
**Causes:**
- File too large (>10MB)
- Wrong MIME type
- Upload directory permissions
**Solution:**
```bash
# Check upload directory
ls -la backend/modules/data/receipts/uploads/
# Create if missing
mkdir -p backend/modules/data/receipts/uploads/
chmod 755 backend/modules/data/receipts/uploads/
```
### 4. Migration errors
**Symptom:** Alembic fails on startup
**Solution:**
```bash
cd backend/modules/data_entry
# Check migration status
alembic current
# Rollback one version
alembic downgrade -1
# Re-apply
alembic upgrade head
```
---
## Testing
### Backend Tests
```bash
cd backend
pytest modules/data_entry/tests/
```
### Frontend Tests
```bash
cd src/modules/data-entry
npm run test
```
### Manual Testing
**Test Workflow:**
1. Login as user (e.g., MARIUS M)
2. Create receipt → status DRAFT
3. Upload attachment
4. Submit → status PENDING_REVIEW
5. Login as accountant
6. Approve/Reject
7. Verify accounting entries generated correctly
---
## Development Workflow
### 1. Start Development Environment
```bash
# Start unified monolith (backend + frontend)
./start.sh prod # Production Oracle server
# OR
./start.sh test # Test Oracle server
```
### 2. Make Changes
**Backend:**
- Edit files in `backend/modules/data_entry/`
- Uvicorn auto-reloads on file changes
**Frontend:**
- Edit files in `src/modules/data-entry/`
- Vite HMR auto-updates browser
### 3. Database Changes
```bash
cd backend/modules/data_entry
# Generate migration
alembic revision --autogenerate -m "Add new field"
# Review migration file
nano migrations/versions/xxx_add_new_field.py
# Apply
alembic upgrade head
```
### 4. Testing
```bash
# Backend
cd backend
pytest modules/data_entry/tests/ -v
# Frontend
cd src/modules/data-entry
npm run test
```
---
## Production Deployment
See `deployment/windows/` for Windows deployment scripts.
**Module Activation:**
Enabled via `.env` flag:
```env
MODULE_DATA_ENTRY_ENABLED=true
```
**Database Location:**
- Development: `backend/modules/data/receipts/receipts.db`
- Production: `C:\inetpub\wwwroot\roa2web\backend\modules\data\receipts\receipts.db`
**Uploads Location:**
- Development: `backend/modules/data/receipts/uploads/`
- Production: `C:\inetpub\wwwroot\roa2web\backend\modules\data\receipts\uploads\`
---
## Future Enhancements (Phase 2)
1. **Oracle Sync Implementation**
- Complete `sync_to_oracle()` function
- Error handling and retry logic
- Sync status tracking
2. **OCR Integration**
- Automatic data extraction from photos
- Pre-fill form fields
- Confidence scores
3. **Approval Notifications**
- Email notifications to accountants
- User notifications on approval/rejection
- Telegram bot integration
4. **Bulk Operations**
- Batch approve/reject
- Bulk upload
- Excel import
5. **Advanced Reporting**
- Expense analytics by type
- Supplier spending reports
- Monthly summaries
---
**Last Updated:** 2025-12-29
**Architecture:** Ultrathin Monolith (integrated module)