From daf185587a4b85a2ba062580769893d52c87223d Mon Sep 17 00:00:00 2001 From: Shayan Azadi Date: Thu, 16 Jul 2026 15:07:56 +0330 Subject: [PATCH] Fix accounting migrations so fresh deploys create regarding and event_name columns. Make 0009/0010 idempotent and aligned with current models so server migrate after a wipe does not miss schema fields. Co-authored-by: Cursor --- .../migrations/0009_treasury_event_name.py | 46 ++++++++-- ...oucher_regarding_invoiceline_event_name.py | 86 +++++++++++++++++++ 2 files changed, 124 insertions(+), 8 deletions(-) create mode 100644 accounting/migrations/0010_voucher_regarding_invoiceline_event_name.py diff --git a/accounting/migrations/0009_treasury_event_name.py b/accounting/migrations/0009_treasury_event_name.py index 6a178a0..e8eec3a 100644 --- a/accounting/migrations/0009_treasury_event_name.py +++ b/accounting/migrations/0009_treasury_event_name.py @@ -1,11 +1,44 @@ from django.db import migrations, models +def _table_columns(schema_editor, table): + connection = schema_editor.connection + with connection.cursor() as cursor: + return { + col.name + for col in connection.introspection.get_table_description(cursor, table) + } + + +def _ensure_varchar_column(schema_editor, table, column, max_length): + """Add a non-null varchar column with empty default when missing; else clear NULLs.""" + connection = schema_editor.connection + q_table = connection.ops.quote_name(table) + q_column = connection.ops.quote_name(column) + + if column in _table_columns(schema_editor, table): + with connection.cursor() as cursor: + cursor.execute( + f"UPDATE {q_table} SET {q_column} = '' WHERE {q_column} IS NULL" + ) + return + + with connection.cursor() as cursor: + cursor.execute( + f"ALTER TABLE {q_table} " + f"ADD COLUMN {q_column} varchar({max_length}) DEFAULT '' NOT NULL" + ) + + +def add_treasury_event_name(apps, schema_editor): + """ + Add TreasuryTxn.event_name when missing. + Safe for fresh DBs and for DBs that already have the column from older deploys. + """ + _ensure_varchar_column(schema_editor, 'accounting_treasury_txns', 'event_name', 200) + + class Migration(migrations.Migration): - """ - DB already has event_name (NOT NULL) from an earlier manual/out-of-band change. - Sync Django state and ensure NULLs become empty string so inserts never omit the column. - """ dependencies = [ ('accounting', '0008_invoice_cancelled_status'), @@ -21,10 +54,7 @@ class Migration(migrations.Migration): ), ], database_operations=[ - migrations.RunSQL( - sql="UPDATE accounting_treasury_txns SET event_name = '' WHERE event_name IS NULL;", - reverse_sql=migrations.RunSQL.noop, - ), + migrations.RunPython(add_treasury_event_name, migrations.RunPython.noop), ], ), ] diff --git a/accounting/migrations/0010_voucher_regarding_invoiceline_event_name.py b/accounting/migrations/0010_voucher_regarding_invoiceline_event_name.py new file mode 100644 index 0000000..1fbf682 --- /dev/null +++ b/accounting/migrations/0010_voucher_regarding_invoiceline_event_name.py @@ -0,0 +1,86 @@ +from django.db import migrations, models + + +def _table_columns(schema_editor, table): + connection = schema_editor.connection + with connection.cursor() as cursor: + return { + col.name + for col in connection.introspection.get_table_description(cursor, table) + } + + +def _ensure_varchar_column(schema_editor, table, column, max_length): + """Add a non-null varchar column with empty default when missing; else clear NULLs.""" + connection = schema_editor.connection + q_table = connection.ops.quote_name(table) + q_column = connection.ops.quote_name(column) + + if column in _table_columns(schema_editor, table): + with connection.cursor() as cursor: + cursor.execute( + f"UPDATE {q_table} SET {q_column} = '' WHERE {q_column} IS NULL" + ) + return + + with connection.cursor() as cursor: + cursor.execute( + f"ALTER TABLE {q_table} " + f"ADD COLUMN {q_column} varchar({max_length}) DEFAULT '' NOT NULL" + ) + + +def add_missing_columns(apps, schema_editor): + """ + Ensure schema columns match current models. + Idempotent for local/prod DBs that already have some of these columns. + Also re-checks treasury event_name in case an older 0009 only updated state. + """ + specs = [ + ('accounting_vouchers', 'regarding', 255), + ('accounting_invoice_lines', 'event_name', 255), + ('accounting_treasury_txns', 'event_name', 200), + ] + for table, column, max_length in specs: + _ensure_varchar_column(schema_editor, table, column, max_length) + + +class Migration(migrations.Migration): + + dependencies = [ + ('accounting', '0009_treasury_event_name'), + ] + + operations = [ + migrations.SeparateDatabaseAndState( + state_operations=[ + migrations.AddField( + model_name='invoiceline', + name='event_name', + field=models.CharField(blank=True, default='', max_length=255), + ), + migrations.AddField( + model_name='voucher', + name='regarding', + field=models.CharField(blank=True, default='', max_length=255), + ), + migrations.AlterField( + model_name='party', + name='external_source', + field=models.CharField( + blank=True, + choices=[ + ('', 'Manual'), + ('funzone_customer', 'FunZone Customer'), + ('funzone_owner', 'FunZone Owner'), + ], + default='', + max_length=30, + ), + ), + ], + database_operations=[ + migrations.RunPython(add_missing_columns, migrations.RunPython.noop), + ], + ), + ]