Files
crmApi/apps/customer/migrations/0008_customer_mobile_unique.py
T
2026-10-08 18:09:12 +03:30

70 lines
2.9 KiB
Python

from django.db import migrations, models
from apps.customer.mobile_migration_repair import repair_conflicts
def canonical_mobile(value):
if value is None:
return None
text = str(value)
if len(text) == 12 and text.startswith('98'):
return int(text[2:])
return value
def normalized_rows(model, alias, per_workspace=False, skip_ids=None):
seen = {}
updates = []
fields = ('pk', 'mobile', 'sys_ws_id') if per_workspace else ('pk', 'mobile')
for row in model.objects.using(alias).only(*fields).iterator():
if skip_ids and row.pk in skip_ids:
continue
mobile = canonical_mobile(row.mobile)
key = (row.sys_ws_id, mobile) if per_workspace else mobile
if mobile is not None and key in seen:
raise RuntimeError(
f'Duplicate mobiles in {model._meta.label}: record IDs {seen[key]} and {row.pk}. '
'Resolve these records before migration. No records were deleted.'
)
if mobile is not None:
seen[key] = row.pk
if mobile != row.mobile:
row.mobile = mobile
updates.append(row)
return updates
def check_and_normalize_mobiles(apps, schema_editor):
alias = schema_editor.connection.alias
customer = apps.get_model('customer', 'Customer')
user = apps.get_model('cuser', 'CUser')
role = apps.get_model('cuser', 'CuserRole')
# Quarantine legacy conflicts automatically, retaining a persistent audit.
skipped_users = repair_conflicts(apps, schema_editor, canonical_mobile)
customer_updates = normalized_rows(customer, alias, per_workspace=True)
user_updates = normalized_rows(user, alias, skip_ids=skipped_users)
role_updates = []
for row in role.objects.using(alias).only('pk', 'mobile').iterator():
normalized = canonical_mobile(row.mobile)
if normalized != row.mobile:
row.mobile = normalized
role_updates.append(row)
customer.objects.using(alias).bulk_update(customer_updates, ['mobile'], batch_size=500)
user.objects.using(alias).bulk_update(user_updates, ['mobile'], batch_size=500)
role.objects.using(alias).bulk_update(role_updates, ['mobile'], batch_size=500)
if schema_editor.connection.vendor == 'postgresql':
# Drain deferred FK checks from panel detachment before AddConstraint
# alters the same table in this atomic migration.
with schema_editor.connection.cursor() as cursor:
cursor.execute('SET CONSTRAINTS ALL IMMEDIATE')
class Migration(migrations.Migration):
dependencies = [('customer', '0007_alter_customer_name_family'), ('cuser', '0013_alter_cuser_name_family')]
operations = [
migrations.RunPython(check_and_normalize_mobiles, migrations.RunPython.noop),
migrations.AddConstraint(
model_name='customer',
constraint=models.UniqueConstraint(fields=('sys_ws', 'mobile'), name='customer_workspace_mobile_unique'),
),
]