diff --git a/app/timetracking/backend/economic_export.py b/app/timetracking/backend/economic_export.py index b29b6c4..e59e72e 100644 --- a/app/timetracking/backend/economic_export.py +++ b/app/timetracking/backend/economic_export.py @@ -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'] diff --git a/app/timetracking/backend/order_service.py b/app/timetracking/backend/order_service.py index 01dbae6..a5081de 100644 --- a/app/timetracking/backend/order_service.py +++ b/app/timetracking/backend/order_service.py @@ -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 diff --git a/compare_schemas.py b/compare_schemas.py new file mode 100755 index 0000000..5cee00a --- /dev/null +++ b/compare_schemas.py @@ -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}")