171 lines
5.9 KiB
Python
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())
|