Feature: Omstruktureret sync - e-conomic er nu primær kilde, vTiger linker bagefter

This commit is contained in:
Christian 2025-12-22 13:02:24 +01:00
parent e5dc0f64d3
commit a9f5714662

View File

@ -28,11 +28,11 @@ def normalize_name(name: str) -> str:
@router.post("/sync/vtiger") @router.post("/sync/vtiger")
async def sync_from_vtiger() -> Dict[str, Any]: async def sync_from_vtiger() -> Dict[str, Any]:
""" """
Sync companies from vTiger Accounts module Link vTiger accounts to existing Hub customers
Matches by CVR number or normalized company name Matches by CVR or normalized name, updates vtiger_id
""" """
try: try:
logger.info("🔄 Starting vTiger accounts sync...") logger.info("🔄 Starting vTiger link sync...")
vtiger = get_vtiger_service() 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") logger.info(f"📥 Fetched total of {len(all_accounts)} accounts from vTiger")
accounts = all_accounts accounts = all_accounts
created_count = 0 linked_count = 0
updated_count = 0 updated_count = 0
skipped_count = 0 not_found_count = 0
for account in accounts: for account in accounts:
vtiger_id = account.get('id') vtiger_id = account.get('id')
name = account.get('accountname', '').strip() name = account.get('accountname', '').strip()
cvr = account.get('cf_accounts_cvr') or account.get('siccode') cvr = account.get('cf_accounts_cvr') or account.get('siccode')
economic_customer_number = None # Will be set by e-conomic sync
if not name: if not name or not vtiger_id:
skipped_count += 1 not_found_count += 1
logger.debug(f"⏭️ Sprunget over: Tomt firmanavn (ID: {vtiger_id})")
continue continue
# Clean CVR number # Clean CVR number
@ -87,93 +85,55 @@ async def sync_from_vtiger() -> Dict[str, Any]:
if len(cvr) != 8: if len(cvr) != 8:
cvr = None cvr = None
# Try to find existing customer by vTiger ID or CVR # Find existing Hub customer by CVR or normalized name
existing = None existing = None
if vtiger_id: if cvr:
existing = execute_query( existing = execute_query(
"SELECT id FROM customers WHERE vtiger_id = %s", "SELECT id, name, vtiger_id FROM customers WHERE cvr_number = %s",
(vtiger_id,)
)
if not existing and cvr:
existing = execute_query(
"SELECT id FROM customers WHERE cvr_number = %s",
(cvr,) (cvr,)
) )
if not existing: if not existing:
# Match by normalized name # Match by normalized name
normalized = normalize_name(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: for customer in all_customers:
if normalize_name(customer['name']) == normalized: if normalize_name(customer['name']) == normalized:
existing = [customer] existing = [customer]
break break
if existing: if existing:
# Update existing customer # Link vTiger ID to existing customer
update_fields = [] current_vtiger_id = existing[0].get('vtiger_id')
params = [] if current_vtiger_id is None:
execute_query(
if vtiger_id: "UPDATE customers SET vtiger_id = %s, last_synced_at = NOW() WHERE id = %s",
update_fields.append("vtiger_id = %s") (vtiger_id, existing[0]['id'])
params.append(vtiger_id) )
linked_count += 1
if cvr: logger.info(f"🔗 Linket: {existing[0]['name']} → vTiger #{vtiger_id} (CVR: {cvr or 'navn-match'})")
update_fields.append("cvr_number = %s") elif current_vtiger_id != vtiger_id:
params.append(cvr) # Update if different vTiger ID
execute_query(
if economic_customer_number: "UPDATE customers SET vtiger_id = %s, last_synced_at = NOW() WHERE id = %s",
update_fields.append("economic_customer_number = %s") (vtiger_id, existing[0]['id'])
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))
updated_count += 1 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: else:
# Create new customer # Already linked, just update timestamp
insert_query = """ execute_query("UPDATE customers SET last_synced_at = NOW() WHERE id = %s", (existing[0]['id'],))
INSERT INTO customers else:
(name, vtiger_id, cvr_number, economic_customer_number, not_found_count += 1
email_domain, city, postal_code, country, website, last_synced_at) logger.debug(f"⏭️ Ikke fundet i Hub: {name} (CVR: {cvr or 'ingen'})")
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 logger.info(f"✅ vTiger link sync fuldført: {linked_count} nye linket, {updated_count} opdateret, {not_found_count} ikke fundet af {len(accounts)} totalt")
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'})")
logger.info(f"✅ vTiger sync fuldført: {created_count} oprettet, {updated_count} opdateret, {skipped_count} sprunget over af {len(accounts)} totalt")
return { return {
"status": "success", "status": "success",
"created": created_count, "linked": linked_count,
"updated": updated_count, "updated": updated_count,
"skipped": skipped_count, "not_found": not_found_count,
"total_processed": len(accounts) "total_processed": len(accounts)
} }
@ -325,11 +285,11 @@ async def sync_vtiger_contacts() -> Dict[str, Any]:
@router.post("/sync/economic") @router.post("/sync/economic")
async def sync_from_economic() -> Dict[str, Any]: async def sync_from_economic() -> Dict[str, Any]:
""" """
Sync customer numbers from e-conomic Sync customers from e-conomic (PRIMARY SOURCE)
Matches Hub customers to e-conomic by CVR number or normalized name Creates/updates Hub customers with e-conomic data
""" """
try: try:
logger.info("🔄 Starting e-conomic sync...") logger.info("🔄 Starting e-conomic customer sync (PRIMARY SOURCE)...")
from app.services.economic_service import EconomicService from app.services.economic_service import EconomicService
economic = EconomicService() economic = EconomicService()
@ -349,16 +309,23 @@ async def sync_from_economic() -> Dict[str, Any]:
economic_customers = all_customers economic_customers = all_customers
logger.info(f"📥 Fetched {len(economic_customers)} customers from e-conomic ({page} pages)") logger.info(f"📥 Fetched {len(economic_customers)} customers from e-conomic ({page} pages)")
matched_count = 0 created_count = 0
verified_count = 0 updated_count = 0
not_matched_count = 0 skipped_count = 0
for eco_customer in economic_customers: for eco_customer in economic_customers:
customer_number = eco_customer.get('customerNumber') customer_number = eco_customer.get('customerNumber')
cvr = eco_customer.get('corporateIdentificationNumber') 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 continue
# Clean CVR # Clean CVR
@ -367,47 +334,60 @@ async def sync_from_economic() -> Dict[str, Any]:
if len(cvr) != 8: if len(cvr) != 8:
cvr = None cvr = None
# Try to match by CVR first # Extract email domain
matched = None email_domain = email.split('@')[-1] if '@' in email else None
if cvr:
matched = execute_query( # Check if customer exists by economic_customer_number
"SELECT id, name, economic_customer_number FROM customers WHERE cvr_number = %s", existing = execute_query(
(cvr,) "SELECT id FROM customers WHERE economic_customer_number = %s",
(customer_number,)
) )
# If no CVR match, try normalized name (only for customers without economic number) if existing:
if not matched and name: # Update existing customer
normalized = normalize_name(name) update_query = """
hub_customers = execute_query("SELECT id, name, economic_customer_number FROM customers WHERE economic_customer_number IS NULL") UPDATE customers SET
for hub_customer in hub_customers: name = %s,
if normalize_name(hub_customer['name']) == normalized: cvr_number = %s,
matched = [hub_customer] email_domain = %s,
break city = %s,
postal_code = %s,
if matched: country = %s,
# Update Hub customer with e-conomic number website = %s,
current_number = matched[0].get('economic_customer_number') last_synced_at = NOW()
if current_number is None: WHERE id = %s
execute_query( """
"UPDATE customers SET economic_customer_number = %s, last_synced_at = NOW() WHERE id = %s", execute_query(update_query, (
(customer_number, matched[0]['id']) name, cvr, email_domain, city, zip_code, country, website, existing[0]['id']
) ))
matched_count += 1 updated_count += 1
logger.info(f"🔗 Matchet: {matched[0]['name']} → e-conomic kunde #{customer_number} (CVR: {cvr or 'navn-match'})") logger.info(f"✏️ Opdateret: {name} (e-conomic #{customer_number}, CVR: {cvr or 'ingen'})")
else: else:
# Already has number, just update sync timestamp # Create new customer from e-conomic
execute_query("UPDATE customers SET last_synced_at = NOW() WHERE id = %s", (matched[0]['id'],)) insert_query = """
verified_count += 1 INSERT INTO customers
else: (name, economic_customer_number, cvr_number, email_domain,
not_matched_count += 1 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
))
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") 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: {created_count} oprettet, {updated_count} opdateret, {skipped_count} sprunget over af {len(economic_customers)} totalt")
return { return {
"status": "success", "status": "success",
"matched": matched_count, "created": created_count,
"verified": verified_count, "updated": updated_count,
"not_matched": not_matched_count, "skipped": skipped_count,
"total_processed": len(economic_customers) "total_processed": len(economic_customers)
} }