From 2ba3a97227d2105f00dbcfd1f645b7e08f267a76 Mon Sep 17 00:00:00 2001 From: Ali <010109ali@gmail.com> Date: Tue, 3 Mar 2026 17:15:45 +0400 Subject: [PATCH] e-taxes settings is single doctype now --- .../e_taxes_settings/e_taxes_settings.js | 8 +- invoice_az/migrate_utils.py | 213 +++++++----------- 2 files changed, 80 insertions(+), 141 deletions(-) diff --git a/invoice_az/invoice_az/doctype/e_taxes_settings/e_taxes_settings.js b/invoice_az/invoice_az/doctype/e_taxes_settings/e_taxes_settings.js index eda8522..40bb596 100644 --- a/invoice_az/invoice_az/doctype/e_taxes_settings/e_taxes_settings.js +++ b/invoice_az/invoice_az/doctype/e_taxes_settings/e_taxes_settings.js @@ -12,17 +12,17 @@ frappe.ui.form.on('E-Taxes Settings', { }, refresh: function(frm) { - frm.add_custom_button(__('Restore from old data'), function() { + frm.add_custom_button(__('Restore from file'), function() { frappe.confirm( - __('This will restore all settings and mappings from the pre-migration database record. Continue?'), + __('Restore all settings and mappings from backup file? Current data will be overwritten.'), function() { frappe.call({ - method: 'invoice_az.migrate_utils.restore_etaxes_settings_from_old_table', + method: 'invoice_az.migrate_utils.restore_etaxes_from_file', freeze: true, freeze_message: __('Restoring...'), callback: function(r) { if (r.message && r.message.success) { - frappe.show_alert({ message: __(r.message.message), indicator: 'green' }, 5); + frappe.show_alert({ message: r.message.message, indicator: 'green' }, 5); frm.reload_doc(); } else { frappe.msgprint({ diff --git a/invoice_az/migrate_utils.py b/invoice_az/migrate_utils.py index 0b37a62..37570d4 100644 --- a/invoice_az/migrate_utils.py +++ b/invoice_az/migrate_utils.py @@ -1,9 +1,10 @@ -""" -Migration utilities for E-Taxes Settings → Single doctype. -""" +import json +import os import frappe +BACKUP_FILENAME = "etaxes_settings_backup.json" + SCALAR_FIELDS = [ "similarity_threshold", "consider_azeri_chars", @@ -15,47 +16,80 @@ SCALAR_FIELDS = [ "default_payment_terms", ] -CHILD_TABLES = [ - "tabE-Taxes Item Mapping", - "tabE-Taxes Customer Mappings", - "tabE-Taxes Supplier Mappings", - "tabE-Taxes Unit Mapping", - "tabE-Taxes VAT Account Mapping", -] +CHILD_TABLES = { + "item_mappings": "tabE-Taxes Item Mapping", + "customer_mappings": "tabE-Taxes Customer Mappings", + "supplier_mappings": "tabE-Taxes Supplier Mappings", + "unit_mappings": "tabE-Taxes Unit Mapping", + "vat_account_mappings": "tabE-Taxes VAT Account Mapping", +} + + +def _backup_path(): + return frappe.get_site_path("private", BACKUP_FILENAME) @frappe.whitelist() -def restore_etaxes_settings_from_old_table(): +def export_etaxes_to_file(): """ - Called from the E-Taxes Settings form button. - Reads data from the old tabE-Taxes Settings (still exists after migration) - and populates the current Single doctype. + Run BEFORE bench migrate: + bench --site execute invoice_az.migrate_utils.export_etaxes_to_file + Saves all E-Taxes Settings data to site/private/etaxes_settings_backup.json """ if not frappe.db.table_exists("tabE-Taxes Settings"): - return {"success": False, "message": "Old table not found"} + print("ERROR: tabE-Taxes Settings not found") + return - # Get the old record - old_row = None - for query in [ - "SELECT * FROM `tabE-Taxes Settings` WHERE name != 'E-Taxes Settings' LIMIT 1", - "SELECT * FROM `tabE-Taxes Settings` LIMIT 1", - ]: - try: - rows = frappe.db.sql(query, as_dict=True) - if rows: - old_row = rows[0] - break - except Exception: - pass + rows = frappe.db.sql("SELECT * FROM `tabE-Taxes Settings` LIMIT 1", as_dict=True) + if not rows: + print("ERROR: no records in tabE-Taxes Settings") + return - if not old_row: - return {"success": False, "message": "No old settings record found in database"} + old_row = rows[0] + old_name = old_row["name"] - old_name = old_row.get("name") + backup = { + "old_name": old_name, + "scalar": {}, + "children": {}, + } - # Write scalar fields to tabSingles for field in SCALAR_FIELDS: - value = old_row.get(field) + backup["scalar"][field] = old_row.get(field) + + for fieldname, table in CHILD_TABLES.items(): + if frappe.db.table_exists(table): + child_rows = frappe.db.sql( + f"SELECT * FROM `{table}` WHERE parent = %s ORDER BY idx", + (old_name,), + as_dict=True, + ) + backup["children"][fieldname] = [dict(r) for r in child_rows] + + path = _backup_path() + with open(path, "w", encoding="utf-8") as f: + json.dump(backup, f, default=str, ensure_ascii=False, indent=2) + + total_children = sum(len(v) for v in backup["children"].values()) + print(f"Saved to: {path}") + print(f"Record: '{old_name}'") + print(f"Child rows: {total_children}") + return path + + +@frappe.whitelist() +def restore_etaxes_from_file(): + """Called from the E-Taxes Settings form button.""" + path = _backup_path() + + if not os.path.exists(path): + return {"success": False, "message": f"Backup file not found: {path}"} + + with open(path, encoding="utf-8") as f: + backup = json.load(f) + + # Scalar fields → tabSingles + for field, value in backup.get("scalar", {}).items(): if value is None: continue frappe.db.sql( @@ -67,26 +101,15 @@ def restore_etaxes_settings_from_old_table(): (field, str(value)), ) - # Restore child table rows - restored_children = {} - for table in CHILD_TABLES: - if not frappe.db.table_exists(table): - continue - try: - child_rows = frappe.db.sql( - f"SELECT * FROM `{table}` WHERE parent = %s ORDER BY idx", - (old_name,), - as_dict=True, - ) - except Exception: - continue - - if not child_rows: + # Child tables → DELETE existing + INSERT from backup + for fieldname, rows in backup.get("children", {}).items(): + table = CHILD_TABLES.get(fieldname) + if not table or not frappe.db.table_exists(table) or not rows: continue frappe.db.sql(f"DELETE FROM `{table}` WHERE parent = 'E-Taxes Settings'") - for row in child_rows: + for row in rows: row["parent"] = "E-Taxes Settings" row["parenttype"] = "E-Taxes Settings" columns = ", ".join(f"`{col}`" for col in row) @@ -99,100 +122,16 @@ def restore_etaxes_settings_from_old_table(): except Exception: pass - restored_children[table] = len(child_rows) - frappe.db.commit() frappe.clear_cache(doctype="E-Taxes Settings") + total = sum(len(v) for v in backup.get("children", {}).values()) return { "success": True, - "message": f"Restored from record '{old_name}'", - "children": restored_children, + "message": f"Restored from '{backup.get('old_name')}' — {total} child rows", } def migrate_etaxes_settings_to_single(): - """ - Called from after_migrate hook. - Reads the old tabE-Taxes Settings (Frappe never drops old tables during sync) - and writes scalar fields to tabSingles + re-parents child table rows. - Safe to call multiple times — skips if tabSingles already has data. - """ - # Skip if already migrated - already_migrated = frappe.db.sql( - "SELECT COUNT(*) as cnt FROM `tabSingles` WHERE doctype = 'E-Taxes Settings'", - as_dict=True, - ) - if already_migrated and already_migrated[0].get("cnt", 0) > 0: - return - - # Old table must exist - if not frappe.db.table_exists("tabE-Taxes Settings"): - return - - # Get the old record — try any row (old name could even be "E-Taxes Settings") - old_row = None - for query in [ - "SELECT * FROM `tabE-Taxes Settings` WHERE name != 'E-Taxes Settings' LIMIT 1", - "SELECT * FROM `tabE-Taxes Settings` LIMIT 1", - ]: - try: - rows = frappe.db.sql(query, as_dict=True) - if rows: - old_row = rows[0] - break - except Exception: - pass - - if not old_row: - return - - old_name = old_row.get("name") - - # Write scalar fields to tabSingles - for field in SCALAR_FIELDS: - value = old_row.get(field) - if value is None: - continue - frappe.db.sql( - """ - INSERT INTO `tabSingles` (doctype, field, value) - VALUES ('E-Taxes Settings', %s, %s) - ON DUPLICATE KEY UPDATE value = VALUES(value) - """, - (field, str(value)), - ) - - # Restore child table rows - for table in CHILD_TABLES: - if not frappe.db.table_exists(table): - continue - try: - child_rows = frappe.db.sql( - f"SELECT * FROM `{table}` WHERE parent = %s ORDER BY idx", - (old_name,), - as_dict=True, - ) - except Exception: - continue - - if not child_rows: - continue - - frappe.db.sql(f"DELETE FROM `{table}` WHERE parent = 'E-Taxes Settings'") - - for row in child_rows: - row["parent"] = "E-Taxes Settings" - row["parenttype"] = "E-Taxes Settings" - columns = ", ".join(f"`{col}`" for col in row) - placeholders = ", ".join(["%s"] * len(row)) - try: - frappe.db.sql( - f"INSERT INTO `{table}` ({columns}) VALUES ({placeholders})", - list(row.values()), - ) - except Exception: - pass - - frappe.db.commit() - frappe.clear_cache(doctype="E-Taxes Settings") + """Called from after_migrate hook — no-op if old table is gone.""" + pass