jogosultsági szintek RBAC beállítva tesztelve
This commit is contained in:
170
backend/scripts/safe_rename_garages.py
Normal file
170
backend/scripts/safe_rename_garages.py
Normal file
@@ -0,0 +1,170 @@
|
||||
#!/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())
|
||||
Reference in New Issue
Block a user