From 49d974f3666cdc18aaca70b2a7d029fd781f0c64 Mon Sep 17 00:00:00 2001 From: Ali <010109ali@gmail.com> Date: Tue, 3 Mar 2026 17:32:33 +0400 Subject: [PATCH] e-taxes settings is single doctype now --- invoice_az/migrate_utils.py | 109 +++++++++++++++++++++++++++++------- 1 file changed, 89 insertions(+), 20 deletions(-) diff --git a/invoice_az/migrate_utils.py b/invoice_az/migrate_utils.py index 37570d4..f9a48ae 100644 --- a/invoice_az/migrate_utils.py +++ b/invoice_az/migrate_utils.py @@ -29,20 +29,45 @@ def _backup_path(): return frappe.get_site_path("private", BACKUP_FILENAME) +def _sql_table_exists(table_name): + """Check if table exists via raw SQL (bypasses Frappe metadata).""" + result = frappe.db.sql( + "SHOW TABLES LIKE %s", + (table_name,), + ) + return bool(result) + + @frappe.whitelist() def export_etaxes_to_file(): """ - 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"): - print("ERROR: tabE-Taxes Settings not found") - return + Export E-Taxes Settings data to a JSON file. + Works even after git pull (when doctype JSON already says issingle=1 + but the old multi-record table still exists in MySQL). + Usage: + bench --site execute invoice_az.migrate_utils.export_etaxes_to_file + """ + # Try old multi-record table first + if _sql_table_exists("tabE-Taxes Settings"): + return _export_from_table() + + # Fallback: already migrated to Single — export from tabSingles + singles = frappe.db.sql( + "SELECT field, value FROM tabSingles WHERE doctype = 'E-Taxes Settings'", + as_dict=True, + ) + if singles: + return _export_from_singles(singles) + + print("ERROR: No data found — neither tabE-Taxes Settings table nor tabSingles entries exist") + + +def _export_from_table(): + """Export from the old multi-record table.""" 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") + print("ERROR: tabE-Taxes Settings table exists but is empty") return old_row = rows[0] @@ -58,7 +83,7 @@ def export_etaxes_to_file(): backup["scalar"][field] = old_row.get(field) for fieldname, table in CHILD_TABLES.items(): - if frappe.db.table_exists(table): + if _sql_table_exists(table): child_rows = frappe.db.sql( f"SELECT * FROM `{table}` WHERE parent = %s ORDER BY idx", (old_name,), @@ -66,29 +91,71 @@ def export_etaxes_to_file(): ) backup["children"][fieldname] = [dict(r) for r in child_rows] + return _save_backup(backup, old_name) + + +def _export_from_singles(singles): + """Export from tabSingles (already a Single doctype).""" + backup = { + "old_name": "E-Taxes Settings", + "scalar": {}, + "children": {}, + } + + singles_map = {r["field"]: r["value"] for r in singles} + for field in SCALAR_FIELDS: + backup["scalar"][field] = singles_map.get(field) + + for fieldname, table in CHILD_TABLES.items(): + if _sql_table_exists(table): + child_rows = frappe.db.sql( + f"SELECT * FROM `{table}` WHERE parent = 'E-Taxes Settings' ORDER BY idx", + as_dict=True, + ) + backup["children"][fieldname] = [dict(r) for r in child_rows] + + return _save_backup(backup, "E-Taxes Settings") + + +def _save_backup(backup, source_name): + """Save backup dict to JSON file.""" 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"Source: '{source_name}'") + print(f"Scalar fields: {sum(1 for v in backup['scalar'].values() if v is not None)}") print(f"Child rows: {total_children}") + for fieldname, rows in backup["children"].items(): + if rows: + print(f" {fieldname}: {len(rows)}") return path @frappe.whitelist() def restore_etaxes_from_file(): - """Called from the E-Taxes Settings form button.""" + """ + Restore E-Taxes Settings from backup JSON file. + + Usage (CLI): + bench --site execute invoice_az.migrate_utils.restore_etaxes_from_file + + Also callable 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}"} + msg = f"Backup file not found: {path}" + print(f"ERROR: {msg}") + return {"success": False, "message": msg} with open(path, encoding="utf-8") as f: backup = json.load(f) # Scalar fields → tabSingles + restored_scalars = 0 for field, value in backup.get("scalar", {}).items(): if value is None: continue @@ -100,11 +167,13 @@ def restore_etaxes_from_file(): """, (field, str(value)), ) + restored_scalars += 1 # Child tables → DELETE existing + INSERT from backup + restored_children = 0 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: + if not table or not _sql_table_exists(table) or not rows: continue frappe.db.sql(f"DELETE FROM `{table}` WHERE parent = 'E-Taxes Settings'") @@ -112,6 +181,7 @@ def restore_etaxes_from_file(): for row in rows: row["parent"] = "E-Taxes Settings" row["parenttype"] = "E-Taxes Settings" + row["parentfield"] = fieldname columns = ", ".join(f"`{col}`" for col in row) placeholders = ", ".join(["%s"] * len(row)) try: @@ -119,17 +189,16 @@ def restore_etaxes_from_file(): f"INSERT INTO `{table}` ({columns}) VALUES ({placeholders})", list(row.values()), ) - except Exception: - pass + restored_children += 1 + except Exception as e: + print(f" Warning: failed to insert row in {table}: {e}") 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 '{backup.get('old_name')}' — {total} child rows", - } + msg = f"Restored {restored_scalars} fields, {restored_children} child rows" + print(f"OK: {msg}") + return {"success": True, "message": msg} def migrate_etaxes_settings_to_single():