From 41f11eca7221e8534f8ad4cfd660057de276e484 Mon Sep 17 00:00:00 2001 From: Marica Date: Fri, 17 Sep 2021 10:27:29 +0530 Subject: [PATCH 01/53] fix: Remove duplicates from customer_code field (#27555) --- erpnext/stock/doctype/item/item.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index 86737f29b0c..768e5eae2da 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -563,8 +563,12 @@ class Item(WebsiteGenerator): _("Default BOM ({0}) must be active for this item or its template").format(bom_item)) def fill_customer_code(self): - """ Append all the customer codes and insert into "customer_code" field of item table """ - self.customer_code = ','.join(d.ref_code for d in self.get("customer_items", [])) + """ + Append all the customer codes and insert into "customer_code" field of item table. + Used to search Item by customer code. + """ + customer_codes = set(d.ref_code for d in self.get("customer_items", [])) + self.customer_code = ','.join(customer_codes) def check_item_tax(self): """Check whether Tax Rate is not entered twice for same Tax Type""" From d49346ac4561ab854dde6f97364b09af878ee877 Mon Sep 17 00:00:00 2001 From: Subin Tom <36098155+nemesis189@users.noreply.github.com> Date: Fri, 17 Sep 2021 10:39:03 +0530 Subject: [PATCH 02/53] fix: Tax breakup based on items, missing GST fields (#27524) * fix: Tax breakup based on items * fix: added gst fields,warehouse validation to pos inv,patch * fix: tax breakup test fix, eway bill hsn fix Co-authored-by: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> --- .../doctype/pos_invoice/pos_invoice.py | 1 + .../sales_invoice/test_sales_invoice.py | 13 ++++-- erpnext/patches.txt | 1 + .../v13_0/gst_fields_for_pos_invoice.py | 44 +++++++++++++++++++ erpnext/regional/india/setup.py | 2 + erpnext/regional/india/utils.py | 31 ++++++------- 6 files changed, 74 insertions(+), 18 deletions(-) create mode 100644 erpnext/patches/v13_0/gst_fields_for_pos_invoice.py diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py index d6e41e6f90d..27d678b212d 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py @@ -40,6 +40,7 @@ class POSInvoice(SalesInvoice): self.validate_change_amount() self.validate_change_account() self.validate_item_cost_centers() + self.validate_warehouse() self.validate_serialised_or_batched_item() self.validate_stock_availablility() self.validate_return_items_qty() diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index da0c3151933..3720ac33bbd 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -1420,15 +1420,22 @@ class TestSalesInvoice(unittest.TestCase): itemised_tax, itemised_taxable_amount = get_itemised_tax_breakup_data(si) expected_itemised_tax = { - "999800": { + "_Test Item": { "Service Tax": { "tax_rate": 10.0, - "tax_amount": 1500.0 + "tax_amount": 1000.0 + } + }, + "_Test Item 2": { + "Service Tax": { + "tax_rate": 10.0, + "tax_amount": 500.0 } } } expected_itemised_taxable_amount = { - "999800": 15000.0 + "_Test Item": 10000.0, + "_Test Item 2": 5000.0 } self.assertEqual(itemised_tax, expected_itemised_tax) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 2148e6eec04..cee796efbc2 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -307,4 +307,5 @@ erpnext.patches.v14_0.delete_shopify_doctypes erpnext.patches.v13_0.replace_supplier_item_group_with_party_specific_item erpnext.patches.v13_0.update_dates_in_tax_withholding_category erpnext.patches.v14_0.update_opportunity_currency_fields +erpnext.patches.v13_0.gst_fields_for_pos_invoice erpnext.patches.v13_0.create_accounting_dimensions_in_pos_doctypes diff --git a/erpnext/patches/v13_0/gst_fields_for_pos_invoice.py b/erpnext/patches/v13_0/gst_fields_for_pos_invoice.py new file mode 100644 index 00000000000..5b790d9f173 --- /dev/null +++ b/erpnext/patches/v13_0/gst_fields_for_pos_invoice.py @@ -0,0 +1,44 @@ +from __future__ import unicode_literals + +import frappe +from frappe.custom.doctype.custom_field.custom_field import create_custom_fields + + +def execute(): + company = frappe.get_all('Company', filters = {'country': 'India'}, fields=['name']) + if not company: + return + + hsn_sac_field = dict(fieldname='gst_hsn_code', label='HSN/SAC', + fieldtype='Data', fetch_from='item_code.gst_hsn_code', insert_after='description', + allow_on_submit=1, print_hide=1, fetch_if_empty=1) + nil_rated_exempt = dict(fieldname='is_nil_exempt', label='Is Nil Rated or Exempted', + fieldtype='Check', fetch_from='item_code.is_nil_exempt', insert_after='gst_hsn_code', + print_hide=1) + is_non_gst = dict(fieldname='is_non_gst', label='Is Non GST', + fieldtype='Check', fetch_from='item_code.is_non_gst', insert_after='is_nil_exempt', + print_hide=1) + taxable_value = dict(fieldname='taxable_value', label='Taxable Value', + fieldtype='Currency', insert_after='base_net_amount', hidden=1, options="Company:company:default_currency", + print_hide=1) + sales_invoice_gst_fields = [ + dict(fieldname='billing_address_gstin', label='Billing Address GSTIN', + fieldtype='Data', insert_after='customer_address', read_only=1, + fetch_from='customer_address.gstin', print_hide=1), + dict(fieldname='customer_gstin', label='Customer GSTIN', + fieldtype='Data', insert_after='shipping_address_name', + fetch_from='shipping_address_name.gstin', print_hide=1), + dict(fieldname='place_of_supply', label='Place of Supply', + fieldtype='Data', insert_after='customer_gstin', + print_hide=1, read_only=1), + dict(fieldname='company_gstin', label='Company GSTIN', + fieldtype='Data', insert_after='company_address', + fetch_from='company_address.gstin', print_hide=1, read_only=1), + ] + + custom_fields = { + 'POS Invoice': sales_invoice_gst_fields, + 'POS Invoice Item': [hsn_sac_field, nil_rated_exempt, is_non_gst, taxable_value], + } + + create_custom_fields(custom_fields, update=True) \ No newline at end of file diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index 79dc4b8fc97..ce346bcc5b1 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -483,6 +483,7 @@ def make_custom_fields(update=True): 'Purchase Order': purchase_invoice_gst_fields, 'Purchase Receipt': purchase_invoice_gst_fields, 'Sales Invoice': sales_invoice_gst_category + invoice_gst_fields + sales_invoice_shipping_fields + sales_invoice_gst_fields + si_ewaybill_fields, + 'POS Invoice': sales_invoice_gst_fields, 'Delivery Note': sales_invoice_gst_fields + ewaybill_fields + sales_invoice_shipping_fields + delivery_note_gst_category, 'Payment Entry': payment_entry_fields, 'Journal Entry': journal_entry_fields, @@ -501,6 +502,7 @@ def make_custom_fields(update=True): 'Sales Order Item': [hsn_sac_field, nil_rated_exempt, is_non_gst], 'Delivery Note Item': [hsn_sac_field, nil_rated_exempt, is_non_gst], 'Sales Invoice Item': [hsn_sac_field, nil_rated_exempt, is_non_gst, taxable_value], + 'POS Invoice Item': [hsn_sac_field, nil_rated_exempt, is_non_gst, taxable_value], 'Purchase Order Item': [hsn_sac_field, nil_rated_exempt, is_non_gst], 'Purchase Receipt Item': [hsn_sac_field, nil_rated_exempt, is_non_gst], 'Purchase Invoice Item': [hsn_sac_field, nil_rated_exempt, is_non_gst, taxable_value], diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index bf06d4a497d..903168d009f 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -117,7 +117,7 @@ def get_itemised_tax_breakup_header(item_doctype, tax_accounts): else: return [_("Item"), _("Taxable Amount")] + tax_accounts -def get_itemised_tax_breakup_data(doc, account_wise=False): +def get_itemised_tax_breakup_data(doc, account_wise=False, hsn_wise=False): itemised_tax = get_itemised_tax(doc.taxes, with_tax_account=account_wise) itemised_taxable_amount = get_itemised_taxable_amount(doc.items) @@ -125,28 +125,29 @@ def get_itemised_tax_breakup_data(doc, account_wise=False): if not frappe.get_meta(doc.doctype + " Item").has_field('gst_hsn_code'): return itemised_tax, itemised_taxable_amount - item_hsn_map = frappe._dict() - for d in doc.items: - item_hsn_map.setdefault(d.item_code or d.item_name, d.get("gst_hsn_code")) + if hsn_wise: + item_hsn_map = frappe._dict() + for d in doc.items: + item_hsn_map.setdefault(d.item_code or d.item_name, d.get("gst_hsn_code")) hsn_tax = {} for item, taxes in itemised_tax.items(): - hsn_code = item_hsn_map.get(item) - hsn_tax.setdefault(hsn_code, frappe._dict()) + item_or_hsn = item if not hsn_wise else item_hsn_map.get(item) + hsn_tax.setdefault(item_or_hsn, frappe._dict()) for tax_desc, tax_detail in taxes.items(): key = tax_desc if account_wise: key = tax_detail.get('tax_account') - hsn_tax[hsn_code].setdefault(key, {"tax_rate": 0, "tax_amount": 0}) - hsn_tax[hsn_code][key]["tax_rate"] = tax_detail.get("tax_rate") - hsn_tax[hsn_code][key]["tax_amount"] += tax_detail.get("tax_amount") + hsn_tax[item_or_hsn].setdefault(key, {"tax_rate": 0, "tax_amount": 0}) + hsn_tax[item_or_hsn][key]["tax_rate"] = tax_detail.get("tax_rate") + hsn_tax[item_or_hsn][key]["tax_amount"] += tax_detail.get("tax_amount") # set taxable amount hsn_taxable_amount = frappe._dict() for item in itemised_taxable_amount: - hsn_code = item_hsn_map.get(item) - hsn_taxable_amount.setdefault(hsn_code, 0) - hsn_taxable_amount[hsn_code] += itemised_taxable_amount.get(item) + item_or_hsn = item if not hsn_wise else item_hsn_map.get(item) + hsn_taxable_amount.setdefault(item_or_hsn, 0) + hsn_taxable_amount[item_or_hsn] += itemised_taxable_amount.get(item) return hsn_tax, hsn_taxable_amount @@ -440,7 +441,7 @@ def get_ewb_data(dt, dn): data.itemList = [] data.totalValue = doc.total - data = get_item_list(data, doc) + data = get_item_list(data, doc, hsn_wise=True) disable_rounded = frappe.db.get_single_value('Global Defaults', 'disable_rounded_total') data.totInvValue = doc.grand_total if disable_rounded else doc.rounded_total @@ -551,7 +552,7 @@ def get_address_details(data, doc, company_address, billing_address, dispatch_ad return data -def get_item_list(data, doc): +def get_item_list(data, doc, hsn_wise=False): for attr in ['cgstValue', 'sgstValue', 'igstValue', 'cessValue', 'OthValue']: data[attr] = 0 @@ -563,7 +564,7 @@ def get_item_list(data, doc): 'cess_account': ['cessRate', 'cessValue'] } item_data_attrs = ['sgstRate', 'cgstRate', 'igstRate', 'cessRate', 'cessNonAdvol'] - hsn_wise_charges, hsn_taxable_amount = get_itemised_tax_breakup_data(doc, account_wise=True) + hsn_wise_charges, hsn_taxable_amount = get_itemised_tax_breakup_data(doc, account_wise=True, hsn_wise=hsn_wise) for hsn_code, taxable_amount in hsn_taxable_amount.items(): item_data = frappe._dict() if not hsn_code: From e03d9aa8890680baefe0d335dafdbfc5d0445fd4 Mon Sep 17 00:00:00 2001 From: Saqib Date: Fri, 17 Sep 2021 13:03:27 +0530 Subject: [PATCH 03/53] fix: unecessary keyword args were passed in mapper functions (#27563) --- erpnext/public/js/utils.js | 7 +++++-- erpnext/stock/doctype/material_request/material_request.py | 5 +++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index 2a74d6015f4..7f39b990bf0 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -714,12 +714,15 @@ erpnext.utils.map_current_doc = function(opts) { child_columns: opts.child_columns, action: function(selections, args) { let values = selections; - if(values.length === 0){ + if (values.length === 0) { frappe.msgprint(__("Please select {0}", [opts.source_doctype])) return; } opts.source_name = values; - opts.args = args; + if (opts.allow_child_item_selection) { + // args contains filtered child docnames + opts.args = args; + } d.dialog.hide(); _map(); }, diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py index 2569c04251c..cf98b19e7a1 100644 --- a/erpnext/stock/doctype/material_request/material_request.py +++ b/erpnext/stock/doctype/material_request/material_request.py @@ -272,8 +272,9 @@ def update_status(name, status): material_request.update_status(status) @frappe.whitelist() -def make_purchase_order(source_name, target_doc=None, args={}): - +def make_purchase_order(source_name, target_doc=None, args=None): + if args is None: + args = {} if isinstance(args, string_types): args = json.loads(args) From bf2a590332100744b1641c3680e296f05f057dcb Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Fri, 17 Sep 2021 18:39:21 +0530 Subject: [PATCH 04/53] fix: View Stock / Accounting Ledger button not showing in the stock entry (#27570) --- erpnext/stock/doctype/stock_entry/stock_entry.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index c819d49f509..7cb9665e857 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -1100,4 +1100,4 @@ function check_should_not_attach_bom_items(bom_no) { ); } -$.extend(cur_frm.cscript, new erpnext.stock.StockEntry({frm: cur_frm})); +extend_cscript(cur_frm.cscript, new erpnext.stock.StockEntry({frm: cur_frm})); From 4f7af79c31720eb32defed0e5f1b60e9837af728 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Sat, 18 Sep 2021 13:34:27 +0530 Subject: [PATCH 05/53] fix: PO/PINV - Check if doctype has company_address field before setting the value (#27441) (#27576) Co-authored-by: Vama Mehta (cherry picked from commit 666eaae6ce976c5d820b3b9f91d23a0ed28a263a) Co-authored-by: vama --- erpnext/public/js/controllers/transaction.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index fd3d4721376..91b1247e6f4 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -884,7 +884,9 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe if (r.message) { me.frm.set_value("billing_address", r.message); } else { - me.frm.set_value("company_address", ""); + if (frappe.meta.get_docfield(me.frm.doctype, 'company_address')) { + me.frm.set_value("company_address", ""); + } } } }); From f07ff92a35c876e024703856cc2c0d31437b6dd5 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Mon, 20 Sep 2021 11:12:39 +0530 Subject: [PATCH 06/53] fix: Improvements in COA Importer (#27584) --- .../chart_of_accounts_importer.js | 56 +++++++------------ .../chart_of_accounts_importer.py | 27 +++++---- 2 files changed, 33 insertions(+), 50 deletions(-) diff --git a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.js b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.js index f795dfa83e6..f67c59c2549 100644 --- a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.js +++ b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.js @@ -79,7 +79,6 @@ frappe.ui.form.on('Chart of Accounts Importer', { $(frm.fields_dict['chart_tree'].wrapper).empty(); // empty wrapper on removing file } else { generate_tree_preview(frm); - validate_csv_data(frm); } }, @@ -104,23 +103,6 @@ frappe.ui.form.on('Chart of Accounts Importer', { } }); -var validate_csv_data = function(frm) { - frappe.call({ - method: "erpnext.accounts.doctype.chart_of_accounts_importer.chart_of_accounts_importer.validate_accounts", - args: {file_name: frm.doc.import_file}, - callback: function(r) { - if(r.message && r.message[0]===true) { - frm.page["show_import_button"] = true; - frm.page["total_accounts"] = r.message[1]; - frm.trigger("refresh"); - } else { - frm.page.set_indicator(__('Resolve error and upload again.'), 'orange'); - frappe.throw(__(r.message)); - } - } - }); -}; - var create_import_button = function(frm) { frm.page.set_primary_action(__("Import"), function () { frappe.call({ @@ -151,23 +133,25 @@ var create_reset_button = function(frm) { }; var generate_tree_preview = function(frm) { - let parent = __('All Accounts'); - $(frm.fields_dict['chart_tree'].wrapper).empty(); // empty wrapper to load new data + if (frm.doc.import_file) { + let parent = __('All Accounts'); + $(frm.fields_dict['chart_tree'].wrapper).empty(); // empty wrapper to load new data - // generate tree structure based on the csv data - new frappe.ui.Tree({ - parent: $(frm.fields_dict['chart_tree'].wrapper), - label: parent, - expandable: true, - method: 'erpnext.accounts.doctype.chart_of_accounts_importer.chart_of_accounts_importer.get_coa', - args: { - file_name: frm.doc.import_file, - parent: parent, - doctype: 'Chart of Accounts Importer', - file_type: frm.doc.file_type - }, - onclick: function(node) { - parent = node.value; - } - }); + // generate tree structure based on the csv data + new frappe.ui.Tree({ + parent: $(frm.fields_dict['chart_tree'].wrapper), + label: parent, + expandable: true, + method: 'erpnext.accounts.doctype.chart_of_accounts_importer.chart_of_accounts_importer.get_coa', + args: { + file_name: frm.doc.import_file, + parent: parent, + doctype: 'Chart of Accounts Importer', + file_type: frm.doc.file_type + }, + onclick: function(node) { + parent = node.value; + } + }); + } }; diff --git a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py index 61968cf627d..9a0234a91f9 100644 --- a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py +++ b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py @@ -25,8 +25,16 @@ from erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts import class ChartofAccountsImporter(Document): - def validate(self): - validate_accounts(self.import_file) + pass + +def validate_columns(data): + if not data: + frappe.throw(_('No data found. Seems like you uploaded a blank file')) + + no_of_columns = max([len(d) for d in data]) + + if no_of_columns > 7: + frappe.throw(_('More columns found than expected. Please compare the uploaded file with standard template')) @frappe.whitelist() def validate_company(company): @@ -131,6 +139,8 @@ def get_coa(doctype, parent, is_root=False, file_name=None): else: data = generate_data_from_excel(file_doc, extension) + validate_columns(data) + validate_accounts(data) forest = build_forest(data) accounts = build_tree_from_json("", chart_data=forest) # returns alist of dict in a tree render-able form @@ -322,9 +332,6 @@ def validate_accounts(file_name): def validate_root(accounts): roots = [accounts[d] for d in accounts if not accounts[d].get('parent_account')] - if len(roots) < 4: - frappe.throw(_("Number of root accounts cannot be less than 4")) - error_messages = [] for account in roots: @@ -364,20 +371,12 @@ def get_mandatory_account_types(): def validate_account_types(accounts): account_types_for_ledger = ["Cost of Goods Sold", "Depreciation", "Fixed Asset", "Payable", "Receivable", "Stock Adjustment"] - account_types = [accounts[d]["account_type"] for d in accounts if not accounts[d]['is_group'] == 1] + account_types = [accounts[d]["account_type"] for d in accounts if not cint(accounts[d]['is_group']) == 1] missing = list(set(account_types_for_ledger) - set(account_types)) if missing: frappe.throw(_("Please identify/create Account (Ledger) for type - {0}").format(' , '.join(missing))) - account_types_for_group = ["Bank", "Cash", "Stock"] - # fix logic bug - account_groups = [accounts[d]["account_type"] for d in accounts if accounts[d]['is_group'] == 1] - - missing = list(set(account_types_for_group) - set(account_groups)) - if missing: - frappe.throw(_("Please identify/create Account (Group) for type - {0}").format(' , '.join(missing))) - def unset_existing_data(company): linked = frappe.db.sql('''select fieldname from tabDocField where fieldtype="Link" and options="Account" and parent="Company"''', as_dict=True) From 648b2d72a547caa0703a334cd61111a7ba35e24b Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 20 Sep 2021 15:27:12 +0530 Subject: [PATCH 07/53] perf: extract loop invariant db calls --- erpnext/controllers/accounts_controller.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index b90db054b57..6034cced8db 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -980,6 +980,9 @@ class AccountsController(TransactionBase): item_allowance = {} global_qty_allowance, global_amount_allowance = None, None + role_allowed_to_over_bill = frappe.db.get_single_value('Accounts Settings', 'role_allowed_to_over_bill') + user_roles = frappe.get_roles() + for item in self.get("items"): if item.get(item_ref_dn): ref_amt = flt(frappe.db.get_value(ref_dt + " Item", @@ -1009,9 +1012,7 @@ class AccountsController(TransactionBase): total_billed_amt = abs(total_billed_amt) max_allowed_amt = abs(max_allowed_amt) - role_allowed_to_over_bill = frappe.db.get_single_value('Accounts Settings', 'role_allowed_to_over_bill') - - if total_billed_amt - max_allowed_amt > 0.01 and role_allowed_to_over_bill not in frappe.get_roles(): + if total_billed_amt - max_allowed_amt > 0.01 and role_allowed_to_over_bill not in user_roles: if self.doctype != "Purchase Invoice": self.throw_overbill_exception(item, max_allowed_amt) elif not cint(frappe.db.get_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice")): From ef66beec51dc98a12207b4c528fe0632413118b4 Mon Sep 17 00:00:00 2001 From: Saqib Date: Mon, 20 Sep 2021 15:33:29 +0530 Subject: [PATCH 08/53] fix(plaid): query to check if bank account exists --- .../doctype/plaid_settings/plaid_settings.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py index d2748c2faad..310afed4811 100644 --- a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py +++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py @@ -85,10 +85,8 @@ def add_bank_accounts(response, bank, company): if not acc_subtype: add_account_subtype(account["subtype"]) - existing_bank_account = frappe.db.exists("Bank Account", { - 'account_name': account["name"], - 'bank': bank["bank_name"] - }) + bank_account_name = "{} - {}".format(account["name"], bank["bank_name"]) + existing_bank_account = frappe.db.exists("Bank Account", bank_account_name) if not existing_bank_account: try: @@ -197,6 +195,7 @@ def get_transactions(bank, bank_account=None, start_date=None, end_date=None): plaid = PlaidConnector(access_token) + transactions = [] try: transactions = plaid.get_transactions(start_date=start_date, end_date=end_date, account_id=account_id) except ItemError as e: @@ -205,7 +204,7 @@ def get_transactions(bank, bank_account=None, start_date=None, end_date=None): msg += _("Please refresh or reset the Plaid linking of the Bank {}.").format(bank) + " " frappe.log_error(msg, title=_("Plaid Link Refresh Required")) - return transactions or [] + return transactions def new_bank_transaction(transaction): From 0ff7367f390682587346ebd54dbddf1e8eb5bb9e Mon Sep 17 00:00:00 2001 From: Subin Tom <36098155+nemesis189@users.noreply.github.com> Date: Mon, 20 Sep 2021 16:13:36 +0530 Subject: [PATCH 09/53] fix: Tax Breakup table headers fix (#27596) --- erpnext/regional/india/utils.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index 903168d009f..091cc8847cb 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -112,10 +112,7 @@ def validate_gstin_check_digit(gstin, label='GSTIN'): frappe.throw(_("""Invalid {0}! The check digit validation has failed. Please ensure you've typed the {0} correctly.""").format(label)) def get_itemised_tax_breakup_header(item_doctype, tax_accounts): - if frappe.get_meta(item_doctype).has_field('gst_hsn_code'): - return [_("HSN/SAC"), _("Taxable Amount")] + tax_accounts - else: - return [_("Item"), _("Taxable Amount")] + tax_accounts + return [_("Item"), _("Taxable Amount")] + tax_accounts def get_itemised_tax_breakup_data(doc, account_wise=False, hsn_wise=False): itemised_tax = get_itemised_tax(doc.taxes, with_tax_account=account_wise) From 43bf82b58beb0d057b10b0f98c398908acc8c00a Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 20 Sep 2021 16:31:20 +0530 Subject: [PATCH 10/53] fix: warn when overbilling checks are skipped. --- erpnext/controllers/accounts_controller.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 6034cced8db..22efcdf1cc2 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -983,6 +983,8 @@ class AccountsController(TransactionBase): role_allowed_to_over_bill = frappe.db.get_single_value('Accounts Settings', 'role_allowed_to_over_bill') user_roles = frappe.get_roles() + total_overbilled_amt = 0.0 + for item in self.get("items"): if item.get(item_ref_dn): ref_amt = flt(frappe.db.get_value(ref_dt + " Item", @@ -1012,12 +1014,19 @@ class AccountsController(TransactionBase): total_billed_amt = abs(total_billed_amt) max_allowed_amt = abs(max_allowed_amt) - if total_billed_amt - max_allowed_amt > 0.01 and role_allowed_to_over_bill not in user_roles: + overbill_amt = total_billed_amt - max_allowed_amt + total_overbilled_amt += overbill_amt + + if overbill_amt > 0.01 and role_allowed_to_over_bill not in user_roles: if self.doctype != "Purchase Invoice": self.throw_overbill_exception(item, max_allowed_amt) elif not cint(frappe.db.get_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice")): self.throw_overbill_exception(item, max_allowed_amt) + if role_allowed_to_over_bill in user_roles and total_overbilled_amt > 0.1: + frappe.msgprint(_("INFO: Overbilling of {} ignored because you have {} role.") + .format(total_overbilled_amt, role_allowed_to_over_bill)) + def throw_overbill_exception(self, item, max_allowed_amt): frappe.throw(_("Cannot overbill for Item {0} in row {1} more than {2}. To allow over-billing, please set allowance in Accounts Settings") .format(item.item_code, item.idx, max_allowed_amt)) From 5e4fbba753f9a968a03324773cbff65c04d67f0c Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 20 Sep 2021 16:36:27 +0530 Subject: [PATCH 11/53] refactor: add guard clause in for loop Reduce overly indented code/improve readability. --- erpnext/controllers/accounts_controller.py | 69 +++++++++++----------- 1 file changed, 36 insertions(+), 33 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 22efcdf1cc2..024a2978700 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -986,46 +986,49 @@ class AccountsController(TransactionBase): total_overbilled_amt = 0.0 for item in self.get("items"): - if item.get(item_ref_dn): - ref_amt = flt(frappe.db.get_value(ref_dt + " Item", - item.get(item_ref_dn), based_on), self.precision(based_on, item)) - if not ref_amt: - frappe.msgprint( - _("Warning: System will not check overbilling since amount for Item {0} in {1} is zero") - .format(item.item_code, ref_dt)) - else: - already_billed = frappe.db.sql(""" - select sum(%s) - from `tab%s` - where %s=%s and docstatus=1 and parent != %s - """ % (based_on, self.doctype + " Item", item_ref_dn, '%s', '%s'), - (item.get(item_ref_dn), self.name))[0][0] + if not item.get(item_ref_dn): + continue - total_billed_amt = flt(flt(already_billed) + flt(item.get(based_on)), - self.precision(based_on, item)) + ref_amt = flt(frappe.db.get_value(ref_dt + " Item", + item.get(item_ref_dn), based_on), self.precision(based_on, item)) + if not ref_amt: + frappe.msgprint( + _("Warning: System will not check overbilling since amount for Item {0} in {1} is zero") + .format(item.item_code, ref_dt)) + continue - allowance, item_allowance, global_qty_allowance, global_amount_allowance = \ - get_allowance_for(item.item_code, item_allowance, global_qty_allowance, global_amount_allowance, "amount") + already_billed = frappe.db.sql(""" + select sum(%s) + from `tab%s` + where %s=%s and docstatus=1 and parent != %s + """ % (based_on, self.doctype + " Item", item_ref_dn, '%s', '%s'), + (item.get(item_ref_dn), self.name))[0][0] - max_allowed_amt = flt(ref_amt * (100 + allowance) / 100) + total_billed_amt = flt(flt(already_billed) + flt(item.get(based_on)), + self.precision(based_on, item)) - if total_billed_amt < 0 and max_allowed_amt < 0: - # while making debit note against purchase return entry(purchase receipt) getting overbill error - total_billed_amt = abs(total_billed_amt) - max_allowed_amt = abs(max_allowed_amt) + allowance, item_allowance, global_qty_allowance, global_amount_allowance = \ + get_allowance_for(item.item_code, item_allowance, global_qty_allowance, global_amount_allowance, "amount") - overbill_amt = total_billed_amt - max_allowed_amt - total_overbilled_amt += overbill_amt + max_allowed_amt = flt(ref_amt * (100 + allowance) / 100) - if overbill_amt > 0.01 and role_allowed_to_over_bill not in user_roles: - if self.doctype != "Purchase Invoice": - self.throw_overbill_exception(item, max_allowed_amt) - elif not cint(frappe.db.get_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice")): - self.throw_overbill_exception(item, max_allowed_amt) + if total_billed_amt < 0 and max_allowed_amt < 0: + # while making debit note against purchase return entry(purchase receipt) getting overbill error + total_billed_amt = abs(total_billed_amt) + max_allowed_amt = abs(max_allowed_amt) - if role_allowed_to_over_bill in user_roles and total_overbilled_amt > 0.1: - frappe.msgprint(_("INFO: Overbilling of {} ignored because you have {} role.") - .format(total_overbilled_amt, role_allowed_to_over_bill)) + overbill_amt = total_billed_amt - max_allowed_amt + total_overbilled_amt += overbill_amt + + if overbill_amt > 0.01 and role_allowed_to_over_bill not in user_roles: + if self.doctype != "Purchase Invoice": + self.throw_overbill_exception(item, max_allowed_amt) + elif not cint(frappe.db.get_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice")): + self.throw_overbill_exception(item, max_allowed_amt) + + if role_allowed_to_over_bill in user_roles and total_overbilled_amt > 0.1: + frappe.msgprint(_("INFO: Overbilling of {} ignored because you have {} role.") + .format(total_overbilled_amt, role_allowed_to_over_bill)) def throw_overbill_exception(self, item, max_allowed_amt): frappe.throw(_("Cannot overbill for Item {0} in row {1} more than {2}. To allow over-billing, please set allowance in Accounts Settings") From 21a955d20b83f6c55aafab294be0f1f4d4b89153 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 20 Sep 2021 16:58:23 +0530 Subject: [PATCH 12/53] fix(ux): better error message Co-authored-by: Saqib --- erpnext/controllers/accounts_controller.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 024a2978700..5359089698a 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -993,8 +993,8 @@ class AccountsController(TransactionBase): item.get(item_ref_dn), based_on), self.precision(based_on, item)) if not ref_amt: frappe.msgprint( - _("Warning: System will not check overbilling since amount for Item {0} in {1} is zero") - .format(item.item_code, ref_dt)) + _("System will not check overbilling since amount for Item {0} in {1} is zero") + .format(item.item_code, ref_dt), title=_("Warning"), indicator="orange") continue already_billed = frappe.db.sql(""" @@ -1002,7 +1002,7 @@ class AccountsController(TransactionBase): from `tab%s` where %s=%s and docstatus=1 and parent != %s """ % (based_on, self.doctype + " Item", item_ref_dn, '%s', '%s'), - (item.get(item_ref_dn), self.name))[0][0] + (item.get(item_ref_dn), self.name))[0][0] total_billed_amt = flt(flt(already_billed) + flt(item.get(based_on)), self.precision(based_on, item)) @@ -1027,8 +1027,8 @@ class AccountsController(TransactionBase): self.throw_overbill_exception(item, max_allowed_amt) if role_allowed_to_over_bill in user_roles and total_overbilled_amt > 0.1: - frappe.msgprint(_("INFO: Overbilling of {} ignored because you have {} role.") - .format(total_overbilled_amt, role_allowed_to_over_bill)) + frappe.msgprint(_("Overbilling of {} ignored because you have {} role.") + .format(total_overbilled_amt, role_allowed_to_over_bill), title=_("Warning"), indicator="orange") def throw_overbill_exception(self, item, max_allowed_amt): frappe.throw(_("Cannot overbill for Item {0} in row {1} more than {2}. To allow over-billing, please set allowance in Accounts Settings") From 190c2d84da998344772fbba9675fada8f555b4fa Mon Sep 17 00:00:00 2001 From: Anuja Pawar <60467153+Anuja-pawar@users.noreply.github.com> Date: Mon, 20 Sep 2021 21:32:38 +0530 Subject: [PATCH 13/53] fix: reference row added in allocation table (#27602) --- .../payment_reconciliation_allocation.json | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.json b/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.json index 36535014320..b8c65eea847 100644 --- a/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.json +++ b/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.json @@ -7,6 +7,7 @@ "field_order": [ "reference_type", "reference_name", + "reference_row", "column_break_3", "invoice_type", "invoice_number", @@ -121,11 +122,17 @@ "label": "Amount", "options": "Currency", "read_only": 1 + }, + { + "fieldname": "reference_row", + "fieldtype": "Data", + "hidden": 1, + "label": "Reference Row" } ], "istable": 1, "links": [], - "modified": "2021-08-30 10:58:42.665107", + "modified": "2021-09-20 17:23:09.455803", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Reconciliation Allocation", From 6d8e869a92285a9be890b58b1754cb373cfcd6fd Mon Sep 17 00:00:00 2001 From: "Abdullah A. Zaqout" <26047413+zaqoutabed@users.noreply.github.com> Date: Mon, 20 Sep 2021 19:05:37 +0300 Subject: [PATCH 14/53] fix(pos): check if item exists before fetching available qty (#27581) --- erpnext/selling/page/point_of_sale/pos_item_details.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/selling/page/point_of_sale/pos_item_details.js b/erpnext/selling/page/point_of_sale/pos_item_details.js index d899c5c19b4..ec861d7c531 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_details.js +++ b/erpnext/selling/page/point_of_sale/pos_item_details.js @@ -236,7 +236,7 @@ erpnext.PointOfSale.ItemDetails = class { if (this.value) { me.events.form_updated(me.current_item, 'warehouse', this.value).then(() => { me.item_stock_map = me.events.get_item_stock_map(); - const available_qty = me.item_stock_map[me.item_row.item_code][this.value]; + const available_qty = me.item_stock_map[me.item_row.item_code] && me.item_stock_map[me.item_row.item_code][this.value]; if (available_qty === undefined) { me.events.get_available_stock(me.item_row.item_code, this.value).then(() => { // item stock map is updated now reset warehouse From 5c400da4e2c3a7831199a3a0d9e9e12b2c135aac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20de=20Ryckel?= Date: Tue, 21 Sep 2021 06:59:15 +0300 Subject: [PATCH 15/53] Update training_result.js (#27615) cur_frm is deprecated --- erpnext/hr/doctype/training_result/training_result.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/hr/doctype/training_result/training_result.js b/erpnext/hr/doctype/training_result/training_result.js index 5cdbcad8058..718b383e721 100644 --- a/erpnext/hr/doctype/training_result/training_result.js +++ b/erpnext/hr/doctype/training_result/training_result.js @@ -21,7 +21,7 @@ frappe.ui.form.on('Training Result', { frm.set_value("employees" ,""); if (r.message) { $.each(r.message, function(i, d) { - var row = frappe.model.add_child(cur_frm.doc, "Training Result Employee", "employees"); + var row = frappe.model.add_child(frm.doc, "Training Result Employee", "employees"); row.employee = d.employee; row.employee_name = d.employee_name; }); From abded895f3e47ac8ac24117eca998e202a5dba16 Mon Sep 17 00:00:00 2001 From: Kenneth Sequeira <33246109+kennethsequeira@users.noreply.github.com> Date: Tue, 21 Sep 2021 10:58:18 +0530 Subject: [PATCH 16/53] fix: update default KSA VAT rate for setup (#27614) --- erpnext/setup/setup_wizard/data/country_wise_tax.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/setup/setup_wizard/data/country_wise_tax.json b/erpnext/setup/setup_wizard/data/country_wise_tax.json index 34af093a231..b7e895db363 100644 --- a/erpnext/setup/setup_wizard/data/country_wise_tax.json +++ b/erpnext/setup/setup_wizard/data/country_wise_tax.json @@ -2116,9 +2116,9 @@ }, "Saudi Arabia": { - "KSA VAT 5%": { - "account_name": "VAT 5%", - "tax_rate": 5.00 + "KSA VAT 15%": { + "account_name": "VAT 15%", + "tax_rate": 15.00 }, "KSA VAT Zero": { "account_name": "VAT Zero", From 9110223341321946699931dc45467f96427d8643 Mon Sep 17 00:00:00 2001 From: Marica Date: Tue, 21 Sep 2021 16:50:14 +0530 Subject: [PATCH 17/53] fix: (ux) Use subassembly schedule date while making WO from Prod Plan (#27628) - Set subassemply WO's planned start date from Production Plan --- .../manufacturing/doctype/production_plan/production_plan.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 18284e0199e..b9efe9b41ea 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -457,7 +457,8 @@ class ProductionPlan(Document): def prepare_args_for_sub_assembly_items(self, row, args): for field in ["production_item", "item_name", "qty", "fg_warehouse", - "description", "bom_no", "stock_uom", "bom_level", "production_plan_item"]: + "description", "bom_no", "stock_uom", "bom_level", + "production_plan_item", "schedule_date"]: args[field] = row.get(field) args.update({ From c302c7ab4286e3547367beffc4feb6fb8c909f1f Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 21 Sep 2021 17:51:46 +0530 Subject: [PATCH 18/53] fix: remove bad default for anniversary reminders (#27632) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤦 --- erpnext/hr/doctype/employee/employee_reminders.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/hr/doctype/employee/employee_reminders.py b/erpnext/hr/doctype/employee/employee_reminders.py index ba086dc0602..216d8f6bb3a 100644 --- a/erpnext/hr/doctype/employee/employee_reminders.py +++ b/erpnext/hr/doctype/employee/employee_reminders.py @@ -184,7 +184,7 @@ def get_employees_having_an_event_today(event_type): # -------------------------- def send_work_anniversary_reminders(): """Send Employee Work Anniversary Reminders if 'Send Work Anniversary Reminders' is checked""" - to_send = int(frappe.db.get_single_value("HR Settings", "send_work_anniversary_reminders") or 1) + to_send = int(frappe.db.get_single_value("HR Settings", "send_work_anniversary_reminders")) if not to_send: return From 2af6ea68ae94fed5f363750b837eaa8a1e877e49 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Tue, 21 Sep 2021 20:33:09 +0530 Subject: [PATCH 19/53] fix: remove unknown field employee_name from query (bp #27634) * fix: remove unknown field employee_name from query * fix: remove unknown fieldname Co-authored-by: root Co-authored-by: Ankush Menat (cherry picked from commit 9ebabb86b3d28e2c94eb2761f1864c88b6eb1e59) Co-authored-by: Bhavesh Maheshwari <34086262+bhavesh95863@users.noreply.github.com> --- erpnext/manufacturing/doctype/job_card/job_card.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index 3209546a12c..e1d79be81c4 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -677,7 +677,7 @@ def get_job_details(start, end, filters=None): conditions = get_filters_cond("Job Card", filters, []) job_cards = frappe.db.sql(""" SELECT `tabJob Card`.name, `tabJob Card`.work_order, - `tabJob Card`.employee_name, `tabJob Card`.status, ifnull(`tabJob Card`.remarks, ''), + `tabJob Card`.status, ifnull(`tabJob Card`.remarks, ''), min(`tabJob Card Time Log`.from_time) as from_time, max(`tabJob Card Time Log`.to_time) as to_time FROM `tabJob Card` , `tabJob Card Time Log` @@ -687,7 +687,7 @@ def get_job_details(start, end, filters=None): for d in job_cards: subject_data = [] - for field in ["name", "work_order", "remarks", "employee_name"]: + for field in ["name", "work_order", "remarks"]: if not d.get(field): continue subject_data.append(d.get(field)) From c8b9a55e9620655850e406889788da15715300f8 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Wed, 22 Sep 2021 12:11:35 +0530 Subject: [PATCH 20/53] feat: add `Partly Paid` status in Invoices (#27625) --- .../purchase_invoice/purchase_invoice.json | 623 +++++------------- .../purchase_invoice/purchase_invoice.py | 12 +- .../purchase_invoice/purchase_invoice_list.js | 76 ++- .../doctype/sales_invoice/sales_invoice.json | 5 +- .../doctype/sales_invoice/sales_invoice.py | 51 +- .../sales_invoice/sales_invoice_list.js | 6 +- .../sales_invoice/test_sales_invoice.py | 51 ++ erpnext/controllers/accounts_controller.py | 18 +- 8 files changed, 319 insertions(+), 523 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index 7822f747f64..dde0328130f 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -177,9 +177,7 @@ "hidden": 1, "label": "Title", "no_copy": 1, - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "naming_series", @@ -191,9 +189,7 @@ "options": "ACC-PINV-.YYYY.-\nACC-PINV-RET-.YYYY.-", "print_hide": 1, "reqd": 1, - "set_only_once": 1, - "show_days": 1, - "show_seconds": 1 + "set_only_once": 1 }, { "fieldname": "supplier", @@ -205,9 +201,7 @@ "options": "Supplier", "print_hide": 1, "reqd": 1, - "search_index": 1, - "show_days": 1, - "show_seconds": 1 + "search_index": 1 }, { "bold": 1, @@ -219,9 +213,7 @@ "label": "Supplier Name", "oldfieldname": "supplier_name", "oldfieldtype": "Data", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fetch_from": "supplier.tax_id", @@ -229,27 +221,21 @@ "fieldtype": "Read Only", "label": "Tax Id", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "due_date", "fieldtype": "Date", "label": "Due Date", "oldfieldname": "due_date", - "oldfieldtype": "Date", - "show_days": 1, - "show_seconds": 1 + "oldfieldtype": "Date" }, { "default": "0", "fieldname": "is_paid", "fieldtype": "Check", "label": "Is Paid", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "default": "0", @@ -257,25 +243,19 @@ "fieldtype": "Check", "label": "Is Return (Debit Note)", "no_copy": 1, - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "default": "0", "fieldname": "apply_tds", "fieldtype": "Check", "label": "Apply Tax Withholding Amount", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "column_break1", "fieldtype": "Column Break", "oldfieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1, "width": "50%" }, { @@ -285,17 +265,13 @@ "label": "Company", "options": "Company", "print_hide": 1, - "remember_last_selected_value": 1, - "show_days": 1, - "show_seconds": 1 + "remember_last_selected_value": 1 }, { "fieldname": "cost_center", "fieldtype": "Link", "label": "Cost Center", - "options": "Cost Center", - "show_days": 1, - "show_seconds": 1 + "options": "Cost Center" }, { "default": "Today", @@ -307,9 +283,7 @@ "oldfieldtype": "Date", "print_hide": 1, "reqd": 1, - "search_index": 1, - "show_days": 1, - "show_seconds": 1 + "search_index": 1 }, { "fieldname": "posting_time", @@ -318,8 +292,6 @@ "no_copy": 1, "print_hide": 1, "print_width": "100px", - "show_days": 1, - "show_seconds": 1, "width": "100px" }, { @@ -328,9 +300,7 @@ "fieldname": "set_posting_time", "fieldtype": "Check", "label": "Edit Posting Date and Time", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "amended_from", @@ -342,58 +312,44 @@ "oldfieldtype": "Link", "options": "Purchase Invoice", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "collapsible": 1, "collapsible_depends_on": "eval:doc.on_hold", "fieldname": "sb_14", "fieldtype": "Section Break", - "label": "Hold Invoice", - "show_days": 1, - "show_seconds": 1 + "label": "Hold Invoice" }, { "default": "0", "fieldname": "on_hold", "fieldtype": "Check", - "label": "Hold Invoice", - "show_days": 1, - "show_seconds": 1 + "label": "Hold Invoice" }, { "depends_on": "eval:doc.on_hold", "description": "Once set, this invoice will be on hold till the set date", "fieldname": "release_date", "fieldtype": "Date", - "label": "Release Date", - "show_days": 1, - "show_seconds": 1 + "label": "Release Date" }, { "fieldname": "cb_17", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "depends_on": "eval:doc.on_hold", "fieldname": "hold_comment", "fieldtype": "Small Text", - "label": "Reason For Putting On Hold", - "show_days": 1, - "show_seconds": 1 + "label": "Reason For Putting On Hold" }, { "collapsible": 1, "collapsible_depends_on": "bill_no", "fieldname": "supplier_invoice_details", "fieldtype": "Section Break", - "label": "Supplier Invoice Details", - "show_days": 1, - "show_seconds": 1 + "label": "Supplier Invoice Details" }, { "fieldname": "bill_no", @@ -401,15 +357,11 @@ "label": "Supplier Invoice No", "oldfieldname": "bill_no", "oldfieldtype": "Data", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "column_break_15", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "fieldname": "bill_date", @@ -418,17 +370,13 @@ "no_copy": 1, "oldfieldname": "bill_date", "oldfieldtype": "Date", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "depends_on": "return_against", "fieldname": "returns", "fieldtype": "Section Break", - "label": "Returns", - "show_days": 1, - "show_seconds": 1 + "label": "Returns" }, { "depends_on": "return_against", @@ -438,34 +386,26 @@ "no_copy": 1, "options": "Purchase Invoice", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "collapsible": 1, "fieldname": "section_addresses", "fieldtype": "Section Break", - "label": "Address and Contact", - "show_days": 1, - "show_seconds": 1 + "label": "Address and Contact" }, { "fieldname": "supplier_address", "fieldtype": "Link", "label": "Select Supplier Address", "options": "Address", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "address_display", "fieldtype": "Small Text", "label": "Address", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "contact_person", @@ -473,67 +413,51 @@ "in_global_search": 1, "label": "Contact Person", "options": "Contact", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "contact_display", "fieldtype": "Small Text", "label": "Contact", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "contact_mobile", "fieldtype": "Small Text", "label": "Mobile No", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "contact_email", "fieldtype": "Small Text", "label": "Contact Email", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "col_break_address", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "fieldname": "shipping_address", "fieldtype": "Link", "label": "Select Shipping Address", "options": "Address", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "shipping_address_display", "fieldtype": "Small Text", "label": "Shipping Address", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "collapsible": 1, "fieldname": "currency_and_price_list", "fieldtype": "Section Break", "label": "Currency and Price List", - "options": "fa fa-tag", - "show_days": 1, - "show_seconds": 1 + "options": "fa fa-tag" }, { "fieldname": "currency", @@ -542,9 +466,7 @@ "oldfieldname": "currency", "oldfieldtype": "Select", "options": "Currency", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "conversion_rate", @@ -553,24 +475,18 @@ "oldfieldname": "conversion_rate", "oldfieldtype": "Currency", "precision": "9", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "column_break2", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "fieldname": "buying_price_list", "fieldtype": "Link", "label": "Price List", "options": "Price List", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "price_list_currency", @@ -578,18 +494,14 @@ "label": "Price List Currency", "options": "Currency", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "plc_conversion_rate", "fieldtype": "Float", "label": "Price List Exchange Rate", "precision": "9", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "default": "0", @@ -598,15 +510,11 @@ "label": "Ignore Pricing Rule", "no_copy": 1, "permlevel": 1, - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "sec_warehouse", - "fieldtype": "Section Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Section Break" }, { "depends_on": "update_stock", @@ -615,9 +523,7 @@ "fieldtype": "Link", "label": "Set Accepted Warehouse", "options": "Warehouse", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "depends_on": "update_stock", @@ -627,15 +533,11 @@ "label": "Rejected Warehouse", "no_copy": 1, "options": "Warehouse", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "col_break_warehouse", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "default": "No", @@ -643,26 +545,20 @@ "fieldtype": "Select", "label": "Raw Materials Supplied", "options": "No\nYes", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "items_section", "fieldtype": "Section Break", "oldfieldtype": "Section Break", - "options": "fa fa-shopping-cart", - "show_days": 1, - "show_seconds": 1 + "options": "fa fa-shopping-cart" }, { "default": "0", "fieldname": "update_stock", "fieldtype": "Check", "label": "Update Stock", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "scan_barcode", @@ -678,33 +574,25 @@ "oldfieldname": "entries", "oldfieldtype": "Table", "options": "Purchase Invoice Item", - "reqd": 1, - "show_days": 1, - "show_seconds": 1 + "reqd": 1 }, { "fieldname": "pricing_rule_details", "fieldtype": "Section Break", - "label": "Pricing Rules", - "show_days": 1, - "show_seconds": 1 + "label": "Pricing Rules" }, { "fieldname": "pricing_rules", "fieldtype": "Table", "label": "Pricing Rule Detail", "options": "Pricing Rule Detail", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "collapsible_depends_on": "supplied_items", "fieldname": "raw_materials_supplied", "fieldtype": "Section Break", - "label": "Raw Materials Supplied", - "show_days": 1, - "show_seconds": 1 + "label": "Raw Materials Supplied" }, { "depends_on": "update_stock", @@ -712,23 +600,17 @@ "fieldtype": "Table", "label": "Supplied Items", "no_copy": 1, - "options": "Purchase Receipt Item Supplied", - "show_days": 1, - "show_seconds": 1 + "options": "Purchase Receipt Item Supplied" }, { "fieldname": "section_break_26", - "fieldtype": "Section Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Section Break" }, { "fieldname": "total_qty", "fieldtype": "Float", "label": "Total Quantity", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "base_total", @@ -736,9 +618,7 @@ "label": "Total (Company Currency)", "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "base_net_total", @@ -748,24 +628,18 @@ "oldfieldtype": "Currency", "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "column_break_28", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "fieldname": "total", "fieldtype": "Currency", "label": "Total", "options": "currency", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "net_total", @@ -775,56 +649,42 @@ "oldfieldtype": "Currency", "options": "currency", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "total_net_weight", "fieldtype": "Float", "label": "Total Net Weight", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "taxes_section", "fieldtype": "Section Break", "oldfieldtype": "Section Break", - "options": "fa fa-money", - "show_days": 1, - "show_seconds": 1 + "options": "fa fa-money" }, { "fieldname": "tax_category", "fieldtype": "Link", "label": "Tax Category", "options": "Tax Category", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "column_break_49", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "fieldname": "shipping_rule", "fieldtype": "Link", "label": "Shipping Rule", "options": "Shipping Rule", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "section_break_51", - "fieldtype": "Section Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Section Break" }, { "fieldname": "taxes_and_charges", @@ -833,9 +693,7 @@ "oldfieldname": "purchase_other_charges", "oldfieldtype": "Link", "options": "Purchase Taxes and Charges Template", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "taxes", @@ -843,17 +701,13 @@ "label": "Purchase Taxes and Charges", "oldfieldname": "purchase_tax_details", "oldfieldtype": "Table", - "options": "Purchase Taxes and Charges", - "show_days": 1, - "show_seconds": 1 + "options": "Purchase Taxes and Charges" }, { "collapsible": 1, "fieldname": "sec_tax_breakup", "fieldtype": "Section Break", - "label": "Tax Breakup", - "show_days": 1, - "show_seconds": 1 + "label": "Tax Breakup" }, { "fieldname": "other_charges_calculation", @@ -862,17 +716,13 @@ "no_copy": 1, "oldfieldtype": "HTML", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "totals", "fieldtype": "Section Break", "oldfieldtype": "Section Break", - "options": "fa fa-money", - "show_days": 1, - "show_seconds": 1 + "options": "fa fa-money" }, { "fieldname": "base_taxes_and_charges_added", @@ -882,9 +732,7 @@ "oldfieldtype": "Currency", "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "base_taxes_and_charges_deducted", @@ -894,9 +742,7 @@ "oldfieldtype": "Currency", "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "base_total_taxes_and_charges", @@ -906,15 +752,11 @@ "oldfieldtype": "Currency", "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "column_break_40", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "fieldname": "taxes_and_charges_added", @@ -924,9 +766,7 @@ "oldfieldtype": "Currency", "options": "currency", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "taxes_and_charges_deducted", @@ -936,9 +776,7 @@ "oldfieldtype": "Currency", "options": "currency", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "total_taxes_and_charges", @@ -946,18 +784,14 @@ "label": "Total Taxes and Charges", "options": "currency", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "collapsible": 1, "collapsible_depends_on": "discount_amount", "fieldname": "section_break_44", "fieldtype": "Section Break", - "label": "Additional Discount", - "show_days": 1, - "show_seconds": 1 + "label": "Additional Discount" }, { "default": "Grand Total", @@ -965,9 +799,7 @@ "fieldtype": "Select", "label": "Apply Additional Discount On", "options": "\nGrand Total\nNet Total", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "base_discount_amount", @@ -975,38 +807,28 @@ "label": "Additional Discount Amount (Company Currency)", "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "column_break_46", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "fieldname": "additional_discount_percentage", "fieldtype": "Float", "label": "Additional Discount Percentage", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "discount_amount", "fieldtype": "Currency", "label": "Additional Discount Amount", "options": "currency", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "section_break_49", - "fieldtype": "Section Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Section Break" }, { "fieldname": "base_grand_total", @@ -1016,9 +838,7 @@ "oldfieldtype": "Currency", "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "depends_on": "eval:!doc.disable_rounded_total", @@ -1028,9 +848,7 @@ "no_copy": 1, "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "depends_on": "eval:!doc.disable_rounded_total", @@ -1040,9 +858,7 @@ "no_copy": 1, "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "base_in_words", @@ -1052,17 +868,13 @@ "oldfieldname": "in_words", "oldfieldtype": "Data", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "column_break8", "fieldtype": "Column Break", "oldfieldtype": "Column Break", "print_hide": 1, - "show_days": 1, - "show_seconds": 1, "width": "50%" }, { @@ -1073,9 +885,7 @@ "oldfieldname": "grand_total_import", "oldfieldtype": "Currency", "options": "currency", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "depends_on": "eval:!doc.disable_rounded_total", @@ -1085,9 +895,7 @@ "no_copy": 1, "options": "currency", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "depends_on": "eval:!doc.disable_rounded_total", @@ -1097,9 +905,7 @@ "no_copy": 1, "options": "currency", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "in_words", @@ -1109,9 +915,7 @@ "oldfieldname": "in_words_import", "oldfieldtype": "Data", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "total_advance", @@ -1122,9 +926,7 @@ "oldfieldtype": "Currency", "options": "party_account_currency", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "outstanding_amount", @@ -1135,18 +937,14 @@ "oldfieldtype": "Currency", "options": "party_account_currency", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "default": "0", "depends_on": "grand_total", "fieldname": "disable_rounded_total", "fieldtype": "Check", - "label": "Disable Rounded Total", - "show_days": 1, - "show_seconds": 1 + "label": "Disable Rounded Total" }, { "collapsible": 1, @@ -1154,26 +952,20 @@ "depends_on": "eval:doc.is_paid===1||(doc.advances && doc.advances.length>0)", "fieldname": "payments_section", "fieldtype": "Section Break", - "label": "Payments", - "show_days": 1, - "show_seconds": 1 + "label": "Payments" }, { "fieldname": "mode_of_payment", "fieldtype": "Link", "label": "Mode of Payment", "options": "Mode of Payment", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "cash_bank_account", "fieldtype": "Link", "label": "Cash/Bank Account", - "options": "Account", - "show_days": 1, - "show_seconds": 1 + "options": "Account" }, { "fieldname": "clearance_date", @@ -1181,15 +973,11 @@ "label": "Clearance Date", "no_copy": 1, "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "col_br_payments", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "depends_on": "is_paid", @@ -1198,9 +986,7 @@ "label": "Paid Amount", "no_copy": 1, "options": "currency", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "base_paid_amount", @@ -1209,9 +995,7 @@ "no_copy": 1, "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "collapsible": 1, @@ -1219,9 +1003,7 @@ "depends_on": "grand_total", "fieldname": "write_off", "fieldtype": "Section Break", - "label": "Write Off", - "show_days": 1, - "show_seconds": 1 + "label": "Write Off" }, { "fieldname": "write_off_amount", @@ -1229,9 +1011,7 @@ "label": "Write Off Amount", "no_copy": 1, "options": "currency", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "base_write_off_amount", @@ -1240,15 +1020,11 @@ "no_copy": 1, "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "column_break_61", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "depends_on": "eval:flt(doc.write_off_amount)!=0", @@ -1256,9 +1032,7 @@ "fieldtype": "Link", "label": "Write Off Account", "options": "Account", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "depends_on": "eval:flt(doc.write_off_amount)!=0", @@ -1266,9 +1040,7 @@ "fieldtype": "Link", "label": "Write Off Cost Center", "options": "Cost Center", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "collapsible": 1, @@ -1278,17 +1050,13 @@ "label": "Advance Payments", "oldfieldtype": "Section Break", "options": "fa fa-money", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "default": "0", "fieldname": "allocate_advances_automatically", "fieldtype": "Check", - "label": "Set Advances and Allocate (FIFO)", - "show_days": 1, - "show_seconds": 1 + "label": "Set Advances and Allocate (FIFO)" }, { "depends_on": "eval:!doc.allocate_advances_automatically", @@ -1296,9 +1064,7 @@ "fieldtype": "Button", "label": "Get Advances Paid", "oldfieldtype": "Button", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "advances", @@ -1308,26 +1074,20 @@ "oldfieldname": "advance_allocation_details", "oldfieldtype": "Table", "options": "Purchase Invoice Advance", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "collapsible": 1, "collapsible_depends_on": "eval:(!doc.is_return)", "fieldname": "payment_schedule_section", "fieldtype": "Section Break", - "label": "Payment Terms", - "show_days": 1, - "show_seconds": 1 + "label": "Payment Terms" }, { "fieldname": "payment_terms_template", "fieldtype": "Link", "label": "Payment Terms Template", - "options": "Payment Terms Template", - "show_days": 1, - "show_seconds": 1 + "options": "Payment Terms Template" }, { "fieldname": "payment_schedule", @@ -1335,9 +1095,7 @@ "label": "Payment Schedule", "no_copy": 1, "options": "Payment Schedule", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "collapsible": 1, @@ -1345,33 +1103,25 @@ "fieldname": "terms_section_break", "fieldtype": "Section Break", "label": "Terms and Conditions", - "options": "fa fa-legal", - "show_days": 1, - "show_seconds": 1 + "options": "fa fa-legal" }, { "fieldname": "tc_name", "fieldtype": "Link", "label": "Terms", "options": "Terms and Conditions", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "terms", "fieldtype": "Text Editor", - "label": "Terms and Conditions1", - "show_days": 1, - "show_seconds": 1 + "label": "Terms and Conditions1" }, { "collapsible": 1, "fieldname": "printing_settings", "fieldtype": "Section Break", - "label": "Printing Settings", - "show_days": 1, - "show_seconds": 1 + "label": "Printing Settings" }, { "allow_on_submit": 1, @@ -1379,9 +1129,7 @@ "fieldtype": "Link", "label": "Letter Head", "options": "Letter Head", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "allow_on_submit": 1, @@ -1389,15 +1137,11 @@ "fieldname": "group_same_items", "fieldtype": "Check", "label": "Group same items", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "column_break_112", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "allow_on_submit": 1, @@ -1409,18 +1153,14 @@ "oldfieldtype": "Link", "options": "Print Heading", "print_hide": 1, - "report_hide": 1, - "show_days": 1, - "show_seconds": 1 + "report_hide": 1 }, { "fieldname": "language", "fieldtype": "Data", "label": "Print Language", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "collapsible": 1, @@ -1429,9 +1169,7 @@ "label": "More Information", "oldfieldtype": "Section Break", "options": "fa fa-file-text", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "credit_to", @@ -1442,9 +1180,7 @@ "options": "Account", "print_hide": 1, "reqd": 1, - "search_index": 1, - "show_days": 1, - "show_seconds": 1 + "search_index": 1 }, { "fieldname": "party_account_currency", @@ -1454,9 +1190,7 @@ "no_copy": 1, "options": "Currency", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "default": "No", @@ -1466,9 +1200,7 @@ "oldfieldname": "is_opening", "oldfieldtype": "Select", "options": "No\nYes", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "against_expense_account", @@ -1478,15 +1210,11 @@ "no_copy": 1, "oldfieldname": "against_expense_account", "oldfieldtype": "Small Text", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "column_break_63", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "default": "Draft", @@ -1494,10 +1222,8 @@ "fieldtype": "Select", "in_standard_filter": 1, "label": "Status", - "options": "\nDraft\nReturn\nDebit Note Issued\nSubmitted\nPaid\nUnpaid\nOverdue\nCancelled\nInternal Transfer", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "options": "\nDraft\nReturn\nDebit Note Issued\nSubmitted\nPaid\nPartly Paid\nUnpaid\nOverdue\nCancelled\nInternal Transfer", + "print_hide": 1 }, { "fieldname": "inter_company_invoice_reference", @@ -1506,9 +1232,7 @@ "no_copy": 1, "options": "Sales Invoice", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "remarks", @@ -1517,18 +1241,14 @@ "no_copy": 1, "oldfieldname": "remarks", "oldfieldtype": "Text", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "collapsible": 1, "fieldname": "subscription_section", "fieldtype": "Section Break", "label": "Subscription Section", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "allow_on_submit": 1, @@ -1537,9 +1257,7 @@ "fieldtype": "Date", "label": "From Date", "no_copy": 1, - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "allow_on_submit": 1, @@ -1548,15 +1266,11 @@ "fieldtype": "Date", "label": "To Date", "no_copy": 1, - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "column_break_114", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "fieldname": "auto_repeat", @@ -1565,32 +1279,24 @@ "no_copy": 1, "options": "Auto Repeat", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "allow_on_submit": 1, "depends_on": "eval: doc.auto_repeat", "fieldname": "update_auto_repeat_reference", "fieldtype": "Button", - "label": "Update Auto Repeat Reference", - "show_days": 1, - "show_seconds": 1 + "label": "Update Auto Repeat Reference" }, { "collapsible": 1, "fieldname": "accounting_dimensions_section", "fieldtype": "Section Break", - "label": "Accounting Dimensions ", - "show_days": 1, - "show_seconds": 1 + "label": "Accounting Dimensions " }, { "fieldname": "dimension_col_break", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "default": "0", @@ -1598,9 +1304,7 @@ "fieldname": "is_internal_supplier", "fieldtype": "Check", "label": "Is Internal Supplier", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "tax_withholding_category", @@ -1608,33 +1312,25 @@ "hidden": 1, "label": "Tax Withholding Category", "options": "Tax Withholding Category", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "billing_address", "fieldtype": "Link", "label": "Select Billing Address", - "options": "Address", - "show_days": 1, - "show_seconds": 1 + "options": "Address" }, { "fieldname": "billing_address_display", "fieldtype": "Small Text", "label": "Billing Address", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "project", "fieldtype": "Link", "label": "Project", - "options": "Project", - "show_days": 1, - "show_seconds": 1 + "options": "Project" }, { "depends_on": "eval:doc.is_internal_supplier", @@ -1642,9 +1338,7 @@ "fieldname": "unrealized_profit_loss_account", "fieldtype": "Link", "label": "Unrealized Profit / Loss Account", - "options": "Account", - "show_days": 1, - "show_seconds": 1 + "options": "Account" }, { "depends_on": "eval:doc.is_internal_supplier", @@ -1653,9 +1347,7 @@ "fieldname": "represents_company", "fieldtype": "Link", "label": "Represents Company", - "options": "Company", - "show_days": 1, - "show_seconds": 1 + "options": "Company" }, { "depends_on": "eval:doc.update_stock && doc.is_internal_supplier", @@ -1667,8 +1359,6 @@ "options": "Warehouse", "print_hide": 1, "print_width": "50px", - "show_days": 1, - "show_seconds": 1, "width": "50px" }, { @@ -1680,8 +1370,6 @@ "options": "Warehouse", "print_hide": 1, "print_width": "50px", - "show_days": 1, - "show_seconds": 1, "width": "50px" }, { @@ -1705,20 +1393,19 @@ "fieldtype": "Check", "hidden": 1, "label": "Ignore Default Payment Terms Template", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 } ], "icon": "fa fa-file-text", "idx": 204, "is_submittable": 1, "links": [], - "modified": "2021-08-17 20:16:12.737743", + "modified": "2021-09-21 09:27:39.967811", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice", "name_case": "Title Case", + "naming_rule": "By \"Naming Series\" field", "owner": "Administrator", "permissions": [ { diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 62cd90ee9fc..dd4a0058938 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -15,6 +15,7 @@ from erpnext.accounts.deferred_revenue import validate_service_stop_date from erpnext.accounts.doctype.gl_entry.gl_entry import update_outstanding_amt from erpnext.accounts.doctype.sales_invoice.sales_invoice import ( check_if_return_invoice_linked_with_payment_entry, + is_overdue, unlink_inter_company_doc, update_linked_doc, validate_inter_company_party, @@ -1175,10 +1176,7 @@ class PurchaseInvoice(BuyingController): self.status = 'Draft' return - precision = self.precision("outstanding_amount") - outstanding_amount = flt(self.outstanding_amount, precision) - due_date = getdate(self.due_date) - nowdate = getdate() + outstanding_amount = flt(self.outstanding_amount, self.precision("outstanding_amount")) if not status: if self.docstatus == 2: @@ -1186,9 +1184,11 @@ class PurchaseInvoice(BuyingController): elif self.docstatus == 1: if self.is_internal_transfer(): self.status = 'Internal Transfer' - elif outstanding_amount > 0 and due_date < nowdate: + elif is_overdue(self): self.status = "Overdue" - elif outstanding_amount > 0 and due_date >= nowdate: + elif 0 < outstanding_amount < flt(self.grand_total, self.precision("grand_total")): + self.status = "Partly Paid" + elif outstanding_amount > 0 and getdate(self.due_date) >= getdate(): self.status = "Unpaid" #Check if outstanding amount is 0 due to debit note issued against invoice elif outstanding_amount <= 0 and self.is_return == 0 and frappe.db.get_value('Purchase Invoice', {'is_return': 1, 'return_against': self.name, 'docstatus': 1}): diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice_list.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice_list.js index 771b49ac629..f6ff83add8c 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice_list.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice_list.js @@ -2,28 +2,58 @@ // License: GNU General Public License v3. See license.txt // render -frappe.listview_settings['Purchase Invoice'] = { - add_fields: ["supplier", "supplier_name", "base_grand_total", "outstanding_amount", "due_date", "company", - "currency", "is_return", "release_date", "on_hold", "represents_company", "is_internal_supplier"], - get_indicator: function(doc) { - if ((flt(doc.outstanding_amount) <= 0) && doc.docstatus == 1 && doc.status == 'Debit Note Issued') { - return [__("Debit Note Issued"), "darkgrey", "outstanding_amount,<=,0"]; - } else if (flt(doc.outstanding_amount) > 0 && doc.docstatus==1) { - if(cint(doc.on_hold) && !doc.release_date) { - return [__("On Hold"), "darkgrey"]; - } else if (cint(doc.on_hold) && doc.release_date && frappe.datetime.get_diff(doc.release_date, frappe.datetime.nowdate()) > 0) { - return [__("Temporarily on Hold"), "darkgrey"]; - } else if (frappe.datetime.get_diff(doc.due_date) < 0) { - return [__("Overdue"), "red", "outstanding_amount,>,0|due_date,<,Today"]; - } else { - return [__("Unpaid"), "orange", "outstanding_amount,>,0|due_date,>=,Today"]; - } - } else if (cint(doc.is_return)) { - return [__("Return"), "gray", "is_return,=,Yes"]; - } else if (doc.company == doc.represents_company && doc.is_internal_supplier) { - return [__("Internal Transfer"), "darkgrey", "outstanding_amount,=,0"]; - } else if (flt(doc.outstanding_amount)==0 && doc.docstatus==1) { - return [__("Paid"), "green", "outstanding_amount,=,0"]; +frappe.listview_settings["Purchase Invoice"] = { + add_fields: [ + "supplier", + "supplier_name", + "base_grand_total", + "outstanding_amount", + "due_date", + "company", + "currency", + "is_return", + "release_date", + "on_hold", + "represents_company", + "is_internal_supplier", + ], + get_indicator(doc) { + if (doc.status == "Debit Note Issued") { + return [__(doc.status), "darkgrey", "status,=," + doc.status]; } - } + + if ( + flt(doc.outstanding_amount) > 0 && + doc.docstatus == 1 && + cint(doc.on_hold) + ) { + if (!doc.release_date) { + return [__("On Hold"), "darkgrey"]; + } else if ( + frappe.datetime.get_diff( + doc.release_date, + frappe.datetime.nowdate() + ) > 0 + ) { + return [__("Temporarily on Hold"), "darkgrey"]; + } + } + + const status_colors = { + "Unpaid": "orange", + "Paid": "green", + "Return": "gray", + "Overdue": "red", + "Partly Paid": "yellow", + "Internal Transfer": "darkgrey", + }; + + if (status_colors[doc.status]) { + return [ + __(doc.status), + status_colors[doc.status], + "status,=," + doc.status, + ]; + } + }, }; diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index e476439c7a2..f383b52b409 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -1651,7 +1651,7 @@ "label": "Status", "length": 30, "no_copy": 1, - "options": "\nDraft\nReturn\nCredit Note Issued\nSubmitted\nPaid\nUnpaid\nUnpaid and Discounted\nOverdue and Discounted\nOverdue\nCancelled\nInternal Transfer", + "options": "\nDraft\nReturn\nCredit Note Issued\nSubmitted\nPaid\nPartly Paid\nUnpaid\nUnpaid and Discounted\nPartly Paid and Discounted\nOverdue and Discounted\nOverdue\nCancelled\nInternal Transfer", "print_hide": 1, "read_only": 1 }, @@ -2022,11 +2022,12 @@ "link_fieldname": "consolidated_invoice" } ], - "modified": "2021-09-08 15:24:25.486499", + "modified": "2021-09-21 09:27:50.191854", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", "name_case": "Title Case", + "naming_rule": "By \"Naming Series\" field", "owner": "Administrator", "permissions": [ { diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index ca6a77a2af7..0ac4fc82792 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -1475,14 +1475,7 @@ class SalesInvoice(SellingController): self.status = 'Draft' return - precision = self.precision("outstanding_amount") - outstanding_amount = flt(self.outstanding_amount, precision) - due_date = getdate(self.due_date) - nowdate = getdate() - - discounting_status = None - if self.is_discounted: - discounting_status = get_discounting_status(self.name) + outstanding_amount = flt(self.outstanding_amount, self.precision("outstanding_amount")) if not status: if self.docstatus == 2: @@ -1490,15 +1483,13 @@ class SalesInvoice(SellingController): elif self.docstatus == 1: if self.is_internal_transfer(): self.status = 'Internal Transfer' - elif outstanding_amount > 0 and due_date < nowdate and self.is_discounted and discounting_status=='Disbursed': - self.status = "Overdue and Discounted" - elif outstanding_amount > 0 and due_date < nowdate: + elif is_overdue(self): self.status = "Overdue" - elif outstanding_amount > 0 and due_date >= nowdate and self.is_discounted and discounting_status=='Disbursed': - self.status = "Unpaid and Discounted" - elif outstanding_amount > 0 and due_date >= nowdate: + elif 0 < outstanding_amount < flt(self.grand_total, self.precision("grand_total")): + self.status = "Partly Paid" + elif outstanding_amount > 0 and getdate(self.due_date) >= getdate(): self.status = "Unpaid" - #Check if outstanding amount is 0 due to credit note issued against invoice + # Check if outstanding amount is 0 due to credit note issued against invoice elif outstanding_amount <= 0 and self.is_return == 0 and frappe.db.get_value('Sales Invoice', {'is_return': 1, 'return_against': self.name, 'docstatus': 1}): self.status = "Credit Note Issued" elif self.is_return == 1: @@ -1507,12 +1498,42 @@ class SalesInvoice(SellingController): self.status = "Paid" else: self.status = "Submitted" + + if ( + self.status in ("Unpaid", "Partly Paid", "Overdue") + and self.is_discounted + and get_discounting_status(self.name) == "Disbursed" + ): + self.status += " and Discounted" + else: self.status = "Draft" if update: self.db_set('status', self.status, update_modified = update_modified) +def is_overdue(doc): + outstanding_amount = flt(doc.outstanding_amount, doc.precision("outstanding_amount")) + + if outstanding_amount <= 0: + return + + grand_total = flt(doc.grand_total, doc.precision("grand_total")) + nowdate = getdate() + if doc.payment_schedule: + # calculate payable amount till date + payable_amount = sum( + payment.payment_amount + for payment in doc.payment_schedule + if getdate(payment.due_date) < nowdate + ) + + if (grand_total - outstanding_amount) < payable_amount: + return True + + elif getdate(doc.due_date) < nowdate: + return True + def get_discounting_status(sales_invoice): status = None diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice_list.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice_list.js index 1a01cb58f2a..06e6f511839 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice_list.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice_list.js @@ -6,18 +6,20 @@ frappe.listview_settings['Sales Invoice'] = { add_fields: ["customer", "customer_name", "base_grand_total", "outstanding_amount", "due_date", "company", "currency", "is_return"], get_indicator: function(doc) { - var status_color = { + const status_colors = { "Draft": "grey", "Unpaid": "orange", "Paid": "green", "Return": "gray", "Credit Note Issued": "gray", "Unpaid and Discounted": "orange", + "Partly Paid and Discounted": "yellow", "Overdue and Discounted": "red", "Overdue": "red", + "Partly Paid": "yellow", "Internal Transfer": "darkgrey" }; - return [__(doc.status), status_color[doc.status], "status,=,"+doc.status]; + return [__(doc.status), status_colors[doc.status], "status,=,"+doc.status]; }, right_column: "grand_total" }; diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 3720ac33bbd..619dc7b0ada 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -133,6 +133,7 @@ class TestSalesInvoice(unittest.TestCase): def test_payment_entry_unlink_against_invoice(self): from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry + si = frappe.copy_doc(test_records[0]) si.is_pos = 0 si.insert() @@ -156,6 +157,7 @@ class TestSalesInvoice(unittest.TestCase): def test_payment_entry_unlink_against_standalone_credit_note(self): from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry + si1 = create_sales_invoice(rate=1000) si2 = create_sales_invoice(rate=300) si3 = create_sales_invoice(qty=-1, rate=300, is_return=1) @@ -1646,6 +1648,7 @@ class TestSalesInvoice(unittest.TestCase): def test_credit_note(self): from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry + si = create_sales_invoice(item_code = "_Test Item", qty = (5 * -1), rate=500, is_return = 1) outstanding_amount = get_outstanding_amount(si.doctype, @@ -2269,6 +2272,54 @@ class TestSalesInvoice(unittest.TestCase): party_link.delete() frappe.db.set_value('Accounts Settings', None, 'enable_common_party_accounting', 0) + def test_payment_statuses(self): + from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry + + today = nowdate() + + # Test Overdue + si = create_sales_invoice(do_not_submit=True) + si.payment_schedule = [] + si.append("payment_schedule", { + "due_date": add_days(today, -5), + "invoice_portion": 50, + "payment_amount": si.grand_total / 2 + }) + si.append("payment_schedule", { + "due_date": add_days(today, 5), + "invoice_portion": 50, + "payment_amount": si.grand_total / 2 + }) + si.submit() + self.assertEqual(si.status, "Overdue") + + # Test payment less than due amount + pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Bank - _TC") + pe.reference_no = "1" + pe.reference_date = nowdate() + pe.paid_amount = 1 + pe.references[0].allocated_amount = pe.paid_amount + pe.submit() + si.reload() + self.assertEqual(si.status, "Overdue") + + # Test Partly Paid + pe = frappe.copy_doc(pe) + pe.paid_amount = si.grand_total / 2 + pe.references[0].allocated_amount = pe.paid_amount + pe.submit() + si.reload() + self.assertEqual(si.status, "Partly Paid") + + # Test Paid + pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Bank - _TC") + pe.reference_no = "1" + pe.reference_date = nowdate() + pe.paid_amount = si.outstanding_amount + pe.submit() + si.reload() + self.assertEqual(si.status, "Paid") + def get_sales_invoice_for_e_invoice(): si = make_sales_invoice_for_ewaybill() si.naming_series = 'INV-2020-.#####' diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 5359089698a..87d5648f6ed 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1681,14 +1681,18 @@ def get_advance_payment_entries(party_type, party, party_account, order_doctype, return list(payment_entries_against_order) + list(unallocated_payment_entries) def update_invoice_status(): - # Daily update the status of the invoices - - frappe.db.sql(""" update `tabSales Invoice` set status = 'Overdue' - where due_date < CURDATE() and docstatus = 1 and outstanding_amount > 0""") - - frappe.db.sql(""" update `tabPurchase Invoice` set status = 'Overdue' - where due_date < CURDATE() and docstatus = 1 and outstanding_amount > 0""") + """Updates status as Overdue for applicable invoices. Runs daily.""" + for doctype in ("Sales Invoice", "Purchase Invoice"): + frappe.db.sql(""" + update `tab{}` as dt set dt.status = 'Overdue' + where dt.docstatus = 1 + and dt.status != 'Overdue' + and dt.outstanding_amount > 0 + and (dt.grand_total - dt.outstanding_amount) < + (select sum(payment_amount) from `tabPayment Schedule` as ps + where ps.parent = dt.name and ps.due_date < %s) + """.format(doctype), getdate()) @frappe.whitelist() def get_payment_terms(terms_template, posting_date=None, grand_total=None, base_grand_total=None, bill_date=None): From dafe99b6e22620dcc52c8a9a26fbaf712c1ad4a4 Mon Sep 17 00:00:00 2001 From: escix Date: Sun, 26 Sep 2021 15:39:13 +1000 Subject: [PATCH 21/53] fix: holiday message reminder Minor grammatical change --- erpnext/templates/emails/holiday_reminder.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/templates/emails/holiday_reminder.html b/erpnext/templates/emails/holiday_reminder.html index e38d27bf8bc..bbef6be6728 100644 --- a/erpnext/templates/emails/holiday_reminder.html +++ b/erpnext/templates/emails/holiday_reminder.html @@ -11,6 +11,6 @@ {% endfor %} {% else %} -

You don't have no upcoming holidays this {{ frequency }}.

+

You have no upcoming holidays this {{ frequency }}.

{% endif %} {% endif %} From 64efe8bf15fb5a0fb61bd152399e069818314b5d Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Sun, 26 Sep 2021 15:46:13 +0530 Subject: [PATCH 22/53] fix: setting of gain/loss if party account is in company currency --- erpnext/controllers/accounts_controller.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index b90db054b57..2da63c834af 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -685,13 +685,17 @@ class AccountsController(TransactionBase): .format(d.reference_name, d.against_order)) def set_advance_gain_or_loss(self): - if not self.get("advances"): + if self.get('conversion_rate') == 1 or not self.get("advances"): + return + + is_purchase_invoice = self.doctype == 'Purchase Invoice' + party_account = self.credit_to if is_purchase_invoice else self.debit_to + if get_account_currency(party_account) != self.currency: return for d in self.get("advances"): advance_exchange_rate = d.ref_exchange_rate - if (d.allocated_amount and self.conversion_rate != 1 - and self.conversion_rate != advance_exchange_rate): + if (d.allocated_amount and self.conversion_rate != advance_exchange_rate): base_allocated_amount_in_ref_rate = advance_exchange_rate * d.allocated_amount base_allocated_amount_in_inv_rate = self.conversion_rate * d.allocated_amount From dd2d039ca8a83141da2038791628634c816f7718 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Sun, 26 Sep 2021 15:51:03 +0530 Subject: [PATCH 23/53] chore: hide exchange gain loss if empty --- .../payment_entry_reference/payment_entry_reference.json | 2 +- .../purchase_invoice_advance/purchase_invoice_advance.json | 4 +++- .../doctype/sales_invoice_advance/sales_invoice_advance.json | 4 +++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json b/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json index 43eb0b6e2aa..8d8d1a3f20f 100644 --- a/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json +++ b/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json @@ -103,7 +103,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-04-21 13:30:11.605388", + "modified": "2021-09-26 15:49:28.018334", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Entry Reference", diff --git a/erpnext/accounts/doctype/purchase_invoice_advance/purchase_invoice_advance.json b/erpnext/accounts/doctype/purchase_invoice_advance/purchase_invoice_advance.json index 63dfff8921f..9fcbf5c6339 100644 --- a/erpnext/accounts/doctype/purchase_invoice_advance/purchase_invoice_advance.json +++ b/erpnext/accounts/doctype/purchase_invoice_advance/purchase_invoice_advance.json @@ -97,6 +97,7 @@ "width": "100px" }, { + "depends_on": "exchange_gain_loss", "fieldname": "exchange_gain_loss", "fieldtype": "Currency", "label": "Exchange Gain/Loss", @@ -104,6 +105,7 @@ "read_only": 1 }, { + "depends_on": "exchange_gain_loss", "fieldname": "ref_exchange_rate", "fieldtype": "Float", "label": "Reference Exchange Rate", @@ -115,7 +117,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-04-20 16:26:53.820530", + "modified": "2021-09-26 15:47:28.167371", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice Advance", diff --git a/erpnext/accounts/doctype/sales_invoice_advance/sales_invoice_advance.json b/erpnext/accounts/doctype/sales_invoice_advance/sales_invoice_advance.json index 29422d68cf6..f92b57a45e1 100644 --- a/erpnext/accounts/doctype/sales_invoice_advance/sales_invoice_advance.json +++ b/erpnext/accounts/doctype/sales_invoice_advance/sales_invoice_advance.json @@ -98,6 +98,7 @@ "width": "120px" }, { + "depends_on": "exchange_gain_loss", "fieldname": "exchange_gain_loss", "fieldtype": "Currency", "label": "Exchange Gain/Loss", @@ -105,6 +106,7 @@ "read_only": 1 }, { + "depends_on": "exchange_gain_loss", "fieldname": "ref_exchange_rate", "fieldtype": "Float", "label": "Reference Exchange Rate", @@ -116,7 +118,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-06-04 20:25:49.832052", + "modified": "2021-09-26 15:47:46.911595", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice Advance", From 78ad50efc2aeaeaa8900419dba5c2e8533d0ba39 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Sun, 26 Sep 2021 16:00:28 +0530 Subject: [PATCH 24/53] fix: cost center in exchange gain loss gl entry --- erpnext/controllers/accounts_controller.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 2da63c834af..00a2c4cbd0c 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -714,7 +714,7 @@ class AccountsController(TransactionBase): gain_loss_account = frappe.db.get_value('Company', self.company, 'exchange_gain_loss_account') if not gain_loss_account: - frappe.throw(_("Please set Default Exchange Gain/Loss Account in Company {}") + frappe.throw(_("Please set default Exchange Gain/Loss Account in Company {}") .format(self.get('company'))) account_currency = get_account_currency(gain_loss_account) if account_currency != self.company_currency: @@ -733,7 +733,7 @@ class AccountsController(TransactionBase): "against": party, dr_or_cr + "_in_account_currency": abs(d.exchange_gain_loss), dr_or_cr: abs(d.exchange_gain_loss), - "cost_center": self.cost_center, + "cost_center": self.cost_center or erpnext.get_default_cost_center(self.company), "project": self.project }, item=d) ) From f8c1c739539267307c836e0b819c89ca430eee17 Mon Sep 17 00:00:00 2001 From: Saqib Date: Sun, 26 Sep 2021 16:27:56 +0530 Subject: [PATCH 25/53] fix: local variable 'fiscal_year_details' referenced before assignment (#27656) --- erpnext/regional/india/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index ce346bcc5b1..633064cf094 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -752,7 +752,7 @@ def set_salary_components(docs): def set_tax_withholding_category(company): accounts = [] - fiscal_year = None + fiscal_year_details = None abbr = frappe.get_value("Company", company, "abbr") tds_account = frappe.get_value("Account", 'TDS Payable - {0}'.format(abbr), 'name') From beebfb323ac50c72df20b6c447087fec84b9c80d Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Sun, 26 Sep 2021 16:58:55 +0530 Subject: [PATCH 26/53] patch: invalid gain loss gl entry --- erpnext/patches.txt | 1 + .../modify_invalid_gain_loss_gl_entries.py | 40 +++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index cee796efbc2..d26c92e749b 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -309,3 +309,4 @@ erpnext.patches.v13_0.update_dates_in_tax_withholding_category erpnext.patches.v14_0.update_opportunity_currency_fields erpnext.patches.v13_0.gst_fields_for_pos_invoice erpnext.patches.v13_0.create_accounting_dimensions_in_pos_doctypes +erpnext.patches.v13_0.modify_invalid_gain_loss_gl_entries \ No newline at end of file diff --git a/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py b/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py new file mode 100644 index 00000000000..4781ccb4b2e --- /dev/null +++ b/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py @@ -0,0 +1,40 @@ +from __future__ import unicode_literals + +import frappe + + +def execute(): + purchase_invoices = frappe.db.sql(""" + select + parenttype as type, parent as name + from + `tabPurchase Invoice Advance` + where + ref_exchange_rate = 1 + and docstatus = 1 + and ifnull(exchange_gain_loss, '') != '' + group by + parent + """, as_dict=1) + + sales_invoices = frappe.db.sql(""" + select + parenttype as type, parent as name + from + `tabSales Invoice Advance` + where + ref_exchange_rate = 1 + and docstatus = 1 + and ifnull(exchange_gain_loss, '') != '' + group by + parent + """, as_dict=1) + + for invoice in purchase_invoices + sales_invoices: + doc = frappe.get_doc(invoice.type, invoice.name) + doc.docstatus = 2 + doc.make_gl_entries() + for advance in doc.advances: + advance.db_set('exchange_gain_loss', 0, False) + doc.docstatus = 1 + doc.make_gl_entries() \ No newline at end of file From e6b4d33f4be5bd359134aa8fd6f7a3bb9edf35ec Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Sun, 26 Sep 2021 17:07:13 +0530 Subject: [PATCH 27/53] chore: hide exchange gain loss if empty --- .../payment_entry_reference/payment_entry_reference.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json b/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json index 8d8d1a3f20f..8961167f018 100644 --- a/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json +++ b/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json @@ -93,6 +93,7 @@ "options": "Payment Term" }, { + "depends_on": "exchange_gain_loss", "fieldname": "exchange_gain_loss", "fieldtype": "Currency", "label": "Exchange Gain/Loss", @@ -103,7 +104,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-09-26 15:49:28.018334", + "modified": "2021-09-26 17:06:55.597389", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Entry Reference", From 18e5d59d24dc1981c2e2df2facf8517988e621e0 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Sun, 26 Sep 2021 17:39:40 +0530 Subject: [PATCH 28/53] fix: unknown column 'ref_exchange_rate' --- .../patches/v13_0/modify_invalid_gain_loss_gl_entries.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py b/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py index 4781ccb4b2e..81b72cf40fb 100644 --- a/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py +++ b/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py @@ -4,6 +4,9 @@ import frappe def execute(): + frappe.reload_doc('accounts', 'doctype', 'purchase_invoice_advance') + frappe.reload_doc('accounts', 'doctype', 'sales_invoice_advance') + purchase_invoices = frappe.db.sql(""" select parenttype as type, parent as name @@ -35,6 +38,7 @@ def execute(): doc.docstatus = 2 doc.make_gl_entries() for advance in doc.advances: - advance.db_set('exchange_gain_loss', 0, False) + if advance.ref_exchange_rate == 1: + advance.db_set('exchange_gain_loss', 0, False) doc.docstatus = 1 doc.make_gl_entries() \ No newline at end of file From 30f59b09fdc1c44999d996407f4c2a283270a4ab Mon Sep 17 00:00:00 2001 From: Saqib Date: Mon, 27 Sep 2021 12:20:16 +0530 Subject: [PATCH 29/53] chore: log modified invoices --- erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py b/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py index 81b72cf40fb..bcd7f13c16f 100644 --- a/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py +++ b/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py @@ -1,5 +1,6 @@ from __future__ import unicode_literals +import json import frappe @@ -33,6 +34,9 @@ def execute(): parent """, as_dict=1) +if purchase_invoices + sales_invoices: + frappe.log_error(json.dumps(purchase_invoices + sales_invoices, indent=2), title="Patch Log") + for invoice in purchase_invoices + sales_invoices: doc = frappe.get_doc(invoice.type, invoice.name) doc.docstatus = 2 From 1f8ad7241861b16d99d9017335b2720e674d2d81 Mon Sep 17 00:00:00 2001 From: Saqib Date: Mon, 27 Sep 2021 12:21:06 +0530 Subject: [PATCH 30/53] fix: indentation --- erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py b/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py index bcd7f13c16f..fab8d59884a 100644 --- a/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py +++ b/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py @@ -34,8 +34,8 @@ def execute(): parent """, as_dict=1) -if purchase_invoices + sales_invoices: - frappe.log_error(json.dumps(purchase_invoices + sales_invoices, indent=2), title="Patch Log") + if purchase_invoices + sales_invoices: + frappe.log_error(json.dumps(purchase_invoices + sales_invoices, indent=2), title="Patch Log") for invoice in purchase_invoices + sales_invoices: doc = frappe.get_doc(invoice.type, invoice.name) From 711395db22617757df612f6bdf179c969674748b Mon Sep 17 00:00:00 2001 From: Saqib Date: Mon, 27 Sep 2021 12:26:18 +0530 Subject: [PATCH 31/53] fix: linting errors --- erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py b/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py index fab8d59884a..fa8a86437d0 100644 --- a/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py +++ b/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py @@ -1,6 +1,7 @@ from __future__ import unicode_literals import json + import frappe From f2fca59ba7370552a1bc1807c80eefb69ac10d3f Mon Sep 17 00:00:00 2001 From: Saqib Date: Mon, 27 Sep 2021 14:28:16 +0530 Subject: [PATCH 32/53] fix: cannot set custom label for 'total' field in print format (#27664) --- erpnext/templates/print_formats/includes/total.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/templates/print_formats/includes/total.html b/erpnext/templates/print_formats/includes/total.html index 81799809ba7..879203bbf25 100644 --- a/erpnext/templates/print_formats/includes/total.html +++ b/erpnext/templates/print_formats/includes/total.html @@ -7,7 +7,7 @@ {% else %}
-
+
{{ doc.get_formatted("total", doc) }}
From b68ac24cd53bf6fbd3c8da24e643346682ae5d21 Mon Sep 17 00:00:00 2001 From: Saqib Date: Mon, 27 Sep 2021 15:03:58 +0530 Subject: [PATCH 33/53] chore: add shipping address in eway bill test (#27662) --- .../sales_invoice/test_sales_invoice.py | 39 +++++++++++++++---- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 619dc7b0ada..bdd30f380f6 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -2352,6 +2352,7 @@ def make_test_address_for_ewaybill(): if not frappe.db.exists('Address', '_Test Address for Eway bill-Billing'): address = frappe.get_doc({ "address_line1": "_Test Address Line 1", + "address_line2": "_Test Address Line 2", "address_title": "_Test Address for Eway bill", "address_type": "Billing", "city": "_Test City", @@ -2373,11 +2374,12 @@ def make_test_address_for_ewaybill(): address.save() - if not frappe.db.exists('Address', '_Test Customer-Address for Eway bill-Shipping'): + if not frappe.db.exists('Address', '_Test Customer-Address for Eway bill-Billing'): address = frappe.get_doc({ "address_line1": "_Test Address Line 1", + "address_line2": "_Test Address Line 2", "address_title": "_Test Customer-Address for Eway bill", - "address_type": "Shipping", + "address_type": "Billing", "city": "_Test City", "state": "Test State", "country": "India", @@ -2397,9 +2399,34 @@ def make_test_address_for_ewaybill(): address.save() + if not frappe.db.exists('Address', '_Test Customer-Address for Eway bill-Shipping'): + address = frappe.get_doc({ + "address_line1": "_Test Address Line 1", + "address_line2": "_Test Address Line 2", + "address_title": "_Test Customer-Address for Eway bill", + "address_type": "Shipping", + "city": "_Test City", + "state": "Test State", + "country": "India", + "doctype": "Address", + "is_primary_address": 1, + "phone": "+910000000000", + "gst_state": "Maharashtra", + "gst_state_number": "27", + "pincode": "410098" + }).insert() + + address.append("links", { + "link_doctype": "Customer", + "link_name": "_Test Customer" + }) + + address.save() + if not frappe.db.exists('Address', '_Test Dispatch-Address for Eway bill-Shipping'): address = frappe.get_doc({ "address_line1": "_Test Dispatch Address Line 1", + "address_line2": "_Test Dispatch Address Line 2", "address_title": "_Test Dispatch-Address for Eway bill", "address_type": "Shipping", "city": "_Test City", @@ -2414,11 +2441,6 @@ def make_test_address_for_ewaybill(): "pincode": "1100101" }).insert() - address.append("links", { - "link_doctype": "Company", - "link_name": "_Test Company" - }) - address.save() def make_test_transporter_for_ewaybill(): @@ -2458,7 +2480,8 @@ def make_sales_invoice_for_ewaybill(): si.distance = 2000 si.company_address = "_Test Address for Eway bill-Billing" - si.customer_address = "_Test Customer-Address for Eway bill-Shipping" + si.customer_address = "_Test Customer-Address for Eway bill-Billing" + si.shipping_address_name = "_Test Customer-Address for Eway bill-Shipping" si.dispatch_address_name = "_Test Dispatch-Address for Eway bill-Shipping" si.vehicle_no = "KA12KA1234" si.gst_category = "Registered Regular" From 596cf3951ddd3fda3c2ee06a0ebd114ec30aac7a Mon Sep 17 00:00:00 2001 From: marination Date: Mon, 27 Sep 2021 15:06:46 +0530 Subject: [PATCH 34/53] fix: Batch scans get overwritten on the same row --- erpnext/public/js/controllers/transaction.js | 154 ++++++++++++------- 1 file changed, 100 insertions(+), 54 deletions(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 91b1247e6f4..f97d8cfe7f9 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -345,26 +345,14 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe } scan_barcode() { - let scan_barcode_field = this.frm.fields_dict["scan_barcode"]; - - let show_description = function(idx, exist = null) { - if (exist) { - frappe.show_alert({ - message: __('Row #{0}: Qty increased by 1', [idx]), - indicator: 'green' - }); - } else { - frappe.show_alert({ - message: __('Row #{0}: Item added', [idx]), - indicator: 'green' - }); - } - } + let me = this; if(this.frm.doc.scan_barcode) { frappe.call({ method: "erpnext.selling.page.point_of_sale.point_of_sale.search_for_serial_or_batch_or_barcode_number", - args: { search_value: this.frm.doc.scan_barcode } + args: { + search_value: this.frm.doc.scan_barcode + } }).then(r => { const data = r && r.message; if (!data || Object.keys(data).length === 0) { @@ -375,49 +363,107 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe return; } - let cur_grid = this.frm.fields_dict.items.grid; - - let row_to_modify = null; - const existing_item_row = this.frm.doc.items.find(d => d.item_code === data.item_code); - const blank_item_row = this.frm.doc.items.find(d => !d.item_code); - - if (existing_item_row) { - row_to_modify = existing_item_row; - } else if (blank_item_row) { - row_to_modify = blank_item_row; - } - - if (!row_to_modify) { - // add new row - row_to_modify = frappe.model.add_child(this.frm.doc, cur_grid.doctype, 'items'); - } - - show_description(row_to_modify.idx, row_to_modify.item_code); - - this.frm.from_barcode = this.frm.from_barcode ? this.frm.from_barcode + 1 : 1; - frappe.model.set_value(row_to_modify.doctype, row_to_modify.name, { - item_code: data.item_code, - qty: (row_to_modify.qty || 0) + 1 - }); - - ['serial_no', 'batch_no', 'barcode'].forEach(field => { - if (data[field] && frappe.meta.has_field(row_to_modify.doctype, field)) { - - let value = (row_to_modify[field] && field === "serial_no") - ? row_to_modify[field] + '\n' + data[field] : data[field]; - - frappe.model.set_value(row_to_modify.doctype, - row_to_modify.name, field, value); - } - }); - - scan_barcode_field.set_value(''); - refresh_field("items"); + me.modify_table_after_scan(data); }); } return false; } + modify_table_after_scan(data) { + let scan_barcode_field = this.frm.fields_dict["scan_barcode"]; + let cur_grid = this.frm.fields_dict.items.grid; + let row_to_modify = null; + + // Check if batch is scanned and table has batch no field + let batch_no_scan = Boolean(data.batch_no) && frappe.meta.has_field(cur_grid.doctype, "batch_no"); + + if (batch_no_scan) { + let duplicate = this.check_duplicate_batch_scan(data.batch_no); + if (duplicate) { + scan_barcode_field.set_value(''); + return; + } + } else { + // serial or barcode scan + row_to_modify = this.get_row_to_modify_on_scan(row_to_modify, data); + } + + if (!row_to_modify || batch_no_scan) { + // add new row if new item scanned or batch is scanned + row_to_modify = frappe.model.add_child(this.frm.doc, cur_grid.doctype, 'items'); + } + + this.show_scan_message(row_to_modify.idx, row_to_modify.item_code); + this.set_scanned_values(row_to_modify, data, scan_barcode_field); + } + + set_scanned_values(row_to_modify, data, scan_barcode_field) { + // increase qty and set scanned value and item in row + this.frm.from_barcode = this.frm.from_barcode ? this.frm.from_barcode + 1 : 1; + frappe.model.set_value(row_to_modify.doctype, row_to_modify.name, { + item_code: data.item_code, + qty: (row_to_modify.qty || 0) + 1 + }); + + ['serial_no', 'batch_no', 'barcode'].forEach(field => { + if (data[field] && frappe.meta.has_field(row_to_modify.doctype, field)) { + let is_serial_no = row_to_modify[field] && field === "serial_no"; + + let value = data[field]; + if (is_serial_no) { + value = row_to_modify[field] + '\n' + data[field]; + } + + frappe.model.set_value(row_to_modify.doctype, row_to_modify.name, field, value); + } + }); + + scan_barcode_field.set_value(''); + refresh_field("items"); + } + + get_row_to_modify_on_scan(row_to_modify, data) { + // get an existing item row to increment or blank row to modify + const existing_item_row = this.frm.doc.items.find(d => d.item_code === data.item_code); + const blank_item_row = this.frm.doc.items.find(d => !d.item_code); + + if (existing_item_row) { + row_to_modify = existing_item_row; + } else if (blank_item_row) { + row_to_modify = blank_item_row; + } + + return row_to_modify; + } + + check_duplicate_batch_scan(batch_no) { + // ignore scan if batch already exists in table + const existing_batch_row = this.frm.doc.items.find(d => d.batch_no === batch_no); + + if (existing_batch_row) { + frappe.show_alert({ + message: __('Batch {0} already added', [batch_no]), + indicator: 'orange' + }); + return true; + } + return false; + } + + show_scan_message (idx, exist = null) { + if (exist) { + frappe.show_alert({ + message: __('Row #{0}: Qty increased by 1', [idx]), + indicator: 'green' + }); + } else { + frappe.show_alert({ + message: __('Row #{0}: Item added', [idx]), + indicator: 'green' + }); + } + } + apply_default_taxes() { var me = this; var taxes_and_charges_field = frappe.meta.get_docfield(me.frm.doc.doctype, "taxes_and_charges", From cc5dd5c67d9a37d1027527ef35d24cb9942a4d6f Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Mon, 27 Sep 2021 21:43:20 +0530 Subject: [PATCH 35/53] feat: TDS deduction using journal entry and other fixes (#27451) * fix: TDS deduction using journal entry * fix: Multi category application against single supplier * refactor: TDS payable monthly report * fix: Server side handling for default tax withholding category * fix: Supplier filter for Journal Entry * refactor: TDS computation summary report --- .../doctype/journal_entry/journal_entry.json | 20 +- .../doctype/journal_entry/journal_entry.py | 76 ++++++- .../purchase_invoice/purchase_invoice.py | 6 + .../tax_withholding_category.py | 32 ++- .../test_tax_withholding_category.py | 62 +++++- .../tds_computation_summary.json | 7 +- .../tds_computation_summary.py | 112 +++------- .../tds_payable_monthly.js | 83 +------ .../tds_payable_monthly.json | 4 +- .../tds_payable_monthly.py | 208 +++++++----------- 10 files changed, 317 insertions(+), 293 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.json b/erpnext/accounts/doctype/journal_entry/journal_entry.json index b7bbb74ce94..20678d787b4 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.json +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.json @@ -13,10 +13,12 @@ "voucher_type", "naming_series", "finance_book", + "tax_withholding_category", "column_break1", "from_template", "company", "posting_date", + "apply_tds", "2_add_edit_gl_entries", "accounts", "section_break99", @@ -498,16 +500,32 @@ "options": "Journal Entry Template", "print_hide": 1, "report_hide": 1 + }, + { + "depends_on": "eval:doc.apply_tds", + "fieldname": "tax_withholding_category", + "fieldtype": "Link", + "label": "Tax Withholding Category", + "mandatory_depends_on": "eval:doc.apply_tds", + "options": "Tax Withholding Category" + }, + { + "default": "0", + "depends_on": "eval:['Credit Note', 'Debit Note'].includes(doc.voucher_type)", + "fieldname": "apply_tds", + "fieldtype": "Check", + "label": "Apply Tax Withholding Amount " } ], "icon": "fa fa-file-text", "idx": 176, "is_submittable": 1, "links": [], - "modified": "2020-10-30 13:56:01.121995", + "modified": "2021-09-09 15:31:14.484029", "modified_by": "Administrator", "module": "Accounts", "name": "Journal Entry", + "naming_rule": "By \"Naming Series\" field", "owner": "Administrator", "permissions": [ { diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index 24368f04419..e568a827617 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -15,6 +15,9 @@ from erpnext.accounts.deferred_revenue import get_deferred_booking_accounts from erpnext.accounts.doctype.invoice_discounting.invoice_discounting import ( get_party_account_based_on_invoice_discounting, ) +from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import ( + get_party_tax_withholding_details, +) from erpnext.accounts.party import get_party_account from erpnext.accounts.utils import ( check_if_stock_and_account_balance_synced, @@ -57,7 +60,8 @@ class JournalEntry(AccountsController): self.validate_against_jv() self.validate_reference_doc() - self.set_against_account() + if self.docstatus == 0: + self.set_against_account() self.create_remarks() self.set_print_format_fields() self.validate_expense_claim() @@ -66,6 +70,10 @@ class JournalEntry(AccountsController): self.set_account_and_party_balance() self.validate_inter_company_accounts() self.validate_stock_accounts() + + if self.docstatus == 0: + self.apply_tax_withholding() + if not self.title: self.title = self.get_title() @@ -139,6 +147,72 @@ class JournalEntry(AccountsController): frappe.throw(_("Account: {0} can only be updated via Stock Transactions") .format(account), StockAccountInvalidTransaction) + def apply_tax_withholding(self): + from erpnext.accounts.report.general_ledger.general_ledger import get_account_type_map + + if not self.apply_tds or self.voucher_type not in ('Debit Note', 'Credit Note'): + return + + parties = [d.party for d in self.get('accounts') if d.party] + parties = list(set(parties)) + + if len(parties) > 1: + frappe.throw(_("Cannot apply TDS against multiple parties in one entry")) + + account_type_map = get_account_type_map(self.company) + party_type = 'supplier' if self.voucher_type == 'Credit Note' else 'customer' + doctype = 'Purchase Invoice' if self.voucher_type == 'Credit Note' else 'Sales Invoice' + debit_or_credit = 'debit_in_account_currency' if self.voucher_type == 'Credit Note' else 'credit_in_account_currency' + rev_debit_or_credit = 'credit_in_account_currency' if debit_or_credit == 'debit_in_account_currency' else 'debit_in_account_currency' + + party_account = get_party_account(party_type.title(), parties[0], self.company) + + net_total = sum(d.get(debit_or_credit) for d in self.get('accounts') if account_type_map.get(d.account) + not in ('Tax', 'Chargeable')) + + party_amount = sum(d.get(rev_debit_or_credit) for d in self.get('accounts') if d.account == party_account) + + inv = frappe._dict({ + party_type: parties[0], + 'doctype': doctype, + 'company': self.company, + 'posting_date': self.posting_date, + 'net_total': net_total + }) + + tax_withholding_details = get_party_tax_withholding_details(inv, self.tax_withholding_category) + + if not tax_withholding_details: + return + + accounts = [] + for d in self.get('accounts'): + if d.get('account') == tax_withholding_details.get("account_head"): + d.update({ + 'account': tax_withholding_details.get("account_head"), + debit_or_credit: tax_withholding_details.get('tax_amount') + }) + + accounts.append(d.get('account')) + + if d.get('account') == party_account: + d.update({ + rev_debit_or_credit: party_amount - tax_withholding_details.get('tax_amount') + }) + + if not accounts or tax_withholding_details.get("account_head") not in accounts: + self.append("accounts", { + 'account': tax_withholding_details.get("account_head"), + rev_debit_or_credit: tax_withholding_details.get('tax_amount'), + 'against_account': parties[0] + }) + + to_remove = [d for d in self.get('accounts') + if not d.get(rev_debit_or_credit) and d.account == tax_withholding_details.get("account_head")] + + for d in to_remove: + self.remove(d) + def update_inter_company_jv(self): if self.voucher_type == "Inter Company Journal Entry" and self.inter_company_journal_entry_reference: frappe.db.set_value("Journal Entry", self.inter_company_journal_entry_reference,\ diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index dd4a0058938..1c9943fd224 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -1146,6 +1146,12 @@ class PurchaseInvoice(BuyingController): if not self.apply_tds: return + if self.apply_tds and not self.get('tax_withholding_category'): + self.tax_withholding_category = frappe.db.get_value('Supplier', self.supplier, 'tax_withholding_category') + + if not self.tax_withholding_category: + return + tax_withholding_details = get_party_tax_withholding_details(self, self.tax_withholding_category) if not tax_withholding_details: diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py index fa4ea218e90..16ef5fc9745 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -100,6 +100,7 @@ def get_tax_withholding_details(tax_withholding_category, posting_date, company) for account_detail in tax_withholding.accounts: if company == account_detail.company: return frappe._dict({ + "tax_withholding_category": tax_withholding_category, "account_head": account_detail.account, "rate": tax_rate_detail.tax_withholding_rate, "from_date": tax_rate_detail.from_date, @@ -206,18 +207,39 @@ def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=N def get_invoice_vouchers(parties, tax_details, company, party_type='Supplier'): dr_or_cr = 'credit' if party_type == 'Supplier' else 'debit' + doctype = 'Purchase Invoice' if party_type == 'Supplier' else 'Sales Invoice' filters = { - dr_or_cr: ['>', 0], 'company': company, - 'party_type': party_type, - 'party': ['in', parties], + frappe.scrub(party_type): ['in', parties], 'posting_date': ['between', (tax_details.from_date, tax_details.to_date)], 'is_opening': 'No', - 'is_cancelled': 0 + 'docstatus': 1 } - return frappe.get_all('GL Entry', filters=filters, distinct=1, pluck="voucher_no") or [""] + if not tax_details.get('consider_party_ledger_amount') and doctype != "Sales Invoice": + filters.update({ + 'apply_tds': 1, + 'tax_withholding_category': tax_details.get('tax_withholding_category') + }) + + invoices = frappe.get_all(doctype, filters=filters, pluck="name") or [""] + + journal_entries = frappe.db.sql(""" + SELECT j.name + FROM `tabJournal Entry` j, `tabJournal Entry Account` ja + WHERE + j.docstatus = 1 + AND j.is_opening = 'No' + AND j.posting_date between %s and %s + AND ja.{dr_or_cr} > 0 + AND ja.party in %s + """.format(dr_or_cr=dr_or_cr), (tax_details.from_date, tax_details.to_date, tuple(parties)), as_list=1) + + if journal_entries: + journal_entries = journal_entries[0] + + return invoices + journal_entries def get_advance_vouchers(parties, company=None, from_date=None, to_date=None, party_type='Supplier'): # for advance vouchers, debit and credit is reversed diff --git a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py index 8a88d798d8b..84b364b3427 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py @@ -176,6 +176,29 @@ class TestTaxWithholdingCategory(unittest.TestCase): for d in invoices: d.cancel() + def test_multi_category_single_supplier(self): + frappe.db.set_value("Supplier", "Test TDS Supplier5", "tax_withholding_category", "Test Service Category") + invoices = [] + + pi = create_purchase_invoice(supplier = "Test TDS Supplier5", rate = 500, do_not_save=True) + pi.tax_withholding_category = "Test Service Category" + pi.save() + pi.submit() + invoices.append(pi) + + # Second Invoice will apply TDS checked + pi1 = create_purchase_invoice(supplier = "Test TDS Supplier5", rate = 2500, do_not_save=True) + pi1.tax_withholding_category = "Test Goods Category" + pi1.save() + pi1.submit() + invoices.append(pi1) + + self.assertEqual(pi1.taxes[0].tax_amount, 250) + + #delete invoices to avoid clashing + for d in invoices: + d.cancel() + def cancel_invoices(): purchase_invoices = frappe.get_all("Purchase Invoice", { 'supplier': ['in', ['Test TDS Supplier', 'Test TDS Supplier1', 'Test TDS Supplier2']], @@ -251,7 +274,8 @@ def create_sales_invoice(**args): def create_records(): # create a new suppliers - for name in ['Test TDS Supplier', 'Test TDS Supplier1', 'Test TDS Supplier2', 'Test TDS Supplier3', 'Test TDS Supplier4']: + for name in ['Test TDS Supplier', 'Test TDS Supplier1', 'Test TDS Supplier2', 'Test TDS Supplier3', + 'Test TDS Supplier4', 'Test TDS Supplier5']: if frappe.db.exists('Supplier', name): continue @@ -390,3 +414,39 @@ def create_tax_with_holding_category(): 'account': 'TDS - _TC' }] }).insert() + + if not frappe.db.exists("Tax Withholding Category", "Test Service Category"): + frappe.get_doc({ + "doctype": "Tax Withholding Category", + "name": "Test Service Category", + "category_name": "Test Service Category", + "rates": [{ + 'from_date': fiscal_year[1], + 'to_date': fiscal_year[2], + 'tax_withholding_rate': 10, + 'single_threshold': 2000, + 'cumulative_threshold': 2000 + }], + "accounts": [{ + 'company': '_Test Company', + 'account': 'TDS - _TC' + }] + }).insert() + + if not frappe.db.exists("Tax Withholding Category", "Test Goods Category"): + frappe.get_doc({ + "doctype": "Tax Withholding Category", + "name": "Test Goods Category", + "category_name": "Test Goods Category", + "rates": [{ + 'from_date': fiscal_year[1], + 'to_date': fiscal_year[2], + 'tax_withholding_rate': 10, + 'single_threshold': 2000, + 'cumulative_threshold': 2000 + }], + "accounts": [{ + 'company': '_Test Company', + 'account': 'TDS - _TC' + }] + }).insert() diff --git a/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.json b/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.json index dfc4b18e07d..91f079824d2 100644 --- a/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.json +++ b/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.json @@ -1,12 +1,15 @@ { - "add_total_row": 0, + "add_total_row": 1, + "columns": [], "creation": "2018-08-21 11:25:00.551823", + "disable_prepared_report": 0, "disabled": 0, "docstatus": 0, "doctype": "Report", + "filters": [], "idx": 0, "is_standard": "Yes", - "modified": "2018-09-21 11:25:00.551823", + "modified": "2021-09-20 17:43:39.518851", "modified_by": "Administrator", "module": "Accounts", "name": "TDS Computation Summary", diff --git a/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py b/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py index c4a8c7a899a..536df1f1a17 100644 --- a/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py +++ b/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py @@ -2,11 +2,10 @@ from __future__ import unicode_literals import frappe from frappe import _ -from frappe.utils import flt -from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import ( - get_advance_vouchers, - get_debit_note_amount, +from erpnext.accounts.report.tds_payable_monthly.tds_payable_monthly import ( + get_result, + get_tds_docs, ) from erpnext.accounts.utils import get_fiscal_year @@ -17,9 +16,12 @@ def execute(filters=None): filters.naming_series = frappe.db.get_single_value('Buying Settings', 'supp_master_name') columns = get_columns(filters) - res = get_result(filters) + tds_docs, tds_accounts, tax_category_map = get_tds_docs(filters) - return columns, res + res = get_result(filters, tds_docs, tds_accounts, tax_category_map) + final_result = group_by_supplier_and_category(res) + + return columns, final_result def validate_filters(filters): ''' Validate if dates are properly set and lie in the same fiscal year''' @@ -33,81 +35,39 @@ def validate_filters(filters): filters["fiscal_year"] = from_year -def get_result(filters): - # if no supplier selected, fetch data for all tds applicable supplier - # else fetch relevant data for selected supplier - pan = "pan" if frappe.db.has_column("Supplier", "pan") else "tax_id" - fields = ["name", pan+" as pan", "tax_withholding_category", "supplier_type", "supplier_name"] +def group_by_supplier_and_category(data): + supplier_category_wise_map = {} - if filters.supplier: - filters.supplier = frappe.db.get_list('Supplier', - {"name": filters.supplier}, fields) - else: - filters.supplier = frappe.db.get_list('Supplier', - {"tax_withholding_category": ["!=", ""]}, fields) + for row in data: + supplier_category_wise_map.setdefault((row.get('supplier'), row.get('section_code')), { + 'pan': row.get('pan'), + 'supplier': row.get('supplier'), + 'supplier_name': row.get('supplier_name'), + 'section_code': row.get('section_code'), + 'entity_type': row.get('entity_type'), + 'tds_rate': row.get('tds_rate'), + 'total_amount_credited': 0.0, + 'tds_deducted': 0.0 + }) + supplier_category_wise_map.get((row.get('supplier'), row.get('section_code')))['total_amount_credited'] += \ + row.get('total_amount_credited', 0.0) + + supplier_category_wise_map.get((row.get('supplier'), row.get('section_code')))['tds_deducted'] += \ + row.get('tds_deducted', 0.0) + + final_result = get_final_result(supplier_category_wise_map) + + return final_result + + +def get_final_result(supplier_category_wise_map): out = [] - for supplier in filters.supplier: - tds = frappe.get_doc("Tax Withholding Category", supplier.tax_withholding_category) - rate = [d.tax_withholding_rate for d in tds.rates if d.fiscal_year == filters.fiscal_year] - - if rate: - rate = rate[0] - - try: - account = [d.account for d in tds.accounts if d.company == filters.company][0] - - except IndexError: - account = [] - total_invoiced_amount, tds_deducted = get_invoice_and_tds_amount(supplier.name, account, - filters.company, filters.from_date, filters.to_date, filters.fiscal_year) - - if total_invoiced_amount or tds_deducted: - row = [supplier.pan, supplier.name] - - if filters.naming_series == 'Naming Series': - row.append(supplier.supplier_name) - - row.extend([tds.name, supplier.supplier_type, rate, total_invoiced_amount, tds_deducted]) - out.append(row) + for key, value in supplier_category_wise_map.items(): + out.append(value) return out -def get_invoice_and_tds_amount(supplier, account, company, from_date, to_date, fiscal_year): - ''' calculate total invoice amount and total tds deducted for given supplier ''' - - entries = frappe.db.sql(""" - select voucher_no, credit - from `tabGL Entry` - where party in (%s) and credit > 0 - and company=%s and is_cancelled = 0 - and posting_date between %s and %s - """, (supplier, company, from_date, to_date), as_dict=1) - - supplier_credit_amount = flt(sum(d.credit for d in entries)) - - vouchers = [d.voucher_no for d in entries] - vouchers += get_advance_vouchers([supplier], company=company, - from_date=from_date, to_date=to_date) - - tds_deducted = 0 - if vouchers: - tds_deducted = flt(frappe.db.sql(""" - select sum(credit) - from `tabGL Entry` - where account=%s and posting_date between %s and %s - and company=%s and credit > 0 and voucher_no in ({0}) - """.format(', '.join("'%s'" % d for d in vouchers)), - (account, from_date, to_date, company))[0][0]) - - date_range_filter = [fiscal_year, from_date, to_date] - - debit_note_amount = get_debit_note_amount([supplier], date_range_filter, company=company) - - total_invoiced_amount = supplier_credit_amount + tds_deducted - debit_note_amount - - return total_invoiced_amount, tds_deducted - def get_columns(filters): columns = [ { @@ -149,7 +109,7 @@ def get_columns(filters): { "label": _("TDS Rate %"), "fieldname": "tds_rate", - "fieldtype": "Float", + "fieldtype": "Percent", "width": 90 }, { diff --git a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.js b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.js index 72de318a48c..ff2aa306017 100644 --- a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.js +++ b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.js @@ -16,69 +16,6 @@ frappe.query_reports["TDS Payable Monthly"] = { "label": __("Supplier"), "fieldtype": "Link", "options": "Supplier", - "get_query": function() { - return { - "filters": { - "tax_withholding_category": ["!=", ""], - } - } - }, - on_change: function() { - frappe.query_report.set_filter_value("purchase_invoice", ""); - frappe.query_report.refresh(); - } - }, - { - "fieldname":"purchase_invoice", - "label": __("Purchase Invoice"), - "fieldtype": "Link", - "options": "Purchase Invoice", - "get_query": function() { - return { - "filters": { - "name": ["in", frappe.query_report.invoices] - } - } - }, - on_change: function() { - let supplier = frappe.query_report.get_filter_value('supplier'); - if(!supplier) return; // return if no supplier selected - - // filter invoices based on selected supplier - let invoices = []; - frappe.query_report.invoice_data.map(d => { - if(d.supplier==supplier) - invoices.push(d.name) - }); - frappe.query_report.invoices = invoices; - frappe.query_report.refresh(); - } - }, - { - "fieldname":"purchase_order", - "label": __("Purchase Order"), - "fieldtype": "Link", - "options": "Purchase Order", - "get_query": function() { - return { - "filters": { - "name": ["in", frappe.query_report.invoices] - } - } - }, - on_change: function() { - let supplier = frappe.query_report.get_filter_value('supplier'); - if(!supplier) return; // return if no supplier selected - - // filter invoices based on selected supplier - let invoices = []; - frappe.query_report.invoice_data.map(d => { - if(d.supplier==supplier) - invoices.push(d.name) - }); - frappe.query_report.invoices = invoices; - frappe.query_report.refresh(); - } }, { "fieldname":"from_date", @@ -96,23 +33,5 @@ frappe.query_reports["TDS Payable Monthly"] = { "reqd": 1, "width": "60px" } - ], - - onload: function(report) { - // fetch all tds applied invoices - frappe.call({ - "method": "erpnext.accounts.report.tds_payable_monthly.tds_payable_monthly.get_tds_invoices_and_orders", - callback: function(r) { - let invoices = []; - - r.message.map(d => { - invoices.push(d.name); - }); - - report["invoice_data"] = r.message.invoices; - report["invoices"] = invoices; - - } - }); - } + ] } diff --git a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.json b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.json index 557a62d8fea..4d555bd8ba1 100644 --- a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.json +++ b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.json @@ -1,13 +1,15 @@ { "add_total_row": 1, + "columns": [], "creation": "2018-08-21 11:32:30.874923", "disable_prepared_report": 0, "disabled": 0, "docstatus": 0, "doctype": "Report", + "filters": [], "idx": 0, "is_standard": "Yes", - "modified": "2019-09-24 13:46:16.473711", + "modified": "2021-09-20 12:05:50.387572", "modified_by": "Administrator", "module": "Accounts", "name": "TDS Payable Monthly", diff --git a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py index 9e1382b9222..621b697aca4 100644 --- a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py +++ b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py @@ -8,19 +8,12 @@ from frappe import _ def execute(filters=None): - filters["invoices"] = frappe.cache().hget("invoices", frappe.session.user) validate_filters(filters) - set_filters(filters) - - # TDS payment entries - payment_entries = get_payment_entires(filters) + tds_docs, tds_accounts, tax_category_map = get_tds_docs(filters) columns = get_columns(filters) - if not filters.get("invoices"): - return columns, [] - - res = get_result(filters, payment_entries) + res = get_result(filters, tds_docs, tds_accounts, tax_category_map) return columns, res def validate_filters(filters): @@ -28,109 +21,59 @@ def validate_filters(filters): if filters.from_date > filters.to_date: frappe.throw(_("From Date must be before To Date")) -def set_filters(filters): - invoices = [] - - if not filters.get("invoices"): - filters["invoices"] = get_tds_invoices_and_orders() - - if filters.supplier and filters.purchase_invoice: - for d in filters["invoices"]: - if d.name == filters.purchase_invoice and d.supplier == filters.supplier: - invoices.append(d) - elif filters.supplier and not filters.purchase_invoice: - for d in filters["invoices"]: - if d.supplier == filters.supplier: - invoices.append(d) - elif filters.purchase_invoice and not filters.supplier: - for d in filters["invoices"]: - if d.name == filters.purchase_invoice: - invoices.append(d) - elif filters.supplier and filters.purchase_order: - for d in filters.get("invoices"): - if d.name == filters.purchase_order and d.supplier == filters.supplier: - invoices.append(d) - elif filters.supplier and not filters.purchase_order: - for d in filters.get("invoices"): - if d.supplier == filters.supplier: - invoices.append(d) - elif filters.purchase_order and not filters.supplier: - for d in filters.get("invoices"): - if d.name == filters.purchase_order: - invoices.append(d) - - filters["invoices"] = invoices if invoices else filters["invoices"] - filters.naming_series = frappe.db.get_single_value('Buying Settings', 'supp_master_name') - - #print(filters.get('invoices')) - -def get_result(filters, payment_entries): - supplier_map, tds_docs = get_supplier_map(filters, payment_entries) - documents = [d.get('name') for d in filters.get('invoices')] + [d.get('name') for d in payment_entries] - - gle_map = get_gle_map(filters, documents) +def get_result(filters, tds_docs, tds_accounts, tax_category_map): + supplier_map = get_supplier_pan_map() + tax_rate_map = get_tax_rate_map(filters) + gle_map = get_gle_map(filters, tds_docs) out = [] - for d in gle_map: + for name, details in gle_map.items(): tds_deducted, total_amount_credited = 0, 0 - supplier = supplier_map[d] + tax_withholding_category = tax_category_map.get(name) + rate = tax_rate_map.get(tax_withholding_category) - tds_doc = tds_docs[supplier.tax_withholding_category] - account_list = [i.account for i in tds_doc.accounts if i.company == filters.company] + for entry in details: + supplier = entry.party or entry.against + posting_date = entry.posting_date + voucher_type = entry.voucher_type - if account_list: - account = account_list[0] + if entry.account in tds_accounts: + tds_deducted += (entry.credit - entry.debit) - for k in gle_map[d]: - if k.party == supplier_map[d] and k.credit > 0: - total_amount_credited += (k.credit - k.debit) - elif account_list and k.account == account and (k.credit - k.debit) > 0: - tds_deducted = (k.credit - k.debit) - total_amount_credited += (k.credit - k.debit) - voucher_type = k.voucher_type + total_amount_credited += (entry.credit - entry.debit) - rate = [i.tax_withholding_rate for i in tds_doc.rates - if i.fiscal_year == gle_map[d][0].fiscal_year] - - if rate and len(rate) > 0 and tds_deducted: - rate = rate[0] - - row = [supplier.pan, supplier.name] + if rate and tds_deducted: + row = { + 'pan' if frappe.db.has_column('Supplier', 'pan') else 'tax_id': supplier_map.get(supplier).pan, + 'supplier': supplier_map.get(supplier).name + } if filters.naming_series == 'Naming Series': - row.append(supplier.supplier_name) + row.update({'supplier_name': supplier_map.get(supplier).supplier_name}) + + row.update({ + 'section_code': tax_withholding_category, + 'entity_type': supplier_map.get(supplier).supplier_type, + 'tds_rate': rate, + 'total_amount_credited': total_amount_credited, + 'tds_deducted': tds_deducted, + 'transaction_date': posting_date, + 'transaction_type': voucher_type, + 'ref_no': name + }) - row.extend([tds_doc.name, supplier.supplier_type, rate, total_amount_credited, - tds_deducted, gle_map[d][0].posting_date, voucher_type, d]) out.append(row) return out -def get_supplier_map(filters, payment_entries): - # create a supplier_map of the form {"purchase_invoice": {supplier_name, pan, tds_name}} - # pre-fetch all distinct applicable tds docs - supplier_map, tds_docs = {}, {} - pan = "pan" if frappe.db.has_column("Supplier", "pan") else "tax_id" - supplier_list = [d.supplier for d in filters["invoices"]] +def get_supplier_pan_map(): + supplier_map = frappe._dict() + suppliers = frappe.db.get_all('Supplier', fields=['name', 'pan', 'supplier_type', 'supplier_name']) - supplier_detail = frappe.db.get_all('Supplier', - {"name": ["in", supplier_list]}, - ["tax_withholding_category", "name", pan+" as pan", "supplier_type", "supplier_name"]) + for d in suppliers: + supplier_map[d.name] = d - for d in filters["invoices"]: - supplier_map[d.get("name")] = [k for k in supplier_detail - if k.name == d.get("supplier")][0] - - for d in payment_entries: - supplier_map[d.get("name")] = [k for k in supplier_detail - if k.name == d.get("supplier")][0] - - for d in supplier_detail: - if d.get("tax_withholding_category") not in tds_docs: - tds_docs[d.get("tax_withholding_category")] = \ - frappe.get_doc("Tax Withholding Category", d.get("tax_withholding_category")) - - return supplier_map, tds_docs + return supplier_map def get_gle_map(filters, documents): # create gle_map of the form @@ -140,10 +83,9 @@ def get_gle_map(filters, documents): gle = frappe.db.get_all('GL Entry', { "voucher_no": ["in", documents], - 'is_cancelled': 0, - 'posting_date': ("between", [filters.get('from_date'), filters.get('to_date')]), + "credit": (">", 0) }, - ["fiscal_year", "credit", "debit", "account", "voucher_no", "posting_date", "voucher_type"], + ["credit", "debit", "account", "voucher_no", "posting_date", "voucher_type", "against", "party"], ) for d in gle: @@ -233,39 +175,57 @@ def get_columns(filters): return columns -def get_payment_entires(filters): - filter_dict = { - 'posting_date': ("between", [filters.get('from_date'), filters.get('to_date')]), - 'party_type': 'Supplier', - 'apply_tax_withholding_amount': 1 +def get_tds_docs(filters): + tds_documents = [] + purchase_invoices = [] + payment_entries = [] + journal_entries = [] + tax_category_map = {} + + tds_accounts = frappe.get_all("Tax Withholding Account", {'company': filters.get('company')}, + pluck="account") + + query_filters = { + "credit": ('>', 0), + "account": ("in", tds_accounts), + "posting_date": ("between", [filters.get("from_date"), filters.get("to_date")]), + "is_cancelled": 0 } - if filters.get('purchase_invoice') or filters.get('purchase_order'): - parent = frappe.db.get_all('Payment Entry Reference', - {'reference_name': ('in', [d.get('name') for d in filters.get('invoices')])}, ['parent']) + if filters.get('supplier'): + query_filters.update({'against': filters.get('supplier')}) - filter_dict.update({'name': ('in', [d.get('parent') for d in parent])}) + tds_docs = frappe.get_all("GL Entry", query_filters, ["voucher_no", "voucher_type", "against", "party"]) - payment_entries = frappe.get_all('Payment Entry', fields=['name', 'party_name as supplier'], - filters=filter_dict) + for d in tds_docs: + if d.voucher_type == "Purchase Invoice": + purchase_invoices.append(d.voucher_no) + elif d.voucher_type == "Payment Entry": + payment_entries.append(d.voucher_no) + elif d.voucher_type == "Journal Entry": + journal_entries.append(d.voucher_no) - return payment_entries + tds_documents.append(d.voucher_no) -@frappe.whitelist() -def get_tds_invoices_and_orders(): - # fetch tds applicable supplier and fetch invoices for these suppliers - suppliers = [d.name for d in frappe.db.get_list("Supplier", - {"tax_withholding_category": ["!=", ""]}, ["name"])] + if purchase_invoices: + get_tax_category_map(purchase_invoices, 'Purchase Invoice', tax_category_map) - invoices = frappe.db.get_list("Purchase Invoice", - {"supplier": ["in", suppliers]}, ["name", "supplier"]) + if payment_entries: + get_tax_category_map(payment_entries, 'Payment Entry', tax_category_map) - orders = frappe.db.get_list("Purchase Order", - {"supplier": ["in", suppliers]}, ["name", "supplier"]) + if journal_entries: + get_tax_category_map(journal_entries, 'Journal Entry', tax_category_map) - invoices = invoices + orders - invoices = [d for d in invoices if d.supplier] + return tds_documents, tds_accounts, tax_category_map - frappe.cache().hset("invoices", frappe.session.user, invoices) +def get_tax_category_map(vouchers, doctype, tax_category_map): + tax_category_map.update(frappe._dict(frappe.get_all(doctype, + filters = {'name': ('in', vouchers)}, fields=['name', 'tax_withholding_category'], as_list=1))) - return invoices +def get_tax_rate_map(filters): + rate_map = frappe.get_all('Tax Withholding Rate', filters={ + 'from_date': ('<=', filters.get('from_date')), + 'to_date': ('>=', filters.get('to_date')) + }, fields=['parent', 'tax_withholding_rate'], as_list=1) + + return frappe._dict(rate_map) \ No newline at end of file From 5c249decbb79c5b9436aa7a0f5ca6670993e143e Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Mon, 27 Sep 2021 22:34:27 +0530 Subject: [PATCH 36/53] fix(ux): added exception of template item in filters (#27560) --- erpnext/manufacturing/doctype/bom/bom.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 232e3a0b0ff..7cfec974fc8 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -1133,7 +1133,8 @@ def item_query(doctype, txt, searchfield, start, page_len, filters): query_filters["has_variants"] = 0 if filters and filters.get("is_stock_item"): - query_filters["is_stock_item"] = 1 + or_cond_filters["is_stock_item"] = 1 + or_cond_filters["has_variants"] = 1 return frappe.get_list("Item", fields = fields, filters=query_filters, From b91333afddae6b8302ad1429e420ce29b9c347ba Mon Sep 17 00:00:00 2001 From: Alan <2.alan.tom@gmail.com> Date: Tue, 28 Sep 2021 12:11:37 +0530 Subject: [PATCH 37/53] fix: set item.qty as mandatory in picklist (#27680) --- erpnext/stock/doctype/pick_list_item/pick_list_item.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/pick_list_item/pick_list_item.json b/erpnext/stock/doctype/pick_list_item/pick_list_item.json index 8665986004d..805286ddcc0 100644 --- a/erpnext/stock/doctype/pick_list_item/pick_list_item.json +++ b/erpnext/stock/doctype/pick_list_item/pick_list_item.json @@ -36,7 +36,8 @@ "fieldname": "qty", "fieldtype": "Float", "in_list_view": 1, - "label": "Qty" + "label": "Qty", + "reqd": 1 }, { "fieldname": "picked_qty", @@ -180,7 +181,7 @@ ], "istable": 1, "links": [], - "modified": "2020-06-24 17:18:57.357120", + "modified": "2021-09-28 12:02:16.923056", "modified_by": "Administrator", "module": "Stock", "name": "Pick List Item", From 72c081fd8fa3994d447944c58c5ea8cef0350a16 Mon Sep 17 00:00:00 2001 From: Alan <2.alan.tom@gmail.com> Date: Tue, 28 Sep 2021 12:56:26 +0530 Subject: [PATCH 38/53] fix: apply price list after batch or serial no insertion (#27566) --- erpnext/public/js/controllers/transaction.js | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 91b1247e6f4..403c4988eb7 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -617,6 +617,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe me.frm.script_manager.trigger('qty', item.doctype, item.name); if (!me.frm.doc.set_warehouse) me.frm.script_manager.trigger('warehouse', item.doctype, item.name); + me.apply_price_list(item, true); }, undefined, !frappe.flags.hide_serial_batch_dialog); } }, From 424efd41e52d1e244227e9db7f606a8ecbfc268b Mon Sep 17 00:00:00 2001 From: Saqib Date: Tue, 28 Sep 2021 18:12:02 +0530 Subject: [PATCH 39/53] feat(regional): toggle for reduced depreciation rate as per IT Act (#27600) --- erpnext/assets/doctype/asset/asset.py | 4 ---- erpnext/assets/doctype/asset/test_asset.py | 6 ++++++ erpnext/patches.txt | 3 ++- .../create_custom_field_for_finance_book.py | 21 +++++++++++++++++++ erpnext/regional/india/setup.py | 9 ++++++++ erpnext/regional/india/utils.py | 13 ++++++------ 6 files changed, 45 insertions(+), 11 deletions(-) create mode 100644 erpnext/patches/v13_0/create_custom_field_for_finance_book.py diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 8ff4f9790aa..39f102e1430 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -394,10 +394,6 @@ class Asset(AccountsController): if cint(self.number_of_depreciations_booked) > cint(row.total_number_of_depreciations): frappe.throw(_("Number of Depreciations Booked cannot be greater than Total Number of Depreciations")) - if row.depreciation_start_date and getdate(row.depreciation_start_date) < getdate(nowdate()): - frappe.msgprint(_("Depreciation Row {0}: Depreciation Start Date is entered as past date") - .format(row.idx), title=_('Warning'), indicator='red') - if row.depreciation_start_date and getdate(row.depreciation_start_date) < getdate(self.purchase_date): frappe.throw(_("Depreciation Row {0}: Next Depreciation Date cannot be before Purchase Date") .format(row.idx)) diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 4cc9be5b05d..7183ee7e369 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -645,12 +645,18 @@ class TestAsset(unittest.TestCase): pr = make_purchase_receipt(item_code="Macbook Pro", qty=1, rate=8000.0, location="Test Location") + finance_book = frappe.new_doc('Finance Book') + finance_book.finance_book_name = 'Income Tax' + finance_book.for_income_tax = 1 + finance_book.insert(ignore_if_duplicate=1) + asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') asset = frappe.get_doc('Asset', asset_name) asset.calculate_depreciation = 1 asset.available_for_use_date = '2030-07-12' asset.purchase_date = '2030-01-01' asset.append("finance_books", { + "finance_book": finance_book.name, "expected_value_after_useful_life": 1000, "depreciation_method": "Written Down Value", "total_number_of_depreciations": 3, diff --git a/erpnext/patches.txt b/erpnext/patches.txt index d26c92e749b..1a88632b16a 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -309,4 +309,5 @@ erpnext.patches.v13_0.update_dates_in_tax_withholding_category erpnext.patches.v14_0.update_opportunity_currency_fields erpnext.patches.v13_0.gst_fields_for_pos_invoice erpnext.patches.v13_0.create_accounting_dimensions_in_pos_doctypes -erpnext.patches.v13_0.modify_invalid_gain_loss_gl_entries \ No newline at end of file +erpnext.patches.v13_0.create_custom_field_for_finance_book +erpnext.patches.v13_0.modify_invalid_gain_loss_gl_entries diff --git a/erpnext/patches/v13_0/create_custom_field_for_finance_book.py b/erpnext/patches/v13_0/create_custom_field_for_finance_book.py new file mode 100644 index 00000000000..313b0e9a2eb --- /dev/null +++ b/erpnext/patches/v13_0/create_custom_field_for_finance_book.py @@ -0,0 +1,21 @@ +import frappe +from frappe.custom.doctype.custom_field.custom_field import create_custom_fields + + +def execute(): + company = frappe.get_all('Company', filters = {'country': 'India'}) + if not company: + return + + custom_field = { + 'Finance Book': [ + { + 'fieldname': 'for_income_tax', + 'label': 'For Income Tax', + 'fieldtype': 'Check', + 'insert_after': 'finance_book_name', + 'description': 'If the asset is put to use for less than 180 days, the first Depreciation Rate will be reduced by 50%.' + } + ] + } + create_custom_fields(custom_field, update=1) diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index 633064cf094..4a2c577e55b 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -663,6 +663,15 @@ def make_custom_fields(update=True): 'fieldtype': 'Data', 'insert_after': 'email' } + ], + 'Finance Book': [ + { + 'fieldname': 'for_income_tax', + 'label': 'For Income Tax', + 'fieldtype': 'Check', + 'insert_after': 'finance_book_name', + 'description': 'If the asset is put to use for less than 180 days, the first Depreciation Rate will be reduced by 50%.' + } ] } create_custom_fields(custom_fields, update=update) diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index 091cc8847cb..0faf80b0024 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -857,12 +857,13 @@ def get_depreciation_amount(asset, depreciable_value, row): rate_of_depreciation = row.rate_of_depreciation # if its the first depreciation if depreciable_value == asset.gross_purchase_amount: - # as per IT act, if the asset is purchased in the 2nd half of fiscal year, then rate is divided by 2 - diff = date_diff(row.depreciation_start_date, asset.available_for_use_date) - if diff <= 180: - rate_of_depreciation = rate_of_depreciation / 2 - frappe.msgprint( - _('As per IT Act, the rate of depreciation for the first depreciation entry is reduced by 50%.')) + if row.finance_book and frappe.db.get_value('Finance Book', row.finance_book, 'for_income_tax'): + # as per IT act, if the asset is purchased in the 2nd half of fiscal year, then rate is divided by 2 + diff = date_diff(row.depreciation_start_date, asset.available_for_use_date) + if diff <= 180: + rate_of_depreciation = rate_of_depreciation / 2 + frappe.msgprint( + _('As per IT Act, the rate of depreciation for the first depreciation entry is reduced by 50%.')) depreciation_amount = flt(depreciable_value * (flt(rate_of_depreciation) / 100)) From 0b040c7437ceafa5412681373fd1870e8fdc26d4 Mon Sep 17 00:00:00 2001 From: Saqib Date: Tue, 28 Sep 2021 18:13:01 +0530 Subject: [PATCH 40/53] fix: cannot delete a project if linked with sales order (#27536) --- erpnext/projects/doctype/project/project.py | 3 +++ .../projects/doctype/project/test_project.py | 17 +++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py index 2a8870b2305..df970f34c2b 100644 --- a/erpnext/projects/doctype/project/project.py +++ b/erpnext/projects/doctype/project/project.py @@ -144,6 +144,9 @@ class Project(Document): if self.sales_order: frappe.db.set_value("Sales Order", self.sales_order, "project", self.name) + def on_trash(self): + frappe.db.set_value("Sales Order", {"project": self.name}, "project", "") + def update_percent_complete(self): if self.percent_complete_method == "Manual": if self.status == "Completed": diff --git a/erpnext/projects/doctype/project/test_project.py b/erpnext/projects/doctype/project/test_project.py index ebc132626ca..c64ac8d0ea8 100644 --- a/erpnext/projects/doctype/project/test_project.py +++ b/erpnext/projects/doctype/project/test_project.py @@ -9,6 +9,8 @@ from frappe.utils import add_days, getdate, nowdate from erpnext.projects.doctype.project_template.test_project_template import make_project_template from erpnext.projects.doctype.task.test_task import create_task +from erpnext.selling.doctype.sales_order.sales_order import make_project as make_project_from_so +from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order test_records = frappe.get_test_records('Project') test_ignore = ["Sales Order"] @@ -96,6 +98,21 @@ class TestProject(unittest.TestCase): self.assertEqual(len(tasks), 2) + def test_project_linking_with_sales_order(self): + so = make_sales_order() + project = make_project_from_so(so.name) + + project.save() + self.assertEqual(project.sales_order, so.name) + + so.reload() + self.assertEqual(so.project, project.name) + + project.delete() + + so.reload() + self.assertFalse(so.project) + def get_project(name, template): project = frappe.get_doc(dict( From b39f8a621504cf7cb3f3a40717638318ada31571 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Wed, 29 Sep 2021 15:54:35 +0530 Subject: [PATCH 41/53] fix: Ignore user permission for Represents Company field in Sales and Purchase docs (#27684) * fix: Ignore user permission for Represents Company field in Sales and Purchase docs * fix: Ignore user permission for fiscal year company --- .../fiscal_year_company.json | 86 ++++++------------- .../purchase_invoice/purchase_invoice.json | 3 +- .../doctype/sales_invoice/sales_invoice.json | 3 +- .../purchase_order/purchase_order.json | 3 +- .../doctype/sales_order/sales_order.json | 3 +- .../doctype/delivery_note/delivery_note.json | 3 +- .../purchase_receipt/purchase_receipt.json | 3 +- 7 files changed, 40 insertions(+), 64 deletions(-) diff --git a/erpnext/accounts/doctype/fiscal_year_company/fiscal_year_company.json b/erpnext/accounts/doctype/fiscal_year_company/fiscal_year_company.json index 3eb0d74ed33..67acb26c7ee 100644 --- a/erpnext/accounts/doctype/fiscal_year_company/fiscal_year_company.json +++ b/erpnext/accounts/doctype/fiscal_year_company/fiscal_year_company.json @@ -1,63 +1,33 @@ { - "allow_copy": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2014-10-02 13:35:44.155278", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Setup", - "editable_grid": 1, + "actions": [], + "creation": "2014-10-02 13:35:44.155278", + "doctype": "DocType", + "document_type": "Setup", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "company" + ], "fields": [ { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "company", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 1, - "label": "Company", - "length": 0, - "no_copy": 0, - "options": "Company", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 + "fieldname": "company", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "in_list_view": 1, + "label": "Company", + "options": "Company" } - ], - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2016-07-11 03:28:00.505946", - "modified_by": "Administrator", - "module": "Accounts", - "name": "Fiscal Year Company", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_seen": 0 + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-09-28 18:01:53.495929", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Fiscal Year Company", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index dde0328130f..55e288eeef9 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -1303,6 +1303,7 @@ "fetch_from": "supplier.is_internal_supplier", "fieldname": "is_internal_supplier", "fieldtype": "Check", + "ignore_user_permissions": 1, "label": "Is Internal Supplier", "read_only": 1 }, @@ -1400,7 +1401,7 @@ "idx": 204, "is_submittable": 1, "links": [], - "modified": "2021-09-21 09:27:39.967811", + "modified": "2021-09-28 13:10:28.351810", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice", diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index f383b52b409..2d6c04ebf9b 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -1953,6 +1953,7 @@ "fetch_from": "customer.represents_company", "fieldname": "represents_company", "fieldtype": "Link", + "ignore_user_permissions": 1, "label": "Represents Company", "options": "Company", "read_only": 1 @@ -2022,7 +2023,7 @@ "link_fieldname": "consolidated_invoice" } ], - "modified": "2021-09-21 09:27:50.191854", + "modified": "2021-09-28 13:09:34.391799", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json index ef54538fcd4..896208f25e1 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.json +++ b/erpnext/buying/doctype/purchase_order/purchase_order.json @@ -1121,6 +1121,7 @@ "fetch_from": "supplier.represents_company", "fieldname": "represents_company", "fieldtype": "Link", + "ignore_user_permissions": 1, "label": "Represents Company", "options": "Company", "read_only": 1 @@ -1143,7 +1144,7 @@ "idx": 105, "is_submittable": 1, "links": [], - "modified": "2021-08-30 20:03:14.008804", + "modified": "2021-09-28 13:10:47.955401", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order", diff --git a/erpnext/selling/doctype/sales_order/sales_order.json b/erpnext/selling/doctype/sales_order/sales_order.json index 85282ca1a07..7c7ed9a9604 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.json +++ b/erpnext/selling/doctype/sales_order/sales_order.json @@ -1480,6 +1480,7 @@ "fetch_from": "customer.represents_company", "fieldname": "represents_company", "fieldtype": "Link", + "ignore_user_permissions": 1, "label": "Represents Company", "options": "Company", "read_only": 1 @@ -1512,7 +1513,7 @@ "idx": 105, "is_submittable": 1, "links": [], - "modified": "2021-09-01 15:12:24.115483", + "modified": "2021-09-28 13:09:51.515542", "modified_by": "Administrator", "module": "Selling", "name": "Sales Order", diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.json b/erpnext/stock/doctype/delivery_note/delivery_note.json index fdc8763baa6..9bf142c4b44 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.json +++ b/erpnext/stock/doctype/delivery_note/delivery_note.json @@ -1277,6 +1277,7 @@ "fetch_from": "customer.represents_company", "fieldname": "represents_company", "fieldtype": "Link", + "ignore_user_permissions": 1, "label": "Represents Company", "options": "Company", "read_only": 1 @@ -1308,7 +1309,7 @@ "idx": 146, "is_submittable": 1, "links": [], - "modified": "2021-08-27 20:14:40.215231", + "modified": "2021-09-28 13:10:09.761714", "modified_by": "Administrator", "module": "Stock", "name": "Delivery Note", diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json index 1a597343c0a..112ddedac29 100755 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json @@ -1140,6 +1140,7 @@ "fetch_from": "supplier.represents_company", "fieldname": "represents_company", "fieldtype": "Link", + "ignore_user_permissions": 1, "label": "Represents Company", "options": "Company", "read_only": 1 @@ -1149,7 +1150,7 @@ "idx": 261, "is_submittable": 1, "links": [], - "modified": "2021-08-17 20:16:40.849885", + "modified": "2021-09-28 13:11:10.181328", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt", From c1f9997a67e4b7e86d72b2e5bb74a59dd56bd1f9 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 29 Sep 2021 23:23:17 +0530 Subject: [PATCH 42/53] fix: added project name in the purchase order analysis --- .../purchase_order_analysis.js | 9 ++++++++- .../purchase_order_analysis.py | 19 ++++++++++++------- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.js b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.js index 701da4380aa..ca3be03da6e 100644 --- a/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.js +++ b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.js @@ -30,7 +30,14 @@ frappe.query_reports["Purchase Order Analysis"] = { "default": frappe.datetime.get_today() }, { - "fieldname": "purchase_order", + "fieldname":"project", + "label": __("Project"), + "fieldtype": "Link", + "width": "80", + "options": "Project" + }, + { + "fieldname": "name", "label": __("Purchase Order"), "fieldtype": "Link", "width": "80", diff --git a/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py index 5d59456550b..1b25dd45d2d 100644 --- a/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py +++ b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py @@ -41,14 +41,12 @@ def get_conditions(filters): if filters.get("from_date") and filters.get("to_date"): conditions += " and po.transaction_date between %(from_date)s and %(to_date)s" - if filters.get("company"): - conditions += " and po.company = %(company)s" + for field in ['company', 'name', 'status']: + if filters.get(field): + conditions += f" and po.{field} = %({field})s" - if filters.get("purchase_order"): - conditions += " and po.name = %(purchase_order)s" - - if filters.get("status"): - conditions += " and po.status in %(status)s" + if filters.get('project'): + conditions += " and poi.project = %(project)s" return conditions @@ -57,6 +55,7 @@ def get_data(conditions, filters): SELECT po.transaction_date as date, poi.schedule_date as required_date, + poi.project, po.name as purchase_order, po.status, po.supplier, poi.item_code, poi.qty, poi.received_qty, @@ -175,6 +174,12 @@ def get_columns(filters): "fieldtype": "Link", "options": "Supplier", "width": 130 + },{ + "label": _("Project"), + "fieldname": "project", + "fieldtype": "Link", + "options": "Project", + "width": 130 }] if not filters.get("group_by_po"): From 13d2e7b19817e2dcd10129985773314f0d3a95e6 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 16 Sep 2021 18:54:57 +0530 Subject: [PATCH 43/53] fix: Deferred revenue entries post account freezing (cherry picked from commit e2eb72eb5ba1aaadddf8278dd77bfe75a178ef01) --- .../sales_invoice/test_sales_invoice.py | 36 +++++++++++++++++++ erpnext/accounts/general_ledger.py | 5 ++- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index bdd30f380f6..25d85d4c12c 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -1800,6 +1800,42 @@ class TestSalesInvoice(unittest.TestCase): check_gl_entries(self, si.name, expected_gle, "2019-01-30") + def test_deferred_revenue_post_account_freeze_upto_by_admin(self): + deferred_account = create_account(account_name="Deferred Revenue", + parent_account="Current Liabilities - _TC", company="_Test Company") + + item = create_item("_Test Item for Deferred Accounting") + item.enable_deferred_revenue = 1 + item.deferred_revenue_account = deferred_account + item.no_of_months = 12 + item.save() + + si = create_sales_invoice(item=item.name, posting_date="2019-01-10", do_not_save=True) + si.items[0].enable_deferred_revenue = 1 + si.items[0].service_start_date = "2019-01-10" + si.items[0].service_end_date = "2019-03-15" + si.items[0].deferred_revenue_account = deferred_account + si.save() + si.submit() + + frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', getdate('2019-01-31')) + frappe.db.set_value('Accounts Settings', None, 'frozen_accounts_modifier', 'System Manager') + + pda1 = frappe.get_doc(dict( + doctype='Process Deferred Accounting', + posting_date=nowdate(), + start_date="2019-01-01", + end_date="2019-03-31", + type="Income", + company="_Test Company" + )) + + pda1.insert() + self.assertRaises(frappe.ValidationError, pda1.submit) + + frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', None) + frappe.db.set_value('Accounts Settings', None, 'frozen_accounts_modifier', None) + def test_fixed_deferred_revenue(self): deferred_account = create_account(account_name="Deferred Revenue", parent_account="Current Liabilities - _TC", company="_Test Company") diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index 4bf2b828edd..0cee6f5b3aa 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -284,13 +284,16 @@ def check_freezing_date(posting_date, adv_adj=False): """ Nobody can do GL Entries where posting date is before freezing date except authorized person + + Administrator has all the roles so this check will be bypassed if any role is allowed to post + Hence stop admin to bypass if accounts are freezed """ if not adv_adj: acc_frozen_upto = frappe.db.get_value('Accounts Settings', None, 'acc_frozen_upto') if acc_frozen_upto: frozen_accounts_modifier = frappe.db.get_value( 'Accounts Settings', None,'frozen_accounts_modifier') if getdate(posting_date) <= getdate(acc_frozen_upto) \ - and not frozen_accounts_modifier in frappe.get_roles(): + and not frozen_accounts_modifier in frappe.get_roles() or frappe.session.user == 'Administrator': frappe.throw(_("You are not authorized to add or update entries before {0}").format(formatdate(acc_frozen_upto))) def set_as_cancel(voucher_type, voucher_no): From 774cd68f4c3f0a7b5951b627069158718fc1dd22 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 20 Sep 2021 11:06:10 +0530 Subject: [PATCH 44/53] fix: Test Case (cherry picked from commit 846d128c5d536b5003dc3055f3239e6f92a3146c) --- erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 25d85d4c12c..8a2e9450e97 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -1801,6 +1801,11 @@ class TestSalesInvoice(unittest.TestCase): check_gl_entries(self, si.name, expected_gle, "2019-01-30") def test_deferred_revenue_post_account_freeze_upto_by_admin(self): + frappe.set_user("Administrator") + + frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', None) + frappe.db.set_value('Accounts Settings', None, 'frozen_accounts_modifier', None) + deferred_account = create_account(account_name="Deferred Revenue", parent_account="Current Liabilities - _TC", company="_Test Company") From f1a669c2f747e3ee4848d4aef272281017016247 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 29 Sep 2021 22:26:33 +0530 Subject: [PATCH 45/53] fix: Test case (cherry picked from commit 23863c7663a94e32e32812301a7efca79575c470) --- erpnext/accounts/deferred_revenue.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/deferred_revenue.py b/erpnext/accounts/deferred_revenue.py index bcd07718a59..71957e67a3c 100644 --- a/erpnext/accounts/deferred_revenue.py +++ b/erpnext/accounts/deferred_revenue.py @@ -374,12 +374,15 @@ def make_gl_entries(doc, credit_account, debit_account, against, try: make_gl_entries(gl_entries, cancel=(doc.docstatus == 2), merge_entries=True) frappe.db.commit() - except Exception: - frappe.db.rollback() - traceback = frappe.get_traceback() - frappe.log_error(message=traceback) + except Exception as e: + if frappe.flags.in_test: + raise e + else: + frappe.db.rollback() + traceback = frappe.get_traceback() + frappe.log_error(message=traceback) - frappe.flags.deferred_accounting_error = True + frappe.flags.deferred_accounting_error = True def send_mail(deferred_process): title = _("Error while processing deferred accounting for {0}").format(deferred_process) From 4685ed5a8c95a64c351b2fe87bc41ef44ebdb4fa Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 30 Sep 2021 09:58:59 +0530 Subject: [PATCH 46/53] fix: distribution of additional costs in mfg stock entry (#27629) * refactor: remove unnecessary list comprehensions * fix: correct cost distribution logic While apportioning costs same condition should be present on both sides so total value is representative of all items to be apportioned. Here while calculating incoming_items_cost only FG items are considered, but while apportioning all items with to_warehouse are considered. Solution: only apportion additional cost on FG items * test: test cost distribution * fix: patch for additional cost fix(patch): consider PCV while patching - consider Period closing voucher while patching - recomute rates for SLE of affected stock entries consider only FG/scrap item SLEs for recomputation of rates * fix: remove client side logic for addn cost All of this is done in python code hence removed client side code. --- erpnext/patches.txt | 1 + .../fix_additional_cost_in_mfg_stock_entry.py | 76 +++++++++++++++++++ .../stock/doctype/stock_entry/stock_entry.js | 42 ---------- .../stock/doctype/stock_entry/stock_entry.py | 57 ++++++++------ .../doctype/stock_entry/test_stock_entry.py | 33 ++++++++ 5 files changed, 145 insertions(+), 64 deletions(-) create mode 100644 erpnext/patches/v13_0/fix_additional_cost_in_mfg_stock_entry.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 1a88632b16a..a4819965810 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -311,3 +311,4 @@ erpnext.patches.v13_0.gst_fields_for_pos_invoice erpnext.patches.v13_0.create_accounting_dimensions_in_pos_doctypes erpnext.patches.v13_0.create_custom_field_for_finance_book erpnext.patches.v13_0.modify_invalid_gain_loss_gl_entries +erpnext.patches.v13_0.fix_additional_cost_in_mfg_stock_entry diff --git a/erpnext/patches/v13_0/fix_additional_cost_in_mfg_stock_entry.py b/erpnext/patches/v13_0/fix_additional_cost_in_mfg_stock_entry.py new file mode 100644 index 00000000000..aeb8d8eb588 --- /dev/null +++ b/erpnext/patches/v13_0/fix_additional_cost_in_mfg_stock_entry.py @@ -0,0 +1,76 @@ +from typing import List, NewType + +import frappe + +StockEntryCode = NewType("StockEntryCode", str) + + +def execute(): + stock_entry_codes = find_broken_stock_entries() + + for stock_entry_code in stock_entry_codes: + patched_stock_entry = patch_additional_cost(stock_entry_code) + create_repost_item_valuation(patched_stock_entry) + + +def find_broken_stock_entries() -> List[StockEntryCode]: + period_closing_date = frappe.db.get_value( + "Period Closing Voucher", {"docstatus": 1}, "posting_date", order_by="posting_date desc" + ) + + stock_entries_to_patch = frappe.db.sql( + """ + select se.name, sum(sed.additional_cost) as item_additional_cost, se.total_additional_costs + from `tabStock Entry` se + join `tabStock Entry Detail` sed + on sed.parent = se.name + where + se.docstatus = 1 and + se.posting_date > %s + group by + sed.parent + having + item_additional_cost != se.total_additional_costs + """, + period_closing_date, + as_dict=True, + ) + + return [d.name for d in stock_entries_to_patch] + + +def patch_additional_cost(code: StockEntryCode): + stock_entry = frappe.get_doc("Stock Entry", code) + stock_entry.distribute_additional_costs() + stock_entry.update_valuation_rate() + stock_entry.set_total_incoming_outgoing_value() + stock_entry.set_total_amount() + stock_entry.db_update() + for item in stock_entry.items: + item.db_update() + return stock_entry + + +def create_repost_item_valuation(stock_entry): + from erpnext.controllers.stock_controller import create_repost_item_valuation_entry + + # turn on recalculate flag so reposting corrects the incoming/outgoing rates. + frappe.db.set_value( + "Stock Ledger Entry", + {"voucher_no": stock_entry.name, "actual_qty": (">", 0)}, + "recalculate_rate", + 1, + update_modified=False, + ) + + create_repost_item_valuation_entry( + args=frappe._dict( + { + "posting_date": stock_entry.posting_date, + "posting_time": stock_entry.posting_time, + "voucher_type": stock_entry.doctype, + "voucher_no": stock_entry.name, + "company": stock_entry.company, + } + ) + ) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index 7cb9665e857..157904bc34e 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -548,44 +548,7 @@ frappe.ui.form.on('Stock Entry', { calculate_basic_amount: function(frm, item) { item.basic_amount = flt(flt(item.transfer_qty) * flt(item.basic_rate), precision("basic_amount", item)); - - frm.events.calculate_amount(frm); - }, - - calculate_amount: function(frm) { frm.events.calculate_total_additional_costs(frm); - let total_basic_amount = 0; - if (in_list(["Repack", "Manufacture"], frm.doc.purpose)) { - total_basic_amount = frappe.utils.sum( - (frm.doc.items || []).map(function(i) { - return i.is_finished_item ? flt(i.basic_amount) : 0; - }) - ); - } else { - total_basic_amount = frappe.utils.sum( - (frm.doc.items || []).map(function(i) { - return i.t_warehouse ? flt(i.basic_amount) : 0; - }) - ); - } - for (let i in frm.doc.items) { - let item = frm.doc.items[i]; - - if (((in_list(["Repack", "Manufacture"], frm.doc.purpose) && item.is_finished_item) || item.t_warehouse) && total_basic_amount) { - item.additional_cost = (flt(item.basic_amount) / total_basic_amount) * frm.doc.total_additional_costs; - } else { - item.additional_cost = 0; - } - - item.amount = flt(item.basic_amount + flt(item.additional_cost), precision("amount", item)); - - if (flt(item.transfer_qty)) { - item.valuation_rate = flt(flt(item.basic_rate) + (flt(item.additional_cost) / flt(item.transfer_qty)), - precision("valuation_rate", item)); - } - } - - refresh_field('items'); }, calculate_total_additional_costs: function(frm) { @@ -781,11 +744,6 @@ frappe.ui.form.on('Landed Cost Taxes and Charges', { amount: function(frm, cdt, cdn) { frm.events.set_base_amount(frm, cdt, cdn); - // Adding this check because same table in used in LCV - // This causes an error if you try to post an LCV immediately after a Stock Entry - if (frm.doc.doctype == 'Stock Entry') { - frm.events.calculate_amount(frm); - } }, expense_account: function(frm, cdt, cdn) { diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 1c9b9614f67..bd7d22bcbc0 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -555,22 +555,27 @@ class StockEntry(StockController): def distribute_additional_costs(self): # If no incoming items, set additional costs blank - if not any([d.item_code for d in self.items if d.t_warehouse]): + if not any(d.item_code for d in self.items if d.t_warehouse): self.additional_costs = [] - self.total_additional_costs = sum([flt(t.base_amount) for t in self.get("additional_costs")]) + self.total_additional_costs = sum(flt(t.base_amount) for t in self.get("additional_costs")) if self.purpose in ("Repack", "Manufacture"): - incoming_items_cost = sum([flt(t.basic_amount) for t in self.get("items") if t.is_finished_item]) + incoming_items_cost = sum(flt(t.basic_amount) for t in self.get("items") if t.is_finished_item) else: - incoming_items_cost = sum([flt(t.basic_amount) for t in self.get("items") if t.t_warehouse]) + incoming_items_cost = sum(flt(t.basic_amount) for t in self.get("items") if t.t_warehouse) - if incoming_items_cost: - for d in self.get("items"): - if (self.purpose in ("Repack", "Manufacture") and d.is_finished_item) or d.t_warehouse: - d.additional_cost = (flt(d.basic_amount) / incoming_items_cost) * self.total_additional_costs - else: - d.additional_cost = 0 + if not incoming_items_cost: + return + + for d in self.get("items"): + if self.purpose in ("Repack", "Manufacture") and not d.is_finished_item: + d.additional_cost = 0 + continue + elif not d.t_warehouse: + d.additional_cost = 0 + continue + d.additional_cost = (flt(d.basic_amount) / incoming_items_cost) * self.total_additional_costs def update_valuation_rate(self): for d in self.get("items"): @@ -805,7 +810,11 @@ class StockEntry(StockController): def get_gl_entries(self, warehouse_account): gl_entries = super(StockEntry, self).get_gl_entries(warehouse_account) - total_basic_amount = sum([flt(t.basic_amount) for t in self.get("items") if t.t_warehouse]) + if self.purpose in ("Repack", "Manufacture"): + total_basic_amount = sum(flt(t.basic_amount) for t in self.get("items") if t.is_finished_item) + else: + total_basic_amount = sum(flt(t.basic_amount) for t in self.get("items") if t.t_warehouse) + divide_based_on = total_basic_amount if self.get("additional_costs") and not total_basic_amount: @@ -816,20 +825,24 @@ class StockEntry(StockController): for t in self.get("additional_costs"): for d in self.get("items"): - if d.t_warehouse: - item_account_wise_additional_cost.setdefault((d.item_code, d.name), {}) - item_account_wise_additional_cost[(d.item_code, d.name)].setdefault(t.expense_account, { - "amount": 0.0, - "base_amount": 0.0 - }) + if self.purpose in ("Repack", "Manufacture") and not d.is_finished_item: + continue + elif not d.t_warehouse: + continue - multiply_based_on = d.basic_amount if total_basic_amount else d.qty + item_account_wise_additional_cost.setdefault((d.item_code, d.name), {}) + item_account_wise_additional_cost[(d.item_code, d.name)].setdefault(t.expense_account, { + "amount": 0.0, + "base_amount": 0.0 + }) - item_account_wise_additional_cost[(d.item_code, d.name)][t.expense_account]["amount"] += \ - flt(t.amount * multiply_based_on) / divide_based_on + multiply_based_on = d.basic_amount if total_basic_amount else d.qty - item_account_wise_additional_cost[(d.item_code, d.name)][t.expense_account]["base_amount"] += \ - flt(t.base_amount * multiply_based_on) / divide_based_on + item_account_wise_additional_cost[(d.item_code, d.name)][t.expense_account]["amount"] += \ + flt(t.amount * multiply_based_on) / divide_based_on + + item_account_wise_additional_cost[(d.item_code, d.name)][t.expense_account]["base_amount"] += \ + flt(t.base_amount * multiply_based_on) / divide_based_on if item_account_wise_additional_cost: for d in self.get("items"): diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py index 46619eb1f35..c9d0af5f3b1 100644 --- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py @@ -837,6 +837,39 @@ class TestStockEntry(unittest.TestCase): frappe.db.set_default("allow_negative_stock", 0) + def test_additional_cost_distribution_manufacture(self): + se = frappe.get_doc( + doctype="Stock Entry", + purpose="Manufacture", + additional_costs=[frappe._dict(base_amount=100)], + items=[ + frappe._dict(item_code="RM", basic_amount=10), + frappe._dict(item_code="FG", basic_amount=20, t_warehouse="X", is_finished_item=1), + frappe._dict(item_code="scrap", basic_amount=30, t_warehouse="X") + ], + ) + + se.distribute_additional_costs() + + distributed_costs = [d.additional_cost for d in se.items] + self.assertEqual([0.0, 100.0, 0.0], distributed_costs) + + def test_additional_cost_distribution_non_manufacture(self): + se = frappe.get_doc( + doctype="Stock Entry", + purpose="Material Receipt", + additional_costs=[frappe._dict(base_amount=100)], + items=[ + frappe._dict(item_code="RECEIVED_1", basic_amount=20, t_warehouse="X"), + frappe._dict(item_code="RECEIVED_2", basic_amount=30, t_warehouse="X") + ], + ) + + se.distribute_additional_costs() + + distributed_costs = [d.additional_cost for d in se.items] + self.assertEqual([40.0, 60.0], distributed_costs) + def make_serialized_item(**args): args = frappe._dict(args) se = frappe.copy_doc(test_records[0]) From 9df5df4d94a882bfd739f5c27ff0f56ae44166fb Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 30 Sep 2021 12:07:38 +0530 Subject: [PATCH 47/53] fix: wrong company selected when marking attendance for all employees (#27685) (#27707) * fix: wrong company selected when marking attendance for all employees * fix: enable caching for repeated queries of the same employee (cherry picked from commit b478e72cefbdffd3e906fdd1ef822becdead78d7) Co-authored-by: Mohammed Yusuf Shaikh <49878143+mohammedyusufshaikh@users.noreply.github.com> --- .../employee_attendance_tool/employee_attendance_tool.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/erpnext/hr/doctype/employee_attendance_tool/employee_attendance_tool.py b/erpnext/hr/doctype/employee_attendance_tool/employee_attendance_tool.py index 7c751a47a6b..1a1bcb2e20f 100644 --- a/erpnext/hr/doctype/employee_attendance_tool/employee_attendance_tool.py +++ b/erpnext/hr/doctype/employee_attendance_tool/employee_attendance_tool.py @@ -55,8 +55,7 @@ def mark_employee_attendance(employee_list, status, date, leave_type=None, compa else: leave_type = None - if not company: - company = frappe.db.get_value("Employee", employee['employee'], "Company") + company = frappe.db.get_value("Employee", employee['employee'], "Company", cache=True) attendance=frappe.get_doc(dict( doctype='Attendance', @@ -68,4 +67,4 @@ def mark_employee_attendance(employee_list, status, date, leave_type=None, compa company=company )) attendance.insert() - attendance.submit() + attendance.submit() \ No newline at end of file From abf450dc706733e2da3463482610f772a817743f Mon Sep 17 00:00:00 2001 From: marination Date: Thu, 30 Sep 2021 14:03:40 +0530 Subject: [PATCH 48/53] fix: Increment batch qty if pre-existing batch is scanned --- erpnext/public/js/controllers/transaction.js | 27 ++++++-------------- erpnext/www/shop-by-category/__init__.py | 0 2 files changed, 8 insertions(+), 19 deletions(-) create mode 100644 erpnext/www/shop-by-category/__init__.py diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index f97d8cfe7f9..149582f48bf 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -378,18 +378,14 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe let batch_no_scan = Boolean(data.batch_no) && frappe.meta.has_field(cur_grid.doctype, "batch_no"); if (batch_no_scan) { - let duplicate = this.check_duplicate_batch_scan(data.batch_no); - if (duplicate) { - scan_barcode_field.set_value(''); - return; - } + row_to_modify = this.get_batch_row_to_modify(data.batch_no); } else { // serial or barcode scan row_to_modify = this.get_row_to_modify_on_scan(row_to_modify, data); } - if (!row_to_modify || batch_no_scan) { - // add new row if new item scanned or batch is scanned + if (!row_to_modify) { + // add new row if new item/batch is scanned row_to_modify = frappe.model.add_child(this.frm.doc, cur_grid.doctype, 'items'); } @@ -408,8 +404,8 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe ['serial_no', 'batch_no', 'barcode'].forEach(field => { if (data[field] && frappe.meta.has_field(row_to_modify.doctype, field)) { let is_serial_no = row_to_modify[field] && field === "serial_no"; - let value = data[field]; + if (is_serial_no) { value = row_to_modify[field] + '\n' + data[field]; } @@ -436,21 +432,14 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe return row_to_modify; } - check_duplicate_batch_scan(batch_no) { - // ignore scan if batch already exists in table + get_batch_row_to_modify(batch_no) { + // get row if batch already exists in table const existing_batch_row = this.frm.doc.items.find(d => d.batch_no === batch_no); - - if (existing_batch_row) { - frappe.show_alert({ - message: __('Batch {0} already added', [batch_no]), - indicator: 'orange' - }); - return true; - } - return false; + return existing_batch_row || null; } show_scan_message (idx, exist = null) { + // show new row or qty increase toast if (exist) { frappe.show_alert({ message: __('Row #{0}: Qty increased by 1', [idx]), diff --git a/erpnext/www/shop-by-category/__init__.py b/erpnext/www/shop-by-category/__init__.py new file mode 100644 index 00000000000..e69de29bb2d From cc143bca0d4e6f30cc7cf1dd69911b09f7a0fd89 Mon Sep 17 00:00:00 2001 From: Marica Date: Thu, 30 Sep 2021 14:08:45 +0530 Subject: [PATCH 49/53] fix: Maintenance Schedule child table status for legacy data (#27554) * fix: Maintenance Schedule child table status for legacy data * fix: Include legacy draft schedules in patch * fix: Pre-commit formatting --- .../maintenance_schedule_detail.json | 8 +++++--- .../v13_0/set_status_in_maintenance_schedule_table.py | 9 +++++++++ 2 files changed, 14 insertions(+), 3 deletions(-) create mode 100644 erpnext/patches/v13_0/set_status_in_maintenance_schedule_table.py diff --git a/erpnext/maintenance/doctype/maintenance_schedule_detail/maintenance_schedule_detail.json b/erpnext/maintenance/doctype/maintenance_schedule_detail/maintenance_schedule_detail.json index 8ccef6a8172..afe273f3102 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule_detail/maintenance_schedule_detail.json +++ b/erpnext/maintenance/doctype/maintenance_schedule_detail/maintenance_schedule_detail.json @@ -89,13 +89,14 @@ "width": "160px" }, { + "allow_on_submit": 1, "columns": 2, + "default": "Pending", "fieldname": "completion_status", "fieldtype": "Select", "in_list_view": 1, "label": "Completion Status", - "options": "Pending\nPartially Completed\nFully Completed", - "read_only": 1 + "options": "Pending\nPartially Completed\nFully Completed" }, { "fieldname": "column_break_3", @@ -125,10 +126,11 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2021-05-27 16:07:25.905015", + "modified": "2021-09-16 21:25:22.506485", "modified_by": "Administrator", "module": "Maintenance", "name": "Maintenance Schedule Detail", + "naming_rule": "Random", "owner": "Administrator", "permissions": [], "sort_field": "modified", diff --git a/erpnext/patches/v13_0/set_status_in_maintenance_schedule_table.py b/erpnext/patches/v13_0/set_status_in_maintenance_schedule_table.py new file mode 100644 index 00000000000..bf6285dbf91 --- /dev/null +++ b/erpnext/patches/v13_0/set_status_in_maintenance_schedule_table.py @@ -0,0 +1,9 @@ +import frappe + + +def execute(): + frappe.db.sql(""" + UPDATE `tabMaintenance Schedule Detail` + SET completion_status = 'Pending' + WHERE docstatus < 2 + """) From 754626a1021bf89a161f152262489195c7cad45d Mon Sep 17 00:00:00 2001 From: Marica Date: Thu, 30 Sep 2021 14:12:19 +0530 Subject: [PATCH 50/53] fix: Delete accidental __init__.py file --- erpnext/www/shop-by-category/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 erpnext/www/shop-by-category/__init__.py diff --git a/erpnext/www/shop-by-category/__init__.py b/erpnext/www/shop-by-category/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 From 73ac3e0c40821aad27faa10688b961a4c9e929a4 Mon Sep 17 00:00:00 2001 From: marination Date: Thu, 30 Sep 2021 14:18:35 +0530 Subject: [PATCH 51/53] fix: Add patch to patches.txt for https://github.com/frappe/erpnext/pull/27554 --- erpnext/patches.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index a4819965810..3adc3e9ff6a 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -312,3 +312,4 @@ erpnext.patches.v13_0.create_accounting_dimensions_in_pos_doctypes erpnext.patches.v13_0.create_custom_field_for_finance_book erpnext.patches.v13_0.modify_invalid_gain_loss_gl_entries erpnext.patches.v13_0.fix_additional_cost_in_mfg_stock_entry +erpnext.patches.v13_0.set_status_in_maintenance_schedule_table From cbffbc38947142c4164453c6fc50d950eac51f28 Mon Sep 17 00:00:00 2001 From: marination Date: Thu, 30 Sep 2021 14:53:47 +0530 Subject: [PATCH 52/53] fix: reload doc in patch --- .../patches/v13_0/set_status_in_maintenance_schedule_table.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/patches/v13_0/set_status_in_maintenance_schedule_table.py b/erpnext/patches/v13_0/set_status_in_maintenance_schedule_table.py index bf6285dbf91..9887ad9df0c 100644 --- a/erpnext/patches/v13_0/set_status_in_maintenance_schedule_table.py +++ b/erpnext/patches/v13_0/set_status_in_maintenance_schedule_table.py @@ -2,6 +2,7 @@ import frappe def execute(): + frappe.reload_doc("maintenance", "doctype", "Maintenance Schedule Detail") frappe.db.sql(""" UPDATE `tabMaintenance Schedule Detail` SET completion_status = 'Pending' From 9e08229b7bdcb5bf63146c7effe1e757e862416e Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Thu, 30 Sep 2021 18:32:10 +0530 Subject: [PATCH 53/53] fix(Org Chart): use attribute selectors instead of ID selector for node IDs with special characters (#27717) * fix(Org Chart): use attribute selectors instead of ID selector for node IDs with special chars * fix: UI tests --- .../test_organizational_chart_desktop.js | 2 +- .../test_organizational_chart_mobile.js | 2 +- .../hierarchy_chart_desktop.js | 24 +++++++++---------- .../hierarchy_chart/hierarchy_chart_mobile.js | 22 ++++++++--------- erpnext/tests/ui_test_helpers.py | 2 ++ 5 files changed, 27 insertions(+), 25 deletions(-) diff --git a/cypress/integration/test_organizational_chart_desktop.js b/cypress/integration/test_organizational_chart_desktop.js index 39b00d32635..79e08b3bbad 100644 --- a/cypress/integration/test_organizational_chart_desktop.js +++ b/cypress/integration/test_organizational_chart_desktop.js @@ -6,7 +6,7 @@ context('Organizational Chart', () => { it('navigates to org chart', () => { cy.visit('/app'); - cy.awesomebar('Organizational Chart'); + cy.visit('/app/organizational-chart'); cy.url().should('include', '/organizational-chart'); cy.window().its('frappe.csrf_token').then(csrf_token => { diff --git a/cypress/integration/test_organizational_chart_mobile.js b/cypress/integration/test_organizational_chart_mobile.js index 6e751513967..161fae098a2 100644 --- a/cypress/integration/test_organizational_chart_mobile.js +++ b/cypress/integration/test_organizational_chart_mobile.js @@ -7,7 +7,7 @@ context('Organizational Chart Mobile', () => { it('navigates to org chart', () => { cy.viewport(375, 667); cy.visit('/app'); - cy.awesomebar('Organizational Chart'); + cy.visit('/app/organizational-chart'); cy.url().should('include', '/organizational-chart'); cy.window().its('frappe.csrf_token').then(csrf_token => { diff --git a/erpnext/public/js/hierarchy_chart/hierarchy_chart_desktop.js b/erpnext/public/js/hierarchy_chart/hierarchy_chart_desktop.js index 62867327537..7b358195c3e 100644 --- a/erpnext/public/js/hierarchy_chart/hierarchy_chart_desktop.js +++ b/erpnext/public/js/hierarchy_chart/hierarchy_chart_desktop.js @@ -63,7 +63,7 @@ erpnext.HierarchyChart = class { }); node.parent.append(node_card); - node.$link = $(`#${node.id}`); + node.$link = $(`[id="${node.id}"]`); } show() { @@ -223,7 +223,7 @@ erpnext.HierarchyChart = class { let node = undefined; $.each(r.message, (_i, data) => { - if ($(`#${data.id}`).length) + if ($(`[id="${data.id}"]`).length) return; node = new me.Node({ @@ -263,7 +263,7 @@ erpnext.HierarchyChart = class { this.refresh_connectors(node.parent_id); // rebuild incoming connections - let grandparent = $(`#${node.parent_id}`).attr('data-parent'); + let grandparent = $(`[id="${node.parent_id}"]`).attr('data-parent'); this.refresh_connectors(grandparent); } @@ -282,7 +282,7 @@ erpnext.HierarchyChart = class { show_active_path(node) { // mark node parent on active path - $(`#${node.parent_id}`).addClass('active-path'); + $(`[id="${node.parent_id}"]`).addClass('active-path'); } load_children(node, deep=false) { @@ -317,7 +317,7 @@ erpnext.HierarchyChart = class { render_child_nodes(node, child_nodes) { const last_level = this.$hierarchy.find('.level:last').index(); - const current_level = $(`#${node.id}`).parent().parent().parent().index(); + const current_level = $(`[id="${node.id}"]`).parent().parent().parent().index(); if (last_level === current_level) { this.$hierarchy.append(` @@ -382,7 +382,7 @@ erpnext.HierarchyChart = class { node.$children = $('
    '); const last_level = this.$hierarchy.find('.level:last').index(); - const node_level = $(`#${node.id}`).parent().parent().parent().index(); + const node_level = $(`[id="${node.id}"]`).parent().parent().parent().index(); if (last_level === node_level) { this.$hierarchy.append(` @@ -489,7 +489,7 @@ erpnext.HierarchyChart = class { set_path_attributes(path, parent_id, child_id) { path.setAttribute("data-parent", parent_id); path.setAttribute("data-child", child_id); - const parent = $(`#${parent_id}`); + const parent = $(`[id="${parent_id}"]`); if (parent.hasClass('active')) { path.setAttribute("class", "active-connector"); @@ -513,7 +513,7 @@ erpnext.HierarchyChart = class { } collapse_previous_level_nodes(node) { - let node_parent = $(`#${node.parent_id}`); + let node_parent = $(`[id="${node.parent_id}"]`); let previous_level_nodes = node_parent.parent().parent().children('li'); let node_card = undefined; @@ -545,7 +545,7 @@ erpnext.HierarchyChart = class { setup_node_click_action(node) { let me = this; - let node_element = $(`#${node.id}`); + let node_element = $(`[id="${node.id}"]`); node_element.click(function() { const is_sibling = me.selected_node.parent_id === node.parent_id; @@ -563,7 +563,7 @@ erpnext.HierarchyChart = class { } setup_edit_node_action(node) { - let node_element = $(`#${node.id}`); + let node_element = $(`[id="${node.id}"]`); let me = this; node_element.find('.btn-edit-node').click(function() { @@ -572,7 +572,7 @@ erpnext.HierarchyChart = class { } remove_levels_after_node(node) { - let level = $(`#${node.id}`).parent().parent().parent().index(); + let level = $(`[id="${node.id}"]`).parent().parent().parent().index(); level = $('.hierarchy > li:eq('+ level + ')'); level.nextAll('li').remove(); @@ -595,7 +595,7 @@ erpnext.HierarchyChart = class { const parent = $(path).data('parent'); const child = $(path).data('child'); - if ($(`#${parent}`).length && $(`#${child}`).length) + if ($(`[id="${parent}"]`).length && $(`[id="${child}"]`).length) return; $(path).remove(); diff --git a/erpnext/public/js/hierarchy_chart/hierarchy_chart_mobile.js b/erpnext/public/js/hierarchy_chart/hierarchy_chart_mobile.js index b1a88795572..0a8ba78f643 100644 --- a/erpnext/public/js/hierarchy_chart/hierarchy_chart_mobile.js +++ b/erpnext/public/js/hierarchy_chart/hierarchy_chart_mobile.js @@ -54,7 +54,7 @@ erpnext.HierarchyChartMobile = class { }); node.parent.append(node_card); - node.$link = $(`#${node.id}`); + node.$link = $(`[id="${node.id}"]`); node.$link.addClass('mobile-node'); } @@ -184,7 +184,7 @@ erpnext.HierarchyChartMobile = class { this.refresh_connectors(node.parent_id, node.id); // rebuild incoming connections of parent - let grandparent = $(`#${node.parent_id}`).attr('data-parent'); + let grandparent = $(`[id="${node.parent_id}"]`).attr('data-parent'); this.refresh_connectors(grandparent, node.parent_id); } @@ -221,7 +221,7 @@ erpnext.HierarchyChartMobile = class { show_active_path(node) { // mark node parent on active path - $(`#${node.parent_id}`).addClass('active-path'); + $(`[id="${node.parent_id}"]`).addClass('active-path'); } load_children(node) { @@ -256,7 +256,7 @@ erpnext.HierarchyChartMobile = class { if (child_nodes) { $.each(child_nodes, (_i, data) => { this.add_node(node, data); - $(`#${data.id}`).addClass('active-child'); + $(`[id="${data.id}"]`).addClass('active-child'); setTimeout(() => { this.add_connector(node.id, data.id); @@ -293,9 +293,9 @@ erpnext.HierarchyChartMobile = class { let connector = undefined; - if ($(`#${parent_id}`).hasClass('active')) { + if ($(`[id="${parent_id}"]`).hasClass('active')) { connector = this.get_connector_for_active_node(parent_node, child_node); - } else if ($(`#${parent_id}`).hasClass('active-path')) { + } else if ($(`[id="${parent_id}"]`).hasClass('active-path')) { connector = this.get_connector_for_collapsed_node(parent_node, child_node); } @@ -351,7 +351,7 @@ erpnext.HierarchyChartMobile = class { set_path_attributes(path, parent_id, child_id) { path.setAttribute("data-parent", parent_id); path.setAttribute("data-child", child_id); - const parent = $(`#${parent_id}`); + const parent = $(`[id="${parent_id}"]`); if (parent.hasClass('active')) { path.setAttribute("class", "active-connector"); @@ -374,7 +374,7 @@ erpnext.HierarchyChartMobile = class { setup_node_click_action(node) { let me = this; - let node_element = $(`#${node.id}`); + let node_element = $(`[id="${node.id}"]`); node_element.click(function() { let el = undefined; @@ -398,7 +398,7 @@ erpnext.HierarchyChartMobile = class { } setup_edit_node_action(node) { - let node_element = $(`#${node.id}`); + let node_element = $(`[id="${node.id}"]`); let me = this; node_element.find('.btn-edit-node').click(function() { @@ -512,7 +512,7 @@ erpnext.HierarchyChartMobile = class { } remove_levels_after_node(node) { - let level = $(`#${node.id}`).parent().parent().index(); + let level = $(`[id="${node.id}"]`).parent().parent().index(); level = $('.hierarchy-mobile > li:eq('+ level + ')'); level.nextAll('li').remove(); @@ -533,7 +533,7 @@ erpnext.HierarchyChartMobile = class { const parent = $(path).data('parent'); const child = $(path).data('child'); - if ($(`#${parent}`).length && $(`#${child}`).length) + if ($(`[id="${parent}"]`).length && $(`[id="${child}"]`).length) return; $(path).remove(); diff --git a/erpnext/tests/ui_test_helpers.py b/erpnext/tests/ui_test_helpers.py index 76c7608c91f..9c8c371e051 100644 --- a/erpnext/tests/ui_test_helpers.py +++ b/erpnext/tests/ui_test_helpers.py @@ -7,6 +7,8 @@ def create_employee_records(): create_company() create_missing_designation() + frappe.db.sql("DELETE FROM tabEmployee WHERE company='Test Org Chart'") + emp1 = create_employee('Test Employee 1', 'CEO') emp2 = create_employee('Test Employee 2', 'CTO') emp3 = create_employee('Test Employee 3', 'Head of Marketing and Sales', emp1)