diff --git a/jey_erp/custom/show_item_tax_template.py b/jey_erp/custom/show_item_tax_template.py new file mode 100644 index 0000000..4e04927 --- /dev/null +++ b/jey_erp/custom/show_item_tax_template.py @@ -0,0 +1,47 @@ +"""Surface `item_tax_template` in the item grid of the selling documents. + +ERPNext ships the field hidden and out of the grid (in_list_view = 0) on both +Sales Invoice Item and Sales Order Item. Here it drives everything downstream — +the VAT split and which tax-article group the picker offers — so the accountant +has to see it on the line, not dig into the row's detail view. + +This used to cover Sales Invoice only, which is why the column showed up on the +invoice and not on the order. + +A Property Setter is only half the story: a user who already has a saved grid +layout gets that layout rendered verbatim, new in_list_view field or not. The +patches (add_item_tax_template_to_so_grid_views) inject the column into the +layouts that predate this. +""" + +import frappe +from frappe.custom.doctype.property_setter.property_setter import make_property_setter + +DOCTYPES = ("Sales Invoice Item", "Sales Order Item") + +PROPERTIES = { + "hidden": (0, "Check"), + "in_list_view": (1, "Check"), + "columns": (2, "Int"), + "insert_after": ("item_name", "Data"), +} + + +def show_item_tax_template(): + for doctype in DOCTYPES: + for prop, (value, property_type) in PROPERTIES.items(): + try: + make_property_setter( + doctype=doctype, + fieldname="item_tax_template", + property=prop, + value=value, + property_type=property_type, + ) + except Exception as e: + frappe.log_error( + f"{doctype}.item_tax_template.{prop}: {e}", + "show_item_tax_template", + ) + + frappe.clear_cache() diff --git a/jey_erp/custom/show_item_tax_template_in_sales_invoice.py b/jey_erp/custom/show_item_tax_template_in_sales_invoice.py deleted file mode 100644 index 6fef565..0000000 --- a/jey_erp/custom/show_item_tax_template_in_sales_invoice.py +++ /dev/null @@ -1,59 +0,0 @@ -import frappe -from frappe.custom.doctype.property_setter.property_setter import make_property_setter - -def show_item_tax_template_in_sales_invoice(): - """ - Создает Property Setters для показа item_tax_template в Sales Invoice Item - """ - - property_setters = [ - { - 'doctype': 'Sales Invoice Item', - 'fieldname': 'item_tax_template', - 'property': 'hidden', - 'value': 0 - }, - { - 'doctype': 'Sales Invoice Item', - 'fieldname': 'item_tax_template', - 'property': 'in_list_view', - 'value': 1 - }, - { - 'doctype': 'Sales Invoice Item', - 'fieldname': 'item_tax_template', - 'property': 'columns', - 'value': 2 - }, - { - 'doctype': 'Sales Invoice Item', - 'fieldname': 'item_tax_template', - 'property': 'insert_after', - 'value': 'item_name' - } - ] - - for ps in property_setters: - try: - make_property_setter( - doctype=ps['doctype'], - fieldname=ps['fieldname'], - property=ps['property'], - value=ps['value'], - property_type=get_property_type(ps['property']) - ) - print(f"✓ Set {ps['fieldname']}.{ps['property']} = {ps['value']}") - except Exception as e: - print(f"✗ Error: {e}") - - frappe.clear_cache() - -def get_property_type(property_name): - """Возвращает тип свойства""" - types = { - 'hidden': 'Check', - 'in_list_view': 'Check', - 'columns': 'Int', - 'insert_after': 'Data' - } - return types.get(property_name, 'Data') \ No newline at end of file diff --git a/jey_erp/hooks.py b/jey_erp/hooks.py index b808f6d..1c540aa 100644 --- a/jey_erp/hooks.py +++ b/jey_erp/hooks.py @@ -168,8 +168,8 @@ def after_migrate_combined(): from jey_erp.custom_fields import create_custom_fields create_custom_fields() - from jey_erp.custom.show_item_tax_template_in_sales_invoice import show_item_tax_template_in_sales_invoice - show_item_tax_template_in_sales_invoice() + from jey_erp.custom.show_item_tax_template import show_item_tax_template + show_item_tax_template() from jey_erp.custom.vat_calculations import patch_sales_documents patch_sales_documents() from jey_erp.custom.rename_default_groups import rename_default_groups diff --git a/jey_erp/patches.txt b/jey_erp/patches.txt index f5b9d87..b66a8a5 100644 --- a/jey_erp/patches.txt +++ b/jey_erp/patches.txt @@ -18,3 +18,4 @@ jey_erp.patches.backfill_sales_invoice_tax_articles_history jey_erp.patches.add_tax_articles_to_si_grid_views jey_erp.patches.add_certificates_to_si_grid_views jey_erp.patches.add_tax_articles_to_so_grid_views +jey_erp.patches.add_item_tax_template_to_so_grid_views diff --git a/jey_erp/patches/add_item_tax_template_to_so_grid_views.py b/jey_erp/patches/add_item_tax_template_to_so_grid_views.py new file mode 100644 index 0000000..187c267 --- /dev/null +++ b/jey_erp/patches/add_item_tax_template_to_so_grid_views.py @@ -0,0 +1,51 @@ +import json + +import frappe + + +def execute(): + """Inject the "Item Tax Template" column into saved Sales Order Item grid layouts. + + show_item_tax_template() flips in_list_view on for the field, but a user with a grid + layout saved in `__UserSettings` gets that list rendered verbatim — Frappe does not + merge newly-in_list_view fields into it. Sales Invoice had the property setter (and + the column) for a while; Sales Order never did, so every existing Sales Order layout + is missing it. + + Insert it just before the Tax Articles cell — you pick the template, then the articles + it allows. Idempotent. + """ + rows = frappe.db.sql( + "select user, data from `__UserSettings` where doctype = 'Sales Order'", + as_dict=True, + ) + for r in rows: + try: + data = json.loads(r.data or "{}") + except (ValueError, TypeError): + continue + + grid_view = data.get("GridView") or {} + cols = grid_view.get("Sales Order Item") + if not cols: + continue + + names = [c.get("fieldname") for c in cols] + if "item_tax_template" in names: + continue + + new_col = {"fieldname": "item_tax_template", "columns": 2} + if "custom_tax_articles_display" in names: + cols.insert(names.index("custom_tax_articles_display"), new_col) + else: + cols.append(new_col) + + grid_view["Sales Order Item"] = cols + data["GridView"] = grid_view + frappe.db.sql( + "update `__UserSettings` set data = %s where user = %s and doctype = 'Sales Order'", + (json.dumps(data), r.user), + ) + + frappe.db.commit() + frappe.cache().delete_value("_user_settings")