e-taxes settings is single doctype now
This commit is contained in:
parent
0b46271628
commit
5ee0badd00
|
|
@ -75,14 +75,13 @@ def after_install():
|
|||
setup_token_renewal()
|
||||
|
||||
|
||||
before_migrate = ["invoice_az.migrate_utils.backup_etaxes_settings"]
|
||||
|
||||
|
||||
def after_migrate():
|
||||
"""Run migration tasks"""
|
||||
from invoice_az.auth import setup_token_renewal
|
||||
from invoice_az.migrate_utils import migrate_etaxes_settings_to_single
|
||||
|
||||
setup_token_renewal()
|
||||
migrate_etaxes_settings_to_single()
|
||||
|
||||
# Fixtures for master data
|
||||
# ------------------------
|
||||
|
|
|
|||
|
|
@ -1,20 +1,9 @@
|
|||
"""
|
||||
Migration utilities for E-Taxes Settings → Single doctype conversion.
|
||||
|
||||
backup_etaxes_settings() is called by the before_migrate hook — at that point
|
||||
the doctype is still a regular doctype and ORM works normally.
|
||||
|
||||
restore_etaxes_settings() is called by the patch — at that point the doctype
|
||||
is already Single, so we use raw SQL and restore everything from the backup file.
|
||||
Migration utilities for E-Taxes Settings → Single doctype.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
|
||||
import frappe
|
||||
|
||||
BACKUP_FILENAME = "etaxes_settings_migration.json"
|
||||
|
||||
SCALAR_FIELDS = [
|
||||
"similarity_threshold",
|
||||
"consider_azeri_chars",
|
||||
|
|
@ -26,83 +15,56 @@ SCALAR_FIELDS = [
|
|||
"default_payment_terms",
|
||||
]
|
||||
|
||||
# SQL table name → parentfield name in E-Taxes Settings
|
||||
CHILD_TABLES = {
|
||||
"tabE-Taxes Item Mapping": "item_mappings",
|
||||
"tabE-Taxes Customer Mappings": "customer_mappings",
|
||||
"tabE-Taxes Supplier Mappings": "supplier_mappings",
|
||||
"tabE-Taxes Unit Mapping": "unit_mappings",
|
||||
"tabE-Taxes VAT Account Mapping": "vat_account_mappings",
|
||||
}
|
||||
CHILD_TABLES = [
|
||||
"tabE-Taxes Item Mapping",
|
||||
"tabE-Taxes Customer Mappings",
|
||||
"tabE-Taxes Supplier Mappings",
|
||||
"tabE-Taxes Unit Mapping",
|
||||
"tabE-Taxes VAT Account Mapping",
|
||||
]
|
||||
|
||||
|
||||
def _backup_path():
|
||||
return frappe.get_site_path("private", BACKUP_FILENAME)
|
||||
|
||||
|
||||
def backup_etaxes_settings():
|
||||
def migrate_etaxes_settings_to_single():
|
||||
"""
|
||||
Called before_migrate (before schema sync).
|
||||
Dumps scalar fields + all child table rows to a JSON file.
|
||||
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
|
||||
|
||||
# Find the best old record — ORM still works normally here
|
||||
rows = None
|
||||
# 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' ORDER BY modified DESC LIMIT 1",
|
||||
"SELECT * FROM `tabE-Taxes Settings` ORDER BY modified DESC LIMIT 1",
|
||||
"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 rows:
|
||||
break
|
||||
|
||||
if not rows:
|
||||
if not old_row:
|
||||
return
|
||||
|
||||
old_row = rows[0]
|
||||
old_name = old_row["name"]
|
||||
old_name = old_row.get("name")
|
||||
|
||||
backup = {
|
||||
"old_name": old_name,
|
||||
"scalar": {f: old_row.get(f) for f in SCALAR_FIELDS},
|
||||
"children": {},
|
||||
}
|
||||
|
||||
# Save ALL columns of each child table row so we can re-insert them if needed
|
||||
for table in CHILD_TABLES:
|
||||
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"][table] = [dict(r) for r in child_rows]
|
||||
|
||||
with open(_backup_path(), "w", encoding="utf-8") as f:
|
||||
json.dump(backup, f, default=str, ensure_ascii=False, indent=2)
|
||||
|
||||
|
||||
def restore_etaxes_settings():
|
||||
"""
|
||||
Called from the migration patch (after schema sync).
|
||||
Reads backup file → writes scalar fields to tabSingles →
|
||||
deletes + re-inserts child table rows → deletes the file.
|
||||
"""
|
||||
path = _backup_path()
|
||||
|
||||
if not os.path.exists(path):
|
||||
return
|
||||
|
||||
with open(path, encoding="utf-8") as f:
|
||||
backup = json.load(f)
|
||||
|
||||
# --- Scalar fields → tabSingles ---
|
||||
for field, value in backup.get("scalar", {}).items():
|
||||
# Write scalar fields to tabSingles
|
||||
for field in SCALAR_FIELDS:
|
||||
value = old_row.get(field)
|
||||
if value is None:
|
||||
continue
|
||||
frappe.db.sql(
|
||||
|
|
@ -114,29 +76,36 @@ def restore_etaxes_settings():
|
|||
(field, str(value)),
|
||||
)
|
||||
|
||||
# --- Child tables: delete existing rows, then re-insert from backup ---
|
||||
for table, rows in backup.get("children", {}).items():
|
||||
if not frappe.db.table_exists(table) or not rows:
|
||||
# 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
|
||||
|
||||
# Remove any rows already parented to the Single (e.g., from a broken previous run)
|
||||
frappe.db.sql(
|
||||
f"DELETE FROM `{table}` WHERE parent = 'E-Taxes Settings'"
|
||||
)
|
||||
if not child_rows:
|
||||
continue
|
||||
|
||||
for row in rows:
|
||||
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))
|
||||
|
||||
frappe.db.sql(
|
||||
f"INSERT INTO `{table}` ({columns}) VALUES ({placeholders})",
|
||||
list(row.values()),
|
||||
)
|
||||
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")
|
||||
|
||||
os.remove(path)
|
||||
|
|
|
|||
|
|
@ -1,18 +1,7 @@
|
|||
"""
|
||||
Migrate E-Taxes Settings from multi-record to Single doctype.
|
||||
|
||||
Data is backed up to a JSON file by the before_migrate hook
|
||||
(invoice_az.migrate_utils.backup_etaxes_settings) which runs before schema
|
||||
changes — at that point the ORM still works normally.
|
||||
|
||||
This patch runs after schema sync (when the doctype is already Single)
|
||||
and restores the data from the backup file via raw SQL.
|
||||
"""
|
||||
|
||||
import frappe
|
||||
|
||||
|
||||
def execute():
|
||||
from invoice_az.migrate_utils import restore_etaxes_settings
|
||||
from invoice_az.migrate_utils import migrate_etaxes_settings_to_single
|
||||
|
||||
restore_etaxes_settings()
|
||||
migrate_etaxes_settings_to_single()
|
||||
|
|
|
|||
Loading…
Reference in New Issue