fix(sales-order): show Item Tax Template in the item grid
The column was on Sales Invoice and not on Sales Order, which reads like a Frappe default but isn't: ERPNext ships item_tax_template with in_list_view = 0 on both child doctypes. The asymmetry came from show_item_tax_template_in_sales_invoice.py, which set the property setters for the invoice and — as the name says — only the invoice. Generalised to both, and renamed, since it is no longer sales-invoice specific. The template is what decides the VAT split and which tax-article group the picker offers, so hiding it from the line the accountant works on is backwards. The property setter alone is not enough. A user with a grid layout already saved in __UserSettings gets that list rendered verbatim — Frappe does not merge a newly-in_list_view field into it — so every pre-existing Sales Order layout would stay without the column. Hence the patch, same shape as the ones that added the tax article and certificate cells. Verified headless: the column now renders in the items grid on both forms. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
df63b036e3
commit
d6af639bd0
|
|
@ -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()
|
||||
|
|
@ -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')
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
Loading…
Reference in New Issue