e-taxes settings is single doctype now

This commit is contained in:
Ali 2026-03-03 17:15:45 +04:00
parent 331aabe1d7
commit 2ba3a97227
2 changed files with 80 additions and 141 deletions

View File

@ -12,17 +12,17 @@ frappe.ui.form.on('E-Taxes Settings', {
}, },
refresh: function(frm) { refresh: function(frm) {
frm.add_custom_button(__('Restore from old data'), function() { frm.add_custom_button(__('Restore from file'), function() {
frappe.confirm( 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() { function() {
frappe.call({ 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: true,
freeze_message: __('Restoring...'), freeze_message: __('Restoring...'),
callback: function(r) { callback: function(r) {
if (r.message && r.message.success) { 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(); frm.reload_doc();
} else { } else {
frappe.msgprint({ frappe.msgprint({

View File

@ -1,9 +1,10 @@
""" import json
Migration utilities for E-Taxes Settings Single doctype. import os
"""
import frappe import frappe
BACKUP_FILENAME = "etaxes_settings_backup.json"
SCALAR_FIELDS = [ SCALAR_FIELDS = [
"similarity_threshold", "similarity_threshold",
"consider_azeri_chars", "consider_azeri_chars",
@ -15,47 +16,80 @@ SCALAR_FIELDS = [
"default_payment_terms", "default_payment_terms",
] ]
CHILD_TABLES = [ CHILD_TABLES = {
"tabE-Taxes Item Mapping", "item_mappings": "tabE-Taxes Item Mapping",
"tabE-Taxes Customer Mappings", "customer_mappings": "tabE-Taxes Customer Mappings",
"tabE-Taxes Supplier Mappings", "supplier_mappings": "tabE-Taxes Supplier Mappings",
"tabE-Taxes Unit Mapping", "unit_mappings": "tabE-Taxes Unit Mapping",
"tabE-Taxes VAT Account Mapping", "vat_account_mappings": "tabE-Taxes VAT Account Mapping",
] }
def _backup_path():
return frappe.get_site_path("private", BACKUP_FILENAME)
@frappe.whitelist() @frappe.whitelist()
def restore_etaxes_settings_from_old_table(): def export_etaxes_to_file():
""" """
Called from the E-Taxes Settings form button. Run BEFORE bench migrate:
Reads data from the old tabE-Taxes Settings (still exists after migration) bench --site <site> execute invoice_az.migrate_utils.export_etaxes_to_file
and populates the current Single doctype. Saves all E-Taxes Settings data to site/private/etaxes_settings_backup.json
""" """
if not frappe.db.table_exists("tabE-Taxes Settings"): 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
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
# 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] old_row = rows[0]
break old_name = old_row["name"]
except Exception:
pass
if not old_row: backup = {
return {"success": False, "message": "No old settings record found in database"} "old_name": old_name,
"scalar": {},
"children": {},
}
old_name = old_row.get("name")
# Write scalar fields to tabSingles
for field in SCALAR_FIELDS: 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: if value is None:
continue continue
frappe.db.sql( frappe.db.sql(
@ -67,26 +101,15 @@ def restore_etaxes_settings_from_old_table():
(field, str(value)), (field, str(value)),
) )
# Restore child table rows # Child tables → DELETE existing + INSERT from backup
restored_children = {} for fieldname, rows in backup.get("children", {}).items():
for table in CHILD_TABLES: table = CHILD_TABLES.get(fieldname)
if not frappe.db.table_exists(table): if not table or not frappe.db.table_exists(table) or not rows:
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 continue
frappe.db.sql(f"DELETE FROM `{table}` WHERE parent = 'E-Taxes Settings'") 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["parent"] = "E-Taxes Settings"
row["parenttype"] = "E-Taxes Settings" row["parenttype"] = "E-Taxes Settings"
columns = ", ".join(f"`{col}`" for col in row) columns = ", ".join(f"`{col}`" for col in row)
@ -99,100 +122,16 @@ def restore_etaxes_settings_from_old_table():
except Exception: except Exception:
pass pass
restored_children[table] = len(child_rows)
frappe.db.commit() frappe.db.commit()
frappe.clear_cache(doctype="E-Taxes Settings") frappe.clear_cache(doctype="E-Taxes Settings")
total = sum(len(v) for v in backup.get("children", {}).values())
return { return {
"success": True, "success": True,
"message": f"Restored from record '{old_name}'", "message": f"Restored from '{backup.get('old_name')}'{total} child rows",
"children": restored_children,
} }
def migrate_etaxes_settings_to_single(): def migrate_etaxes_settings_to_single():
""" """Called from after_migrate hook — no-op if old table is gone."""
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 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")