diff --git a/app/billing/frontend/supplier_invoices.html b/app/billing/frontend/supplier_invoices.html index b46103b..946fd8c 100644 --- a/app/billing/frontend/supplier_invoices.html +++ b/app/billing/frontend/supplier_invoices.html @@ -1238,9 +1238,16 @@ async function editInvoiceFull(invoiceId) { const pdfTextView = document.getElementById('pdfTextView'); const pdfViewer = document.getElementById('manualEntryPdfViewer'); - if (invoice.notes && (invoice.notes.includes('file_id') || invoice.notes.includes('fil ID'))) { + if (invoice.notes) { // Try multiple patterns: "file_id: 4" or "fil ID 13" or "file ID 13" - const match = invoice.notes.match(/file[_\s]id[:\s]+(\d+)/i); + let match = invoice.notes.match(/file_id:\s*(\d+)/i); + if (!match) { + match = invoice.notes.match(/fil\s+ID\s+(\d+)/i); + } + if (!match) { + match = invoice.notes.match(/file\s+ID\s+(\d+)/i); + } + if (match) { const fileId = match[1]; console.log('📄 Found file_id:', fileId); diff --git a/app/core/config.py b/app/core/config.py index 0ecbca8..2c658d1 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -76,6 +76,11 @@ class Settings(BaseSettings): VTIGER_USERNAME: str = "" VTIGER_API_KEY: str = "" + # Data Consistency Settings + VTIGER_SYNC_ENABLED: bool = True + ECONOMIC_SYNC_ENABLED: bool = True + AUTO_CHECK_CONSISTENCY: bool = True + # Time Tracking Module Settings TIMETRACKING_DEFAULT_HOURLY_RATE: float = 1200.00 TIMETRACKING_AUTO_ROUND: bool = True diff --git a/app/customers/backend/router.py b/app/customers/backend/router.py index e66a170..3163a29 100644 --- a/app/customers/backend/router.py +++ b/app/customers/backend/router.py @@ -12,6 +12,7 @@ import logging from app.core.database import execute_query, execute_query_single from app.services.cvr_service import get_cvr_service from app.services.customer_activity_logger import CustomerActivityLogger +from app.services.customer_consistency import CustomerConsistencyService logger = logging.getLogger(__name__) @@ -474,6 +475,101 @@ async def update_customer(customer_id: int, update: CustomerUpdate): raise HTTPException(status_code=500, detail=str(e)) +@router.get("/customers/{customer_id}/data-consistency") +async def check_customer_data_consistency(customer_id: int): + """ + 🔍 Check data consistency across Hub, vTiger, and e-conomic + + Returns discrepancies found between the three systems + """ + try: + from app.core.config import settings + + if not settings.AUTO_CHECK_CONSISTENCY: + return { + "enabled": False, + "message": "Data consistency checking is disabled" + } + + consistency_service = CustomerConsistencyService() + + # Fetch data from all systems + all_data = await consistency_service.fetch_all_data(customer_id) + + # Compare data + discrepancies = consistency_service.compare_data(all_data) + + # Count actual discrepancies + discrepancy_count = sum( + 1 for field_data in discrepancies.values() + if field_data['discrepancy'] + ) + + return { + "enabled": True, + "customer_id": customer_id, + "discrepancy_count": discrepancy_count, + "discrepancies": discrepancies, + "systems_available": { + "hub": True, + "vtiger": all_data.get('vtiger') is not None, + "economic": all_data.get('economic') is not None + } + } + + except Exception as e: + logger.error(f"❌ Failed to check consistency for customer {customer_id}: {e}") + raise HTTPException(status_code=500, detail=str(e)) + + +@router.post("/customers/{customer_id}/sync-field") +async def sync_customer_field( + customer_id: int, + field_name: str = Query(..., description="Hub field name to sync"), + source_system: str = Query(..., description="Source system: hub, vtiger, or economic"), + source_value: str = Query(..., description="The correct value to sync") +): + """ + 🔄 Sync a single field across all systems + + Takes the correct value from one system and updates the others + """ + try: + from app.core.config import settings + + # Validate source system + if source_system not in ['hub', 'vtiger', 'economic']: + raise HTTPException( + status_code=400, + detail=f"Invalid source_system: {source_system}. Must be hub, vtiger, or economic" + ) + + consistency_service = CustomerConsistencyService() + + # Perform sync + results = await consistency_service.sync_field( + customer_id=customer_id, + field_name=field_name, + source_system=source_system, + source_value=source_value + ) + + return { + "success": True, + "customer_id": customer_id, + "field_name": field_name, + "source_system": source_system, + "source_value": source_value, + "sync_results": results + } + + except ValueError as e: + raise HTTPException(status_code=400, detail=str(e)) + except Exception as e: + logger.error(f"❌ Failed to sync field {field_name} for customer {customer_id}: {e}") + raise HTTPException(status_code=500, detail=str(e)) + + @router.post("/customers/sync-economic-from-simplycrm") async def sync_economic_numbers_from_simplycrm(): """ diff --git a/app/customers/frontend/customer_detail.html b/app/customers/frontend/customer_detail.html index a09732e..687d196 100644 --- a/app/customers/frontend/customer_detail.html +++ b/app/customers/frontend/customer_detail.html @@ -163,6 +163,56 @@ border-radius: 50%; border: 3px solid var(--bg-body); } + + /* Enhanced Edit Button */ + .btn-edit-customer { + background: linear-gradient(135deg, #0f4c75 0%, #1a5f8e 100%); + color: white; + border: none; + padding: 0.75rem 1.75rem; + border-radius: 10px; + font-weight: 600; + letter-spacing: 0.3px; + box-shadow: 0 4px 15px rgba(15, 76, 117, 0.3); + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + position: relative; + overflow: hidden; + } + + .btn-edit-customer::before { + content: ''; + position: absolute; + top: 0; + left: -100%; + width: 100%; + height: 100%; + background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent); + transition: left 0.5s; + } + + .btn-edit-customer:hover { + background: linear-gradient(135deg, #1a5f8e 0%, #0f4c75 100%); + transform: translateY(-2px); + box-shadow: 0 6px 20px rgba(15, 76, 117, 0.4); + color: white; + } + + .btn-edit-customer:hover::before { + left: 100%; + } + + .btn-edit-customer:active { + transform: translateY(0); + box-shadow: 0 2px 8px rgba(15, 76, 117, 0.3); + } + + .btn-edit-customer i { + transition: transform 0.3s; + } + + .btn-edit-customer:hover i { + transform: rotate(-15deg) scale(1.1); + } {% endblock %} @@ -183,8 +233,8 @@
-
+ + +
@@ -423,6 +490,119 @@
+ + +