260 lines
8.2 KiB
Python
Executable File
260 lines
8.2 KiB
Python
Executable File
"""Consolidate fixed customer fields into custom_fields JSON.
|
|
|
|
Sprint 12 Phase 1: Move name, phone, address fields into custom_fields.
|
|
This eliminates redundancy between fixed columns and flexible JSON storage.
|
|
|
|
Run with:
|
|
docker exec customer_portal python -m customer_portal.migrations.004_consolidate_customer_fields
|
|
|
|
Rollback with:
|
|
docker exec customer_portal python -m customer_portal.migrations.004_consolidate_customer_fields --rollback
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
import sys
|
|
|
|
from sqlalchemy import create_engine, text
|
|
|
|
|
|
def get_engine():
|
|
"""Get database engine."""
|
|
database_url = os.environ.get(
|
|
"DATABASE_URL", "postgresql://portal:portal@localhost:5432/customer_portal"
|
|
)
|
|
return create_engine(database_url)
|
|
|
|
|
|
def run_migration():
|
|
"""Move fixed field values into custom_fields JSON."""
|
|
engine = get_engine()
|
|
|
|
print("Sprint 12 Phase 1: Consolidating customer fields...")
|
|
print("=" * 60)
|
|
|
|
with engine.connect() as conn:
|
|
# Get all customers
|
|
result = conn.execute(
|
|
text(
|
|
"""
|
|
SELECT id, email, name, phone, address_street, address_city, address_zip, custom_fields
|
|
FROM customers
|
|
ORDER BY id
|
|
"""
|
|
)
|
|
)
|
|
customers = result.fetchall()
|
|
|
|
migrated = 0
|
|
skipped = 0
|
|
|
|
for row in customers:
|
|
customer_id = row[0]
|
|
email = row[1]
|
|
name = row[2]
|
|
phone = row[3]
|
|
address_street = row[4]
|
|
address_city = row[5]
|
|
address_zip = row[6]
|
|
custom_fields_raw = row[7]
|
|
|
|
# Parse existing custom_fields
|
|
try:
|
|
custom_fields = (
|
|
json.loads(custom_fields_raw) if custom_fields_raw else {}
|
|
)
|
|
except (json.JSONDecodeError, TypeError):
|
|
custom_fields = {}
|
|
|
|
# Track changes
|
|
changes = []
|
|
|
|
# Migrate name (only if not already in custom_fields)
|
|
if name and "name" not in custom_fields:
|
|
custom_fields["name"] = name
|
|
changes.append(f"name={name}")
|
|
|
|
# Migrate phone (only if not already in custom_fields)
|
|
if phone and "phone" not in custom_fields:
|
|
custom_fields["phone"] = phone
|
|
changes.append(f"phone={phone}")
|
|
|
|
# Migrate address_street (only if not already in custom_fields)
|
|
if address_street and "address_street" not in custom_fields:
|
|
custom_fields["address_street"] = address_street
|
|
changes.append(f"address_street={address_street[:30]}...")
|
|
|
|
# Migrate address_city (only if not already in custom_fields)
|
|
if address_city and "address_city" not in custom_fields:
|
|
custom_fields["address_city"] = address_city
|
|
changes.append(f"address_city={address_city[:30]}...")
|
|
|
|
# Migrate address_zip (only if not already in custom_fields)
|
|
if address_zip and "address_zip" not in custom_fields:
|
|
custom_fields["address_zip"] = address_zip
|
|
changes.append(f"address_zip={address_zip}")
|
|
|
|
if changes:
|
|
# Update customer
|
|
new_custom_fields = json.dumps(custom_fields, ensure_ascii=False)
|
|
conn.execute(
|
|
text(
|
|
"""
|
|
UPDATE customers
|
|
SET custom_fields = :custom_fields
|
|
WHERE id = :id
|
|
"""
|
|
),
|
|
{"custom_fields": new_custom_fields, "id": customer_id},
|
|
)
|
|
migrated += 1
|
|
print(f" [OK] Customer {customer_id} ({email}): {', '.join(changes)}")
|
|
else:
|
|
skipped += 1
|
|
|
|
conn.commit()
|
|
|
|
print("=" * 60)
|
|
print(
|
|
f"Migration completed: {migrated} migrated, {skipped} skipped (already consolidated)"
|
|
)
|
|
|
|
|
|
def run_rollback():
|
|
"""Restore fixed fields from custom_fields (for rollback)."""
|
|
engine = get_engine()
|
|
|
|
print("Rolling back Sprint 12 Phase 1...")
|
|
print("=" * 60)
|
|
|
|
with engine.connect() as conn:
|
|
# Get all customers
|
|
result = conn.execute(
|
|
text(
|
|
"""
|
|
SELECT id, email, name, phone, address_street, address_city, address_zip, custom_fields
|
|
FROM customers
|
|
ORDER BY id
|
|
"""
|
|
)
|
|
)
|
|
customers = result.fetchall()
|
|
|
|
rolled_back = 0
|
|
|
|
for row in customers:
|
|
customer_id = row[0]
|
|
email = row[1]
|
|
current_name = row[2]
|
|
current_phone = row[3]
|
|
current_street = row[4]
|
|
current_city = row[5]
|
|
current_zip = row[6]
|
|
custom_fields_raw = row[7]
|
|
|
|
# Parse custom_fields
|
|
try:
|
|
custom_fields = (
|
|
json.loads(custom_fields_raw) if custom_fields_raw else {}
|
|
)
|
|
except (json.JSONDecodeError, TypeError):
|
|
continue
|
|
|
|
# Check if we have migrated data in custom_fields
|
|
updates = {}
|
|
|
|
# Restore name if it differs
|
|
if "name" in custom_fields and custom_fields["name"] != current_name:
|
|
updates["name"] = custom_fields["name"]
|
|
|
|
# Restore phone if it differs
|
|
if "phone" in custom_fields and custom_fields["phone"] != current_phone:
|
|
updates["phone"] = custom_fields["phone"]
|
|
|
|
# Restore address fields
|
|
if (
|
|
"address_street" in custom_fields
|
|
and custom_fields["address_street"] != current_street
|
|
):
|
|
updates["address_street"] = custom_fields["address_street"]
|
|
|
|
if (
|
|
"address_city" in custom_fields
|
|
and custom_fields["address_city"] != current_city
|
|
):
|
|
updates["address_city"] = custom_fields["address_city"]
|
|
|
|
if (
|
|
"address_zip" in custom_fields
|
|
and custom_fields["address_zip"] != current_zip
|
|
):
|
|
updates["address_zip"] = custom_fields["address_zip"]
|
|
|
|
if updates:
|
|
# Build update query
|
|
set_clauses = ", ".join([f"{k} = :{k}" for k in updates])
|
|
updates["id"] = customer_id
|
|
conn.execute(
|
|
text(f"UPDATE customers SET {set_clauses} WHERE id = :id"), updates
|
|
)
|
|
rolled_back += 1
|
|
print(
|
|
f" [OK] Customer {customer_id} ({email}): restored {list(updates.keys())}"
|
|
)
|
|
|
|
conn.commit()
|
|
|
|
print("=" * 60)
|
|
print(f"Rollback completed: {rolled_back} customers restored")
|
|
|
|
|
|
def verify_migration():
|
|
"""Verify migration by checking custom_fields content."""
|
|
engine = get_engine()
|
|
|
|
print("\nVerifying migration...")
|
|
print("=" * 60)
|
|
|
|
with engine.connect() as conn:
|
|
result = conn.execute(
|
|
text(
|
|
"""
|
|
SELECT id, email, custom_fields
|
|
FROM customers
|
|
WHERE custom_fields IS NOT NULL AND custom_fields != '{}'
|
|
ORDER BY id
|
|
LIMIT 10
|
|
"""
|
|
)
|
|
)
|
|
|
|
for row in result:
|
|
customer_id = row[0]
|
|
email = row[1]
|
|
custom_fields_raw = row[2]
|
|
|
|
try:
|
|
custom_fields = json.loads(custom_fields_raw)
|
|
has_name = "name" in custom_fields
|
|
has_phone = "phone" in custom_fields
|
|
has_address = "address_street" in custom_fields
|
|
|
|
print(f" Customer {customer_id} ({email}):")
|
|
print(f" name: {'OK' if has_name else 'MISSING'}")
|
|
print(f" phone: {'OK' if has_phone else 'MISSING'}")
|
|
print(f" address: {'OK' if has_address else 'MISSING'}")
|
|
except json.JSONDecodeError:
|
|
print(f" Customer {customer_id} ({email}): INVALID JSON")
|
|
|
|
print("=" * 60)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
if "--rollback" in sys.argv:
|
|
run_rollback()
|
|
elif "--verify" in sys.argv:
|
|
verify_migration()
|
|
else:
|
|
run_migration()
|
|
verify_migration()
|