Files
service-finder/backend/scripts/safe_rename_garages.py

171 lines
5.9 KiB
Python

#!/usr/bin/env python3
"""
🔐 SAFE RENAME GARAGES — Private Garage Display Name Normalization
This script targets private garages (organizations with org_type='individual'
or those lacking a tax_number, representing individuals rather than corporate entities).
What it does:
1. Finds all private/individual garages in fleet.organizations
2. Sets `name` to: "{last_name} {first_name} - Privát Garázs (#{user_id})"
(internal name with ID for uniqueness)
3. Sets `display_name` to: "{last_name} {first_name} - Privát Garázs"
(clean display name without exposing the internal ID)
Strict Rules:
- READ/UPDATE only — NO DELETES
- Does NOT touch corporate/business organizations
- Does NOT modify any user records
- Preserves all existing data relationships
Usage:
docker compose exec sf_api python3 /app/scripts/safe_rename_garages.py
"""
import asyncio
import logging
import os
import sys
from sqlalchemy import text
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
handlers=[logging.StreamHandler(sys.stdout)],
)
logger = logging.getLogger("safe_rename_garages")
async def main():
"""Main execution: find private garages and update their names."""
# Use the same DATABASE_URL as the application
database_url = os.environ.get(
"DATABASE_URL",
"postgresql+asyncpg://service_finder_app:AppSafePass_2026@shared-postgres:5432/service_finder",
)
engine = create_async_engine(database_url, echo=False)
async with engine.connect() as conn:
# ── Step 1: Find private/individual garages ──
logger.info("🔍 Scanning for private garages (individual org_type)...")
result = await conn.execute(
text("""
SELECT
o.id,
o.name,
o.full_name,
o.display_name,
o.org_type,
o.tax_number,
o.owner_id,
o.legal_owner_id,
p.last_name,
p.first_name,
u.email as owner_email
FROM fleet.organizations o
LEFT JOIN identity.users u ON u.id = o.owner_id
LEFT JOIN identity.persons p ON p.id = COALESCE(o.legal_owner_id, u.person_id)
WHERE
(o.org_type = 'individual' OR o.org_type IS NULL)
AND o.is_deleted = false
ORDER BY o.id
""")
)
rows = result.fetchall()
logger.info(f"📊 Found {len(rows)} private garage(s) to process")
if not rows:
logger.info("✅ No private garages found. Nothing to do.")
return
updated_count = 0
skipped_count = 0
for row in rows:
org_id = row[0]
current_name = row[1]
current_full_name = row[2]
current_display_name = row[3]
org_type = row[4]
tax_number = row[5]
owner_id = row[6]
legal_owner_id = row[7]
last_name = row[8]
first_name = row[9]
owner_email = row[10]
# Determine the person's name
person_name = None
if last_name and first_name:
person_name = f"{last_name} {first_name}"
elif current_full_name:
person_name = current_full_name
elif owner_email:
person_name = owner_email.split("@")[0]
else:
person_name = f"User #{owner_id or '?'}"
# Use owner_id for uniqueness in the internal name
uid = owner_id or legal_owner_id or org_id
# Build the new names
new_internal_name = f"{person_name} - Privát Garázs (#{uid})"
new_display_name = f"{person_name} - Privát Garázs"
# Check if update is needed
if current_name == new_internal_name and current_display_name == new_display_name:
logger.info(f" ⏭️ Org #{org_id}: already up-to-date ('{new_display_name}')")
skipped_count += 1
continue
# ── Step 2: Update the organization ──
logger.info(f" ✏️ Org #{org_id}: '{current_name or 'N/A'}''{new_internal_name}'")
logger.info(f" Display: '{current_display_name or 'N/A'}''{new_display_name}'")
await conn.execute(
text("""
UPDATE fleet.organizations
SET
name = :new_name,
display_name = :new_display_name,
updated_at = NOW()
WHERE id = :org_id
"""),
{
"new_name": new_internal_name,
"new_display_name": new_display_name,
"org_id": org_id,
}
)
updated_count += 1
# ── Commit all changes ──
await conn.commit()
logger.info(f"✅ Done! Updated: {updated_count}, Skipped: {skipped_count}")
# ── Step 3: Verification ──
logger.info("🔍 Verifying updates...")
verify_result = await conn.execute(
text("""
SELECT id, name, display_name, org_type
FROM fleet.organizations
WHERE org_type = 'individual' OR org_type IS NULL
ORDER BY id
""")
)
verify_rows = verify_result.fetchall()
for vrow in verify_rows:
logger.info(f" Org #{vrow[0]}: name='{vrow[1]}', display_name='{vrow[2]}', type='{vrow[3]}'")
await engine.dispose()
logger.info("🎉 Safe rename completed successfully!")
if __name__ == "__main__":
asyncio.run(main())