Feature: Omstruktureret sync - e-conomic er nu primær kilde, vTiger linker bagefter
This commit is contained in:
parent
e5dc0f64d3
commit
a9f5714662
@ -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:
|
||||||
|
# Already linked, just update timestamp
|
||||||
|
execute_query("UPDATE customers SET last_synced_at = NOW() WHERE id = %s", (existing[0]['id'],))
|
||||||
else:
|
else:
|
||||||
# Create new customer
|
not_found_count += 1
|
||||||
insert_query = """
|
logger.debug(f"⏭️ Ikke fundet i Hub: {name} (CVR: {cvr or 'ingen'})")
|
||||||
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'})")
|
|
||||||
|
|
||||||
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 {
|
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(
|
|
||||||
"SELECT id, name, economic_customer_number FROM customers WHERE cvr_number = %s",
|
|
||||||
(cvr,)
|
|
||||||
)
|
|
||||||
|
|
||||||
# If no CVR match, try normalized name (only for customers without economic number)
|
# Check if customer exists by economic_customer_number
|
||||||
if not matched and name:
|
existing = execute_query(
|
||||||
normalized = normalize_name(name)
|
"SELECT id FROM customers WHERE economic_customer_number = %s",
|
||||||
hub_customers = execute_query("SELECT id, name, economic_customer_number FROM customers WHERE economic_customer_number IS NULL")
|
(customer_number,)
|
||||||
for hub_customer in hub_customers:
|
)
|
||||||
if normalize_name(hub_customer['name']) == normalized:
|
|
||||||
matched = [hub_customer]
|
|
||||||
break
|
|
||||||
|
|
||||||
if matched:
|
if existing:
|
||||||
# Update Hub customer with e-conomic number
|
# Update existing customer
|
||||||
current_number = matched[0].get('economic_customer_number')
|
update_query = """
|
||||||
if current_number is None:
|
UPDATE customers SET
|
||||||
execute_query(
|
name = %s,
|
||||||
"UPDATE customers SET economic_customer_number = %s, last_synced_at = NOW() WHERE id = %s",
|
cvr_number = %s,
|
||||||
(customer_number, matched[0]['id'])
|
email_domain = %s,
|
||||||
)
|
city = %s,
|
||||||
matched_count += 1
|
postal_code = %s,
|
||||||
logger.info(f"🔗 Matchet: {matched[0]['name']} → e-conomic kunde #{customer_number} (CVR: {cvr or 'navn-match'})")
|
country = %s,
|
||||||
else:
|
website = %s,
|
||||||
# Already has number, just update sync timestamp
|
last_synced_at = NOW()
|
||||||
execute_query("UPDATE customers SET last_synced_at = NOW() WHERE id = %s", (matched[0]['id'],))
|
WHERE id = %s
|
||||||
verified_count += 1
|
"""
|
||||||
|
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:
|
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 {
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user