Integrate shared JWT authentication into data-entry-app: - Add Oracle pool initialization for auth service - Add AuthenticationMiddleware to protect API routes - Update all receipt endpoints to use CurrentUser from JWT - Add shared auth router (/api/auth/login, /api/auth/refresh) Add nomenclature synchronization feature: - Create SQLite models for synced suppliers, local suppliers, and cash registers - Add nomenclature router with sync triggers and CRUD endpoints - Add sync service for Oracle → SQLite nomenclature data - Update nomenclature_service to use synced SQLite data with fallbacks Create shared frontend components: - Add shared/frontend/ with LoginView.vue, auth store factory, login.css - Integrate shared login and auth into data-entry-app frontend - Add axios-based API service with token refresh interceptor 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
456 lines
13 KiB
Markdown
456 lines
13 KiB
Markdown
# Implementation Summary: Nomenclature Sync (FAZA 3)
|
|
|
|
**Date**: 2025-12-13
|
|
**Status**: COMPLETED
|
|
**Developer**: Claude Code
|
|
|
|
---
|
|
|
|
## Overview
|
|
|
|
Successfully implemented FAZA 3: Nomenclature Sync for the data-entry-app. This feature enables the application to maintain a local SQLite cache of nomenclatures (suppliers, cash registers) from Oracle, reducing latency and improving performance.
|
|
|
|
## Files Created
|
|
|
|
### 1. Models
|
|
**File**: `/app/db/models/nomenclature.py`
|
|
- `SyncedSupplier` - Suppliers synced from Oracle NOM_PARTENERI
|
|
- `LocalSupplier` - Suppliers created locally from OCR (not yet in Oracle)
|
|
- `SyncedCashRegister` - Cash registers and bank accounts synced from Oracle
|
|
|
|
### 2. Service Layer
|
|
**File**: `/app/services/sync_service.py`
|
|
- `SyncService.sync_suppliers()` - Sync suppliers from Oracle to SQLite
|
|
- `SyncService.sync_cash_registers()` - Sync cash registers from Oracle to SQLite
|
|
- `SyncService.search_supplier()` - Search in synced + local suppliers
|
|
- `SyncService.create_local_supplier()` - Create local supplier from OCR data
|
|
- `SyncService.get_all_suppliers()` - Get all suppliers for dropdown
|
|
- `SyncService.get_all_cash_registers()` - Get all cash registers for dropdown
|
|
- `SyncService.get_schema_for_company()` - Map company ID to Oracle schema
|
|
|
|
**Company-to-Schema Mapping**:
|
|
```python
|
|
COMPANY_SCHEMAS = {
|
|
1: "CONTAFIN",
|
|
2: "CONTAFIN2",
|
|
}
|
|
```
|
|
> **TODO**: Move to config table or environment variable
|
|
|
|
### 3. API Router
|
|
**File**: `/app/routers/nomenclature.py`
|
|
|
|
New endpoints:
|
|
- `GET /api/nomenclature/suppliers` - Get all suppliers (synced + local)
|
|
- `GET /api/nomenclature/suppliers/search` - Search supplier by fiscal code or name
|
|
- `POST /api/nomenclature/suppliers/local` - Create local supplier from OCR
|
|
- `GET /api/nomenclature/cash-registers` - Get all cash registers
|
|
- `POST /api/nomenclature/sync/suppliers` - Manual supplier sync
|
|
- `POST /api/nomenclature/sync/cash-registers` - Manual cash register sync
|
|
- `POST /api/nomenclature/sync/all` - Sync all nomenclatures
|
|
|
|
### 4. Database Migration
|
|
**File**: `/migrations/versions/20251213_002805_add_nomenclature_tables.py`
|
|
- Creates `synced_suppliers` table with indexes
|
|
- Creates `local_suppliers` table with indexes
|
|
- Creates `synced_cash_registers` table with indexes
|
|
|
|
**Applied**: Yes (migration revision: 3a653da79002)
|
|
|
|
### 5. Documentation
|
|
**File**: `NOMENCLATURE_SYNC.md`
|
|
- Complete implementation guide
|
|
- Architecture overview
|
|
- API reference
|
|
- Usage examples
|
|
- Troubleshooting guide
|
|
|
|
**File**: `IMPLEMENTATION_SUMMARY_NOMENCLATURE_SYNC.md` (this file)
|
|
- Implementation summary
|
|
- Files changed
|
|
- Testing checklist
|
|
|
|
## Files Modified
|
|
|
|
### 1. `/app/db/models/__init__.py`
|
|
**Change**: Added imports for nomenclature models
|
|
```python
|
|
from .nomenclature import SyncedSupplier, LocalSupplier, SyncedCashRegister
|
|
```
|
|
|
|
### 2. `/app/services/nomenclature_service.py`
|
|
**Changes**:
|
|
- Updated `get_partners()` to accept optional `session` parameter
|
|
- Added SQLite fallback: returns synced/local suppliers if available
|
|
- Falls back to mock data if no synced data
|
|
- Updated `get_cash_registers()` to accept optional `session` parameter
|
|
- Added SQLite fallback for cash registers
|
|
|
|
### 3. `/app/routers/receipts.py`
|
|
**Changes**:
|
|
- Updated `get_partners()` endpoint to pass `session` to service
|
|
- Updated `get_cash_registers()` endpoint to pass `session` to service
|
|
|
|
### 4. `/app/routers/__init__.py`
|
|
**Change**: Added nomenclature router to exports
|
|
```python
|
|
from . import receipts, nomenclature
|
|
__all__ = ["receipts", "nomenclature"]
|
|
```
|
|
|
|
### 5. `/app/main.py`
|
|
**Change**: Registered nomenclature router
|
|
```python
|
|
from app.routers import receipts, ocr, nomenclature
|
|
app.include_router(nomenclature.router, prefix="/api/nomenclature", tags=["nomenclature"])
|
|
```
|
|
|
|
## Database Schema
|
|
|
|
### synced_suppliers
|
|
```sql
|
|
CREATE TABLE synced_suppliers (
|
|
id INTEGER PRIMARY KEY,
|
|
oracle_id INTEGER NOT NULL,
|
|
company_id INTEGER NOT NULL,
|
|
name VARCHAR(200) NOT NULL,
|
|
fiscal_code VARCHAR(50),
|
|
address VARCHAR(500),
|
|
synced_at DATETIME NOT NULL
|
|
);
|
|
CREATE INDEX ix_synced_suppliers_oracle_id ON synced_suppliers(oracle_id);
|
|
CREATE INDEX ix_synced_suppliers_company_id ON synced_suppliers(company_id);
|
|
CREATE INDEX ix_synced_suppliers_fiscal_code ON synced_suppliers(fiscal_code);
|
|
```
|
|
|
|
### local_suppliers
|
|
```sql
|
|
CREATE TABLE local_suppliers (
|
|
id INTEGER PRIMARY KEY,
|
|
company_id INTEGER NOT NULL,
|
|
name VARCHAR(200) NOT NULL,
|
|
fiscal_code VARCHAR(50),
|
|
address VARCHAR(500),
|
|
created_by VARCHAR(100) NOT NULL,
|
|
created_at DATETIME NOT NULL,
|
|
pending_oracle_sync BOOLEAN NOT NULL
|
|
);
|
|
CREATE INDEX ix_local_suppliers_company_id ON local_suppliers(company_id);
|
|
CREATE INDEX ix_local_suppliers_fiscal_code ON local_suppliers(fiscal_code);
|
|
```
|
|
|
|
### synced_cash_registers
|
|
```sql
|
|
CREATE TABLE synced_cash_registers (
|
|
id INTEGER PRIMARY KEY,
|
|
oracle_id INTEGER NOT NULL,
|
|
company_id INTEGER NOT NULL,
|
|
name VARCHAR(100) NOT NULL,
|
|
account_code VARCHAR(20) NOT NULL,
|
|
register_type VARCHAR(10) NOT NULL,
|
|
synced_at DATETIME NOT NULL
|
|
);
|
|
CREATE INDEX ix_synced_cash_registers_oracle_id ON synced_cash_registers(oracle_id);
|
|
CREATE INDEX ix_synced_cash_registers_company_id ON synced_cash_registers(company_id);
|
|
```
|
|
|
|
## API Endpoints Summary
|
|
|
|
### Nomenclature Endpoints
|
|
|
|
#### GET /api/nomenclature/suppliers
|
|
Get all suppliers (synced + local) for dropdown/autocomplete.
|
|
|
|
**Query Params**:
|
|
- `search` (optional) - Filter by name or fiscal code
|
|
- `company_id` (optional) - Company ID (defaults to user's first company)
|
|
|
|
**Response**:
|
|
```json
|
|
[
|
|
{
|
|
"id": 1,
|
|
"oracle_id": 123,
|
|
"name": "OMV Petrom",
|
|
"fiscal_code": "RO123456",
|
|
"source": "synced"
|
|
},
|
|
{
|
|
"id": 2,
|
|
"name": "Local Supplier SRL",
|
|
"fiscal_code": "RO789012",
|
|
"source": "local"
|
|
}
|
|
]
|
|
```
|
|
|
|
#### GET /api/nomenclature/suppliers/search
|
|
Search for supplier by fiscal code or name.
|
|
|
|
**Query Params**:
|
|
- `fiscal_code` (optional) - Fiscal code to search
|
|
- `name` (optional) - Name to search (partial match)
|
|
- `company_id` (optional) - Company ID
|
|
|
|
**Response**:
|
|
```json
|
|
{
|
|
"found": true,
|
|
"supplier": {
|
|
"id": 1,
|
|
"oracle_id": 123,
|
|
"name": "OMV Petrom",
|
|
"fiscal_code": "RO123456",
|
|
"address": "Str. Example 123"
|
|
},
|
|
"source": "synced"
|
|
}
|
|
```
|
|
|
|
#### POST /api/nomenclature/suppliers/local
|
|
Create a local supplier from OCR data.
|
|
|
|
**Body**:
|
|
```json
|
|
{
|
|
"name": "New Supplier SRL",
|
|
"fiscal_code": "RO12345678",
|
|
"address": "Str. Example 123"
|
|
}
|
|
```
|
|
|
|
**Response**:
|
|
```json
|
|
{
|
|
"id": 5,
|
|
"name": "New Supplier SRL",
|
|
"fiscal_code": "RO12345678",
|
|
"address": "Str. Example 123",
|
|
"is_local": true
|
|
}
|
|
```
|
|
|
|
#### GET /api/nomenclature/cash-registers
|
|
Get all cash registers for a company.
|
|
|
|
**Query Params**:
|
|
- `company_id` (optional) - Company ID
|
|
|
|
**Response**:
|
|
```json
|
|
[
|
|
{
|
|
"id": 1,
|
|
"oracle_id": 10,
|
|
"name": "Casa principala",
|
|
"account_code": "5311",
|
|
"register_type": "cash"
|
|
},
|
|
{
|
|
"id": 2,
|
|
"oracle_id": 20,
|
|
"name": "Cont BCR",
|
|
"account_code": "5121",
|
|
"register_type": "bank"
|
|
}
|
|
]
|
|
```
|
|
|
|
#### POST /api/nomenclature/sync/suppliers
|
|
Manually trigger supplier sync from Oracle.
|
|
|
|
**Response**:
|
|
```json
|
|
{
|
|
"synced": 150,
|
|
"errors": 0,
|
|
"message": "Synced 150 suppliers with 0 errors"
|
|
}
|
|
```
|
|
|
|
#### POST /api/nomenclature/sync/cash-registers
|
|
Manually trigger cash register sync from Oracle.
|
|
|
|
**Response**:
|
|
```json
|
|
{
|
|
"synced": 5,
|
|
"errors": 0,
|
|
"message": "Synced 5 cash registers with 0 errors"
|
|
}
|
|
```
|
|
|
|
#### POST /api/nomenclature/sync/all
|
|
Sync all nomenclatures (suppliers + cash registers).
|
|
|
|
**Response**:
|
|
```json
|
|
{
|
|
"suppliers": {
|
|
"synced": 150,
|
|
"errors": 0
|
|
},
|
|
"cash_registers": {
|
|
"synced": 5,
|
|
"errors": 0
|
|
},
|
|
"total_synced": 155,
|
|
"total_errors": 0,
|
|
"message": "Synced 150 suppliers and 5 cash registers"
|
|
}
|
|
```
|
|
|
|
## Testing Checklist
|
|
|
|
### Unit Tests
|
|
- [ ] Test `SyncService.sync_suppliers()` with mock Oracle data
|
|
- [ ] Test `SyncService.sync_cash_registers()` with mock Oracle data
|
|
- [ ] Test `SyncService.search_supplier()` for synced suppliers
|
|
- [ ] Test `SyncService.search_supplier()` for local suppliers
|
|
- [ ] Test `SyncService.create_local_supplier()`
|
|
- [ ] Test upsert logic (update existing vs insert new)
|
|
|
|
### Integration Tests
|
|
- [ ] Test nomenclature router endpoints with authentication
|
|
- [ ] Test `/api/nomenclature/suppliers` endpoint
|
|
- [ ] Test `/api/nomenclature/suppliers/search` endpoint
|
|
- [ ] Test `/api/nomenclature/suppliers/local` endpoint
|
|
- [ ] Test `/api/nomenclature/cash-registers` endpoint
|
|
- [ ] Test `/api/nomenclature/sync/suppliers` endpoint
|
|
- [ ] Test `/api/nomenclature/sync/all` endpoint
|
|
|
|
### Manual Testing
|
|
- [ ] Start backend: `uvicorn app.main:app --reload --port 8003`
|
|
- [ ] Verify `/docs` shows new nomenclature endpoints
|
|
- [ ] Test sync endpoint (requires Oracle connection)
|
|
- [ ] Test search endpoint with various queries
|
|
- [ ] Test create local supplier endpoint
|
|
- [ ] Verify existing `/api/receipts/nomenclature/partners` still works
|
|
- [ ] Verify existing `/api/receipts/nomenclature/cash-registers` still works
|
|
|
|
### Oracle Connection Testing
|
|
- [ ] Verify SSH tunnel is running (dev/Linux)
|
|
- [ ] Test Oracle connection via health endpoint
|
|
- [ ] Verify company schema mapping is correct
|
|
- [ ] Test sync with real Oracle data
|
|
- [ ] Verify table names match actual Oracle schema
|
|
|
|
### Error Handling Testing
|
|
- [ ] Test sync with invalid company ID
|
|
- [ ] Test sync with Oracle connection error
|
|
- [ ] Test search with no results
|
|
- [ ] Test create local supplier with duplicate fiscal code
|
|
- [ ] Test endpoints with missing authentication token
|
|
|
|
## Dependencies
|
|
|
|
All required dependencies are already in `requirements.txt`:
|
|
- `oracledb>=2.0.1` - Oracle database connection
|
|
- `sqlmodel>=0.0.14` - ORM for SQLite
|
|
- `alembic>=1.13.1` - Database migrations
|
|
|
|
## Deployment Notes
|
|
|
|
### Development
|
|
1. Ensure SSH tunnel is running: `./ssh_tunnel.sh start`
|
|
2. Apply migration: `alembic upgrade head`
|
|
3. Run initial sync: `POST /api/nomenclature/sync/all`
|
|
4. Start backend: `uvicorn app.main:app --reload --port 8003`
|
|
|
|
### Production
|
|
1. Update `COMPANY_SCHEMAS` in `sync_service.py` with production mappings
|
|
2. Apply migration: `alembic upgrade head`
|
|
3. Set up cron job for periodic sync (daily recommended)
|
|
4. Configure Oracle connection (no SSH tunnel needed on Windows prod)
|
|
|
|
### Migration Commands
|
|
```bash
|
|
# Check current version
|
|
alembic current
|
|
|
|
# Apply migration
|
|
alembic upgrade head
|
|
|
|
# Rollback migration
|
|
alembic downgrade -1
|
|
|
|
# View migration history
|
|
alembic history
|
|
```
|
|
|
|
## Known Issues / TODOs
|
|
|
|
1. **Company Schema Mapping**: Currently hardcoded in `sync_service.py`
|
|
- TODO: Move to config table or environment variable
|
|
|
|
2. **Oracle Table Names**: Assumes `NOM_PARTENERI` and `NOM_CASE` exist
|
|
- TODO: Verify actual table names in production Oracle schema
|
|
- TODO: Add error handling for missing tables
|
|
|
|
3. **Sync Scheduling**: No automatic periodic sync implemented
|
|
- TODO: Add background task or cron job for daily sync
|
|
|
|
4. **Conflict Resolution**: No logic to handle local supplier matching synced supplier
|
|
- TODO: Implement merge logic when OCR supplier matches Oracle supplier
|
|
|
|
5. **Bidirectional Sync**: Local suppliers not pushed to Oracle
|
|
- TODO: Implement sync from SQLite to Oracle for approved local suppliers
|
|
|
|
6. **Performance**: Sync loads all records at once
|
|
- TODO: Implement batch processing for large datasets
|
|
- TODO: Add incremental sync (requires Oracle last_modified timestamp)
|
|
|
|
7. **Validation**: No validation for duplicate fiscal codes
|
|
- TODO: Add uniqueness constraint and conflict resolution
|
|
|
|
8. **Testing**: No unit tests written yet
|
|
- TODO: Add comprehensive test suite
|
|
|
|
## Success Criteria
|
|
|
|
✅ **Completed**:
|
|
- SQLite tables created for synced nomenclatures
|
|
- Sync service implemented with Oracle integration
|
|
- API endpoints for sync and query operations
|
|
- Updated existing nomenclature service to use synced data
|
|
- Database migration created and applied
|
|
- All files have correct Python syntax
|
|
- Documentation created
|
|
|
|
⏳ **Pending**:
|
|
- Unit tests
|
|
- Integration tests
|
|
- Manual testing with real Oracle data
|
|
- Production deployment
|
|
- Scheduled sync setup
|
|
|
|
## Next Steps
|
|
|
|
1. **Testing Phase**:
|
|
- Write unit tests for sync service
|
|
- Write integration tests for API endpoints
|
|
- Manual testing with real Oracle connection
|
|
- Performance testing with large datasets
|
|
|
|
2. **Production Readiness**:
|
|
- Update company schema mappings for production
|
|
- Verify Oracle table names
|
|
- Set up cron job for periodic sync
|
|
- Add monitoring and alerting
|
|
|
|
3. **Enhancements**:
|
|
- Implement scheduled background sync
|
|
- Add sync status dashboard in frontend
|
|
- Implement conflict resolution
|
|
- Add bidirectional sync (SQLite → Oracle)
|
|
|
|
## Related Documentation
|
|
|
|
- Complete Guide: `NOMENCLATURE_SYNC.md`
|
|
- Architecture: `docs/data-entry/ARCHITECTURE.md`
|
|
- API Docs: Available at `/docs` when app is running
|
|
|
|
---
|
|
|
|
**Implementation completed successfully!** All core features are in place and ready for testing.
|