From a9f5714662238a836eb7b4e08dd08e8b924a4a4f Mon Sep 17 00:00:00 2001 From: Christian Date: Mon, 22 Dec 2025 13:02:24 +0100 Subject: [PATCH] =?UTF-8?q?Feature:=20Omstruktureret=20sync=20-=20e-conomi?= =?UTF-8?q?c=20er=20nu=20prim=C3=A6r=20kilde,=20vTiger=20linker=20bagefter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/system/backend/sync_router.py | 214 ++++++++++++++---------------- 1 file changed, 97 insertions(+), 117 deletions(-) diff --git a/app/system/backend/sync_router.py b/app/system/backend/sync_router.py index 14975ca..ef2d496 100644 --- a/app/system/backend/sync_router.py +++ b/app/system/backend/sync_router.py @@ -28,11 +28,11 @@ def normalize_name(name: str) -> str: @router.post("/sync/vtiger") async def sync_from_vtiger() -> Dict[str, Any]: """ - Sync companies from vTiger Accounts module - Matches by CVR number or normalized company name + Link vTiger accounts to existing Hub customers + Matches by CVR or normalized name, updates vtiger_id """ try: - logger.info("🔄 Starting vTiger accounts sync...") + logger.info("🔄 Starting vTiger link sync...") vtiger = get_vtiger_service() @@ -66,19 +66,17 @@ async def sync_from_vtiger() -> Dict[str, Any]: logger.info(f"📥 Fetched total of {len(all_accounts)} accounts from vTiger") accounts = all_accounts - created_count = 0 + linked_count = 0 updated_count = 0 - skipped_count = 0 + not_found_count = 0 for account in accounts: vtiger_id = account.get('id') name = account.get('accountname', '').strip() cvr = account.get('cf_accounts_cvr') or account.get('siccode') - economic_customer_number = None # Will be set by e-conomic sync - if not name: - skipped_count += 1 - logger.debug(f"⏭️ Sprunget over: Tomt firmanavn (ID: {vtiger_id})") + if not name or not vtiger_id: + not_found_count += 1 continue # Clean CVR number @@ -87,93 +85,55 @@ async def sync_from_vtiger() -> Dict[str, Any]: if len(cvr) != 8: cvr = None - # Try to find existing customer by vTiger ID or CVR + # Find existing Hub customer by CVR or normalized name existing = None - if vtiger_id: + if cvr: existing = execute_query( - "SELECT id FROM customers WHERE vtiger_id = %s", - (vtiger_id,) - ) - - if not existing and cvr: - existing = execute_query( - "SELECT id FROM customers WHERE cvr_number = %s", + "SELECT id, name, vtiger_id FROM customers WHERE cvr_number = %s", (cvr,) ) if not existing: # Match by normalized name normalized = normalize_name(name) - all_customers = execute_query("SELECT id, name FROM customers") + all_customers = execute_query("SELECT id, name, vtiger_id FROM customers") for customer in all_customers: if normalize_name(customer['name']) == normalized: existing = [customer] break if existing: - # Update existing customer - update_fields = [] - params = [] - - if vtiger_id: - update_fields.append("vtiger_id = %s") - params.append(vtiger_id) - - if cvr: - update_fields.append("cvr_number = %s") - params.append(cvr) - - if economic_customer_number: - update_fields.append("economic_customer_number = %s") - params.append(int(economic_customer_number)) - - update_fields.append("last_synced_at = NOW()") - - if update_fields: - params.append(existing[0]['id']) - query = f"UPDATE customers SET {', '.join(update_fields)} WHERE id = %s" - execute_query(query, tuple(params)) + # Link vTiger ID to existing customer + current_vtiger_id = existing[0].get('vtiger_id') + if current_vtiger_id is None: + execute_query( + "UPDATE customers SET vtiger_id = %s, last_synced_at = NOW() WHERE id = %s", + (vtiger_id, existing[0]['id']) + ) + linked_count += 1 + logger.info(f"🔗 Linket: {existing[0]['name']} → vTiger #{vtiger_id} (CVR: {cvr or 'navn-match'})") + elif current_vtiger_id != vtiger_id: + # Update if different vTiger ID + execute_query( + "UPDATE customers SET vtiger_id = %s, last_synced_at = NOW() WHERE id = %s", + (vtiger_id, existing[0]['id']) + ) updated_count += 1 - logger.info(f"✏️ Opdateret: {name} (CVR: {cvr or 'ingen'}) - Felter: {', '.join([f.split(' = ')[0] for f in update_fields if 'last_synced' not in f])}") + logger.info(f"✏️ Opdateret vTiger ID: {existing[0]['name']} → {vtiger_id} (var: {current_vtiger_id})") + else: + # Already linked, just update timestamp + execute_query("UPDATE customers SET last_synced_at = NOW() WHERE id = %s", (existing[0]['id'],)) else: - # Create new customer - insert_query = """ - INSERT INTO customers - (name, vtiger_id, cvr_number, economic_customer_number, - email_domain, city, postal_code, country, website, last_synced_at) - VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, NOW()) - RETURNING id - """ - - email_domain = account.get('email1', '').split('@')[-1] if '@' in account.get('email1', '') else None - city = account.get('bill_city') - postal_code = account.get('bill_code') - country = account.get('bill_country') or 'DK' - website = account.get('website1') - - result = execute_query(insert_query, ( - name, - vtiger_id, - cvr, - int(economic_customer_number) if economic_customer_number else None, - email_domain, - city, - postal_code, - country, - website - )) - - if result: - created_count += 1 - logger.info(f"✨ Oprettet: {name} (CVR: {cvr or 'ingen'}, By: {city or 'ukendt'})") + not_found_count += 1 + logger.debug(f"⏭️ Ikke fundet i Hub: {name} (CVR: {cvr or 'ingen'})") - logger.info(f"✅ vTiger sync fuldført: {created_count} oprettet, {updated_count} opdateret, {skipped_count} sprunget over af {len(accounts)} totalt") + logger.info(f"✅ vTiger link sync fuldført: {linked_count} nye linket, {updated_count} opdateret, {not_found_count} ikke fundet af {len(accounts)} totalt") return { "status": "success", - "created": created_count, + "linked": linked_count, "updated": updated_count, - "skipped": skipped_count, + "not_found": not_found_count, "total_processed": len(accounts) } @@ -325,11 +285,11 @@ async def sync_vtiger_contacts() -> Dict[str, Any]: @router.post("/sync/economic") async def sync_from_economic() -> Dict[str, Any]: """ - Sync customer numbers from e-conomic - Matches Hub customers to e-conomic by CVR number or normalized name + Sync customers from e-conomic (PRIMARY SOURCE) + Creates/updates Hub customers with e-conomic data """ try: - logger.info("🔄 Starting e-conomic sync...") + logger.info("🔄 Starting e-conomic customer sync (PRIMARY SOURCE)...") from app.services.economic_service import EconomicService economic = EconomicService() @@ -349,16 +309,23 @@ async def sync_from_economic() -> Dict[str, Any]: economic_customers = all_customers logger.info(f"📥 Fetched {len(economic_customers)} customers from e-conomic ({page} pages)") - matched_count = 0 - verified_count = 0 - not_matched_count = 0 + created_count = 0 + updated_count = 0 + skipped_count = 0 for eco_customer in economic_customers: customer_number = eco_customer.get('customerNumber') cvr = eco_customer.get('corporateIdentificationNumber') - name = eco_customer.get('name', '') + name = eco_customer.get('name', '').strip() + address = eco_customer.get('address', '') + city = eco_customer.get('city', '') + zip_code = eco_customer.get('zip', '') + country = eco_customer.get('country', 'DK') + email = eco_customer.get('email', '') + website = eco_customer.get('website', '') - if not customer_number: + if not customer_number or not name: + skipped_count += 1 continue # Clean CVR @@ -367,47 +334,60 @@ async def sync_from_economic() -> Dict[str, Any]: if len(cvr) != 8: cvr = None - # Try to match by CVR first - matched = None - if cvr: - matched = execute_query( - "SELECT id, name, economic_customer_number FROM customers WHERE cvr_number = %s", - (cvr,) - ) + # Extract email domain + email_domain = email.split('@')[-1] if '@' in email else None - # If no CVR match, try normalized name (only for customers without economic number) - if not matched and name: - normalized = normalize_name(name) - hub_customers = execute_query("SELECT id, name, economic_customer_number FROM customers WHERE economic_customer_number IS NULL") - for hub_customer in hub_customers: - if normalize_name(hub_customer['name']) == normalized: - matched = [hub_customer] - break + # Check if customer exists by economic_customer_number + existing = execute_query( + "SELECT id FROM customers WHERE economic_customer_number = %s", + (customer_number,) + ) - if matched: - # Update Hub customer with e-conomic number - current_number = matched[0].get('economic_customer_number') - if current_number is None: - execute_query( - "UPDATE customers SET economic_customer_number = %s, last_synced_at = NOW() WHERE id = %s", - (customer_number, matched[0]['id']) - ) - matched_count += 1 - logger.info(f"🔗 Matchet: {matched[0]['name']} → e-conomic kunde #{customer_number} (CVR: {cvr or 'navn-match'})") - else: - # Already has number, just update sync timestamp - execute_query("UPDATE customers SET last_synced_at = NOW() WHERE id = %s", (matched[0]['id'],)) - verified_count += 1 + if existing: + # Update existing customer + update_query = """ + UPDATE customers SET + name = %s, + cvr_number = %s, + email_domain = %s, + city = %s, + postal_code = %s, + country = %s, + website = %s, + last_synced_at = NOW() + WHERE id = %s + """ + execute_query(update_query, ( + name, cvr, email_domain, city, zip_code, country, website, existing[0]['id'] + )) + updated_count += 1 + logger.info(f"✏️ Opdateret: {name} (e-conomic #{customer_number}, CVR: {cvr or 'ingen'})") else: - not_matched_count += 1 + # Create new customer from e-conomic + insert_query = """ + INSERT INTO customers + (name, economic_customer_number, cvr_number, email_domain, + city, postal_code, country, website, last_synced_at) + VALUES (%s, %s, %s, %s, %s, %s, %s, %s, NOW()) + RETURNING id + """ + result = execute_query(insert_query, ( + name, customer_number, cvr, email_domain, city, zip_code, country, website + )) + + if result: + created_count += 1 + logger.info(f"✨ Oprettet: {name} (e-conomic #{customer_number}, CVR: {cvr or 'ingen'})") + else: + skipped_count += 1 - logger.info(f"✅ e-conomic sync fuldført: {matched_count} nye matchet, {verified_count} verificeret, {not_matched_count} ikke matchet af {len(economic_customers)} totalt") + logger.info(f"✅ e-conomic sync fuldført: {created_count} oprettet, {updated_count} opdateret, {skipped_count} sprunget over af {len(economic_customers)} totalt") return { "status": "success", - "matched": matched_count, - "verified": verified_count, - "not_matched": not_matched_count, + "created": created_count, + "updated": updated_count, + "skipped": skipped_count, "total_processed": len(economic_customers) }