fix(client): stop duplicate form handlers; remove tax_type grid toggle

Split list-view code (frappe.listview_settings) out of the form client
scripts into dedicated *_list.js files. The form files were registered
under both doctype_js and doctype_list_js, so frappe.ui.form.on handlers
were registered twice and form events (e.g. validate) fired twice,
duplicating msgprint messages.

- New: purchase_invoice_list.js, purchase_order_list.js,
  sales_order_list.js, sales_invoice_list.js, employee_list.js
- doctype_list_js now points to the *_list.js files; journal_entry.js
  (list-only) removed from doctype_js
- purchase_invoice.js: drop toggle_purchase_fields_visibility and the
  grid hidden/visible_columns hacks; tax_type is always visible and its
  mandatory state is driven by mandatory_depends_on. validate and tax
  calc now also honor agricultural_goods (equal trigger to purchase_type)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ali 2026-06-23 11:43:32 +00:00
parent c29ac5bd1b
commit 4adde142b4
11 changed files with 4742 additions and 1576 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -606,22 +606,23 @@ frappe.ui.form.on('Purchase Invoice', {
// Make is_taxes_doc field read-only when document loads // Make is_taxes_doc field read-only when document loads
onload: function(frm) { onload: function(frm) {
frm.set_df_property('is_taxes_doc', 'read_only', 1); frm.set_df_property('is_taxes_doc', 'read_only', 1);
// Set visibility of purchase fields on load
toggle_purchase_fields_visibility(frm);
}, },
purchase_type: function(frm) { purchase_type: function(frm) {
// Toggle visibility of tax_type and purchase_tax_amount fields // tax_type / purchase_tax_amount are always-visible columns; their
toggle_purchase_fields_visibility(frm); // mandatory state is driven by mandatory_depends_on (see custom_fields.py),
// so no manual grid toggling is needed. Just refresh to show/hide buttons.
frm.refresh();
},
// Refresh to show/hide buttons when checkbox changes agricultural_goods: function(frm) {
// Equal trigger to purchase_type for button visibility.
frm.refresh(); frm.refresh();
}, },
validate: function(frm) { validate: function(frm) {
// Client-side validation before save // Client-side validation before save
if (frm.doc.purchase_type && frm.doc.items) { if ((frm.doc.purchase_type || frm.doc.agricultural_goods) && frm.doc.items) {
// Validate act_kind is selected // Validate act_kind is selected
if (!frm.doc.act_kind) { if (!frm.doc.act_kind) {
frappe.msgprint({ frappe.msgprint({
@ -676,37 +677,14 @@ frappe.ui.form.on('Purchase Invoice Item', {
} }
}); });
/**
* Toggle visibility of purchase fields in items table
*/
function toggle_purchase_fields_visibility(frm) {
// Get the grid object
let grid = frm.fields_dict.items.grid;
if (frm.doc.purchase_type) {
// Show fields
grid.update_docfield_property('tax_type', 'hidden', 0);
grid.update_docfield_property('purchase_tax_amount', 'hidden', 0);
grid.update_docfield_property('tax_type', 'reqd', 1); // Make required
} else {
// Hide fields
grid.update_docfield_property('tax_type', 'hidden', 1);
grid.update_docfield_property('purchase_tax_amount', 'hidden', 1);
grid.update_docfield_property('tax_type', 'reqd', 0); // Not required
}
// Refresh grid to apply changes
grid.refresh();
}
/** /**
* Calculate purchase tax amount (5% if Taxable) * Calculate purchase tax amount (5% if Taxable)
*/ */
function calculate_purchase_tax(frm, cdt, cdn) { function calculate_purchase_tax(frm, cdt, cdn) {
let item = locals[cdt][cdn]; let item = locals[cdt][cdn];
// Only calculate if parent has purchase_type checked // Only calculate if parent has purchase_type or agricultural_goods checked
if (!frm.doc.purchase_type) { if (!frm.doc.purchase_type && !frm.doc.agricultural_goods) {
frappe.model.set_value(cdt, cdn, 'purchase_tax_amount', 0); frappe.model.set_value(cdt, cdn, 'purchase_tax_amount', 0);
return; return;
} }
@ -1053,28 +1031,8 @@ function get_status_color(status) {
return colors[status] || 'gray'; return colors[status] || 'gray';
} }
frappe.listview_settings['Purchase Invoice'] = { // NOTE: list view customizations live in client/purchase_invoice_list.js,
onload: function(listview) { // loaded via the doctype_list_js hook. This file is form-only (doctype_js).
// Add field as column (proper way) // Keeping frappe.ui.form.on(...) out of the list bundle prevents the form
listview.columns.push({ // handlers (e.g. validate) from being registered twice, which previously
type: 'Check', // caused validation messages to appear duplicated.
df: {
label: __('Tax Doc'),
fieldname: 'is_taxes_doc'
},
width: 120
});
// Force refresh list with new column
listview.refresh();
},
// Format is_taxes_doc field display
formatters: {
is_taxes_doc: function(value) {
return value ?
`<span class="indicator-pill green" title="${__('Tax Document')}"></span>` :
`<span class="indicator-pill gray" title="${__('Regular Document')}"></span>`;
}
}
};

View File

@ -0,0 +1,31 @@
// List view customizations for Purchase Invoice.
// Loaded via the `doctype_list_js` hook ONLY. Do not add frappe.ui.form.on(...)
// handlers here — this file runs in list context and any form handler defined
// here would be registered a second time (in addition to client/purchase_invoice.js
// loaded by doctype_js), causing form events like `validate` to fire twice.
frappe.listview_settings['Purchase Invoice'] = {
onload: function(listview) {
// Add field as column (proper way)
listview.columns.push({
type: 'Check',
df: {
label: __('Tax Doc'),
fieldname: 'is_taxes_doc'
},
width: 120
});
// Force refresh list with new column
listview.refresh();
},
// Format is_taxes_doc field display
formatters: {
is_taxes_doc: function(value) {
return value ?
`<span class="indicator-pill green" title="${__('Tax Document')}"></span>` :
`<span class="indicator-pill gray" title="${__('Regular Document')}"></span>`;
}
}
};

View File

@ -1567,56 +1567,6 @@ frappe.ui.form.on('Purchase Order', {
} }
}); });
// Purchase Order List
frappe.listview_settings['Purchase Order'] = {
add_fields: ['supplier', 'transaction_date', 'status', 'docstatus', 'is_taxes_doc', 'taxes_doc'],
get_indicator: function(doc) {
if (doc.status === 'Closed') {
return [__('Closed'), 'green', 'status,=,Closed'];
} else if (doc.status === 'On Hold') {
return [__('On Hold'), 'orange', 'status,=,On Hold'];
} else if (doc.status === 'Delivered') {
return [__('Delivered'), 'green', 'status,=,Delivered'];
} else if (doc.docstatus == 1) {
return [__('To Receive and Bill'), 'orange', 'docstatus,=,1'];
} else if (doc.docstatus == 0) {
return [__('Draft'), 'red', 'docstatus,=,0'];
} else if (doc.docstatus == 2) {
return [__('Cancelled'), 'grey', 'docstatus,=,2'];
}
},
onload: function(listview) {
listview.page.add_inner_button(__('Import from E-Taxes'), function() {
ETaxes.import.showDialog();
}, __('E-Taxes'));
listview.page.add_inner_button(__('Open Settings'), function() {
frappe.set_route('Form', 'E-Taxes Settings');
}, __('E-Taxes'));
listview.columns.push({
type: 'Check',
df: {
label: __('Tax Doc'),
fieldname: 'is_taxes_doc'
},
width: 120
});
listview.refresh();
},
formatters: {
is_taxes_doc: function(value) {
return value ?
`<span class="indicator-pill green"></span>` :
`<span class="indicator-pill gray"></span>`;
}
}
};
// Purchase Invoice Form // Purchase Invoice Form
frappe.ui.form.on('Purchase Invoice', { frappe.ui.form.on('Purchase Invoice', {
refresh: function(frm) { refresh: function(frm) {
@ -1629,28 +1579,4 @@ frappe.ui.form.on('Purchase Invoice', {
onload: function(frm) { onload: function(frm) {
frm.set_df_property('is_taxes_doc', 'read_only', 1); frm.set_df_property('is_taxes_doc', 'read_only', 1);
} }
}); });
// Purchase Invoice List
frappe.listview_settings['Purchase Invoice'] = {
onload: function(listview) {
listview.columns.push({
type: 'Check',
df: {
label: __('Tax Doc'),
fieldname: 'is_taxes_doc'
},
width: 120
});
listview.refresh();
},
formatters: {
is_taxes_doc: function(value) {
return value ?
`<span class="indicator-pill green"></span>` :
`<span class="indicator-pill gray"></span>`;
}
}
};

File diff suppressed because it is too large Load Diff

View File

@ -2218,55 +2218,3 @@ function show_etaxes_details(frm) {
message: details_html message: details_html
}); });
} }
/**
* List view settings
*/
frappe.listview_settings['Sales Invoice'] = {
// Make sure both signals reach the formatter, even though the column is bound
// to only one of them.
add_fields: ['is_taxes_doc', 'etaxes_send_status'],
onload: function(listview) {
// Single universal E-Taxes column. It reflects everything related to the
// tax portal for this document: whether it was loaded from E-Taxes,
// whether a draft was created there, or whether it was sent and signed.
listview.columns.push({
type: 'Select',
df: {
label: __('E-Taxes'),
fieldname: 'etaxes_send_status'
},
width: 170
});
// Force refresh list with the new column
listview.refresh();
},
formatters: {
// Universal E-Taxes status. The formatter receives the full row as its
// third argument (value, df, doc), so we combine the send status with the
// "loaded from E-Taxes" flag into a single indicator.
etaxes_send_status: function(value, df, doc) {
const send_status = doc.etaxes_send_status;
// Outgoing flow (document sent to the portal) takes priority — it is
// the most specific state.
if (send_status === 'Sent and Signed') {
return `<span class="indicator-pill green">${__('Sent to E-Taxes')}</span>`;
}
if (send_status === 'Created, not signed') {
return `<span class="indicator-pill orange">${__('Draft on E-Taxes')}</span>`;
}
// Incoming flow (document imported from the portal).
if (doc.is_taxes_doc) {
return `<span class="indicator-pill blue">${__('Loaded from E-Taxes')}</span>`;
}
// Not related to the tax portal — keep the cell clean.
return '';
}
}
};

View File

@ -0,0 +1,50 @@
// List view settings for Sales Invoice. Loaded only via doctype_list_js. Do NOT add frappe.ui.form.on(...) here.
frappe.listview_settings['Sales Invoice'] = {
// Make sure both signals reach the formatter, even though the column is bound
// to only one of them.
add_fields: ['is_taxes_doc', 'etaxes_send_status'],
onload: function(listview) {
// Single universal E-Taxes column. It reflects everything related to the
// tax portal for this document: whether it was loaded from E-Taxes,
// whether a draft was created there, or whether it was sent and signed.
listview.columns.push({
type: 'Select',
df: {
label: __('E-Taxes'),
fieldname: 'etaxes_send_status'
},
width: 170
});
// Force refresh list with the new column
listview.refresh();
},
formatters: {
// Universal E-Taxes status. The formatter receives the full row as its
// third argument (value, df, doc), so we combine the send status with the
// "loaded from E-Taxes" flag into a single indicator.
etaxes_send_status: function(value, df, doc) {
const send_status = doc.etaxes_send_status;
// Outgoing flow (document sent to the portal) takes priority — it is
// the most specific state.
if (send_status === 'Sent and Signed') {
return `<span class="indicator-pill green">${__('Sent to E-Taxes')}</span>`;
}
if (send_status === 'Created, not signed') {
return `<span class="indicator-pill orange">${__('Draft on E-Taxes')}</span>`;
}
// Incoming flow (document imported from the portal).
if (doc.is_taxes_doc) {
return `<span class="indicator-pill blue">${__('Loaded from E-Taxes')}</span>`;
}
// Not related to the tax portal — keep the cell clean.
return '';
}
}
};

View File

@ -1638,56 +1638,6 @@ frappe.ui.form.on('Sales Order', {
} }
}); });
frappe.listview_settings['Sales Order'] = {
add_fields: ['customer', 'transaction_date', 'status', 'docstatus', 'is_taxes_doc', 'taxes_doc'],
get_indicator: function(doc) {
if (doc.status === 'Closed') {
return [__('Closed'), 'green', 'status,=,Closed'];
} else if (doc.status === 'On Hold') {
return [__('On Hold'), 'orange', 'status,=,On Hold'];
} else if (doc.status === 'Delivered') {
return [__('Delivered'), 'green', 'status,=,Delivered'];
} else if (doc.docstatus == 1) {
return [__('To Deliver and Bill'), 'orange', 'docstatus,=,1'];
} else if (doc.docstatus == 0) {
return [__('Draft'), 'red', 'docstatus,=,0'];
} else if (doc.docstatus == 2) {
return [__('Cancelled'), 'grey', 'docstatus,=,2'];
}
},
onload: function(listview) {
// ДОБАВЛЕНО: кнопка импорта из E-Taxes для Sales Order
listview.page.add_inner_button(__('Import from E-Taxes'), function() {
SalesETaxes.import.showDialog();
}, __('E-Taxes'));
listview.page.add_inner_button(__('Open Settings'), function() {
frappe.set_route('Form', 'E-Taxes Settings');
}, __('E-Taxes'));
listview.columns.push({
type: 'Check',
df: {
label: __('Tax Doc'),
fieldname: 'is_taxes_doc'
},
width: 120
});
listview.refresh();
},
formatters: {
is_taxes_doc: function(value) {
return value ?
`<span class="indicator-pill green"></span>` :
`<span class="indicator-pill gray"></span>`;
}
}
};
// Sales Invoice Form // Sales Invoice Form
frappe.ui.form.on('Sales Invoice', { frappe.ui.form.on('Sales Invoice', {
refresh: function(frm) { refresh: function(frm) {
@ -1700,32 +1650,4 @@ frappe.ui.form.on('Sales Invoice', {
onload: function(frm) { onload: function(frm) {
frm.set_df_property('is_taxes_doc', 'read_only', 1); frm.set_df_property('is_taxes_doc', 'read_only', 1);
} }
}); });
// Sales Invoice List - УБРАНО: кнопка импорта (теперь только в Sales Order)
frappe.listview_settings['Sales Invoice'] = {
add_fields: ['customer', 'posting_date', 'status', 'docstatus', 'is_taxes_doc'],
onload: function(listview) {
// УБРАНО: кнопка импорта - теперь импорт только через Sales Order
listview.columns.push({
type: 'Check',
df: {
label: __('Tax Doc'),
fieldname: 'is_taxes_doc'
},
width: 120
});
listview.refresh();
},
formatters: {
is_taxes_doc: function(value) {
return value ?
`<span class="indicator-pill green"></span>` :
`<span class="indicator-pill gray"></span>`;
}
}
};

File diff suppressed because it is too large Load Diff

View File

@ -10,7 +10,6 @@ doctype_js = {
"Purchase Invoice": "client/purchase_invoice.js", "Purchase Invoice": "client/purchase_invoice.js",
"Sales Order": "client/sales_order.js", "Sales Order": "client/sales_order.js",
"Sales Invoice": "client/sales_invoice.js", "Sales Invoice": "client/sales_invoice.js",
"Journal Entry": "client/journal_entry.js",
"Supplier": "client/supplier.js", "Supplier": "client/supplier.js",
"Amas Employees": "client/amas_employees.js", "Amas Employees": "client/amas_employees.js",
"Employee": "client/employee.js", "Employee": "client/employee.js",
@ -22,12 +21,12 @@ doctype_list_js = {
"E-Taxes Suppliers": "client/e_taxes_suppliers_list.js", "E-Taxes Suppliers": "client/e_taxes_suppliers_list.js",
"E-Taxes Customers": "client/e_taxes_customers_list.js", "E-Taxes Customers": "client/e_taxes_customers_list.js",
"E-Taxes Unit": "client/e_taxes_unit_list.js", "E-Taxes Unit": "client/e_taxes_unit_list.js",
"Purchase Order": "client/purchase_order.js", "Purchase Order": "client/purchase_order_list.js",
"Purchase Invoice": "client/purchase_invoice.js", "Purchase Invoice": "client/purchase_invoice_list.js",
"Sales Order": "client/sales_order.js", "Sales Order": "client/sales_order_list.js",
"Sales Invoice": "client/sales_invoice.js", "Sales Invoice": "client/sales_invoice_list.js",
"Journal Entry": "client/journal_entry.js", "Journal Entry": "client/journal_entry.js",
"Employee": "client/employee.js" "Employee": "client/employee_list.js"
} }
# Хуки для добавления обработчиков событий Purchase Order # Хуки для добавления обработчиков событий Purchase Order