Fix: Bedre case title fallback og klarere fejlbesked for manglende customer linking
This commit is contained in:
parent
e69f211fbf
commit
68eb1d31d1
@ -248,10 +248,21 @@ class EconomicExportService:
|
||||
customer_data = execute_query_single(customer_number_query, (order['customer_id'],))
|
||||
|
||||
if not customer_data or not customer_data.get('economic_customer_number'):
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=f"Customer {order['customer_name']} has no e-conomic customer number. Link customer to Hub customer first."
|
||||
# Check if customer is linked at all
|
||||
check_link = execute_query_single(
|
||||
"SELECT hub_customer_id FROM tmodule_customers WHERE id = %s",
|
||||
(order['customer_id'],)
|
||||
)
|
||||
if not check_link or not check_link.get('hub_customer_id'):
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=f"Customer '{order['customer_name']}' er ikke linket til en Hub kunde. Gå til vTiger kunder og link kunden først."
|
||||
)
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=f"Customer '{order['customer_name']}' mangler e-conomic kundenummer. Opdater kunde i Customers modulet."
|
||||
)
|
||||
|
||||
customer_number = customer_data['economic_customer_number']
|
||||
|
||||
|
||||
@ -125,9 +125,11 @@ class OrderService:
|
||||
for time_entry in approved_times:
|
||||
case_id = time_entry['case_id']
|
||||
if case_id not in case_groups:
|
||||
# Prioriter case title fra vTiger (c.title), fallback til vtiger_data title
|
||||
case_title = time_entry.get('case_title') or time_entry.get('vtiger_title')
|
||||
case_groups[case_id] = {
|
||||
'case_vtiger_id': time_entry.get('case_vtiger_id'),
|
||||
'case_title': time_entry.get('case_title'), # Case titel fra vTiger
|
||||
'case_title': case_title, # Case titel fra vTiger
|
||||
'contact_name': time_entry.get('contact_name'),
|
||||
'worked_date': time_entry.get('worked_date'), # Seneste dato
|
||||
'is_travel': False, # Marker hvis nogen entry er rejse
|
||||
|
||||
155
compare_schemas.py
Executable file
155
compare_schemas.py
Executable file
@ -0,0 +1,155 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Compare local dev database schema with production to find missing columns
|
||||
"""
|
||||
import subprocess
|
||||
import json
|
||||
from collections import defaultdict
|
||||
|
||||
# Get schema from local dev database
|
||||
def get_local_schema():
|
||||
"""Get all tables and columns from local dev database"""
|
||||
print("🔍 Fetching LOCAL dev schema...")
|
||||
result = subprocess.run(
|
||||
["docker", "exec", "bmc_hub_dev-postgres-1",
|
||||
"psql", "-U", "bmcnetworks", "-d", "bmc_hub", "-t", "-c",
|
||||
"""
|
||||
SELECT table_name, column_name, data_type, character_maximum_length, is_nullable, column_default
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'public'
|
||||
ORDER BY table_name, ordinal_position;
|
||||
"""],
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
|
||||
schema = defaultdict(list)
|
||||
for line in result.stdout.strip().split('\n'):
|
||||
if not line.strip():
|
||||
continue
|
||||
parts = [p.strip() for p in line.split('|')]
|
||||
if len(parts) >= 2:
|
||||
table = parts[0]
|
||||
column = parts[1]
|
||||
data_type = parts[2] if len(parts) > 2 else ''
|
||||
schema[table].append({
|
||||
'column': column,
|
||||
'type': data_type,
|
||||
'full': line
|
||||
})
|
||||
|
||||
return schema
|
||||
|
||||
# Get schema from production via SSH
|
||||
def get_prod_schema():
|
||||
"""Get all tables and columns from production database"""
|
||||
print("🔍 Fetching PRODUCTION schema...")
|
||||
result = subprocess.run(
|
||||
["ssh", "bmcadmin@172.16.31.183",
|
||||
"sudo podman exec bmc-hub-postgres-prod psql -U bmc_hub -d bmc_hub -t -c \"SELECT table_name, column_name, data_type, character_maximum_length, is_nullable, column_default FROM information_schema.columns WHERE table_schema = 'public' ORDER BY table_name, ordinal_position;\""],
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
|
||||
schema = defaultdict(list)
|
||||
for line in result.stdout.strip().split('\n'):
|
||||
if not line.strip():
|
||||
continue
|
||||
parts = [p.strip() for p in line.split('|')]
|
||||
if len(parts) >= 2:
|
||||
table = parts[0]
|
||||
column = parts[1]
|
||||
data_type = parts[2] if len(parts) > 2 else ''
|
||||
schema[table].append({
|
||||
'column': column,
|
||||
'type': data_type,
|
||||
'full': line
|
||||
})
|
||||
|
||||
return schema
|
||||
|
||||
def compare_schemas(local, prod):
|
||||
"""Compare schemas and find missing columns"""
|
||||
print("\n📊 Comparing schemas...\n")
|
||||
|
||||
missing = defaultdict(list)
|
||||
|
||||
# Check each table in local
|
||||
for table in sorted(local.keys()):
|
||||
local_cols = {col['column']: col for col in local[table]}
|
||||
prod_cols = {col['column']: col for col in prod.get(table, [])}
|
||||
|
||||
# Find missing columns
|
||||
for col_name, col_info in local_cols.items():
|
||||
if col_name not in prod_cols:
|
||||
missing[table].append(col_info)
|
||||
|
||||
return missing
|
||||
|
||||
def print_missing(missing):
|
||||
"""Print missing columns in a readable format"""
|
||||
if not missing:
|
||||
print("✅ No missing columns! Schemas are in sync.")
|
||||
return
|
||||
|
||||
print("❌ MISSING COLUMNS IN PRODUCTION:\n")
|
||||
print("=" * 80)
|
||||
|
||||
for table in sorted(missing.keys()):
|
||||
print(f"\n📋 Table: {table}")
|
||||
print("-" * 80)
|
||||
for col in missing[table]:
|
||||
print(f" ❌ {col['column']} ({col['type']})")
|
||||
|
||||
print("\n" + "=" * 80)
|
||||
print(f"\n📊 Summary: {sum(len(cols) for cols in missing.values())} missing columns across {len(missing)} tables")
|
||||
|
||||
def generate_sql(missing):
|
||||
"""Generate SQL to add missing columns"""
|
||||
if not missing:
|
||||
return
|
||||
|
||||
print("\n\n🔧 SQL TO ADD MISSING COLUMNS:")
|
||||
print("=" * 80)
|
||||
print()
|
||||
|
||||
for table in sorted(missing.keys()):
|
||||
print(f"-- Table: {table}")
|
||||
for col in missing[table]:
|
||||
col_type = col['type']
|
||||
# Simplified type mapping - you may need to adjust
|
||||
if 'character varying' in col_type:
|
||||
col_type = 'VARCHAR(255)'
|
||||
elif col_type == 'integer':
|
||||
col_type = 'INTEGER'
|
||||
elif 'numeric' in col_type:
|
||||
col_type = 'NUMERIC(10,2)'
|
||||
elif col_type == 'boolean':
|
||||
col_type = 'BOOLEAN DEFAULT false'
|
||||
elif col_type == 'text':
|
||||
col_type = 'TEXT'
|
||||
elif 'timestamp' in col_type:
|
||||
col_type = 'TIMESTAMP'
|
||||
elif col_type == 'date':
|
||||
col_type = 'DATE'
|
||||
elif col_type == 'jsonb':
|
||||
col_type = 'JSONB'
|
||||
|
||||
print(f"ALTER TABLE {table} ADD COLUMN IF NOT EXISTS {col['column']} {col_type};")
|
||||
print()
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
local = get_local_schema()
|
||||
prod = get_prod_schema()
|
||||
|
||||
missing = compare_schemas(local, prod)
|
||||
|
||||
print_missing(missing)
|
||||
generate_sql(missing)
|
||||
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"❌ Error: {e}")
|
||||
print(f"Output: {e.output}")
|
||||
except Exception as e:
|
||||
print(f"❌ Error: {e}")
|
||||
Loading…
Reference in New Issue
Block a user