From 639d580c19a1cc74d957d8cef60910dd9f8c4d1a Mon Sep 17 00:00:00 2001 From: Ali <010109ali@gmail.com> Date: Tue, 3 Mar 2026 16:17:07 +0400 Subject: [PATCH] e-taxes settings is single doctype now --- invoice_az/api.py | 107 +++++++----------- .../e_taxes_settings/e_taxes_settings.js | 15 --- .../e_taxes_settings/e_taxes_settings.json | 29 +---- .../e_taxes_settings/e_taxes_settings.py | 47 ++++++-- invoice_az/patches.txt | 1 + invoice_az/patches/__init__.py | 0 invoice_az/patches/v1_0/__init__.py | 0 .../v1_0/migrate_etaxes_settings_to_single.py | 90 +++++++++++++++ invoice_az/sales_api.py | 10 +- invoice_az/vat_api.py | 21 +--- 10 files changed, 174 insertions(+), 146 deletions(-) create mode 100644 invoice_az/patches/__init__.py create mode 100644 invoice_az/patches/v1_0/__init__.py create mode 100644 invoice_az/patches/v1_0/migrate_etaxes_settings_to_single.py diff --git a/invoice_az/api.py b/invoice_az/api.py index 6b6ee11..b73cf79 100644 --- a/invoice_az/api.py +++ b/invoice_az/api.py @@ -367,27 +367,8 @@ def load_items_from_invoices(date_from, date_to, max_count=200, offset=0): @frappe.whitelist() def get_default_settings(): """Getting active E-Taxes settings""" - try: - settings_list = frappe.get_all('E-Taxes Settings', - filters={'is_active': 1}, - limit=1) - - if settings_list: - settings = frappe.get_doc('E-Taxes Settings', settings_list[0].name) - return { - 'found': True, - 'settings': settings.as_dict() - } - else: - return { - 'found': False, - 'message': 'No active E-Taxes settings found' - } - except Exception as e: - return { - 'found': False, - 'message': str(e) - } + settings = frappe.get_single('E-Taxes Settings') + return {'found': True, 'settings': settings.as_dict()} @frappe.whitelist() def normalize_azeri_text(text): @@ -599,7 +580,7 @@ def match_similar_items(): total_processed = len(items_to_process) # Get fresh copy of settings document - doc = frappe.get_doc("E-Taxes Settings", settings.name) + doc = frappe.get_single('E-Taxes Settings') # Process all items for etaxes_item in items_to_process: @@ -668,16 +649,16 @@ def match_similar_items(): } @frappe.whitelist() -def create_unmapped_items(settings_name): +def create_unmapped_items(): """Creating items for unmapped elements from E-Taxes settings table with EQM Code support""" # Записываем активность record_etaxes_activity() - + try: import re - + # Get settings - settings_doc = frappe.get_doc("E-Taxes Settings", settings_name) + settings_doc = frappe.get_single('E-Taxes Settings') # Get unmapped items efficiently unmapped_etaxes_items = [] @@ -1606,7 +1587,7 @@ def match_similar_units(): total_processed = len(units_to_process) # Get fresh copy of settings document - doc = frappe.get_doc("E-Taxes Settings", settings.name) + doc = frappe.get_single('E-Taxes Settings') # Process all units for etaxes_unit in units_to_process: @@ -1675,16 +1656,16 @@ def match_similar_units(): } @frappe.whitelist() -def create_unmapped_units(settings_name): +def create_unmapped_units(): """Creating units for unmapped elements from E-Taxes settings table""" # Записываем активность record_etaxes_activity() - + try: import re - + # Get settings - settings_doc = frappe.get_doc("E-Taxes Settings", settings_name) + settings_doc = frappe.get_single('E-Taxes Settings') # Get elements from table that have no mapping unmapped_units = [] @@ -2060,11 +2041,11 @@ def update_mapped_statuses(doc, method=None): "E-Taxes Settings Error") @frappe.whitelist() -def refresh_item_mappings_display(settings_name): +def refresh_item_mappings_display(): """Операция-пустышка для обновления отображения item mappings""" try: # Получаем документ настроек - settings_doc = frappe.get_doc("E-Taxes Settings", settings_name) + settings_doc = frappe.get_single('E-Taxes Settings') # Подготавливаем данные для обновления отображения на клиенте updated_mappings = [] @@ -2912,7 +2893,7 @@ def match_similar_customers(): total_processed = len(customers_to_process) # Get fresh copy of settings document - doc = frappe.get_doc("E-Taxes Settings", settings.name) + doc = frappe.get_single('E-Taxes Settings') # Process all customers for etaxes_customer in customers_to_process: @@ -3039,7 +3020,7 @@ def match_similar_suppliers(): total_processed = len(suppliers_to_process) # Get fresh copy of settings document - doc = frappe.get_doc("E-Taxes Settings", settings.name) + doc = frappe.get_single('E-Taxes Settings') # Process all suppliers for etaxes_supplier in suppliers_to_process: @@ -3109,18 +3090,18 @@ def match_similar_suppliers(): } @frappe.whitelist() -def create_unmapped_customers(settings_name): +def create_unmapped_customers(): """Creating customers for unmapped elements from E-Taxes settings table""" # Записываем активность record_etaxes_activity() - + try: # Get settings - settings_doc = frappe.get_doc("E-Taxes Settings", settings_name) - + settings_doc = frappe.get_single('E-Taxes Settings') + # Get elements from table that have no mapping unmapped_customers = [] - + for mapping_customer in settings_doc.customer_mappings: # Check that customer has no mapping or it's empty if not mapping_customer.erp_customer: @@ -3128,7 +3109,7 @@ def create_unmapped_customers(settings_name): if frappe.db.exists('E-Taxes Customers', mapping_customer.etaxes_customer_name): etaxes_customer = frappe.get_doc('E-Taxes Customers', mapping_customer.etaxes_customer_name) unmapped_customers.append(etaxes_customer) - + if len(unmapped_customers) == 0: return { "success": True, @@ -3136,10 +3117,10 @@ def create_unmapped_customers(settings_name): "error_count": 0, "message": "No unmapped customers in table to create" } - + created_count = 0 error_count = 0 - + # Collect individual settings for each element mapping_settings = {} for mapping in settings_doc.customer_mappings: @@ -3150,10 +3131,10 @@ def create_unmapped_customers(settings_name): customer_settings['territory'] = mapping.territory if mapping.payment_terms: customer_settings['payment_terms'] = mapping.payment_terms - + if customer_settings: mapping_settings[mapping.etaxes_customer_name] = customer_settings - + for etaxes_customer in unmapped_customers: try: # Check if such customer with this name already exists @@ -3232,14 +3213,14 @@ def create_unmapped_customers(settings_name): } @frappe.whitelist() -def create_unmapped_suppliers(settings_name): +def create_unmapped_suppliers(): """Creating suppliers for unmapped elements from E-Taxes settings table""" # Записываем активность record_etaxes_activity() - + try: # Get settings - settings_doc = frappe.get_doc("E-Taxes Settings", settings_name) + settings_doc = frappe.get_single('E-Taxes Settings') # Get elements from table that have no mapping unmapped_suppliers = [] @@ -3352,18 +3333,18 @@ def create_unmapped_suppliers(settings_name): } @frappe.whitelist() -def create_unmapped_customers(settings_name): +def create_unmapped_customers_v2(): """Creating customers for unmapped elements from E-Taxes settings table""" # Записываем активность record_etaxes_activity() - + try: # Get settings - settings_doc = frappe.get_doc("E-Taxes Settings", settings_name) - + settings_doc = frappe.get_single('E-Taxes Settings') + # Get elements from table that have no mapping unmapped_customers = [] - + for mapping_customer in settings_doc.customer_mappings: # Check that customer has no mapping or it's empty if not mapping_customer.erp_customer: @@ -3371,7 +3352,7 @@ def create_unmapped_customers(settings_name): if frappe.db.exists('E-Taxes Customers', mapping_customer.etaxes_customer_name): etaxes_customer = frappe.get_doc('E-Taxes Customers', mapping_customer.etaxes_customer_name) unmapped_customers.append(etaxes_customer) - + if len(unmapped_customers) == 0: return { "success": True, @@ -3379,10 +3360,10 @@ def create_unmapped_customers(settings_name): "error_count": 0, "message": "No unmapped customers in table to create" } - + created_count = 0 error_count = 0 - + # Collect individual settings for each element mapping_settings = {} for mapping in settings_doc.customer_mappings: @@ -3393,10 +3374,10 @@ def create_unmapped_customers(settings_name): customer_settings['territory'] = mapping.territory if mapping.payment_terms: customer_settings['payment_terms'] = mapping.payment_terms - + if customer_settings: mapping_settings[mapping.etaxes_customer_name] = customer_settings - + for etaxes_customer in unmapped_customers: try: # ИСПРАВЛЕНИЕ: Обрезаем название клиента до 140 символов @@ -3484,15 +3465,7 @@ def create_unmapped_customers(settings_name): @frappe.whitelist() def get_active_settings(): """Getting active E-Taxes settings""" - settings_list = frappe.get_all('E-Taxes Settings', - filters={'is_active': 1}, - order_by='creation desc', - limit=1) - - if not settings_list: - return None - - return frappe.get_doc('E-Taxes Settings', settings_list[0].name) + return frappe.get_single('E-Taxes Settings') @frappe.whitelist() def normalize_string(text, consider_azeri=True): 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 608f4f0..62691b5 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 @@ -114,9 +114,6 @@ frappe.ui.form.on('E-Taxes Settings', { function() { frappe.call({ method: 'invoice_az.api.create_unmapped_items', - args: { - 'settings_name': frm.doc.name - }, callback: function(r) { if (r.message && r.message.success) { frappe.show_alert({ @@ -186,9 +183,6 @@ frappe.ui.form.on('E-Taxes Settings', { frm.save().then(function() { frappe.call({ method: 'invoice_az.api.refresh_item_mappings_display', - args: { - 'settings_name': frm.doc.name - }, callback: function(refresh_r) { if (refresh_r.message && refresh_r.message.success && refresh_r.message.updated_mappings) { refresh_r.message.updated_mappings.forEach(function(mapping) { @@ -351,9 +345,6 @@ frappe.ui.form.on('E-Taxes Settings', { function() { frappe.call({ method: 'invoice_az.api.create_unmapped_customers', - args: { - 'settings_name': frm.doc.name - }, callback: function(r) { if (r.message && r.message.success) { frappe.show_alert({ @@ -535,9 +526,6 @@ frappe.ui.form.on('E-Taxes Settings', { function() { frappe.call({ method: 'invoice_az.api.create_unmapped_suppliers', - args: { - 'settings_name': frm.doc.name - }, callback: function(r) { if (r.message && r.message.success) { frappe.show_alert({ @@ -713,9 +701,6 @@ frappe.ui.form.on('E-Taxes Settings', { function() { frappe.call({ method: 'invoice_az.api.create_unmapped_units', - args: { - 'settings_name': frm.doc.name - }, callback: function(r) { if (r.message && r.message.success) { frappe.show_alert({ diff --git a/invoice_az/invoice_az/doctype/e_taxes_settings/e_taxes_settings.json b/invoice_az/invoice_az/doctype/e_taxes_settings/e_taxes_settings.json index 7e3f678..e9a4843 100644 --- a/invoice_az/invoice_az/doctype/e_taxes_settings/e_taxes_settings.json +++ b/invoice_az/invoice_az/doctype/e_taxes_settings/e_taxes_settings.json @@ -1,15 +1,10 @@ { "actions": [], - "allow_rename": 1, - "autoname": "field:settings_name", "creation": "2025-05-06 12:00:00", "doctype": "DocType", "engine": "InnoDB", "field_order": [ "main_tab", - "settings_name", - "is_default", - "is_active", "general_section", "similarity_threshold", "consider_azeri_chars", @@ -55,26 +50,6 @@ "fieldtype": "Tab Break", "label": "Settings" }, - { - "fieldname": "settings_name", - "fieldtype": "Data", - "in_list_view": 1, - "label": "Settings Name", - "reqd": 1, - "unique": 1 - }, - { - "default": "0", - "fieldname": "is_default", - "fieldtype": "Check", - "label": "Is Default" - }, - { - "default": "1", - "fieldname": "is_active", - "fieldtype": "Check", - "label": "Is Active" - }, { "fieldname": "general_section", "fieldtype": "Section Break", @@ -102,7 +77,6 @@ { "fieldname": "default_item_group", "fieldtype": "Link", - "in_list_view": 1, "label": "Default Item Group", "options": "Item Group", "reqd": 1 @@ -110,7 +84,6 @@ { "fieldname": "default_uom", "fieldtype": "Link", - "in_list_view": 1, "label": "Default Unit of Measure", "options": "UOM", "reqd": 1 @@ -297,12 +270,12 @@ } ], "index_web_pages_for_search": 1, + "issingle": 1, "links": [], "modified": "2026-01-14 15:35:33.639586", "modified_by": "Administrator", "module": "Invoice Az", "name": "E-Taxes Settings", - "naming_rule": "By fieldname", "owner": "Administrator", "permissions": [ { diff --git a/invoice_az/invoice_az/doctype/e_taxes_settings/e_taxes_settings.py b/invoice_az/invoice_az/doctype/e_taxes_settings/e_taxes_settings.py index f17fda6..6edb954 100644 --- a/invoice_az/invoice_az/doctype/e_taxes_settings/e_taxes_settings.py +++ b/invoice_az/invoice_az/doctype/e_taxes_settings/e_taxes_settings.py @@ -7,16 +7,47 @@ from frappe.model.document import Document class ETaxesSettings(Document): def validate(self): - # Проверка на единственную активную настройку - if self.is_active: - active_settings = frappe.get_all('E-Taxes Settings', - filters={'is_active': 1, 'name': ['!=', self.name]}) - for setting in active_settings: - frappe.db.set_value('E-Taxes Settings', setting.name, 'is_active', 0) - - # Проверка уникальности VAT Account Mappings self.validate_vat_account_mappings() + def on_update(self): + self._sync_registry_statuses() + + def _sync_registry_statuses(self): + """Reset registry record statuses when their rows are removed from mapping tables.""" + self._reset_orphaned( + mapped_names={row.etaxes_customer_name for row in self.customer_mappings if row.etaxes_customer_name}, + doctype="E-Taxes Customers", + name_field="etaxes_party_name", + reset_fields={"status": "New", "mapped_customer": None, + "customer_group": None, "territory": None, "payment_terms": None}, + ) + self._reset_orphaned( + mapped_names={row.etaxes_supplier_name for row in self.supplier_mappings if row.etaxes_supplier_name}, + doctype="E-Taxes Suppliers", + name_field="etaxes_party_name", + reset_fields={"status": "New", "mapped_supplier": None, + "supplier_group": None, "payment_terms": None}, + ) + self._reset_orphaned( + mapped_names={row.etaxes_item_name for row in self.item_mappings if row.etaxes_item_name}, + doctype="E-Taxes Item", + name_field="etaxes_item_name", + reset_fields={"status": "New", "mapped_item": None, + "item_group": None, "uom": None, "item_tax_template": None}, + ) + self._reset_orphaned( + mapped_names={row.etaxes_unit_name for row in self.unit_mappings if row.etaxes_unit_name}, + doctype="E-Taxes Unit", + name_field="etaxes_unit_name", + reset_fields={"status": "New", "mapped_unit": None}, + ) + + def _reset_orphaned(self, mapped_names, doctype, name_field, reset_fields): + orphaned = frappe.get_all(doctype, filters={"status": "Mapped"}, fields=["name", name_field]) + for record in orphaned: + if record.get(name_field) not in mapped_names: + frappe.db.set_value(doctype, record.name, reset_fields, update_modified=False) + def validate_vat_account_mappings(self): """ Проверка уникальности комбинаций operation_type + expense_income + classification_code diff --git a/invoice_az/patches.txt b/invoice_az/patches.txt index e69de29..18be6e7 100644 --- a/invoice_az/patches.txt +++ b/invoice_az/patches.txt @@ -0,0 +1 @@ +invoice_az.patches.v1_0.migrate_etaxes_settings_to_single diff --git a/invoice_az/patches/__init__.py b/invoice_az/patches/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/invoice_az/patches/v1_0/__init__.py b/invoice_az/patches/v1_0/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/invoice_az/patches/v1_0/migrate_etaxes_settings_to_single.py b/invoice_az/patches/v1_0/migrate_etaxes_settings_to_single.py new file mode 100644 index 0000000..7074261 --- /dev/null +++ b/invoice_az/patches/v1_0/migrate_etaxes_settings_to_single.py @@ -0,0 +1,90 @@ +""" +Migrate E-Taxes Settings from multi-record to Single doctype. + +Before this patch the doctype had multiple records (with is_active/is_default flags). +After converting to issingle=1 the data must live in tabSingles and child tables +must have parent = "E-Taxes Settings". +""" + +import frappe + + +def execute(): + # Nothing to do if the old table doesn't exist (fresh install) + if not frappe.db.table_exists("tabE-Taxes Settings"): + return + + # Find the best old record: prefer is_default=1, then is_active=1, then any + old_name = ( + frappe.db.get_value("E-Taxes Settings", {"is_default": 1}, "name", ignore=True) + or frappe.db.get_value("E-Taxes Settings", {"is_active": 1}, "name", ignore=True) + or frappe.db.get_value("E-Taxes Settings", {}, "name", ignore=True) + ) + + if not old_name or old_name == "E-Taxes Settings": + # Already migrated or nothing to migrate + _migrate_child_tables(old_name) + return + + # Read the old record as raw dict + old_row = frappe.db.get_value( + "E-Taxes Settings", + old_name, + [ + "similarity_threshold", + "consider_azeri_chars", + "default_item_group", + "default_uom", + "default_item_tax_template", + "default_supplier_group", + "default_customer_group", + "default_payment_terms", + ], + as_dict=True, + ignore=True, + ) + + if not old_row: + return + + # Write scalar fields into the Single record + settings = frappe.get_single("E-Taxes Settings") + for field, value in old_row.items(): + if value is not None: + setattr(settings, field, value) + + # Migrate child tables: update parent to "E-Taxes Settings" + _migrate_child_tables(old_name) + + # Save the single record (scalar fields only — child rows are already re-parented) + settings.flags.ignore_validate = True + settings.save(ignore_permissions=True) + + frappe.db.commit() + + +def _migrate_child_tables(old_name): + """Re-parent child table rows from the old record name to 'E-Taxes Settings'.""" + if not old_name or old_name == "E-Taxes Settings": + return + + child_tables = [ + "tabE-Taxes Item Mapping", + "tabE-Taxes Customer Mappings", + "tabE-Taxes Supplier Mappings", + "tabE-Taxes Unit Mapping", + "tabE-Taxes VAT Account Mapping", + ] + + for table in child_tables: + if frappe.db.table_exists(table): + frappe.db.sql( + f""" + UPDATE `{table}` + SET parent = 'E-Taxes Settings', + parenttype = 'E-Taxes Settings' + WHERE parent = %s + AND parenttype = 'E-Taxes Settings' + """, + (old_name,), + ) diff --git a/invoice_az/sales_api.py b/invoice_az/sales_api.py index daf06a9..343909b 100644 --- a/invoice_az/sales_api.py +++ b/invoice_az/sales_api.py @@ -8,15 +8,7 @@ import re @frappe.whitelist() def get_active_settings(): """Getting active E-Taxes settings""" - settings_list = frappe.get_all('E-Taxes Settings', - filters={'is_active': 1}, - order_by='creation desc', - limit=1) - - if not settings_list: - return None - - return frappe.get_doc('E-Taxes Settings', settings_list[0].name) + return frappe.get_single('E-Taxes Settings') @frappe.whitelist() def record_etaxes_activity(asan_login_name=None): diff --git a/invoice_az/vat_api.py b/invoice_az/vat_api.py index d9fc6d5..e0c45e1 100644 --- a/invoice_az/vat_api.py +++ b/invoice_az/vat_api.py @@ -552,16 +552,7 @@ def get_accounts_for_operation_type(operation_type, company, expense_income_type tuple: (debit_account, credit_account) или (None, None) если маппинг не найден """ try: - # Ищем дефолтные E-Taxes Settings - settings_name = frappe.db.get_value('E-Taxes Settings', - {'is_default': 1, 'is_active': 1}, - 'name') - - if not settings_name: - # Если нет дефолтных, берем любые активные - settings_name = frappe.db.get_value('E-Taxes Settings', - {'is_active': 1}, - 'name') + settings_name = 'E-Taxes Settings' if settings_name and operation_type: # СТРОГОЕ СООТВЕТСТВИЕ: ищем в зависимости от наличия tax_code @@ -709,15 +700,7 @@ def create_journal_entry_from_vat_operation(operation_data, company, create_as_d # Получаем настройки E-Taxes для маппингов settings_doc = None try: - settings_name = frappe.db.get_value('E-Taxes Settings', - {'is_default': 1, 'is_active': 1}, - 'name') - if not settings_name: - settings_name = frappe.db.get_value('E-Taxes Settings', - {'is_active': 1}, - 'name') - if settings_name: - settings_doc = frappe.get_doc('E-Taxes Settings', settings_name) + settings_doc = frappe.get_single('E-Taxes Settings') except Exception as e: frappe.logger().warning(f"[VAT] Could not load E-Taxes Settings: {str(e)}")