Add: Customer linking verification endpoint med health score og anbefalinger

This commit is contained in:
Christian 2026-01-05 11:34:39 +01:00
parent 05ec5b5903
commit 5e66ef6563

View File

@ -170,6 +170,183 @@ async def list_customers(
}
@router.get("/customers/verify-linking")
async def verify_customer_linking():
"""
🔍 Verificer kunde-linking tværs af systemer.
Krydstjek:
1. tmodule_customers Hub customers (via hub_customer_id)
2. Hub customers e-conomic (via economic_customer_number)
3. tmodule_customers e-conomic (via economic_customer_number match)
"""
try:
logger.info("🔍 Starting customer linking verification...")
# 1. Tmodule customers overview
tmodule_stats = execute_query_single("""
SELECT
COUNT(*) as total,
COUNT(hub_customer_id) as linked_to_hub,
COUNT(economic_customer_number) as has_economic_number
FROM tmodule_customers
""")
# 2. Hub customers overview
hub_stats = execute_query_single("""
SELECT
COUNT(*) as total,
COUNT(economic_customer_number) as has_economic_number,
COUNT(CASE WHEN economic_customer_number IS NOT NULL
AND economic_customer_number::text != ''
THEN 1 END) as valid_economic_number
FROM customers
""")
# 3. Find tmodule customers UDEN Hub link
tmodule_unlinked = execute_query("""
SELECT id, name, economic_customer_number, email
FROM tmodule_customers
WHERE hub_customer_id IS NULL
ORDER BY name
LIMIT 20
""")
# 4. Find tmodule customers med Hub link MEN Hub customer mangler economic number
tmodule_linked_but_no_economic = execute_query("""
SELECT
tc.id as tmodule_id,
tc.name as tmodule_name,
tc.economic_customer_number as tmodule_economic,
c.id as hub_id,
c.name as hub_name,
c.economic_customer_number as hub_economic
FROM tmodule_customers tc
JOIN customers c ON tc.hub_customer_id = c.id
WHERE c.economic_customer_number IS NULL
OR c.economic_customer_number::text = ''
LIMIT 20
""")
# 5. Find economic number mismatches
economic_mismatches = execute_query("""
SELECT
tc.id as tmodule_id,
tc.name as tmodule_name,
tc.economic_customer_number as tmodule_economic,
c.id as hub_id,
c.name as hub_name,
c.economic_customer_number as hub_economic
FROM tmodule_customers tc
JOIN customers c ON tc.hub_customer_id = c.id
WHERE tc.economic_customer_number IS NOT NULL
AND c.economic_customer_number IS NOT NULL
AND tc.economic_customer_number::text != c.economic_customer_number::text
LIMIT 20
""")
# 6. Find Hub customers der kunne matches på navn men ikke er linket
potential_name_matches = execute_query("""
SELECT
tc.id as tmodule_id,
tc.name as tmodule_name,
tc.economic_customer_number as tmodule_economic,
c.id as hub_id,
c.name as hub_name,
c.economic_customer_number as hub_economic
FROM tmodule_customers tc
JOIN customers c ON LOWER(TRIM(tc.name)) = LOWER(TRIM(c.name))
WHERE tc.hub_customer_id IS NULL
LIMIT 20
""")
# 7. Beregn health score
tmodule_link_pct = (tmodule_stats['linked_to_hub'] / tmodule_stats['total'] * 100) if tmodule_stats['total'] > 0 else 0
hub_economic_pct = (hub_stats['valid_economic_number'] / hub_stats['total'] * 100) if hub_stats['total'] > 0 else 0
health_score = (tmodule_link_pct * 0.6) + (hub_economic_pct * 0.4)
if health_score >= 90:
health_status = "excellent"
elif health_score >= 75:
health_status = "good"
elif health_score >= 50:
health_status = "fair"
else:
health_status = "poor"
result = {
"status": "success",
"health": {
"score": round(health_score, 1),
"status": health_status,
"description": f"{round(tmodule_link_pct, 1)}% tmodule linked, {round(hub_economic_pct, 1)}% hub has economic numbers"
},
"tmodule_customers": {
"total": tmodule_stats['total'],
"linked_to_hub": tmodule_stats['linked_to_hub'],
"has_economic_number": tmodule_stats['has_economic_number'],
"link_percentage": round(tmodule_link_pct, 1)
},
"hub_customers": {
"total": hub_stats['total'],
"has_economic_number": hub_stats['valid_economic_number'],
"economic_percentage": round(hub_economic_pct, 1)
},
"issues": {
"tmodule_unlinked_count": len(tmodule_unlinked),
"tmodule_unlinked_sample": tmodule_unlinked[:5],
"hub_missing_economic_count": len(tmodule_linked_but_no_economic),
"hub_missing_economic_sample": tmodule_linked_but_no_economic[:5],
"economic_mismatches_count": len(economic_mismatches),
"economic_mismatches_sample": economic_mismatches[:5],
"potential_name_matches_count": len(potential_name_matches),
"potential_name_matches_sample": potential_name_matches[:5]
},
"recommendations": []
}
# Generer anbefalinger
if len(tmodule_unlinked) > 0:
result["recommendations"].append({
"issue": "unlinked_tmodule_customers",
"count": len(tmodule_unlinked),
"action": "POST /api/v1/timetracking/sync/relink-customers",
"description": "Kør re-linking for at matche på economic_customer_number eller navn"
})
if len(tmodule_linked_but_no_economic) > 0:
result["recommendations"].append({
"issue": "hub_customers_missing_economic_number",
"count": len(tmodule_linked_but_no_economic),
"action": "POST /api/v1/customers/sync-economic-from-simplycrm",
"description": "Sync e-conomic numre fra Simply-CRM"
})
if len(economic_mismatches) > 0:
result["recommendations"].append({
"issue": "economic_number_mismatches",
"count": len(economic_mismatches),
"action": "Manual review required",
"description": "Forskellige e-conomic numre i tmodule vs hub - tjek data manuelt"
})
if len(potential_name_matches) > 0:
result["recommendations"].append({
"issue": "potential_name_matches",
"count": len(potential_name_matches),
"action": "POST /api/v1/timetracking/sync/relink-customers",
"description": "Disse kunder kunne linkes på navn"
})
logger.info(f"✅ Verification complete - Health: {health_status} ({health_score:.1f}%)")
return result
except Exception as e:
logger.error(f"❌ Verification failed: {e}")
raise HTTPException(status_code=500, detail=str(e))
@router.get("/customers/{customer_id}")
async def get_customer(customer_id: int):
"""Get single customer by ID with contact count and vTiger BMC Låst status"""