Add: Customer linking verification endpoint med health score og anbefalinger
This commit is contained in:
parent
05ec5b5903
commit
5e66ef6563
@ -170,6 +170,183 @@ async def list_customers(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/customers/verify-linking")
|
||||||
|
async def verify_customer_linking():
|
||||||
|
"""
|
||||||
|
🔍 Verificer kunde-linking på 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}")
|
@router.get("/customers/{customer_id}")
|
||||||
async def get_customer(customer_id: int):
|
async def get_customer(customer_id: int):
|
||||||
"""Get single customer by ID with contact count and vTiger BMC Låst status"""
|
"""Get single customer by ID with contact count and vTiger BMC Låst status"""
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user