From 1590ac3ec79afadf046a66fc43e9787fe0406a9a Mon Sep 17 00:00:00 2001 From: marination Date: Mon, 11 Oct 2021 23:14:28 +0530 Subject: [PATCH 001/164] fix: Avoid automatic customer creation on website user login (cherry picked from commit d824a90fac68eb76d3f824249e83466201e96b02) # Conflicts: # erpnext/e_commerce/shopping_cart/utils.py --- erpnext/e_commerce/shopping_cart/utils.py | 28 +++++++++++++++-------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/erpnext/e_commerce/shopping_cart/utils.py b/erpnext/e_commerce/shopping_cart/utils.py index 51398596fd8..5d9e239f3f3 100644 --- a/erpnext/e_commerce/shopping_cart/utils.py +++ b/erpnext/e_commerce/shopping_cart/utils.py @@ -1,8 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals - import frappe import frappe.defaults @@ -17,10 +14,21 @@ def show_cart_count(): return False def set_cart_count(login_manager): - role, parties = check_customer_or_supplier() - if role == 'Supplier': return + # since this is run only on hooks login event + # make sure user is already a customer + # before trying to set cart count + user_is_customer = is_customer() + if not user_is_customer: return + if show_cart_count(): +<<<<<<< HEAD:erpnext/e_commerce/shopping_cart/utils.py from erpnext.e_commerce.shopping_cart.cart import set_cart_count +======= + from erpnext.shopping_cart.cart import set_cart_count + # set_cart_count will try to fetch existing cart quotation + # or create one if non existent (and create a customer too) + # cart count is calculated from this quotation's items +>>>>>>> d824a90fac (fix: Avoid automatic customer creation on website user login):erpnext/shopping_cart/utils.py set_cart_count() def clear_cart_count(login_manager): @@ -31,13 +39,13 @@ def update_website_context(context): cart_enabled = is_cart_enabled() context["shopping_cart_enabled"] = cart_enabled -def check_customer_or_supplier(): - if frappe.session.user: +def is_customer(): + if frappe.session.user and frappe.session.user != "Guest": contact_name = frappe.get_value("Contact", {"email_id": frappe.session.user}) if contact_name: contact = frappe.get_doc('Contact', contact_name) for link in contact.links: - if link.link_doctype in ('Customer', 'Supplier'): - return link.link_doctype, link.link_name + if link.link_doctype == 'Customer': + return True - return 'Customer', None + return False From 6b8dad945487d1652c2db445e3c06996100f9047 Mon Sep 17 00:00:00 2001 From: marination Date: Tue, 12 Oct 2021 13:18:28 +0530 Subject: [PATCH 002/164] fix: Sider, Linter - Moved return to next line - Space between function import and body (cherry picked from commit a780f78f38b247e5679053e15ee5113f9065a68e) --- erpnext/e_commerce/shopping_cart/utils.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/e_commerce/shopping_cart/utils.py b/erpnext/e_commerce/shopping_cart/utils.py index 5d9e239f3f3..7665f4b5a9d 100644 --- a/erpnext/e_commerce/shopping_cart/utils.py +++ b/erpnext/e_commerce/shopping_cart/utils.py @@ -18,13 +18,15 @@ def set_cart_count(login_manager): # make sure user is already a customer # before trying to set cart count user_is_customer = is_customer() - if not user_is_customer: return + if not user_is_customer: + return if show_cart_count(): <<<<<<< HEAD:erpnext/e_commerce/shopping_cart/utils.py from erpnext.e_commerce.shopping_cart.cart import set_cart_count ======= from erpnext.shopping_cart.cart import set_cart_count + # set_cart_count will try to fetch existing cart quotation # or create one if non existent (and create a customer too) # cart count is calculated from this quotation's items From f3650b4f9d0f66eb4445161703ef84d552c35330 Mon Sep 17 00:00:00 2001 From: marination Date: Tue, 12 Oct 2021 20:29:13 +0530 Subject: [PATCH 003/164] fix: Merge conflicts with e-commerce --- erpnext/e_commerce/shopping_cart/utils.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/erpnext/e_commerce/shopping_cart/utils.py b/erpnext/e_commerce/shopping_cart/utils.py index 7665f4b5a9d..0cc0ab7c002 100644 --- a/erpnext/e_commerce/shopping_cart/utils.py +++ b/erpnext/e_commerce/shopping_cart/utils.py @@ -22,15 +22,11 @@ def set_cart_count(login_manager): return if show_cart_count(): -<<<<<<< HEAD:erpnext/e_commerce/shopping_cart/utils.py from erpnext.e_commerce.shopping_cart.cart import set_cart_count -======= - from erpnext.shopping_cart.cart import set_cart_count # set_cart_count will try to fetch existing cart quotation # or create one if non existent (and create a customer too) # cart count is calculated from this quotation's items ->>>>>>> d824a90fac (fix: Avoid automatic customer creation on website user login):erpnext/shopping_cart/utils.py set_cart_count() def clear_cart_count(login_manager): From 666e8cd07657b92d9198c699f74a63396e23324d Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Tue, 14 Sep 2021 11:41:19 +0530 Subject: [PATCH 004/164] perf: Optimize get_attribute_filters (#26729) * perf: Optimize get_attribute_filters * fix: handle when filter attributes are undefined * chore: unused imports Co-authored-by: Ankush Menat --- .../e_commerce/product_data_engine/filters.py | 50 ++++++++----------- erpnext/templates/includes/macros.html | 8 +-- 2 files changed, 26 insertions(+), 32 deletions(-) diff --git a/erpnext/e_commerce/product_data_engine/filters.py b/erpnext/e_commerce/product_data_engine/filters.py index f9e3b6ae32e..ba1b2ea9a56 100644 --- a/erpnext/e_commerce/product_data_engine/filters.py +++ b/erpnext/e_commerce/product_data_engine/filters.py @@ -96,38 +96,32 @@ class ProductFiltersBuilder: return attributes = [row.attribute for row in self.doc.filter_attributes] - attribute_docs = [ - frappe.get_doc('Item Attribute', attribute) for attribute in attributes - ] - valid_attributes = [] + if not attributes: + return [] - for attr_doc in attribute_docs: - selected_attributes = [] - for attr in attr_doc.item_attribute_values: - or_filters = [] - filters= [ - ["Item Variant Attribute", "attribute", "=", attr.parent], - ["Item Variant Attribute", "attribute_value", "=", attr.attribute_value] - ] - if self.item_group: - or_filters.extend([ - ["item_group", "=", self.item_group], - ["Website Item Group", "item_group", "=", self.item_group] - ]) + result = frappe.db.sql( + """ + select + distinct attribute, attribute_value + from + `tabItem Variant Attribute` + where + attribute in %(attributes)s + and attribute_value is not null + """, + {"attributes": attributes}, + as_dict=1, + ) - if frappe.db.get_all("Item", filters, or_filters=or_filters, limit=1): - selected_attributes.append(attr) + attribute_value_map = {} + for d in result: + attribute_value_map.setdefault(d.attribute, []).append(d.attribute_value) - if selected_attributes: - valid_attributes.append( - _dict( - item_attribute_values=selected_attributes, - name=attr_doc.name - ) - ) - - return valid_attributes + out = [] + for name, values in attribute_value_map.items(): + out.append(frappe._dict(name=name, item_attribute_values=values)) + return out def get_discount_filters(self, discounts): discount_filters = [] diff --git a/erpnext/templates/includes/macros.html b/erpnext/templates/includes/macros.html index 889cf1ab38d..892d62513e2 100644 --- a/erpnext/templates/includes/macros.html +++ b/erpnext/templates/includes/macros.html @@ -339,15 +339,15 @@
{% for attr_value in attribute.item_attribute_values %}
-
{% endfor %} From 02ddd5f2b02d3d9b53f2ab0f1f6cf27d9061bcd9 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 14 Oct 2021 20:06:07 +0530 Subject: [PATCH 005/164] fix: remove bad description (#27963) (#27964) (cherry picked from commit 4437eb0c4b0bdacb6569d5067e2e3c1aba1191ac) Co-authored-by: Ankush Menat --- .../support/doctype/support_settings/support_settings.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/erpnext/support/doctype/support_settings/support_settings.json b/erpnext/support/doctype/support_settings/support_settings.json index 5d3d3ace59d..bf1daa16f86 100644 --- a/erpnext/support/doctype/support_settings/support_settings.json +++ b/erpnext/support/doctype/support_settings/support_settings.json @@ -37,7 +37,6 @@ }, { "default": "7", - "description": "Auto close Issue after 7 days", "fieldname": "close_issue_after_days", "fieldtype": "Int", "label": "Close Issue After Days" @@ -164,7 +163,7 @@ ], "issingle": 1, "links": [], - "modified": "2020-06-11 13:08:38.473616", + "modified": "2021-10-14 13:08:38.473616", "modified_by": "Administrator", "module": "Support", "name": "Support Settings", @@ -185,4 +184,4 @@ "sort_field": "modified", "sort_order": "DESC", "track_changes": 1 -} \ No newline at end of file +} From 2e9d08f8b1b87b3e64b1562638e2dff13fba8f07 Mon Sep 17 00:00:00 2001 From: marination Date: Thu, 14 Oct 2021 18:29:46 +0530 Subject: [PATCH 006/164] fix: Retain space inside Serial no string while cleaning serial nos (cherry picked from commit 41035b033047dfe00a4a881a7255fd677e78fd90) --- erpnext/controllers/stock_controller.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 4697205d72d..dfb53c7543c 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -79,8 +79,15 @@ class StockController(AccountsController): def clean_serial_nos(self): for row in self.get("items"): if hasattr(row, "serial_no") and row.serial_no: - # replace commas by linefeed and remove all spaces in string - row.serial_no = row.serial_no.replace(",", "\n").replace(" ", "") + # replace commas by linefeed + row.serial_no = row.serial_no.replace(",", "\n") + + # strip preceeding and succeeding spaces for each SN + # (SN could have valid spaces in between e.g. SN - 123 - 2021) + serial_no_list = row.serial_no.split("\n") + serial_no_list = [sn.lstrip().rstrip() for sn in serial_no_list] + + row.serial_no = "\n".join(serial_no_list) def get_gl_entries(self, warehouse_account=None, default_expense_account=None, default_cost_center=None): From db9f56bee85d2eb5da15d61e2e9389820cd6611c Mon Sep 17 00:00:00 2001 From: Marica Date: Thu, 14 Oct 2021 19:21:41 +0530 Subject: [PATCH 007/164] fix: Use strip instead of lstrip and rstrip Co-authored-by: Ankush Menat (cherry picked from commit 8cf188d9c0f195d9caca5caa698ebc9b675e5906) --- erpnext/controllers/stock_controller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index dfb53c7543c..08d422d3bcd 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -85,7 +85,7 @@ class StockController(AccountsController): # strip preceeding and succeeding spaces for each SN # (SN could have valid spaces in between e.g. SN - 123 - 2021) serial_no_list = row.serial_no.split("\n") - serial_no_list = [sn.lstrip().rstrip() for sn in serial_no_list] + serial_no_list = [sn.strip() for sn in serial_no_list] row.serial_no = "\n".join(serial_no_list) From 0d1b04326af1104bbb07f93ecc171ec567490f61 Mon Sep 17 00:00:00 2001 From: marination Date: Thu, 14 Oct 2021 19:25:14 +0530 Subject: [PATCH 008/164] test: Include serial no with spaces in it in sanitation test (cherry picked from commit a9341672cf6c33d0d7df67dfe828dc8a7a26a940) --- erpnext/stock/doctype/serial_no/test_serial_no.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/stock/doctype/serial_no/test_serial_no.py b/erpnext/stock/doctype/serial_no/test_serial_no.py index 546e21bde04..570f22e6af3 100644 --- a/erpnext/stock/doctype/serial_no/test_serial_no.py +++ b/erpnext/stock/doctype/serial_no/test_serial_no.py @@ -184,14 +184,14 @@ class TestSerialNo(ERPNextTestCase): se = frappe.copy_doc(test_records[0]) se.get("items")[0].item_code = item_code - se.get("items")[0].qty = 3 - se.get("items")[0].serial_no = " _TS1, _TS2 , _TS3 " - se.get("items")[0].transfer_qty = 3 + se.get("items")[0].qty = 4 + se.get("items")[0].serial_no = " _TS1, _TS2 , _TS3 , _TS4 - 2021" + se.get("items")[0].transfer_qty = 4 se.set_stock_entry_type() se.insert() se.submit() - self.assertEqual(se.get("items")[0].serial_no, "_TS1\n_TS2\n_TS3") + self.assertEqual(se.get("items")[0].serial_no, "_TS1\n_TS2\n_TS3\n_TS4 - 2021") frappe.db.rollback() From 268893d5d0c8f9786c6554458310d4729a6f97a8 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 15 Oct 2021 14:32:55 +0530 Subject: [PATCH 009/164] fix: POS Profile payment methods table (#27956) (#27966) Co-authored-by: Afshan <33727827+AfshanKhan@users.noreply.github.com> (cherry picked from commit f3cf36f6130983456bce96d1fbcf8c953bff8f1a) Co-authored-by: Subin Tom <36098155+nemesis189@users.noreply.github.com> --- erpnext/accounts/doctype/pos_profile/pos_profile.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/pos_profile/pos_profile.json b/erpnext/accounts/doctype/pos_profile/pos_profile.json index 8afa0abd36c..9c9f37bba27 100644 --- a/erpnext/accounts/doctype/pos_profile/pos_profile.json +++ b/erpnext/accounts/doctype/pos_profile/pos_profile.json @@ -120,6 +120,7 @@ { "fieldname": "payments", "fieldtype": "Table", + "label": "Payment Methods", "options": "POS Payment Method", "reqd": 1 }, @@ -377,7 +378,7 @@ "link_fieldname": "pos_profile" } ], - "modified": "2021-02-01 13:52:51.081311", + "modified": "2021-10-14 14:17:00.469298", "modified_by": "Administrator", "module": "Accounts", "name": "POS Profile", From 289650748c727b51c5033478f501414c9c3fa834 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sat, 16 Oct 2021 17:09:37 +0530 Subject: [PATCH 010/164] fix: Interstate internal transfer invoices not visible in GSTR-1 (cherry picked from commit d9d42b13ab70f4677a5e8ba45af1cbaf484dedc4) --- erpnext/regional/report/gstr_1/gstr_1.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py index 23924c5fb66..7d401bab669 100644 --- a/erpnext/regional/report/gstr_1/gstr_1.py +++ b/erpnext/regional/report/gstr_1/gstr_1.py @@ -172,13 +172,6 @@ class Gstr1Report(object): self.invoices = frappe._dict() conditions = self.get_conditions() - company_gstins = get_company_gstin_number(self.filters.get('company'), all_gstins=True) - - if company_gstins: - self.filters.update({ - 'company_gstins': company_gstins - }) - invoice_data = frappe.db.sql(""" select {select_columns} @@ -242,7 +235,7 @@ class Gstr1Report(object): elif self.filters.get("type_of_business") == "EXPORT": conditions += """ AND is_return !=1 and gst_category = 'Overseas' """ - conditions += " AND IFNULL(billing_address_gstin, '') NOT IN %(company_gstins)s" + conditions += " AND IFNULL(billing_address_gstin, '') != company_gstin" return conditions From 50e718f5080838cb2842d2d89f09c1019fdd4857 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 13 Oct 2021 11:58:42 +0530 Subject: [PATCH 011/164] fix: TDS round off not working from second transaction (cherry picked from commit ca0067212dd153566df2965c12aace641ff68720) --- .../tax_withholding_category/tax_withholding_category.py | 3 +++ 1 file changed, 3 insertions(+) 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 16ef5fc9745..cc8ef58e203 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -191,6 +191,9 @@ def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=N tax_amount = get_tds_amount_from_ldc(ldc, parties, pan_no, tax_details, posting_date, net_total) else: tax_amount = net_total * tax_details.rate / 100 if net_total > 0 else 0 + + if cint(tax_details.round_off_tax_amount): + tax_amount = round(tax_amount) else: tax_amount = get_tds_amount(ldc, parties, inv, tax_details, tax_deducted, vouchers) From 9b71e02003406c55ba8f9398505495769a2a91f1 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sat, 16 Oct 2021 20:58:24 +0530 Subject: [PATCH 012/164] fix: TDS round off not working from second transaction (cherry picked from commit b7a08535b5b4e47f89a1ea642f8e71953f357cff) --- .../tax_withholding_category/tax_withholding_category.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) 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 cc8ef58e203..c3cb8396d0d 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -191,9 +191,6 @@ def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=N tax_amount = get_tds_amount_from_ldc(ldc, parties, pan_no, tax_details, posting_date, net_total) else: tax_amount = net_total * tax_details.rate / 100 if net_total > 0 else 0 - - if cint(tax_details.round_off_tax_amount): - tax_amount = round(tax_amount) else: tax_amount = get_tds_amount(ldc, parties, inv, tax_details, tax_deducted, vouchers) @@ -206,6 +203,9 @@ def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=N # then chargeable value is "prev invoices + advances" value which cross the threshold tax_amount = get_tcs_amount(parties, inv, tax_details, vouchers, advance_vouchers) + if cint(tax_details.round_off_tax_amount): + tax_amount = round(tax_amount) + return tax_amount, tax_deducted def get_invoice_vouchers(parties, tax_details, company, party_type='Supplier'): @@ -325,9 +325,6 @@ def get_tds_amount(ldc, parties, inv, tax_details, tax_deducted, vouchers): else: tds_amount = supp_credit_amt * tax_details.rate / 100 if supp_credit_amt > 0 else 0 - if cint(tax_details.round_off_tax_amount): - tds_amount = round(tds_amount) - return tds_amount def get_tcs_amount(parties, inv, tax_details, vouchers, adv_vouchers): From f2340b360f33cabd185c74ee1b8577aebd1ddd06 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 15 Oct 2021 21:19:41 +0530 Subject: [PATCH 013/164] fix: Account number and name incorrectly import using COA importer (cherry picked from commit 17a8649500dee2989b2f1231434a207288c7e3a7) --- .../account/chart_of_accounts/chart_of_accounts.py | 13 ++++++++----- .../chart_of_accounts_importer.py | 7 +++++-- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py b/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py index d6ccd169362..05caafe1c47 100644 --- a/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py +++ b/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py @@ -12,7 +12,7 @@ from six import iteritems from unidecode import unidecode -def create_charts(company, chart_template=None, existing_company=None, custom_chart=None): +def create_charts(company, chart_template=None, existing_company=None, custom_chart=None, from_coa_importer=None): chart = custom_chart or get_chart(chart_template, existing_company) if chart: accounts = [] @@ -22,7 +22,7 @@ def create_charts(company, chart_template=None, existing_company=None, custom_ch if root_account: root_type = child.get("root_type") - if account_name not in ["account_number", "account_type", + if account_name not in ["account_name", "account_number", "account_type", "root_type", "is_group", "tax_rate"]: account_number = cstr(child.get("account_number")).strip() @@ -35,7 +35,7 @@ def create_charts(company, chart_template=None, existing_company=None, custom_ch account = frappe.get_doc({ "doctype": "Account", - "account_name": account_name, + "account_name": child.get('account_name') if from_coa_importer else account_name, "company": company, "parent_account": parent, "is_group": is_group, @@ -213,7 +213,7 @@ def validate_bank_account(coa, bank_account): return (bank_account in accounts) @frappe.whitelist() -def build_tree_from_json(chart_template, chart_data=None): +def build_tree_from_json(chart_template, chart_data=None, from_coa_importer=False): ''' get chart template from its folder and parse the json to be rendered as tree ''' chart = chart_data or get_chart(chart_template) @@ -226,9 +226,12 @@ def build_tree_from_json(chart_template, chart_data=None): ''' recursively called to form a parent-child based list of dict from chart template ''' for account_name, child in iteritems(children): account = {} - if account_name in ["account_number", "account_type",\ + if account_name in ["account_name", "account_number", "account_type",\ "root_type", "is_group", "tax_rate"]: continue + if from_coa_importer: + account_name = child['account_name'] + account['parent_account'] = parent account['expandable'] = True if identify_is_group(child) else False account['value'] = (cstr(child.get('account_number')).strip() + ' - ' + account_name) \ 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 5e596f8677d..eabe408d640 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 @@ -69,7 +69,7 @@ def import_coa(file_name, company): frappe.local.flags.ignore_root_company_validation = True forest = build_forest(data) - create_charts(company, custom_chart=forest) + create_charts(company, custom_chart=forest, from_coa_importer=True) # trigger on_update for company to reset default accounts set_default_accounts(company) @@ -148,7 +148,7 @@ def get_coa(doctype, parent, is_root=False, file_name=None, for_validate=0): if not for_validate: forest = build_forest(data) - accounts = build_tree_from_json("", chart_data=forest) # returns a list of dict in a tree render-able form + accounts = build_tree_from_json("", chart_data=forest, from_coa_importer=True) # returns a list of dict in a tree render-able form # filter out to show data for the selected node only accounts = [d for d in accounts if d['parent_account']==parent] @@ -212,11 +212,14 @@ def build_forest(data): if not account_name: error_messages.append("Row {0}: Please enter Account Name".format(line_no)) + name = account_name if account_number: account_number = cstr(account_number).strip() account_name = "{} - {}".format(account_number, account_name) charts_map[account_name] = {} + charts_map[account_name]['account_name'] = name + if account_number: charts_map[account_name]["account_number"] = account_number if cint(is_group) == 1: charts_map[account_name]["is_group"] = is_group if account_type: charts_map[account_name]["account_type"] = account_type if root_type: charts_map[account_name]["root_type"] = root_type From acc5cff7ff3180ba5dea5b6d8828a4a301db01a5 Mon Sep 17 00:00:00 2001 From: pateljannat Date: Thu, 7 Oct 2021 17:10:45 +0530 Subject: [PATCH 014/164] fix: exclude inactive employees from auto attendance (cherry picked from commit 921b4be3481af9745811ca197b08dded8296da95) --- erpnext/hr/doctype/shift_type/shift_type.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/hr/doctype/shift_type/shift_type.py b/erpnext/hr/doctype/shift_type/shift_type.py index e53373df279..7a35b28ac43 100644 --- a/erpnext/hr/doctype/shift_type/shift_type.py +++ b/erpnext/hr/doctype/shift_type/shift_type.py @@ -97,7 +97,7 @@ class ShiftType(Document): assigned_employees = [x[0] for x in assigned_employees] if consider_default_shift: - filters = {'default_shift': self.name} + filters = {'default_shift': self.name, 'status': ['!=', 'Inactive']} default_shift_employees = frappe.get_all('Employee', 'name', filters, as_list=True) default_shift_employees = [x[0] for x in default_shift_employees] return list(set(assigned_employees+default_shift_employees)) From f328b226fbeebdef575d7e8eb9eac35ed582e297 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 18 Oct 2021 11:47:12 +0530 Subject: [PATCH 015/164] fix: flaky Org Chart Test (#27971) (#27988) (cherry picked from commit 8eacaddde73f29f8b9c6de43547a56f6be5a6687) Co-authored-by: Rucha Mahabal --- .../integration/test_organizational_chart_desktop.js | 2 +- .../integration/test_organizational_chart_mobile.js | 2 +- .../js/hierarchy_chart/hierarchy_chart_desktop.js | 10 ++++++---- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/cypress/integration/test_organizational_chart_desktop.js b/cypress/integration/test_organizational_chart_desktop.js index 79e08b3bbad..464cce48d03 100644 --- a/cypress/integration/test_organizational_chart_desktop.js +++ b/cypress/integration/test_organizational_chart_desktop.js @@ -24,7 +24,7 @@ context('Organizational Chart', () => { cy.get('.frappe-control[data-fieldname=company] input').focus().as('input'); cy.get('@input') .clear({ force: true }) - .type('Test Org Chart{enter}', { force: true }) + .type('Test Org Chart{downarrow}{enter}', { force: true }) .blur({ force: true }); }); }); diff --git a/cypress/integration/test_organizational_chart_mobile.js b/cypress/integration/test_organizational_chart_mobile.js index 161fae098a2..971ac6d3ef3 100644 --- a/cypress/integration/test_organizational_chart_mobile.js +++ b/cypress/integration/test_organizational_chart_mobile.js @@ -25,7 +25,7 @@ context('Organizational Chart Mobile', () => { cy.get('.frappe-control[data-fieldname=company] input').focus().as('input'); cy.get('@input') .clear({ force: true }) - .type('Test Org Chart{enter}', { force: true }) + .type('Test Org Chart{downarrow}{enter}', { force: true }) .blur({ force: true }); }); }); diff --git a/erpnext/public/js/hierarchy_chart/hierarchy_chart_desktop.js b/erpnext/public/js/hierarchy_chart/hierarchy_chart_desktop.js index 7b358195c3e..831626aa915 100644 --- a/erpnext/public/js/hierarchy_chart/hierarchy_chart_desktop.js +++ b/erpnext/public/js/hierarchy_chart/hierarchy_chart_desktop.js @@ -334,10 +334,12 @@ erpnext.HierarchyChart = class { if (child_nodes) { $.each(child_nodes, (_i, data) => { - this.add_node(node, data); - setTimeout(() => { - this.add_connector(node.id, data.id); - }, 250); + if (!$(`[id="${data.id}"]`).length) { + this.add_node(node, data); + setTimeout(() => { + this.add_connector(node.id, data.id); + }, 250); + } }); } } From 37088391bb609df085558f8662109b151c9c9162 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 19 Oct 2021 10:56:33 +0530 Subject: [PATCH 016/164] feat: add enabled field in UOM (#27993) (#27996) (cherry picked from commit 7717b99edbae6a6d85c5582cf11c1a25eb9c3a5e) Co-authored-by: Himanshu --- erpnext/setup/doctype/uom/uom.json | 126 +++++------------------------ 1 file changed, 22 insertions(+), 104 deletions(-) diff --git a/erpnext/setup/doctype/uom/uom.json b/erpnext/setup/doctype/uom/uom.json index 3a4e7f6dc4b..844a11f1397 100644 --- a/erpnext/setup/doctype/uom/uom.json +++ b/erpnext/setup/doctype/uom/uom.json @@ -1,164 +1,82 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, + "actions": [], "allow_import": 1, "allow_rename": 1, "autoname": "field:uom_name", - "beta": 0, "creation": "2013-01-10 16:34:24", - "custom": 0, - "docstatus": 0, "doctype": "DocType", "document_type": "Setup", - "editable_grid": 0, + "engine": "InnoDB", + "field_order": [ + "enabled", + "uom_name", + "must_be_whole_number" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "uom_name", "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, "in_list_view": 1, - "in_standard_filter": 0, "label": "UOM Name", - "length": 0, - "no_copy": 0, "oldfieldname": "uom_name", "oldfieldtype": "Data", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, "unique": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, + "default": "0", "description": "Check this to disallow fractions. (for Nos)", "fieldname": "must_be_whole_number", "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Must be Whole Number", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Must be Whole Number" + }, + { + "default": "1", + "fieldname": "enabled", + "fieldtype": "Check", + "label": "Enabled" } ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, "icon": "fa fa-compass", "idx": 1, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2018-08-29 06:35:56.143361", + "links": [], + "modified": "2021-10-18 14:07:43.722144", "modified_by": "Administrator", "module": "Setup", "name": "UOM", + "naming_rule": "By fieldname", "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, "create": 1, "delete": 1, "email": 1, "export": 1, - "if_owner": 0, "import": 1, - "permlevel": 0, "print": 1, "read": 1, "report": 1, "role": "Item Manager", - "set_user_permissions": 0, "share": 1, - "submit": 0, "write": 1 }, { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, "print": 1, "read": 1, "report": 1, - "role": "Stock Manager", - "set_user_permissions": 0, - "share": 0, - "submit": 0, - "write": 0 + "role": "Stock Manager" }, { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, "print": 1, "read": 1, "report": 1, - "role": "Stock User", - "set_user_permissions": 0, - "share": 0, - "submit": 0, - "write": 0 + "role": "Stock User" } ], "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, "show_name_in_global_search": 1, - "sort_order": "ASC", - "track_changes": 0, - "track_seen": 0, - "track_views": 0 + "sort_field": "modified", + "sort_order": "ASC" } \ No newline at end of file From efc292a5ddc00e433d75b87d6b3378455bac4438 Mon Sep 17 00:00:00 2001 From: Govind S Menokee Date: Tue, 19 Oct 2021 12:49:00 +0530 Subject: [PATCH 017/164] YTD and MTD Messed up in Salary Slip The filter for YTD, MTD etc are based on employee name. This seems like an amateur mistake. It should be based on employee id. --- erpnext/payroll/doctype/salary_slip/salary_slip.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py index 888150f0ae3..8ebf1af99ef 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py @@ -1248,7 +1248,7 @@ class SalarySlip(TransactionBase): salary_slip_sum = frappe.get_list('Salary Slip', fields = ['sum(net_pay) as net_sum', 'sum(gross_pay) as gross_sum'], - filters = {'employee_name' : self.employee_name, + filters = {'employee' : self.employee, 'start_date' : ['>=', period_start_date], 'end_date' : ['<', period_end_date], 'name': ['!=', self.name], @@ -1268,7 +1268,7 @@ class SalarySlip(TransactionBase): first_day_of_the_month = get_first_day(self.start_date) salary_slip_sum = frappe.get_list('Salary Slip', fields = ['sum(net_pay) as sum'], - filters = {'employee_name' : self.employee_name, + filters = {'employee' : self.employee, 'start_date' : ['>=', first_day_of_the_month], 'end_date' : ['<', self.start_date], 'name': ['!=', self.name], @@ -1292,13 +1292,13 @@ class SalarySlip(TransactionBase): INNER JOIN `tabSalary Slip` as salary_slip ON detail.parent = salary_slip.name WHERE - salary_slip.employee_name = %(employee_name)s + salary_slip.employee = %(employee)s AND detail.salary_component = %(component)s AND salary_slip.start_date >= %(period_start_date)s AND salary_slip.end_date < %(period_end_date)s AND salary_slip.name != %(docname)s AND salary_slip.docstatus = 1""", - {'employee_name': self.employee_name, 'component': component.salary_component, 'period_start_date': period_start_date, + {'employee': self.employee, 'component': component.salary_component, 'period_start_date': period_start_date, 'period_end_date': period_end_date, 'docname': self.name} ) From 3af060c78751de312636253eb9820981a2846512 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 19 Oct 2021 13:38:50 +0530 Subject: [PATCH 018/164] fix: TaxJar update - added nexus list, making api call only for nexus (backport #27497) (#27940) * fix: TaxJar update - nexus, selective api call (cherry picked from commit b01fe1c3e2db9f1cd39a8a96c3539dbf50d59bf6) * fix: sales_tax attribute in api call before submit (cherry picked from commit 3bb60a439a54350765d82c017bfa061cf41004e0) * Update erpnext/erpnext_integrations/taxjar_integration.py Co-authored-by: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> (cherry picked from commit 0e527311b959fcb0d980ebebff671c75ca997f28) * Update erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.py Co-authored-by: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> (cherry picked from commit 486d7c3a39fb77f23de49d650b91c05758c7fbd7) * Update erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.js Co-authored-by: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> (cherry picked from commit 435a5e4fa318801c7b6e0f84c37a5894ed4c2043) * Update erpnext/erpnext_integrations/taxjar_integration.py Co-authored-by: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> (cherry picked from commit 11bd42467e5346c1d286c2fd9ec4b1d032b32d85) * fix: Renamed child table doctype, delete taxes for non nexus state (cherry picked from commit 5c18654113d58363aa52dde30be3b65621747363) * fix: updated patch, add fields only if fields are checked (cherry picked from commit 54754f4eb8a615d92d52d1322dad41ccfe9e8556) # Conflicts: # erpnext/patches/v13_0/custom_fields_for_taxjar_integration.py * fix: patch fix, fields disabling (cherry picked from commit 0a28fed679a6ce22a9d88d45ca3f05ea7cdb68d9) * fix: using db.exists and get_value instead of get_doc (cherry picked from commit 8675ca5bdd838460d56be8eadf6790cadc605ba3) * fix: dt instead of document in set_value query (cherry picked from commit d3bb920e715a49ee47e52eef1b53d40dc573d877) * minor fixes (cherry picked from commit 254b20bc093805a6642e6ddedcd8917a535d42b1) * fix: improved on_update method, added validation for tax calculation, sandbox mode checks (cherry picked from commit 7114659eccfa13c50ba6ce8d7fb2ad74838169d5) * fix: linters fix (cherry picked from commit 3ece05a9f7976a031efcb441defa77d51c351145) * fix: patch fix added reload_doctype (cherry picked from commit eaa3614155838df0ddcdb11f3154a9cb94f86b11) * fix: patch fixes- force reload doc, check for company (cherry picked from commit 1b25e69af4f4069c5e8df713bc852aee06b8acb8) * fix: 'Taxjar' type fix (cherry picked from commit ea2038489fd6b225154eb0d11bf9ce5eb5983e14) * fix: Update pacthes.txt (cherry picked from commit 5d4c919c5ce8ce0c112e368fb94b1fb862ea3c78) * fix: Patch (cherry picked from commit 2d19e2d54b3d03f70996ac104508a14b21bc2911) # Conflicts: # erpnext/patches.txt * fix: Move product tax category folder to taxjar settings (cherry picked from commit bd8cfb2e3079012f4e9c0732f3d14db93917bd31) * Update custom_fields_for_taxjar_integration.py * fix: conflicts * fix: linter issues removed extra line * fix: patch fix einvoicing deprecation patch removed Co-authored-by: Subin Tom Co-authored-by: Subin Tom <36098155+nemesis189@users.noreply.github.com> Co-authored-by: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Co-authored-by: Deepesh Garg Co-authored-by: Afshan <33727827+AfshanKhan@users.noreply.github.com> --- .../doctype/taxjar_nexus/__init__.py | 0 .../doctype/taxjar_nexus/taxjar_nexus.json | 51 ++++++++++ .../doctype/taxjar_nexus/taxjar_nexus.py | 9 ++ .../product_tax_category_data.json | 0 .../taxjar_settings/taxjar_settings.js | 13 ++- .../taxjar_settings/taxjar_settings.json | 28 +++++- .../taxjar_settings/taxjar_settings.py | 93 ++++++++++++++++++- .../taxjar_integration.py | 29 +++++- erpnext/patches.txt | 2 + .../custom_fields_for_taxjar_integration.py | 11 ++- erpnext/regional/united_states/setup.py | 40 -------- 11 files changed, 223 insertions(+), 53 deletions(-) create mode 100644 erpnext/erpnext_integrations/doctype/taxjar_nexus/__init__.py create mode 100644 erpnext/erpnext_integrations/doctype/taxjar_nexus/taxjar_nexus.json create mode 100644 erpnext/erpnext_integrations/doctype/taxjar_nexus/taxjar_nexus.py rename erpnext/{regional/united_states => erpnext_integrations/doctype/taxjar_settings}/product_tax_category_data.json (100%) diff --git a/erpnext/erpnext_integrations/doctype/taxjar_nexus/__init__.py b/erpnext/erpnext_integrations/doctype/taxjar_nexus/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/erpnext_integrations/doctype/taxjar_nexus/taxjar_nexus.json b/erpnext/erpnext_integrations/doctype/taxjar_nexus/taxjar_nexus.json new file mode 100644 index 00000000000..d4d4a512b58 --- /dev/null +++ b/erpnext/erpnext_integrations/doctype/taxjar_nexus/taxjar_nexus.json @@ -0,0 +1,51 @@ +{ + "actions": [], + "allow_rename": 1, + "creation": "2021-09-11 05:09:53.773838", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "region", + "region_code", + "country", + "country_code" + ], + "fields": [ + { + "fieldname": "region", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Region" + }, + { + "fieldname": "region_code", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Region Code" + }, + { + "fieldname": "country", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Country" + }, + { + "fieldname": "country_code", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Country Code" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-09-14 05:33:06.444710", + "modified_by": "Administrator", + "module": "ERPNext Integrations", + "name": "TaxJar Nexus", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/erpnext_integrations/doctype/taxjar_nexus/taxjar_nexus.py b/erpnext/erpnext_integrations/doctype/taxjar_nexus/taxjar_nexus.py new file mode 100644 index 00000000000..c24aa8ca7d4 --- /dev/null +++ b/erpnext/erpnext_integrations/doctype/taxjar_nexus/taxjar_nexus.py @@ -0,0 +1,9 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class TaxJarNexus(Document): + pass diff --git a/erpnext/regional/united_states/product_tax_category_data.json b/erpnext/erpnext_integrations/doctype/taxjar_settings/product_tax_category_data.json similarity index 100% rename from erpnext/regional/united_states/product_tax_category_data.json rename to erpnext/erpnext_integrations/doctype/taxjar_settings/product_tax_category_data.json diff --git a/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.js b/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.js index 62d5709f51f..d49598932fe 100644 --- a/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.js +++ b/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.js @@ -5,5 +5,16 @@ frappe.ui.form.on('TaxJar Settings', { is_sandbox: (frm) => { frm.toggle_reqd("api_key", !frm.doc.is_sandbox); frm.toggle_reqd("sandbox_api_key", frm.doc.is_sandbox); - } + }, + + refresh: (frm) => { + frm.add_custom_button(__('Update Nexus List'), function() { + frm.call({ + doc: frm.doc, + method: 'update_nexus_list' + }); + }); + }, + + }); diff --git a/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.json b/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.json index c0d60f7a317..2d17f2ed832 100644 --- a/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.json +++ b/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.json @@ -6,8 +6,8 @@ "editable_grid": 1, "engine": "InnoDB", "field_order": [ - "is_sandbox", "taxjar_calculate_tax", + "is_sandbox", "taxjar_create_transactions", "credentials", "api_key", @@ -16,7 +16,10 @@ "configuration", "tax_account_head", "configuration_cb", - "shipping_account_head" + "shipping_account_head", + "section_break_12", + "nexus_address", + "nexus" ], "fields": [ { @@ -54,6 +57,7 @@ }, { "default": "0", + "depends_on": "taxjar_calculate_tax", "fieldname": "is_sandbox", "fieldtype": "Check", "label": "Sandbox Mode" @@ -69,6 +73,7 @@ }, { "default": "0", + "depends_on": "taxjar_calculate_tax", "fieldname": "taxjar_create_transactions", "fieldtype": "Check", "label": "Create TaxJar Transaction" @@ -82,11 +87,28 @@ { "fieldname": "cb_keys", "fieldtype": "Column Break" + }, + { + "fieldname": "section_break_12", + "fieldtype": "Section Break", + "label": "Nexus List" + }, + { + "fieldname": "nexus_address", + "fieldtype": "HTML", + "label": "Nexus Address" + }, + { + "fieldname": "nexus", + "fieldtype": "Table", + "label": "Nexus", + "options": "TaxJar Nexus", + "read_only": 1 } ], "issingle": 1, "links": [], - "modified": "2020-04-30 04:38:03.311089", + "modified": "2021-10-06 10:59:13.475442", "modified_by": "Administrator", "module": "ERPNext Integrations", "name": "TaxJar Settings", diff --git a/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.py b/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.py index 9dd481747ec..f430a9e9bae 100644 --- a/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.py +++ b/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.py @@ -4,9 +4,98 @@ from __future__ import unicode_literals -# import frappe +import json +import os + +import frappe +from frappe.custom.doctype.custom_field.custom_field import create_custom_fields from frappe.model.document import Document +from frappe.permissions import add_permission, update_permission_property + +from erpnext.erpnext_integrations.taxjar_integration import get_client class TaxJarSettings(Document): - pass + + def on_update(self): + TAXJAR_CREATE_TRANSACTIONS = frappe.db.get_single_value("TaxJar Settings", "taxjar_create_transactions") + TAXJAR_CALCULATE_TAX = frappe.db.get_single_value("TaxJar Settings", "taxjar_calculate_tax") + TAXJAR_SANDBOX_MODE = frappe.db.get_single_value("TaxJar Settings", "is_sandbox") + + fields_already_exist = frappe.db.exists('Custom Field', {'dt': ('in', ['Item','Sales Invoice Item']), 'fieldname':'product_tax_category'}) + fields_hidden = frappe.get_value('Custom Field', {'dt': ('in', ['Sales Invoice Item'])}, 'hidden') + + if (TAXJAR_CREATE_TRANSACTIONS or TAXJAR_CALCULATE_TAX or TAXJAR_SANDBOX_MODE): + if not fields_already_exist: + add_product_tax_categories() + make_custom_fields() + add_permissions() + frappe.enqueue('erpnext.regional.united_states.setup.add_product_tax_categories', now=False) + + elif fields_already_exist and fields_hidden: + toggle_tax_category_fields(hidden='0') + + elif fields_already_exist: + toggle_tax_category_fields(hidden='1') + + def validate(self): + self.calculate_taxes_validation_for_create_transactions() + + @frappe.whitelist() + def update_nexus_list(self): + client = get_client() + nexus = client.nexus_regions() + + new_nexus_list = [frappe._dict(address) for address in nexus] + + self.set('nexus', []) + self.set('nexus', new_nexus_list) + self.save() + + def calculate_taxes_validation_for_create_transactions(self): + if not self.taxjar_calculate_tax and (self.taxjar_create_transactions or self.is_sandbox): + frappe.throw(frappe._('Before enabling Create Transaction or Sandbox Mode, you need to check the Enable Tax Calculation box')) + + +def toggle_tax_category_fields(hidden): + frappe.set_value('Custom Field', {'dt':'Sales Invoice Item', 'fieldname':'product_tax_category'}, 'hidden', hidden) + frappe.set_value('Custom Field', {'dt':'Item', 'fieldname':'product_tax_category'}, 'hidden', hidden) + + +def add_product_tax_categories(): + with open(os.path.join(os.path.dirname(__file__), 'product_tax_category_data.json'), 'r') as f: + tax_categories = json.loads(f.read()) + create_tax_categories(tax_categories['categories']) + +def create_tax_categories(data): + for d in data: + if not frappe.db.exists('Product Tax Category',{'product_tax_code':d.get('product_tax_code')}): + tax_category = frappe.new_doc('Product Tax Category') + tax_category.description = d.get("description") + tax_category.product_tax_code = d.get("product_tax_code") + tax_category.category_name = d.get("name") + tax_category.db_insert() + +def make_custom_fields(update=True): + custom_fields = { + 'Sales Invoice Item': [ + dict(fieldname='product_tax_category', fieldtype='Link', insert_after='description', options='Product Tax Category', + label='Product Tax Category', fetch_from='item_code.product_tax_category'), + dict(fieldname='tax_collectable', fieldtype='Currency', insert_after='net_amount', + label='Tax Collectable', read_only=1), + dict(fieldname='taxable_amount', fieldtype='Currency', insert_after='tax_collectable', + label='Taxable Amount', read_only=1) + ], + 'Item': [ + dict(fieldname='product_tax_category', fieldtype='Link', insert_after='item_group', options='Product Tax Category', + label='Product Tax Category') + ] + } + create_custom_fields(custom_fields, update=update) + +def add_permissions(): + doctype = "Product Tax Category" + for role in ('Accounts Manager', 'Accounts User', 'System Manager','Item Manager', 'Stock Manager'): + add_permission(doctype, role, 0) + update_permission_property(doctype, role, 0, 'write', 1) + update_permission_property(doctype, role, 0, 'create', 1) diff --git a/erpnext/erpnext_integrations/taxjar_integration.py b/erpnext/erpnext_integrations/taxjar_integration.py index 870a4ef54cc..2a7243c2430 100644 --- a/erpnext/erpnext_integrations/taxjar_integration.py +++ b/erpnext/erpnext_integrations/taxjar_integration.py @@ -4,7 +4,7 @@ import frappe import taxjar from frappe import _ from frappe.contacts.doctype.address.address import get_company_address -from frappe.utils import cint +from frappe.utils import cint, flt from erpnext import get_default_company @@ -103,7 +103,7 @@ def get_tax_data(doc): shipping = sum([tax.tax_amount for tax in doc.taxes if tax.account_head == SHIP_ACCOUNT_HEAD]) - line_items = [get_line_item_dict(item) for item in doc.items] + line_items = [get_line_item_dict(item, doc.docstatus) for item in doc.items] if from_shipping_state not in SUPPORTED_STATE_CODES: from_shipping_state = get_state_code(from_address, 'Company') @@ -139,14 +139,21 @@ def get_state_code(address, location): return state_code -def get_line_item_dict(item): - return dict( +def get_line_item_dict(item, docstatus): + tax_dict = dict( id = item.get('idx'), quantity = item.get('qty'), unit_price = item.get('rate'), product_tax_code = item.get('product_tax_category') ) + if docstatus == 1: + tax_dict.update({ + 'sales_tax':item.get('tax_collectable') + }) + + return tax_dict + def set_sales_tax(doc, method): if not TAXJAR_CALCULATE_TAX: return @@ -164,6 +171,9 @@ def set_sales_tax(doc, method): setattr(doc, "taxes", [tax for tax in doc.taxes if tax.account_head != TAX_ACCOUNT_HEAD]) return + # check if delivering within a nexus + check_for_nexus(doc, tax_dict) + tax_data = validate_tax_request(tax_dict) if tax_data is not None: if not tax_data.amount_to_collect: @@ -191,6 +201,17 @@ def set_sales_tax(doc, method): doc.run_method("calculate_taxes_and_totals") +def check_for_nexus(doc, tax_dict): + if not frappe.db.get_value('TaxJar Nexus', {'region_code': tax_dict["to_state"]}): + for item in doc.get("items"): + item.tax_collectable = flt(0) + item.taxable_amount = flt(0) + + for tax in doc.taxes: + if tax.account_head == TAX_ACCOUNT_HEAD: + doc.taxes.remove(tax) + return + def check_sales_tax_exemption(doc): # if the party is exempt from sales tax, then set all tax account heads to zero sales_tax_exempted = hasattr(doc, "exempt_from_sales_tax") and doc.exempt_from_sales_tax \ diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 4ce8717952d..41d14bd7402 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -306,6 +306,8 @@ erpnext.patches.v13_0.add_custom_field_for_south_africa #2 erpnext.patches.v13_0.rename_discharge_ordered_date_in_ip_record erpnext.patches.v13_0.migrate_stripe_api erpnext.patches.v13_0.reset_clearance_date_for_intracompany_payment_entries +execute:frappe.reload_doc("erpnext_integrations", "doctype", "TaxJar Settings") +execute:frappe.reload_doc("erpnext_integrations", "doctype", "Product Tax Category") erpnext.patches.v13_0.custom_fields_for_taxjar_integration erpnext.patches.v13_0.set_operation_time_based_on_operating_cost erpnext.patches.v13_0.validate_options_for_data_field diff --git a/erpnext/patches/v13_0/custom_fields_for_taxjar_integration.py b/erpnext/patches/v13_0/custom_fields_for_taxjar_integration.py index 43a9aeb6fe6..e136d64bb56 100644 --- a/erpnext/patches/v13_0/custom_fields_for_taxjar_integration.py +++ b/erpnext/patches/v13_0/custom_fields_for_taxjar_integration.py @@ -3,7 +3,7 @@ from __future__ import unicode_literals import frappe from frappe.custom.doctype.custom_field.custom_field import create_custom_fields -from erpnext.regional.united_states.setup import add_permissions +from erpnext.erpnext_integrations.doctype.taxjar_settings.taxjar_settings import add_permissions def execute(): @@ -11,7 +11,12 @@ def execute(): if not company: return - frappe.reload_doc("regional", "doctype", "product_tax_category") + TAXJAR_CREATE_TRANSACTIONS = frappe.db.get_single_value("TaxJar Settings", "taxjar_create_transactions") + TAXJAR_CALCULATE_TAX = frappe.db.get_single_value("TaxJar Settings", "taxjar_calculate_tax") + TAXJAR_SANDBOX_MODE = frappe.db.get_single_value("TaxJar Settings", "is_sandbox") + + if (not TAXJAR_CREATE_TRANSACTIONS and not TAXJAR_CALCULATE_TAX and not TAXJAR_SANDBOX_MODE): + return custom_fields = { 'Sales Invoice Item': [ @@ -29,4 +34,4 @@ def execute(): } create_custom_fields(custom_fields, update=True) add_permissions() - frappe.enqueue('erpnext.regional.united_states.setup.add_product_tax_categories', now=True) \ No newline at end of file + frappe.enqueue('erpnext.erpnext_integrations.doctype.taxjar_settings.taxjar_settings.add_product_tax_categories', now=True) diff --git a/erpnext/regional/united_states/setup.py b/erpnext/regional/united_states/setup.py index 25982b81227..f7b921a491b 100644 --- a/erpnext/regional/united_states/setup.py +++ b/erpnext/regional/united_states/setup.py @@ -16,30 +16,9 @@ def setup(company=None, patch=True): setup_company_independent_fixtures(patch=patch) def setup_company_independent_fixtures(company=None, patch=True): - add_product_tax_categories() make_custom_fields() - add_permissions() - frappe.enqueue('erpnext.regional.united_states.setup.add_product_tax_categories', now=False) add_print_formats() -# Product Tax categories imported from taxjar api -def add_product_tax_categories(): - with open(os.path.join(os.path.dirname(__file__), 'product_tax_category_data.json'), 'r') as f: - tax_categories = json.loads(f.read()) - create_tax_categories(tax_categories['categories']) - -def create_tax_categories(data): - for d in data: - tax_category = frappe.new_doc('Product Tax Category') - tax_category.description = d.get("description") - tax_category.product_tax_code = d.get("product_tax_code") - tax_category.category_name = d.get("name") - try: - tax_category.db_insert() - except frappe.DuplicateEntryError: - pass - - def make_custom_fields(update=True): custom_fields = { 'Supplier': [ @@ -61,29 +40,10 @@ def make_custom_fields(update=True): 'Quotation': [ dict(fieldname='exempt_from_sales_tax', fieldtype='Check', insert_after='taxes_and_charges', label='Is customer exempted from sales tax?') - ], - 'Sales Invoice Item': [ - dict(fieldname='product_tax_category', fieldtype='Link', insert_after='description', options='Product Tax Category', - label='Product Tax Category', fetch_from='item_code.product_tax_category'), - dict(fieldname='tax_collectable', fieldtype='Currency', insert_after='net_amount', - label='Tax Collectable', read_only=1), - dict(fieldname='taxable_amount', fieldtype='Currency', insert_after='tax_collectable', - label='Taxable Amount', read_only=1) - ], - 'Item': [ - dict(fieldname='product_tax_category', fieldtype='Link', insert_after='item_group', options='Product Tax Category', - label='Product Tax Category') ] } create_custom_fields(custom_fields, update=update) -def add_permissions(): - doctype = "Product Tax Category" - for role in ('Accounts Manager', 'Accounts User', 'System Manager','Item Manager', 'Stock Manager'): - add_permission(doctype, role, 0) - update_permission_property(doctype, role, 0, 'write', 1) - update_permission_property(doctype, role, 0, 'create', 1) - def add_print_formats(): frappe.reload_doc("regional", "print_format", "irs_1099_form") frappe.db.set_value("Print Format", "IRS 1099 Form", "disabled", 0) From 35448886bcc1aaf44e1ef373fedeec5b20111552 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 19 Oct 2021 14:33:57 +0530 Subject: [PATCH 019/164] Merge pull request #27999 from frappe/mergify/bp/version-13-hotfix/pr-27990 fix: changes in schedules gets overwritten on save (backport #27990) --- .../doctype/maintenance_schedule/maintenance_schedule.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py index a1df9cfd0eb..adb57f9f397 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py +++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py @@ -199,12 +199,16 @@ class MaintenanceSchedule(TransactionBase): if chk: throw(_("Maintenance Schedule {0} exists against {1}").format(chk[0][0], d.sales_order)) + def validate_no_of_visits(self): + return len(self.schedules) != sum(d.no_of_visits for d in self.items) + def validate(self): self.validate_end_date_visits() self.validate_maintenance_detail() self.validate_dates_with_periodicity() self.validate_sales_order() - self.generate_schedule() + if not self.schedules or self.validate_no_of_visits(): + self.generate_schedule() def on_update(self): frappe.db.set(self, 'status', 'Draft') From ebe68c1a7a47f5e711cc9fc1d40c07e140b76888 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Tue, 19 Oct 2021 15:08:05 +0530 Subject: [PATCH 020/164] fix: Totals row incorrect value in GL Entry (#27867) --- erpnext/accounts/report/general_ledger/general_ledger.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index 5bd6e583dbb..0094bc2eebe 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -421,8 +421,6 @@ def get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map): update_value_in_dict(totals, 'closing', gle) elif gle.posting_date <= to_date: - update_value_in_dict(gle_map[gle.get(group_by)].totals, 'total', gle) - update_value_in_dict(totals, 'total', gle) if filters.get("group_by") != 'Group by Voucher (Consolidated)': gle_map[gle.get(group_by)].entries.append(gle) elif filters.get("group_by") == 'Group by Voucher (Consolidated)': @@ -436,10 +434,11 @@ def get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map): else: update_value_in_dict(consolidated_gle, key, gle) - update_value_in_dict(gle_map[gle.get(group_by)].totals, 'closing', gle) - update_value_in_dict(totals, 'closing', gle) - for key, value in consolidated_gle.items(): + update_value_in_dict(gle_map[value.get(group_by)].totals, 'total', value) + update_value_in_dict(totals, 'total', value) + update_value_in_dict(gle_map[value.get(group_by)].totals, 'closing', value) + update_value_in_dict(totals, 'closing', value) entries.append(value) return totals, entries From 94177c076467d84949153d8d91c361b2b8043de3 Mon Sep 17 00:00:00 2001 From: marination Date: Tue, 19 Oct 2021 17:31:55 +0530 Subject: [PATCH 021/164] fix: Fetch thumbnail from Item master instead of regenerating --- .../doctype/website_item/website_item.py | 2 +- erpnext/patches/v13_0/create_website_items.py | 20 ++++++++++++++----- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/erpnext/e_commerce/doctype/website_item/website_item.py b/erpnext/e_commerce/doctype/website_item/website_item.py index c7c68cf09cf..03b9834c1d1 100644 --- a/erpnext/e_commerce/doctype/website_item/website_item.py +++ b/erpnext/e_commerce/doctype/website_item/website_item.py @@ -147,7 +147,7 @@ class WebsiteItem(WebsiteGenerator): def make_thumbnail(self): """Make a thumbnail of `website_image`""" - if frappe.flags.in_import: + if frappe.flags.in_import or frappe.flags.in_migrate: return import requests.exceptions diff --git a/erpnext/patches/v13_0/create_website_items.py b/erpnext/patches/v13_0/create_website_items.py index 5c245ca29fc..d041540ecc7 100644 --- a/erpnext/patches/v13_0/create_website_items.py +++ b/erpnext/patches/v13_0/create_website_items.py @@ -14,7 +14,7 @@ def execute(): item_fields = ["item_code", "item_name", "item_group", "stock_uom", "brand", "image", "has_variants", "variant_of", "description", "weightage"] web_fields_to_map = ["route", "slideshow", "website_image_alt", - "website_warehouse", "web_long_description", "website_content"] + "website_warehouse", "web_long_description", "website_content", "thumbnail"] item_table_fields = frappe.db.sql("desc `tabItem`", as_dict=1) item_table_fields = [d.get('Field') for d in item_table_fields] @@ -47,16 +47,26 @@ def execute(): for item in items: if frappe.db.exists("Website Item", {"item_code": item.item_code}): # if website item already exists check for empty thumbnail - web_item_doc = frappe.get_doc("Website Item", {"item_code": item.item_code}) - if web_item_doc.website_image and not web_item_doc.thumbnail: - web_item_doc.make_thumbnail() - web_item_doc.save() + # if empty, fetch thumbnail from Item master + web_item_doc = frappe.db.get_values( + "Website Item", + filters={ + "item_code": item.item_code + }, + fieldname=["website_image", "thumbnail", "name"] + )[0] + + if web_item_doc.get("website_image") and not web_item_doc.get("thumbnail"): + thumbnail = frappe.db.get_value("Item", item.item_code, "thumbnail") + frappe.db.set_value("Website Item", web_item_doc.name, "thumbnail", thumbnail) else: # else make new website item from item (publish item) website_item = make_website_item(item, save=False) website_item.ranking = item.get("weightage") + for field in web_fields_to_map: website_item.update({field: item.get(field)}) + website_item.save() # move Website Item Group & Website Specification table to Website Item From 11c498d9e57f17c3a5cb2361482a846d038f2045 Mon Sep 17 00:00:00 2001 From: marination Date: Tue, 19 Oct 2021 18:32:43 +0530 Subject: [PATCH 022/164] fix: re-run patch - Patch will just fetch thumbnails if website items are created, else it will create new website items --- erpnext/patches.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index f457ceeb2c6..2d8ab2557ea 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -309,7 +309,7 @@ erpnext.patches.v13_0.reset_clearance_date_for_intracompany_payment_entries erpnext.patches.v13_0.custom_fields_for_taxjar_integration erpnext.patches.v13_0.set_operation_time_based_on_operating_cost erpnext.patches.v13_0.validate_options_for_data_field -erpnext.patches.v13_0.create_website_items #30-09-2021 +erpnext.patches.v13_0.create_website_items #19-10-2021 erpnext.patches.v13_0.populate_e_commerce_settings erpnext.patches.v13_0.make_homepage_products_website_items erpnext.patches.v13_0.update_dates_in_tax_withholding_category From 46a5a83789059f0ddfdd6b61c5f2b8605ae6551e Mon Sep 17 00:00:00 2001 From: marination Date: Tue, 19 Oct 2021 18:39:48 +0530 Subject: [PATCH 023/164] fix: Get db values as dict when checking for thumbnail in existing web item --- erpnext/patches/v13_0/create_website_items.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/patches/v13_0/create_website_items.py b/erpnext/patches/v13_0/create_website_items.py index d041540ecc7..51be13f554a 100644 --- a/erpnext/patches/v13_0/create_website_items.py +++ b/erpnext/patches/v13_0/create_website_items.py @@ -53,7 +53,8 @@ def execute(): filters={ "item_code": item.item_code }, - fieldname=["website_image", "thumbnail", "name"] + fieldname=["website_image", "thumbnail", "name"], + as_dict=True )[0] if web_item_doc.get("website_image") and not web_item_doc.get("thumbnail"): From ac8014e24c761f3be302bad1f4d14c4a65c8f78f Mon Sep 17 00:00:00 2001 From: marination Date: Tue, 19 Oct 2021 18:50:44 +0530 Subject: [PATCH 024/164] fix: Check if thumbnail column exists in case of table trimming --- erpnext/patches/v13_0/create_website_items.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/erpnext/patches/v13_0/create_website_items.py b/erpnext/patches/v13_0/create_website_items.py index 51be13f554a..a1e14e48a13 100644 --- a/erpnext/patches/v13_0/create_website_items.py +++ b/erpnext/patches/v13_0/create_website_items.py @@ -16,6 +16,7 @@ def execute(): web_fields_to_map = ["route", "slideshow", "website_image_alt", "website_warehouse", "web_long_description", "website_content", "thumbnail"] + # get all valid columns (fields) from Item master DB schema item_table_fields = frappe.db.sql("desc `tabItem`", as_dict=1) item_table_fields = [d.get('Field') for d in item_table_fields] @@ -45,7 +46,10 @@ def execute(): count = 0 for item in items: - if frappe.db.exists("Website Item", {"item_code": item.item_code}): + web_item_exists = frappe.db.exists("Website Item", {"item_code": item.item_code}) + thumbnail_column_exists = "thumbnail" in item_table_fields + + if web_item_exists and thumbnail_column_exists: # if website item already exists check for empty thumbnail # if empty, fetch thumbnail from Item master web_item_doc = frappe.db.get_values( From 2fff5e5cd1af24ec0bd4c57ce89373bcb4380544 Mon Sep 17 00:00:00 2001 From: Jannat Patel <31363128+pateljannat@users.noreply.github.com> Date: Wed, 20 Oct 2021 10:53:39 +0530 Subject: [PATCH 025/164] fix: map missing fields in opportunity (#27904) (cherry picked from commit d81f81134955783d632b7d4005999da2092e82e7) # Conflicts: # erpnext/crm/doctype/opportunity/opportunity.py --- .../crm/doctype/opportunity/opportunity.py | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/erpnext/crm/doctype/opportunity/opportunity.py b/erpnext/crm/doctype/opportunity/opportunity.py index 0b3f50897ab..3e9d76ee9c1 100644 --- a/erpnext/crm/doctype/opportunity/opportunity.py +++ b/erpnext/crm/doctype/opportunity/opportunity.py @@ -34,6 +34,7 @@ class Opportunity(TransactionBase): self.validate_item_details() self.validate_uom_is_integer("uom", "qty") self.validate_cust_name() + self.map_fields() if not self.title: self.title = self.customer_name @@ -41,6 +42,35 @@ class Opportunity(TransactionBase): if not self.with_items: self.items = [] +<<<<<<< HEAD +======= + else: + self.calculate_totals() + + def map_fields(self): + for field in self.meta.fields: + if not self.get(field.fieldname): + try: + value = frappe.db.get_value(self.opportunity_from, self.party_name, field.fieldname) + frappe.db.set(self, field.fieldname, value) + except Exception: + continue + + def calculate_totals(self): + total = base_total = 0 + for item in self.get('items'): + item.amount = flt(item.rate) * flt(item.qty) + item.base_rate = flt(self.conversion_rate * item.rate) + item.base_amount = flt(self.conversion_rate * item.amount) + total += item.amount + base_total += item.base_amount + + self.total = flt(total) + self.base_total = flt(base_total) + self.grand_total = flt(self.total) + flt(self.opportunity_amount) + self.base_grand_total = flt(self.base_total) + flt(self.base_opportunity_amount) + +>>>>>>> d81f811349 (fix: map missing fields in opportunity (#27904)) def make_new_lead_if_required(self): """Set lead against new opportunity""" if (not self.get("party_name")) and self.contact_email: From ae948e31eae4c2685d7fbd10c2a952cfa34d4b17 Mon Sep 17 00:00:00 2001 From: Jannat Patel <31363128+pateljannat@users.noreply.github.com> Date: Wed, 20 Oct 2021 11:30:33 +0530 Subject: [PATCH 026/164] fix: linters --- erpnext/crm/doctype/opportunity/opportunity.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/erpnext/crm/doctype/opportunity/opportunity.py b/erpnext/crm/doctype/opportunity/opportunity.py index 3e9d76ee9c1..6ee95b020dc 100644 --- a/erpnext/crm/doctype/opportunity/opportunity.py +++ b/erpnext/crm/doctype/opportunity/opportunity.py @@ -42,8 +42,6 @@ class Opportunity(TransactionBase): if not self.with_items: self.items = [] -<<<<<<< HEAD -======= else: self.calculate_totals() @@ -70,7 +68,6 @@ class Opportunity(TransactionBase): self.grand_total = flt(self.total) + flt(self.opportunity_amount) self.base_grand_total = flt(self.base_total) + flt(self.base_opportunity_amount) ->>>>>>> d81f811349 (fix: map missing fields in opportunity (#27904)) def make_new_lead_if_required(self): """Set lead against new opportunity""" if (not self.get("party_name")) and self.contact_email: From f12deae24b7ab164edd0868a1254bdf28bfeac09 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 20 Oct 2021 12:07:22 +0530 Subject: [PATCH 027/164] fix: Error in TDS computation summary --- .../accounts/report/tds_payable_monthly/tds_payable_monthly.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 621b697aca4..4a25bcdee34 100644 --- a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py +++ b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py @@ -44,7 +44,7 @@ def get_result(filters, tds_docs, tds_accounts, tax_category_map): if rate and tds_deducted: row = { - 'pan' if frappe.db.has_column('Supplier', 'pan') else 'tax_id': supplier_map.get(supplier).pan, + 'pan' if frappe.db.has_column('Supplier', 'pan') else 'tax_id': supplier_map.get(supplier, {}).get('pan'), 'supplier': supplier_map.get(supplier).name } From 8c57394e7893490b637467c5213a50f18e8ed5d5 Mon Sep 17 00:00:00 2001 From: Jannat Patel <31363128+pateljannat@users.noreply.github.com> Date: Wed, 20 Oct 2021 12:14:15 +0530 Subject: [PATCH 028/164] fix: conflicts --- erpnext/crm/doctype/opportunity/opportunity.py | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/erpnext/crm/doctype/opportunity/opportunity.py b/erpnext/crm/doctype/opportunity/opportunity.py index 6ee95b020dc..8027cbc69cc 100644 --- a/erpnext/crm/doctype/opportunity/opportunity.py +++ b/erpnext/crm/doctype/opportunity/opportunity.py @@ -42,9 +42,6 @@ class Opportunity(TransactionBase): if not self.with_items: self.items = [] - else: - self.calculate_totals() - def map_fields(self): for field in self.meta.fields: if not self.get(field.fieldname): @@ -54,20 +51,6 @@ class Opportunity(TransactionBase): except Exception: continue - def calculate_totals(self): - total = base_total = 0 - for item in self.get('items'): - item.amount = flt(item.rate) * flt(item.qty) - item.base_rate = flt(self.conversion_rate * item.rate) - item.base_amount = flt(self.conversion_rate * item.amount) - total += item.amount - base_total += item.base_amount - - self.total = flt(total) - self.base_total = flt(base_total) - self.grand_total = flt(self.total) + flt(self.opportunity_amount) - self.base_grand_total = flt(self.base_total) + flt(self.base_opportunity_amount) - def make_new_lead_if_required(self): """Set lead against new opportunity""" if (not self.get("party_name")) and self.contact_email: From b7befe49dc83b938b74b7a63d31787734d7857f8 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 20 Oct 2021 12:16:22 +0530 Subject: [PATCH 029/164] fix: Check for other properties --- .../report/tds_payable_monthly/tds_payable_monthly.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 4a25bcdee34..5c47514cc31 100644 --- a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py +++ b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py @@ -45,7 +45,7 @@ def get_result(filters, tds_docs, tds_accounts, tax_category_map): if rate and tds_deducted: row = { 'pan' if frappe.db.has_column('Supplier', 'pan') else 'tax_id': supplier_map.get(supplier, {}).get('pan'), - 'supplier': supplier_map.get(supplier).name + 'supplier': supplier_map.get(supplier, {}).get('name') } if filters.naming_series == 'Naming Series': @@ -53,7 +53,7 @@ def get_result(filters, tds_docs, tds_accounts, tax_category_map): row.update({ 'section_code': tax_withholding_category, - 'entity_type': supplier_map.get(supplier).supplier_type, + 'entity_type': supplier_map.get(supplier, {}).get('supplier_type'), 'tds_rate': rate, 'total_amount_credited': total_amount_credited, 'tds_deducted': tds_deducted, From 944e3d467c5ab0a2a4a76a8b532e53bca77f8e61 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 20 Oct 2021 12:17:13 +0530 Subject: [PATCH 030/164] fix: Check for supplier name --- .../accounts/report/tds_payable_monthly/tds_payable_monthly.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 5c47514cc31..6a7f2e5b535 100644 --- a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py +++ b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py @@ -49,7 +49,7 @@ def get_result(filters, tds_docs, tds_accounts, tax_category_map): } if filters.naming_series == 'Naming Series': - row.update({'supplier_name': supplier_map.get(supplier).supplier_name}) + row.update({'supplier_name': supplier_map.get(supplier, {}).get('supplier_name')}) row.update({ 'section_code': tax_withholding_category, From 50af32f35c03520abd09ce92444a7a816e5a68f9 Mon Sep 17 00:00:00 2001 From: Goh Yan Chang Date: Fri, 1 Oct 2021 16:30:33 +0800 Subject: [PATCH 031/164] Update employee_leave_balance.py fix: Employee Leave Balance report showing wrong figures (cherry picked from commit 632f7848a32f577b9bd094d5368acf675f5121ca) --- .../hr/report/employee_leave_balance/employee_leave_balance.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py b/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py index 6bca1368d3f..d463b9b62a8 100644 --- a/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py +++ b/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py @@ -182,10 +182,11 @@ def get_allocated_and_expired_leaves(from_date, to_date, employee, leave_type): records= frappe.db.sql(""" SELECT employee, leave_type, from_date, to_date, leaves, transaction_name, - is_carry_forward, is_expired + transaction_type, is_carry_forward, is_expired FROM `tabLeave Ledger Entry` WHERE employee=%(employee)s AND leave_type=%(leave_type)s AND docstatus=1 + AND transaction_type = 'Leave Allocation' AND (from_date between %(from_date)s AND %(to_date)s OR to_date between %(from_date)s AND %(to_date)s OR (from_date < %(from_date)s AND to_date > %(to_date)s)) From 348a961b5374a692cd82950f626f62df8fdc147f Mon Sep 17 00:00:00 2001 From: marination Date: Wed, 20 Oct 2021 14:08:14 +0530 Subject: [PATCH 032/164] fix: Move thumbnail updation to different patch - Thumbnail updation handled via different patch - create_website_items will only have one purpose - added progress bar to `create_website_items` - code cleanup --- erpnext/patches.txt | 3 +- erpnext/patches/v13_0/create_website_items.py | 61 ++++++------------- .../v13_0/fetch_thumbnail_in_website_items.py | 16 +++++ 3 files changed, 38 insertions(+), 42 deletions(-) create mode 100644 erpnext/patches/v13_0/fetch_thumbnail_in_website_items.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 2d8ab2557ea..6abde839025 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -309,7 +309,7 @@ erpnext.patches.v13_0.reset_clearance_date_for_intracompany_payment_entries erpnext.patches.v13_0.custom_fields_for_taxjar_integration erpnext.patches.v13_0.set_operation_time_based_on_operating_cost erpnext.patches.v13_0.validate_options_for_data_field -erpnext.patches.v13_0.create_website_items #19-10-2021 +erpnext.patches.v13_0.create_website_items #30-09-2021 erpnext.patches.v13_0.populate_e_commerce_settings erpnext.patches.v13_0.make_homepage_products_website_items erpnext.patches.v13_0.update_dates_in_tax_withholding_category @@ -324,3 +324,4 @@ erpnext.patches.v13_0.set_status_in_maintenance_schedule_table erpnext.patches.v13_0.add_default_interview_notification_templates erpnext.patches.v13_0.trim_sales_invoice_custom_field_length erpnext.patches.v13_0.requeue_failed_reposts +erpnext.patches.v13_0.fetch_thumbnail_in_website_items \ No newline at end of file diff --git a/erpnext/patches/v13_0/create_website_items.py b/erpnext/patches/v13_0/create_website_items.py index a1e14e48a13..3baa34b71c0 100644 --- a/erpnext/patches/v13_0/create_website_items.py +++ b/erpnext/patches/v13_0/create_website_items.py @@ -43,51 +43,30 @@ def execute(): fields=item_fields, or_filters=or_filters ) + total_count = len(items) - count = 0 - for item in items: - web_item_exists = frappe.db.exists("Website Item", {"item_code": item.item_code}) - thumbnail_column_exists = "thumbnail" in item_table_fields + for count, item in enumerate(items, start=1): + if frappe.db.exists("Website Item", {"item_code": item.item_code}): + continue - if web_item_exists and thumbnail_column_exists: - # if website item already exists check for empty thumbnail - # if empty, fetch thumbnail from Item master - web_item_doc = frappe.db.get_values( - "Website Item", - filters={ - "item_code": item.item_code - }, - fieldname=["website_image", "thumbnail", "name"], - as_dict=True - )[0] + # make new website item from item (publish item) + website_item = make_website_item(item, save=False) + website_item.ranking = item.get("weightage") - if web_item_doc.get("website_image") and not web_item_doc.get("thumbnail"): - thumbnail = frappe.db.get_value("Item", item.item_code, "thumbnail") - frappe.db.set_value("Website Item", web_item_doc.name, "thumbnail", thumbnail) - else: - # else make new website item from item (publish item) - website_item = make_website_item(item, save=False) - website_item.ranking = item.get("weightage") + for field in web_fields_to_map: + website_item.update({field: item.get(field)}) - for field in web_fields_to_map: - website_item.update({field: item.get(field)}) + website_item.save() - website_item.save() + # move Website Item Group & Website Specification table to Website Item + for doctype in ("Website Item Group", "Item Website Specification"): + frappe.db.set_value( + doctype, + {"parenttype": "Item", "parent": item.item_code}, # filters + {"parenttype": "Website Item", "parent": website_item.name} # value dict + ) - # move Website Item Group & Website Specification table to Website Item - for doctype in ("Website Item Group", "Item Website Specification"): - web_item, item_code = website_item.name, item.item_code - frappe.db.sql(f""" - Update - `tab{doctype}` - set - parenttype = 'Website Item', - parent = '{web_item}' - where - parenttype = 'Item' - and parent = '{item_code}' - """) - - count += 1 if count % 20 == 0: # commit after every 20 items - frappe.db.commit() \ No newline at end of file + frappe.db.commit() + + frappe.utils.update_progress_bar('Creating Website Items', count, total_count) \ No newline at end of file diff --git a/erpnext/patches/v13_0/fetch_thumbnail_in_website_items.py b/erpnext/patches/v13_0/fetch_thumbnail_in_website_items.py new file mode 100644 index 00000000000..32ad542cf88 --- /dev/null +++ b/erpnext/patches/v13_0/fetch_thumbnail_in_website_items.py @@ -0,0 +1,16 @@ +import frappe + + +def execute(): + if frappe.db.has_column("Item", "thumbnail"): + website_item = frappe.qb.DocType("Website Item").as_("wi") + item = frappe.qb.DocType("Item") + + frappe.qb.update(website_item).inner_join(item).on( + website_item.item_code == item.item_code + ).set( + website_item.thumbnail, item.thumbnail + ).where( + website_item.website_image.notnull() + & website_item.thumbnail.isnull() + ).run() From a739bf90c4584d69107a493872a3126e55011ad8 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 20 Oct 2021 16:20:25 +0530 Subject: [PATCH 033/164] feat: Tax for recurring additional salary (backport #27459) (#28024) * feat: Tax for recurring additional salary (#27459) * fix: Logic for tax calculation on recurring additional salary * fix: Get actual amount always in case of overwritten additional salary even if based on payment days * feat: Test case added for recurring additional salary * fix: use query builder to get additional salaries instead of raw SQL * fix: query formatting and remove trailing spaces Co-authored-by: Rucha Mahabal (cherry picked from commit 2ef4844a3ca56d261cbc61085fd030d969e62b28) # Conflicts: # erpnext/payroll/doctype/salary_slip/salary_slip.py # erpnext/payroll/doctype/salary_slip/test_salary_slip.py * fix: conflicts Co-authored-by: Nabin Hait Co-authored-by: Rucha Mahabal --- erpnext/hr/doctype/employee/test_employee.py | 1 + .../additional_salary/additional_salary.py | 41 +++++------ .../doctype/salary_detail/salary_detail.json | 13 +++- .../doctype/salary_slip/salary_slip.py | 50 +++++++++---- .../doctype/salary_slip/test_salary_slip.py | 71 ++++++++++++++++++- 5 files changed, 138 insertions(+), 38 deletions(-) diff --git a/erpnext/hr/doctype/employee/test_employee.py b/erpnext/hr/doctype/employee/test_employee.py index 8d6dfa2c1d2..8a2da0866e9 100644 --- a/erpnext/hr/doctype/employee/test_employee.py +++ b/erpnext/hr/doctype/employee/test_employee.py @@ -55,6 +55,7 @@ def make_employee(user, company=None, **kwargs): "email": user, "first_name": user, "new_password": "password", + "send_welcome_email": 0, "roles": [{"doctype": "Has Role", "role": "Employee"}] }).insert() diff --git a/erpnext/payroll/doctype/additional_salary/additional_salary.py b/erpnext/payroll/doctype/additional_salary/additional_salary.py index 7c0a8eac99c..b6377f40066 100644 --- a/erpnext/payroll/doctype/additional_salary/additional_salary.py +++ b/erpnext/payroll/doctype/additional_salary/additional_salary.py @@ -125,27 +125,28 @@ class AdditionalSalary(Document): no_of_days = date_diff(getdate(end_date), getdate(start_date)) + 1 return amount_per_day * no_of_days +@frappe.whitelist() def get_additional_salaries(employee, start_date, end_date, component_type): - additional_salary_list = frappe.db.sql(""" - select name, salary_component as component, type, amount, - overwrite_salary_structure_amount as overwrite, - deduct_full_tax_on_selected_payroll_date - from `tabAdditional Salary` - where employee=%(employee)s - and docstatus = 1 - and ( - payroll_date between %(from_date)s and %(to_date)s - or - from_date <= %(to_date)s and to_date >= %(to_date)s - ) - and type = %(component_type)s - order by salary_component, overwrite ASC - """, { - 'employee': employee, - 'from_date': start_date, - 'to_date': end_date, - 'component_type': "Earning" if component_type == "earnings" else "Deduction" - }, as_dict=1) + comp_type = 'Earning' if component_type == 'earnings' else 'Deduction' + + additional_sal = frappe.qb.DocType('Additional Salary') + component_field = additional_sal.salary_component.as_('component') + overwrite_field = additional_sal.overwrite_salary_structure_amount.as_('overwrite') + + additional_salary_list = frappe.qb.from_( + additional_sal + ).select( + additional_sal.name, component_field, additional_sal.type, + additional_sal.amount, additional_sal.is_recurring, overwrite_field, + additional_sal.deduct_full_tax_on_selected_payroll_date + ).where( + (additional_sal.employee == employee) + & (additional_sal.docstatus == 1) + & (additional_sal.type == comp_type) + ).where( + additional_sal.payroll_date[start_date: end_date] + | ((additional_sal.from_date <= end_date) & (additional_sal.to_date >= end_date)) + ).run(as_dict=True) additional_salaries = [] components_to_overwrite = [] diff --git a/erpnext/payroll/doctype/salary_detail/salary_detail.json b/erpnext/payroll/doctype/salary_detail/salary_detail.json index 393f647cc88..665f0a8297e 100644 --- a/erpnext/payroll/doctype/salary_detail/salary_detail.json +++ b/erpnext/payroll/doctype/salary_detail/salary_detail.json @@ -12,6 +12,7 @@ "year_to_date", "section_break_5", "additional_salary", + "is_recurring_additional_salary", "statistical_component", "depends_on_payment_days", "exempted_from_income_tax", @@ -235,11 +236,19 @@ "label": "Year To Date", "options": "currency", "read_only": 1 - } + }, + { + "default": "0", + "depends_on": "eval:doc.parenttype=='Salary Slip' && doc.additional_salary", + "fieldname": "is_recurring_additional_salary", + "fieldtype": "Check", + "label": "Is Recurring Additional Salary", + "read_only": 1 + } ], "istable": 1, "links": [], - "modified": "2021-01-14 13:39:15.847158", + "modified": "2021-08-30 13:39:15.847158", "modified_by": "Administrator", "module": "Payroll", "name": "Salary Detail", diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py index 8ebf1af99ef..15e908720d1 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py @@ -630,7 +630,8 @@ class SalarySlip(TransactionBase): get_salary_component_data(additional_salary.component), additional_salary.amount, component_type, - additional_salary + additional_salary, + is_recurring = additional_salary.is_recurring ) def add_tax_components(self, payroll_period): @@ -651,7 +652,7 @@ class SalarySlip(TransactionBase): tax_row = get_salary_component_data(d) self.update_component_row(tax_row, tax_amount, "deductions") - def update_component_row(self, component_data, amount, component_type, additional_salary=None): + def update_component_row(self, component_data, amount, component_type, additional_salary=None, is_recurring = 0): component_row = None for d in self.get(component_type): if d.salary_component != component_data.salary_component: @@ -702,6 +703,7 @@ class SalarySlip(TransactionBase): component_row.default_amount = 0 component_row.additional_amount = amount + component_row.is_recurring_additional_salary = is_recurring component_row.additional_salary = additional_salary.name component_row.deduct_full_tax_on_selected_payroll_date = \ additional_salary.deduct_full_tax_on_selected_payroll_date @@ -898,25 +900,33 @@ class SalarySlip(TransactionBase): amount, additional_amount = earning.default_amount, earning.additional_amount if earning.is_tax_applicable: - if additional_amount: - taxable_earnings += (amount - additional_amount) - additional_income += additional_amount - if earning.deduct_full_tax_on_selected_payroll_date: - additional_income_with_full_tax += additional_amount - continue - if earning.is_flexible_benefit: flexi_benefits += amount else: - taxable_earnings += amount + taxable_earnings += (amount - additional_amount) + additional_income += additional_amount + + # Get additional amount based on future recurring additional salary + if additional_amount and earning.is_recurring_additional_salary: + additional_income += self.get_future_recurring_additional_amount(earning.additional_salary, + earning.additional_amount) # Used earning.additional_amount to consider the amount for the full month + + if earning.deduct_full_tax_on_selected_payroll_date: + additional_income_with_full_tax += additional_amount if allow_tax_exemption: for ded in self.deductions: if ded.exempted_from_income_tax: - amount = ded.amount + amount, additional_amount = ded.amount, ded.additional_amount if based_on_payment_days: - amount = self.get_amount_based_on_payment_days(ded, joining_date, relieving_date)[0] - taxable_earnings -= flt(amount) + amount, additional_amount = self.get_amount_based_on_payment_days(ded, joining_date, relieving_date) + + taxable_earnings -= flt(amount - additional_amount) + additional_income -= additional_amount + + if additional_amount and ded.is_recurring_additional_salary: + additional_income -= self.get_future_recurring_additional_amount(ded.additional_salary, + ded.additional_amount) # Used ded.additional_amount to consider the amount for the full month return frappe._dict({ "taxable_earnings": taxable_earnings, @@ -925,11 +935,21 @@ class SalarySlip(TransactionBase): "flexi_benefits": flexi_benefits }) + def get_future_recurring_additional_amount(self, additional_salary, monthly_additional_amount): + future_recurring_additional_amount = 0 + to_date = frappe.db.get_value("Additional Salary", additional_salary, 'to_date') + # future month count excluding current + future_recurring_period = (getdate(to_date).month - getdate(self.start_date).month) + if future_recurring_period > 0: + future_recurring_additional_amount = monthly_additional_amount * future_recurring_period # Used earning.additional_amount to consider the amount for the full month + return future_recurring_additional_amount + def get_amount_based_on_payment_days(self, row, joining_date, relieving_date): amount, additional_amount = row.amount, row.additional_amount if (self.salary_structure and - cint(row.depends_on_payment_days) and cint(self.total_working_days) and - (not self.salary_slip_based_on_timesheet or + cint(row.depends_on_payment_days) and cint(self.total_working_days) + and not (row.additional_salary and row.default_amount) # to identify overwritten additional salary + and (not self.salary_slip_based_on_timesheet or getdate(self.start_date) < joining_date or (relieving_date and getdate(self.end_date) > relieving_date) )): diff --git a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py index 91b109bb408..a44af09b793 100644 --- a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py @@ -540,6 +540,61 @@ class TestSalarySlip(unittest.TestCase): # undelete fixture data frappe.db.rollback() + def test_tax_for_recurring_additional_salary(self): + frappe.db.sql("""delete from `tabPayroll Period`""") + frappe.db.sql("""delete from `tabSalary Component`""") + + payroll_period = create_payroll_period() + + create_tax_slab(payroll_period, allow_tax_exemption=True) + + employee = make_employee("test_tax@salary.slip") + delete_docs = [ + "Salary Slip", + "Additional Salary", + "Employee Tax Exemption Declaration", + "Employee Tax Exemption Proof Submission", + "Employee Benefit Claim", + "Salary Structure Assignment" + ] + for doc in delete_docs: + frappe.db.sql("delete from `tab%s` where employee='%s'" % (doc, employee)) + + from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure + + salary_structure = make_salary_structure("Stucture to test tax", "Monthly", + other_details={"max_benefits": 100000}, test_tax=True, + employee=employee, payroll_period=payroll_period) + + + create_salary_slips_for_payroll_period(employee, salary_structure.name, + payroll_period, deduct_random=False, num=3) + + tax_paid = get_tax_paid_in_period(employee) + + annual_tax = 23196.0 + self.assertEqual(tax_paid, annual_tax) + + frappe.db.sql("""delete from `tabSalary Slip` where employee=%s""", (employee)) + + #------------------------------------ + # Recurring additional salary + start_date = add_months(payroll_period.start_date, 3) + end_date = add_months(payroll_period.start_date, 5) + create_recurring_additional_salary(employee, "Performance Bonus", 20000, start_date, end_date) + + frappe.db.sql("""delete from `tabSalary Slip` where employee=%s""", (employee)) + + create_salary_slips_for_payroll_period(employee, salary_structure.name, + payroll_period, deduct_random=False, num=4) + + tax_paid = get_tax_paid_in_period(employee) + + annual_tax = 32315.0 + self.assertEqual(tax_paid, annual_tax) + + frappe.db.rollback() + def make_activity_for_employee(self): activity_type = frappe.get_doc("Activity Type", "_Test Activity Type") activity_type.billing_rate = 50 @@ -1010,4 +1065,18 @@ def make_salary_slip_for_payment_days_dependency_test(employee, salary_structure else: salary_slip = frappe.get_doc("Salary Slip", salary_slip_name) - return salary_slip \ No newline at end of file + return salary_slip + +def create_recurring_additional_salary(employee, salary_component, amount, from_date, to_date, company=None): + frappe.get_doc({ + "doctype": "Additional Salary", + "employee": employee, + "company": company or erpnext.get_default_company(), + "salary_component": salary_component, + "is_recurring": 1, + "from_date": from_date, + "to_date": to_date, + "amount": amount, + "type": "Earning", + "currency": erpnext.get_default_currency() + }).submit() From 9ce7ea692d21aec1c25d6a4c2ee1191d12570184 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 20 Oct 2021 17:45:43 +0530 Subject: [PATCH 034/164] fix: avoid resetting employee on amending timesheets (#28025) (#28026) (cherry picked from commit 261f80c5ca65c6abda1620717df54d3fc4548858) Co-authored-by: Rucha Mahabal --- erpnext/projects/doctype/timesheet/timesheet.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/projects/doctype/timesheet/timesheet.js b/erpnext/projects/doctype/timesheet/timesheet.js index 1655b76b988..65a8566a274 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.js +++ b/erpnext/projects/doctype/timesheet/timesheet.js @@ -32,12 +32,12 @@ frappe.ui.form.on("Timesheet", { }; }, - onload: function(frm){ + onload: function(frm) { if (frm.doc.__islocal && frm.doc.time_logs) { calculate_time_and_amount(frm); } - if (frm.is_new()) { + if (frm.is_new() && !frm.doc.employee) { set_employee_and_company(frm); } }, From 34e92ab229ad425e77900c11dcdc22038738e181 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 20 Oct 2021 19:31:47 +0530 Subject: [PATCH 035/164] fix: incorrect status being set in Invoices (backport #28019) (#28031) * fix: incorrect status being set in Invoices (#28019) Co-authored-by: Pruthvi Patel (cherry picked from commit 8d9d0987fe67689696742cad3898870d085f24f2) # Conflicts: # erpnext/patches.txt * fix: merge conflict Co-authored-by: Sagar Vora --- .../purchase_invoice/purchase_invoice.py | 6 +- .../doctype/sales_invoice/sales_invoice.py | 50 +++++--- erpnext/controllers/accounts_controller.py | 57 +++++++-- erpnext/patches.txt | 3 +- erpnext/patches/v13_0/fix_invoice_statuses.py | 113 ++++++++++++++++++ 5 files changed, 201 insertions(+), 28 deletions(-) create mode 100644 erpnext/patches/v13_0/fix_invoice_statuses.py diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 16f06bd9176..be0dd92f340 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, + get_total_in_party_account_currency, is_overdue, unlink_inter_company_doc, update_linked_doc, @@ -1147,6 +1148,7 @@ class PurchaseInvoice(BuyingController): return outstanding_amount = flt(self.outstanding_amount, self.precision("outstanding_amount")) + total = get_total_in_party_account_currency(self) if not status: if self.docstatus == 2: @@ -1154,9 +1156,9 @@ class PurchaseInvoice(BuyingController): elif self.docstatus == 1: if self.is_internal_transfer(): self.status = 'Internal Transfer' - elif is_overdue(self): + elif is_overdue(self, total): self.status = "Overdue" - elif 0 < outstanding_amount < flt(self.grand_total, self.precision("grand_total")): + elif 0 < outstanding_amount < total: self.status = "Partly Paid" elif outstanding_amount > 0 and getdate(self.due_date) >= getdate(): self.status = "Unpaid" diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 27c09c22796..902fceeac75 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -1478,6 +1478,7 @@ class SalesInvoice(SellingController): return outstanding_amount = flt(self.outstanding_amount, self.precision("outstanding_amount")) + total = get_total_in_party_account_currency(self) if not status: if self.docstatus == 2: @@ -1485,9 +1486,9 @@ class SalesInvoice(SellingController): elif self.docstatus == 1: if self.is_internal_transfer(): self.status = 'Internal Transfer' - elif is_overdue(self): + elif is_overdue(self, total): self.status = "Overdue" - elif 0 < outstanding_amount < flt(self.grand_total, self.precision("grand_total")): + elif 0 < outstanding_amount < total: self.status = "Partly Paid" elif outstanding_amount > 0 and getdate(self.due_date) >= getdate(): self.status = "Unpaid" @@ -1514,27 +1515,42 @@ class SalesInvoice(SellingController): 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")) +def get_total_in_party_account_currency(doc): + total_fieldname = ( + "grand_total" + if doc.disable_rounded_total + else "rounded_total" + ) + if doc.party_account_currency != doc.currency: + total_fieldname = "base_" + total_fieldname + + return flt(doc.get(total_fieldname), doc.precision(total_fieldname)) + +def is_overdue(doc, total): + 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 - ) + today = getdate() + if doc.get('is_pos') or not doc.get('payment_schedule'): + return getdate(doc.due_date) < today - if (grand_total - outstanding_amount) < payable_amount: - return True + # calculate payable amount till date + payment_amount_field = ( + "base_payment_amount" + if doc.party_account_currency != doc.currency + else "payment_amount" + ) + + payable_amount = sum( + payment.get(payment_amount_field) + for payment in doc.payment_schedule + if getdate(payment.due_date) < today + ) + + return (total - outstanding_amount) < payable_amount - elif getdate(doc.due_date) < nowdate: - return True def get_discounting_status(sales_invoice): status = None diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 835a16f77f6..829da8953bf 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1691,17 +1691,58 @@ def get_advance_payment_entries(party_type, party, party_account, order_doctype, def update_invoice_status(): """Updates status as Overdue for applicable invoices. Runs daily.""" + today = getdate() 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()) + UPDATE `tab{doctype}` invoice SET invoice.status = 'Overdue' + WHERE invoice.docstatus = 1 + AND invoice.status REGEXP '^Unpaid|^Partly Paid' + AND invoice.outstanding_amount > 0 + AND ( + {or_condition} + ( + ( + CASE + WHEN invoice.party_account_currency = invoice.currency + THEN ( + CASE + WHEN invoice.disable_rounded_total + THEN invoice.grand_total + ELSE invoice.rounded_total + END + ) + ELSE ( + CASE + WHEN invoice.disable_rounded_total + THEN invoice.base_grand_total + ELSE invoice.base_rounded_total + END + ) + END + ) - invoice.outstanding_amount + ) < ( + SELECT SUM( + CASE + WHEN invoice.party_account_currency = invoice.currency + THEN ps.payment_amount + ELSE ps.base_payment_amount + END + ) + FROM `tabPayment Schedule` ps + WHERE ps.parent = invoice.name + AND ps.due_date < %(today)s + ) + ) + """.format( + doctype=doctype, + or_condition=( + "invoice.is_pos AND invoice.due_date < %(today)s OR" + if doctype == "Sales Invoice" + else "" + ) + ), {"today": today} + ) @frappe.whitelist() def get_payment_terms(terms_template, posting_date=None, grand_total=None, base_grand_total=None, bill_date=None): diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 5d1182f6ed8..c8c7520260c 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -315,6 +315,7 @@ erpnext.patches.v13_0.create_website_items #30-09-2021 erpnext.patches.v13_0.populate_e_commerce_settings erpnext.patches.v13_0.make_homepage_products_website_items erpnext.patches.v13_0.update_dates_in_tax_withholding_category +erpnext.patches.v13_0.fix_invoice_statuses erpnext.patches.v13_0.replace_supplier_item_group_with_party_specific_item erpnext.patches.v13_0.gst_fields_for_pos_invoice erpnext.patches.v13_0.create_accounting_dimensions_in_pos_doctypes @@ -327,4 +328,4 @@ erpnext.patches.v13_0.add_default_interview_notification_templates erpnext.patches.v13_0.trim_sales_invoice_custom_field_length erpnext.patches.v13_0.enable_scheduler_job_for_item_reposting erpnext.patches.v13_0.requeue_failed_reposts -erpnext.patches.v13_0.fetch_thumbnail_in_website_items \ No newline at end of file +erpnext.patches.v13_0.fetch_thumbnail_in_website_items diff --git a/erpnext/patches/v13_0/fix_invoice_statuses.py b/erpnext/patches/v13_0/fix_invoice_statuses.py new file mode 100644 index 00000000000..4395757159f --- /dev/null +++ b/erpnext/patches/v13_0/fix_invoice_statuses.py @@ -0,0 +1,113 @@ +import frappe +from frappe.utils import flt, getdate + +from erpnext.accounts.doctype.sales_invoice.sales_invoice import ( + get_total_in_party_account_currency, + is_overdue, +) + +TODAY = getdate() + +def execute(): + # This fix is not related to Party Specific Item, + # but it is needed for code introduced after Party Specific Item was + # If your DB doesn't have this doctype yet, you should be fine + if not frappe.db.exists("DocType", "Party Specific Item"): + return + + for doctype in ("Purchase Invoice", "Sales Invoice"): + fields = [ + "name", + "status", + "due_date", + "outstanding_amount", + "grand_total", + "base_grand_total", + "rounded_total", + "base_rounded_total", + "disable_rounded_total", + ] + if doctype == "Sales Invoice": + fields.append("is_pos") + + invoices_to_update = frappe.get_all( + doctype, + fields=fields, + filters={ + "docstatus": 1, + "status": ("in", ( + "Overdue", + "Overdue and Discounted", + "Partly Paid", + "Partly Paid and Discounted" + )), + "outstanding_amount": (">", 0), + "modified": (">", "2021-01-01") + # an assumption is being made that only invoices modified + # after 2021 got affected as incorrectly overdue. + # required for performance reasons. + } + ) + + invoices_to_update = { + invoice.name: invoice for invoice in invoices_to_update + } + + payment_schedule_items = frappe.get_all( + "Payment Schedule", + fields=( + "due_date", + "payment_amount", + "base_payment_amount", + "parent" + ), + filters={"parent": ("in", invoices_to_update)} + ) + + for item in payment_schedule_items: + invoices_to_update[item.parent].setdefault( + "payment_schedule", [] + ).append(item) + + status_map = {} + + for invoice in invoices_to_update.values(): + invoice.doctype = doctype + doc = frappe.get_doc(invoice) + correct_status = get_correct_status(doc) + if not correct_status or doc.status == correct_status: + continue + + status_map.setdefault(correct_status, []).append(doc.name) + + for status, docs in status_map.items(): + frappe.db.set_value( + doctype, {"name": ("in", docs)}, + "status", + status, + update_modified=False + ) + + + +def get_correct_status(doc): + outstanding_amount = flt( + doc.outstanding_amount, doc.precision("outstanding_amount") + ) + total = get_total_in_party_account_currency(doc) + + status = "" + if is_overdue(doc, total): + status = "Overdue" + elif 0 < outstanding_amount < total: + status = "Partly Paid" + elif outstanding_amount > 0 and getdate(doc.due_date) >= TODAY: + status = "Unpaid" + + if not status: + return + + if doc.status.endswith(" and Discounted"): + status += " and Discounted" + + return status From 8fffd1597d3f49ae7d561a42bb46433fd2e6b032 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 19 Oct 2021 14:05:44 +0530 Subject: [PATCH 036/164] fix: dont recompute item wise taxes from front end (cherry picked from commit 393749a61132ea3948b85ddf5fa491b16b100199) --- erpnext/public/js/controllers/taxes_and_totals.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js index 90cb5559394..019f3fbec12 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -137,7 +137,9 @@ erpnext.taxes_and_totals = erpnext.payments.extend({ var me = this; $.each(this.frm.doc["taxes"] || [], function(i, tax) { - tax.item_wise_tax_detail = {}; + if (!tax.dont_recompute_tax) { + tax.item_wise_tax_detail = {}; + } var tax_fields = ["total", "tax_amount_after_discount_amount", "tax_amount_for_current_item", "grand_total_for_current_item", "tax_fraction_for_current_item", "grand_total_fraction_for_current_item"]; @@ -419,7 +421,9 @@ erpnext.taxes_and_totals = erpnext.payments.extend({ current_tax_amount = tax_rate * item.qty; } - this.set_item_wise_tax(item, tax, tax_rate, current_tax_amount); + if (!tax.dont_recompute_tax) { + this.set_item_wise_tax(item, tax, tax_rate, current_tax_amount); + } return current_tax_amount; }, @@ -587,7 +591,9 @@ erpnext.taxes_and_totals = erpnext.payments.extend({ delete tax[fieldname]; }); - tax.item_wise_tax_detail = JSON.stringify(tax.item_wise_tax_detail); + if (!tax.dont_recompute_tax) { + tax.item_wise_tax_detail = JSON.stringify(tax.item_wise_tax_detail); + } }); } }, From 1fae8f1fcb7aa8098fcb998fd10ae7bbcab6ccaf Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 20 Oct 2021 20:55:00 +0530 Subject: [PATCH 037/164] Merge pull request #28038 from frappe/mergify/bp/version-13-hotfix/pr-28036 fix: incorrect field name (backport #28036) --- .../manufacturing/doctype/production_plan/production_plan.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index b9efe9b41ea..7e6fc3c4a64 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -424,7 +424,7 @@ class ProductionPlan(Document): po = frappe.new_doc('Purchase Order') po.supplier = supplier po.schedule_date = getdate(po_list[0].schedule_date) if po_list[0].schedule_date else nowdate() - po.is_subcontracted_item = 'Yes' + po.is_subcontracted = 'Yes' for row in po_list: args = { 'item_code': row.production_item, From e357541509e0ef9ba1951fefabfd2cb51be0b821 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 20 Oct 2021 19:29:31 +0530 Subject: [PATCH 038/164] fix: remove employee_name from job card summary This field doesn't exist and it's moved on individual line level logs. (cherry picked from commit 8221e7e01ff2fda65953c2454be0aa9fde3b8ac1) --- .../report/job_card_summary/job_card_summary.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/erpnext/manufacturing/report/job_card_summary/job_card_summary.py b/erpnext/manufacturing/report/job_card_summary/job_card_summary.py index a7aec315ff2..0d5181e95b3 100644 --- a/erpnext/manufacturing/report/job_card_summary/job_card_summary.py +++ b/erpnext/manufacturing/report/job_card_summary/job_card_summary.py @@ -24,7 +24,7 @@ def get_data(filters): } fields = ["name", "status", "work_order", "production_item", "item_name", "posting_date", - "total_completed_qty", "workstation", "operation", "employee_name", "total_time_in_mins"] + "total_completed_qty", "workstation", "operation", "total_time_in_mins"] for field in ["work_order", "workstation", "operation", "company"]: if filters.get(field): @@ -172,12 +172,6 @@ def get_columns(filters): "options": "Operation", "width": 110 }, - { - "label": _("Employee Name"), - "fieldname": "employee_name", - "fieldtype": "Data", - "width": 110 - }, { "label": _("Total Completed Qty"), "fieldname": "total_completed_qty", From 408c4a6f547c702726ee7751ff9bc53861c2be91 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 20 Oct 2021 19:42:37 +0530 Subject: [PATCH 039/164] fix: remove debug from query (cherry picked from commit 126ba1674089609adacafaa9a4a380361fdd3a50) --- .../manufacturing/report/job_card_summary/job_card_summary.py | 2 +- erpnext/stock/report/process_loss_report/process_loss_report.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/manufacturing/report/job_card_summary/job_card_summary.py b/erpnext/manufacturing/report/job_card_summary/job_card_summary.py index 0d5181e95b3..74bd685b799 100644 --- a/erpnext/manufacturing/report/job_card_summary/job_card_summary.py +++ b/erpnext/manufacturing/report/job_card_summary/job_card_summary.py @@ -45,7 +45,7 @@ def get_data(filters): job_card_time_details = {} for job_card_data in frappe.get_all("Job Card Time Log", fields=["min(from_time) as from_time", "max(to_time) as to_time", "parent"], - filters=job_card_time_filter, group_by="parent", debug=1): + filters=job_card_time_filter, group_by="parent"): job_card_time_details[job_card_data.parent] = job_card_data res = [] diff --git a/erpnext/stock/report/process_loss_report/process_loss_report.py b/erpnext/stock/report/process_loss_report/process_loss_report.py index 5c6a3bb4566..d3dfd52b773 100644 --- a/erpnext/stock/report/process_loss_report/process_loss_report.py +++ b/erpnext/stock/report/process_loss_report/process_loss_report.py @@ -111,7 +111,7 @@ def run_query(query_args: QueryArgs) -> Data: {work_order_filter} GROUP BY se.work_order - """.format(**query_args), query_args, as_dict=1, debug=1) + """.format(**query_args), query_args, as_dict=1) def update_data_with_total_pl_value(data: Data) -> None: for row in data: From 5ba315eb96c1ba1ed6dcffec99fbc202a52b380f Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 20 Oct 2021 19:42:56 +0530 Subject: [PATCH 040/164] test: execute manufacturing reports (cherry picked from commit da3635b94f950d7498a259a4babb4b27b3865b04) --- erpnext/manufacturing/report/test_reports.py | 63 ++++++++++++++++++++ erpnext/stock/report/test_reports.py | 1 + 2 files changed, 64 insertions(+) create mode 100644 erpnext/manufacturing/report/test_reports.py diff --git a/erpnext/manufacturing/report/test_reports.py b/erpnext/manufacturing/report/test_reports.py new file mode 100644 index 00000000000..fa7f91a81bd --- /dev/null +++ b/erpnext/manufacturing/report/test_reports.py @@ -0,0 +1,63 @@ +import unittest +from typing import List, Tuple + +import frappe + +from erpnext.tests.utils import ReportFilters, ReportName, execute_script_report + +DEFAULT_FILTERS = { + "company": "_Test Company", + "from_date": "2010-01-01", + "to_date": "2030-01-01", + "warehouse": "_Test Warehouse - _TC", +} + + +REPORT_FILTER_TEST_CASES: List[Tuple[ReportName, ReportFilters]] = [ + ("BOM Explorer", {"bom": frappe.get_last_doc("BOM").name}), + ("BOM Operations Time", {}), + ("BOM Stock Calculated", {"bom": frappe.get_last_doc("BOM").name, "qty_to_make": 2}), + ("BOM Stock Report", {"bom": frappe.get_last_doc("BOM").name, "qty_to_produce": 2}), + ("Cost of Poor Quality Report", {}), + ("Downtime Analysis", {}), + ( + "Exponential Smoothing Forecasting", + { + "based_on_document": "Sales Order", + "based_on_field": "Qty", + "no_of_years": 3, + "periodicity": "Yearly", + "smoothing_constant": 0.3, + }, + ), + ("Job Card Summary", {"fiscal_year": "2021-2022"}), + ("Production Analytics", {"range": "Monthly"}), + ("Quality Inspection Summary", {}), + ("Work Order Stock Report", {}), + ("Work Order Summary", {"fiscal_year": "2021-2022", "age": 0}), +] + + +if frappe.db.a_row_exists("Production Plan"): + REPORT_FILTER_TEST_CASES.append( + ("Production Plan Summary", {"production_plan": frappe.get_last_doc("Production Plan").name}) + ) + +OPTIONAL_FILTERS = { + "warehouse": "_Test Warehouse - _TC", + "item": "_Test Item", + "item_group": "_Test Item Group", +} + + +class TestManufacturingReports(unittest.TestCase): + def test_execute_all_manufacturing_reports(self): + """Test that all script report in manufacturing modules are executable with supported filters""" + for report, filter in REPORT_FILTER_TEST_CASES: + execute_script_report( + report_name=report, + module="Manufacturing", + filters=filter, + default_filters=DEFAULT_FILTERS, + optional_filters=OPTIONAL_FILTERS if filter.get("_optional") else None, + ) diff --git a/erpnext/stock/report/test_reports.py b/erpnext/stock/report/test_reports.py index d7fb5b2bf3f..9802f600add 100644 --- a/erpnext/stock/report/test_reports.py +++ b/erpnext/stock/report/test_reports.py @@ -40,6 +40,7 @@ REPORT_FILTER_TEST_CASES: List[Tuple[ReportName, ReportFilters]] = [ ("Item Variant Details", {"item": "_Test Variant Item",}), ("Total Stock Summary", {"group_by": "warehouse",}), ("Batch Item Expiry Status", {}), + ("Process Loss Report", {}), ("Stock Ageing", {"range1": 30, "range2": 60, "range3": 90, "_optional": True}), ] From 4f5d67883c809cf8338edfd7a3b4bfc9d83d3d5c Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 20 Oct 2021 21:10:45 +0530 Subject: [PATCH 041/164] fix: consolidated report issue #28035 (#28039) fix: consolidated report issue (cherry picked from commit 871cb1157fe2ff10e8027e86cc37400fa47688ce) Co-authored-by: rohitwaghchaure --- .../consolidated_financial_statement.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py index a600ead9e54..0de2a9854d6 100644 --- a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py +++ b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py @@ -115,7 +115,7 @@ def prepare_companywise_opening_balance(asset_data, liability_data, equity_data, # opening_value = Aseet - liability - equity for data in [asset_data, liability_data, equity_data]: account_name = get_root_account_name(data[0].root_type, company) - opening_value += get_opening_balance(account_name, data, company) + opening_value += (get_opening_balance(account_name, data, company) or 0.0) opening_balance[company] = opening_value From e0f21307315c3219616bd0ef447f354ffe753d2c Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 21 Oct 2021 10:34:31 +0530 Subject: [PATCH 042/164] feat: employee initial work history updated when transfer is performed (#27768) (#28045) * feat: employee initial work history updated when transfer is performed * fix: sider * fix: remove commit statement * fix: tests and code formatting * fix: tests Co-authored-by: Rucha Mahabal (cherry picked from commit 03bfc7794036663261b5e136c9bdf2be53d0b247) Co-authored-by: Mohammed Yusuf Shaikh <49878143+mohammedyusufshaikh@users.noreply.github.com> --- .../employee_promotion/employee_promotion.py | 6 +- .../employee_transfer/employee_transfer.py | 8 +- .../test_employee_transfer.py | 82 ++++++++++++++++++- erpnext/hr/utils.py | 34 +++++++- 4 files changed, 121 insertions(+), 9 deletions(-) diff --git a/erpnext/hr/doctype/employee_promotion/employee_promotion.py b/erpnext/hr/doctype/employee_promotion/employee_promotion.py index 164d48b8952..b05175200e9 100644 --- a/erpnext/hr/doctype/employee_promotion/employee_promotion.py +++ b/erpnext/hr/doctype/employee_promotion/employee_promotion.py @@ -9,7 +9,7 @@ from frappe import _ from frappe.model.document import Document from frappe.utils import getdate -from erpnext.hr.utils import update_employee, validate_active_employee +from erpnext.hr.utils import update_employee_work_history, validate_active_employee class EmployeePromotion(Document): @@ -23,10 +23,10 @@ class EmployeePromotion(Document): def on_submit(self): employee = frappe.get_doc("Employee", self.employee) - employee = update_employee(employee, self.promotion_details, date=self.promotion_date) + employee = update_employee_work_history(employee, self.promotion_details, date=self.promotion_date) employee.save() def on_cancel(self): employee = frappe.get_doc("Employee", self.employee) - employee = update_employee(employee, self.promotion_details, cancel=True) + employee = update_employee_work_history(employee, self.promotion_details, cancel=True) employee.save() diff --git a/erpnext/hr/doctype/employee_transfer/employee_transfer.py b/erpnext/hr/doctype/employee_transfer/employee_transfer.py index b1f66098f0d..29d93f348cc 100644 --- a/erpnext/hr/doctype/employee_transfer/employee_transfer.py +++ b/erpnext/hr/doctype/employee_transfer/employee_transfer.py @@ -9,7 +9,7 @@ from frappe import _ from frappe.model.document import Document from frappe.utils import getdate -from erpnext.hr.utils import update_employee +from erpnext.hr.utils import update_employee_work_history class EmployeeTransfer(Document): @@ -24,7 +24,7 @@ class EmployeeTransfer(Document): new_employee = frappe.copy_doc(employee) new_employee.name = None new_employee.employee_number = None - new_employee = update_employee(new_employee, self.transfer_details, date=self.transfer_date) + new_employee = update_employee_work_history(new_employee, self.transfer_details, date=self.transfer_date) if self.new_company and self.company != self.new_company: new_employee.internal_work_history = [] new_employee.date_of_joining = self.transfer_date @@ -39,7 +39,7 @@ class EmployeeTransfer(Document): employee.db_set("relieving_date", self.transfer_date) employee.db_set("status", "Left") else: - employee = update_employee(employee, self.transfer_details, date=self.transfer_date) + employee = update_employee_work_history(employee, self.transfer_details, date=self.transfer_date) if self.new_company and self.company != self.new_company: employee.company = self.new_company employee.date_of_joining = self.transfer_date @@ -56,7 +56,7 @@ class EmployeeTransfer(Document): employee.status = "Active" employee.relieving_date = '' else: - employee = update_employee(employee, self.transfer_details, cancel=True) + employee = update_employee_work_history(employee, self.transfer_details, date=self.transfer_date, cancel=True) if self.new_company != self.company: employee.company = self.company employee.save() diff --git a/erpnext/hr/doctype/employee_transfer/test_employee_transfer.py b/erpnext/hr/doctype/employee_transfer/test_employee_transfer.py index ad2f3ade054..c0440d09e74 100644 --- a/erpnext/hr/doctype/employee_transfer/test_employee_transfer.py +++ b/erpnext/hr/doctype/employee_transfer/test_employee_transfer.py @@ -4,6 +4,7 @@ from __future__ import unicode_literals import unittest +from datetime import date import frappe from frappe.utils import add_days, getdate @@ -15,7 +16,12 @@ class TestEmployeeTransfer(unittest.TestCase): def setUp(self): make_employee("employee2@transfers.com") make_employee("employee3@transfers.com") - frappe.db.sql("""delete from `tabEmployee Transfer`""") + create_company() + create_employee() + create_employee_transfer() + + def tearDown(self): + frappe.db.rollback() def test_submit_before_transfer_date(self): transfer_obj = frappe.get_doc({ @@ -57,3 +63,77 @@ class TestEmployeeTransfer(unittest.TestCase): self.assertTrue(transfer.new_employee_id) self.assertEqual(frappe.get_value("Employee", transfer.new_employee_id, "status"), "Active") self.assertEqual(frappe.get_value("Employee", transfer.employee, "status"), "Left") + + def test_employee_history(self): + name = frappe.get_value("Employee", {"first_name": "John", "company": "Test Company"}, "name") + doc = frappe.get_doc("Employee",name) + count = 0 + department = ["Accounts - TC", "Management - TC"] + designation = ["Accountant", "Manager"] + dt = [getdate("01-10-2021"), date.today()] + + for data in doc.internal_work_history: + self.assertEqual(data.department, department[count]) + self.assertEqual(data.designation, designation[count]) + self.assertEqual(data.from_date, dt[count]) + count = count + 1 + + data = frappe.db.get_list("Employee Transfer", filters={"employee":name}, fields=["*"]) + doc = frappe.get_doc("Employee Transfer", data[0]["name"]) + doc.cancel() + employee_doc = frappe.get_doc("Employee",name) + + for data in employee_doc.internal_work_history: + self.assertEqual(data.designation, designation[0]) + self.assertEqual(data.department, department[0]) + self.assertEqual(data.from_date, dt[0]) + +def create_employee(): + doc = frappe.get_doc({ + "doctype": "Employee", + "first_name": "John", + "company": "Test Company", + "gender": "Male", + "date_of_birth": getdate("30-09-1980"), + "date_of_joining": getdate("01-10-2021"), + "department": "Accounts - TC", + "designation": "Accountant" + }) + + doc.save() + +def create_company(): + exists = frappe.db.exists("Company", "Test Company") + if not exists: + doc = frappe.get_doc({ + "doctype": "Company", + "company_name": "Test Company", + "default_currency": "INR", + "country": "India" + }) + + doc.save() + +def create_employee_transfer(): + doc = frappe.get_doc({ + "doctype": "Employee Transfer", + "employee": frappe.get_value("Employee", {"first_name": "John", "company": "Test Company"}, "name"), + "transfer_date": date.today(), + "transfer_details": [ + { + "property": "Designation", + "current": "Accountant", + "new": "Manager", + "fieldname": "designation" + }, + { + "property": "Department", + "current": "Accounts - TC", + "new": "Management - TC", + "fieldname": "department" + } + ] + }) + + doc.save() + doc.submit() \ No newline at end of file diff --git a/erpnext/hr/utils.py b/erpnext/hr/utils.py index 8df5cb582e3..0b2f99c358e 100644 --- a/erpnext/hr/utils.py +++ b/erpnext/hr/utils.py @@ -145,7 +145,15 @@ def set_employee_name(doc): if doc.employee and not doc.employee_name: doc.employee_name = frappe.db.get_value("Employee", doc.employee, "employee_name") -def update_employee(employee, details, date=None, cancel=False): +def update_employee_work_history(employee, details, date=None, cancel=False): + if not employee.internal_work_history and not cancel: + employee.append("internal_work_history", { + "branch": employee.branch, + "designation": employee.designation, + "department": employee.department, + "from_date": employee.date_of_joining + }) + internal_work_history = {} for item in details: field = frappe.get_meta("Employee").get_field(item.fieldname) @@ -160,11 +168,35 @@ def update_employee(employee, details, date=None, cancel=False): setattr(employee, item.fieldname, new_data) if item.fieldname in ["department", "designation", "branch"]: internal_work_history[item.fieldname] = item.new + if internal_work_history and not cancel: internal_work_history["from_date"] = date employee.append("internal_work_history", internal_work_history) + + if cancel: + delete_employee_work_history(details, employee, date) + return employee +def delete_employee_work_history(details, employee, date): + filters = {} + for d in details: + for history in employee.internal_work_history: + if d.property == "Department" and history.department == d.new: + department = d.new + filters["department"] = department + if d.property == "Designation" and history.designation == d.new: + designation = d.new + filters["designation"] = designation + if d.property == "Branch" and history.branch == d.new: + branch = d.new + filters["branch"] = branch + if date and date == history.from_date: + filters["from_date"] = date + if filters: + frappe.db.delete("Employee Internal Work History", filters) + + @frappe.whitelist() def get_employee_fields_label(): fields = [] From 71e517f04348099f42cb4583b18a49d6d1e6d494 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 21 Oct 2021 10:55:11 +0530 Subject: [PATCH 043/164] test: fix get_attribute_filter test The function returns strings directly instead of objects now. --- .../product_data_engine/test_product_data_engine.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/erpnext/e_commerce/product_data_engine/test_product_data_engine.py b/erpnext/e_commerce/product_data_engine/test_product_data_engine.py index 925e6e7be3c..b52e140fcc4 100644 --- a/erpnext/e_commerce/product_data_engine/test_product_data_engine.py +++ b/erpnext/e_commerce/product_data_engine/test_product_data_engine.py @@ -175,9 +175,7 @@ class TestProductDataEngine(unittest.TestCase): filter_engine = ProductFiltersBuilder() attribute_filter = filter_engine.get_attribute_filters()[0] - attributes = attribute_filter.item_attribute_values - - attribute_values = [d.attribute_value for d in attributes] + attribute_values = attribute_filter.item_attribute_values self.assertEqual(attribute_filter.name, "Test Size") self.assertGreater(len(attribute_values), 0) @@ -349,4 +347,4 @@ def create_variant_web_item(): variant.save() if not frappe.db.exists("Website Item", {"variant_of": "Test Web Item"}): - make_website_item(variant, save=True) \ No newline at end of file + make_website_item(variant, save=True) From 152f9b0a432361f1c801364d1c85c42a16691a8a Mon Sep 17 00:00:00 2001 From: Devin Slauenwhite Date: Thu, 21 Oct 2021 01:28:35 -0400 Subject: [PATCH 044/164] fix: useless validation message (#28029) Co-authored-by: Rucha Mahabal --- erpnext/payroll/doctype/salary_slip/salary_slip.py | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py index 15e908720d1..28e6a3ccbba 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py @@ -172,7 +172,6 @@ class SalarySlip(TransactionBase): and employee = %s and name != %s {0}""".format(cond), (self.start_date, self.end_date, self.employee, self.name)) if ret_exist: - self.employee = '' frappe.throw(_("Salary Slip of employee {0} already created for this period").format(self.employee)) else: for data in self.timesheets: From 5ed32b59953d117041e55209066aab2666c8f9ff Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 21 Oct 2021 11:04:40 +0530 Subject: [PATCH 045/164] fix: unused imports --- erpnext/e_commerce/product_data_engine/filters.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/e_commerce/product_data_engine/filters.py b/erpnext/e_commerce/product_data_engine/filters.py index ba1b2ea9a56..6d44b2cb977 100644 --- a/erpnext/e_commerce/product_data_engine/filters.py +++ b/erpnext/e_commerce/product_data_engine/filters.py @@ -1,7 +1,6 @@ # Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt import frappe -from frappe import _dict from frappe.utils import floor @@ -141,4 +140,4 @@ class ProductFiltersBuilder: label = f"{discount}% and below" discount_filters.append([discount, label]) - return discount_filters \ No newline at end of file + return discount_filters From 46567f0fe9958bd14a3f074cc8b60f845764c1f0 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 21 Oct 2021 11:11:53 +0530 Subject: [PATCH 046/164] refactor: move process loss report to manufacturing (#28043) (#28047) * refactor: move process loss report to manufacturing * test: fix process loss report test Co-authored-by: Ankush Menat (cherry picked from commit 2849297471ece8c829e8701134c1150eb3efc4a3) Co-authored-by: Alan <2.alan.tom@gmail.com> --- .../report/process_loss_report/__init__.py | 0 .../report/process_loss_report/process_loss_report.js | 0 .../report/process_loss_report/process_loss_report.json | 7 ++----- .../report/process_loss_report/process_loss_report.py | 0 erpnext/manufacturing/report/test_reports.py | 1 + erpnext/stock/report/test_reports.py | 1 - 6 files changed, 3 insertions(+), 6 deletions(-) rename erpnext/{stock => manufacturing}/report/process_loss_report/__init__.py (100%) rename erpnext/{stock => manufacturing}/report/process_loss_report/process_loss_report.js (100%) rename erpnext/{stock => manufacturing}/report/process_loss_report/process_loss_report.json (83%) rename erpnext/{stock => manufacturing}/report/process_loss_report/process_loss_report.py (100%) diff --git a/erpnext/stock/report/process_loss_report/__init__.py b/erpnext/manufacturing/report/process_loss_report/__init__.py similarity index 100% rename from erpnext/stock/report/process_loss_report/__init__.py rename to erpnext/manufacturing/report/process_loss_report/__init__.py diff --git a/erpnext/stock/report/process_loss_report/process_loss_report.js b/erpnext/manufacturing/report/process_loss_report/process_loss_report.js similarity index 100% rename from erpnext/stock/report/process_loss_report/process_loss_report.js rename to erpnext/manufacturing/report/process_loss_report/process_loss_report.js diff --git a/erpnext/stock/report/process_loss_report/process_loss_report.json b/erpnext/manufacturing/report/process_loss_report/process_loss_report.json similarity index 83% rename from erpnext/stock/report/process_loss_report/process_loss_report.json rename to erpnext/manufacturing/report/process_loss_report/process_loss_report.json index afe4aff7f1c..7d3d13d98cf 100644 --- a/erpnext/stock/report/process_loss_report/process_loss_report.json +++ b/erpnext/manufacturing/report/process_loss_report/process_loss_report.json @@ -9,9 +9,9 @@ "filters": [], "idx": 0, "is_standard": "Yes", - "modified": "2021-08-24 16:38:15.233395", + "modified": "2021-10-20 22:03:57.606612", "modified_by": "Administrator", - "module": "Stock", + "module": "Manufacturing", "name": "Process Loss Report", "owner": "Administrator", "prepared_report": 0, @@ -21,9 +21,6 @@ "roles": [ { "role": "Manufacturing User" - }, - { - "role": "Stock User" } ] } \ No newline at end of file diff --git a/erpnext/stock/report/process_loss_report/process_loss_report.py b/erpnext/manufacturing/report/process_loss_report/process_loss_report.py similarity index 100% rename from erpnext/stock/report/process_loss_report/process_loss_report.py rename to erpnext/manufacturing/report/process_loss_report/process_loss_report.py diff --git a/erpnext/manufacturing/report/test_reports.py b/erpnext/manufacturing/report/test_reports.py index fa7f91a81bd..1de472659eb 100644 --- a/erpnext/manufacturing/report/test_reports.py +++ b/erpnext/manufacturing/report/test_reports.py @@ -33,6 +33,7 @@ REPORT_FILTER_TEST_CASES: List[Tuple[ReportName, ReportFilters]] = [ ("Job Card Summary", {"fiscal_year": "2021-2022"}), ("Production Analytics", {"range": "Monthly"}), ("Quality Inspection Summary", {}), + ("Process Loss Report", {}), ("Work Order Stock Report", {}), ("Work Order Summary", {"fiscal_year": "2021-2022", "age": 0}), ] diff --git a/erpnext/stock/report/test_reports.py b/erpnext/stock/report/test_reports.py index 9802f600add..d7fb5b2bf3f 100644 --- a/erpnext/stock/report/test_reports.py +++ b/erpnext/stock/report/test_reports.py @@ -40,7 +40,6 @@ REPORT_FILTER_TEST_CASES: List[Tuple[ReportName, ReportFilters]] = [ ("Item Variant Details", {"item": "_Test Variant Item",}), ("Total Stock Summary", {"group_by": "warehouse",}), ("Batch Item Expiry Status", {}), - ("Process Loss Report", {}), ("Stock Ageing", {"range1": 30, "range2": 60, "range3": 90, "_optional": True}), ] From d7e55387fcfe10d225fbf64a001acdf1c9aa6d56 Mon Sep 17 00:00:00 2001 From: marination Date: Thu, 21 Oct 2021 20:14:28 +0530 Subject: [PATCH 047/164] fix: SO Portal fixes - SO Portal: fixed missing images issue - SO Portal: fallback state when no images - SO Portal: code indentation in `order_macros.html` - SO Portal: Actions and indicator font size - Recommendations: fixed space between image and title - Recommendations: give empty image state min height --- erpnext/public/scss/shopping_cart.scss | 26 ++++--- .../includes/order/order_macros.html | 74 ++++++++++--------- erpnext/templates/pages/order.html | 4 +- 3 files changed, 56 insertions(+), 48 deletions(-) diff --git a/erpnext/public/scss/shopping_cart.scss b/erpnext/public/scss/shopping_cart.scss index e07bcbd28ec..429f4ca35df 100644 --- a/erpnext/public/scss/shopping_cart.scss +++ b/erpnext/public/scss/shopping_cart.scss @@ -459,6 +459,7 @@ body.product-page { min-height: 0px; .r-item-image { + min-height: 100px; width: 40%; .r-product-image { @@ -480,6 +481,7 @@ body.product-page { .r-item-info { font-size: 14px; padding-right: 0; + padding-left: 10px; width: 60%; a { @@ -672,18 +674,6 @@ body.product-page { img { max-height: 112px; } - - .no-image-cart-item { - max-height: 112px; - display: flex; justify-content: center; - background-color: var(--gray-200); - align-items: center; - color: var(--gray-400); - margin-top: .15rem; - border-radius: 6px; - height: 100%; - font-size: 24px; - } } .cart-items { @@ -862,6 +852,18 @@ body.product-page { } } +.no-image-cart-item { + max-height: 112px; + display: flex; justify-content: center; + background-color: var(--gray-200); + align-items: center; + color: var(--gray-400); + margin-top: .15rem; + border-radius: 6px; + height: 100%; + font-size: 24px; +} + .cart-empty.frappe-card { min-height: 76vh; @include flex(flex, center, center, column); diff --git a/erpnext/templates/includes/order/order_macros.html b/erpnext/templates/includes/order/order_macros.html index 7b3c9a41318..3f2c1f2a1df 100644 --- a/erpnext/templates/includes/order/order_macros.html +++ b/erpnext/templates/includes/order/order_macros.html @@ -1,43 +1,49 @@ -{% from "erpnext/templates/includes/macros.html" import product_image_square %} +{% from "erpnext/templates/includes/macros.html" import product_image %} {% macro item_name_and_description(d) %} -
-
-
- {{ product_image_square(d.thumbnail or d.image) }} -
-
-
- {{ d.item_code }} -
+
+
+
+ {% if d.thumbnail or d.image %} + {{ product_image(d.thumbnail or d.image, no_border=True) }} + {% else %} +
+ {{ frappe.utils.get_abbr(d.item_name) or "NA" }} +
+ {% endif %} +
+
+
+ {{ d.item_code }} +
{{ html2text(d.description) | truncate(140) }}
-
-
+
+
{% endmacro %} {% macro item_name_and_description_cart(d) %} -
-
-
- {{ product_image_square(d.thumbnail or d.image) }} -
-
-
- {{ d.item_name|truncate(25) }} -
- - - - - - - +
+
+
+ {{ product_image_square(d.thumbnail or d.image) }}
-
-
+
+
+ {{ d.item_name|truncate(25) }} +
+ + + + + + + +
+
+
{% endmacro %} diff --git a/erpnext/templates/pages/order.html b/erpnext/templates/pages/order.html index 19191d89096..f7775d5a283 100644 --- a/erpnext/templates/pages/order.html +++ b/erpnext/templates/pages/order.html @@ -14,7 +14,7 @@ {% block header_actions %}
diff --git a/erpnext/regional/india/e_invoice/einv_template.json b/erpnext/regional/india/e_invoice/einv_template.json index 60f490d6166..c2a28f20494 100644 --- a/erpnext/regional/india/e_invoice/einv_template.json +++ b/erpnext/regional/india/e_invoice/einv_template.json @@ -38,7 +38,7 @@ "Pos": "{buyer_details.place_of_supply}" }}, "DispDtls": {{ - "Nm": "{dispatch_details.company_name}", + "Nm": "{dispatch_details.legal_name}", "Addr1": "{dispatch_details.address_line1}", "Addr2": "{dispatch_details.address_line2}", "Loc": "{dispatch_details.location}", diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index f7aa65776b0..cd2c8a26b1c 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -138,8 +138,8 @@ def get_doc_details(invoice): invoice_date=invoice_date )) -def validate_address_fields(address, is_shipping_address): - if ((not address.gstin and not is_shipping_address) +def validate_address_fields(address, skip_gstin_validation): + if ((not address.gstin and not skip_gstin_validation) or not address.city or not address.pincode or not address.address_title @@ -151,10 +151,10 @@ def validate_address_fields(address, is_shipping_address): title=_('Missing Address Fields') ) -def get_party_details(address_name, is_shipping_address=False): +def get_party_details(address_name, skip_gstin_validation=False): addr = frappe.get_doc('Address', address_name) - validate_address_fields(addr, is_shipping_address) + validate_address_fields(addr, skip_gstin_validation) if addr.gst_state_number == 97: # according to einvoice standard @@ -443,7 +443,11 @@ def make_einvoice(invoice): if invoice.gst_category == 'Overseas': shipping_details = get_overseas_address_details(invoice.shipping_address_name) else: - shipping_details = get_party_details(invoice.shipping_address_name, is_shipping_address=True) + shipping_details = get_party_details(invoice.shipping_address_name, skip_gstin_validation=True) + + dispatch_details = frappe._dict({}) + if invoice.dispatch_address_name: + dispatch_details = get_party_details(invoice.dispatch_address_name, skip_gstin_validation=True) if invoice.is_pos and invoice.base_paid_amount: payment_details = get_payment_details(invoice) @@ -455,7 +459,7 @@ def make_einvoice(invoice): eway_bill_details = get_eway_bill_details(invoice) # not yet implemented - dispatch_details = period_details = export_details = frappe._dict({}) + period_details = export_details = frappe._dict({}) einvoice = schema.format( transaction_details=transaction_details, doc_details=doc_details, dispatch_details=dispatch_details, From de38c7070779775f3862939ab0d13a7c93b24f58 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Tue, 12 Oct 2021 13:25:55 +0530 Subject: [PATCH 062/164] perf: skip get_pricing_rules if no pricing rule exists (cherry picked from commit a5002525704d5e9d4a33913143f49a355ac20f16) --- erpnext/accounts/doctype/pricing_rule/utils.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py index 0637fdaef02..ef44b414761 100644 --- a/erpnext/accounts/doctype/pricing_rule/utils.py +++ b/erpnext/accounts/doctype/pricing_rule/utils.py @@ -29,6 +29,9 @@ def get_pricing_rules(args, doc=None): pricing_rules = [] values = {} + if not frappe.db.exists('Pricing Rule', {'disable': 0, args.transaction_type: 1}): + return + for apply_on in ['Item Code', 'Item Group', 'Brand']: pricing_rules.extend(_get_pricing_rules(apply_on, args, values)) if pricing_rules and not apply_multiple_pricing_rules(pricing_rules): From cb0168ae6547387bf7493e70703f06299bbe5c8e Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Tue, 12 Oct 2021 13:28:31 +0530 Subject: [PATCH 063/164] perf: fetch mode of payments data in single query (cherry picked from commit 7b691beabb40cdf3156a5cf81355d8d4c6b8bd7b) --- .../doctype/sales_invoice/sales_invoice.py | 34 +++++++++++++++---- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 902fceeac75..77dc52b463d 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -2026,22 +2026,23 @@ def update_multi_mode_option(doc, pos_profile): def append_payment(payment_mode): payment = doc.append('payments', {}) payment.default = payment_mode.default - payment.mode_of_payment = payment_mode.parent + payment.mode_of_payment = payment_mode.mop payment.account = payment_mode.default_account payment.type = payment_mode.type doc.set('payments', []) invalid_modes = [] - for pos_payment_method in pos_profile.get('payments'): - pos_payment_method = pos_payment_method.as_dict() + mode_of_payments = [d.mode_of_payment for d in pos_profile.get('payments')] + mode_of_payments_info = get_mode_of_payments_info(mode_of_payments, doc.company) - payment_mode = get_mode_of_payment_info(pos_payment_method.mode_of_payment, doc.company) + for row in pos_profile.get('payments'): + payment_mode = mode_of_payments_info.get(row.mode_of_payment) if not payment_mode: - invalid_modes.append(get_link_to_form("Mode of Payment", pos_payment_method.mode_of_payment)) + invalid_modes.append(get_link_to_form("Mode of Payment", row.mode_of_payment)) continue - payment_mode[0].default = pos_payment_method.default - append_payment(payment_mode[0]) + payment_mode.default = row.default + append_payment(payment_mode) if invalid_modes: if invalid_modes == 1: @@ -2057,6 +2058,25 @@ def get_all_mode_of_payments(doc): where mpa.parent = mp.name and mpa.company = %(company)s and mp.enabled = 1""", {'company': doc.company}, as_dict=1) +def get_mode_of_payments_info(mode_of_payments, company): + data = frappe.db.sql( + """ + select + mpa.default_account, mpa.parent as mop, mp.type as type + from + `tabMode of Payment Account` mpa,`tabMode of Payment` mp + where + mpa.parent = mp.name and + mpa.company = %s and + mp.enabled = 1 and + mp.name in (%s) + group by + mp.name + """, + (company, mode_of_payments), as_dict=1) + + return {row.get('mop'): row for row in data} + def get_mode_of_payment_info(mode_of_payment, company): return frappe.db.sql(""" select mpa.default_account, mpa.parent, mp.type as type From 97fc449222ce0b0b3fe5855c5c40f4db6748a935 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Tue, 12 Oct 2021 13:29:32 +0530 Subject: [PATCH 064/164] perf: get total company stock only for purchase order (cherry picked from commit eb3aae870f92e73b2b7babdb1bba01aaebf6e854) --- erpnext/stock/get_item_details.py | 36 ++++++++++++++++++------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index cbff2149d64..e0190b64a75 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -89,7 +89,13 @@ def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=Tru out.update(get_bin_details(args.item_code, args.get("from_warehouse"))) elif out.get("warehouse"): - out.update(get_bin_details(args.item_code, out.warehouse, args.company)) + if doc and doc.get('doctype') == 'Purchase Order': + # calculate company_total_stock only for po + bin_details = get_bin_details(args.item_code, out.warehouse, args.company) + else: + bin_details = get_bin_details(args.item_code, out.warehouse) + + out.update(bin_details) # update args with out, if key or value not exists for key, value in iteritems(out): @@ -485,8 +491,9 @@ def get_item_tax_template(args, item, out): "item_tax_template": None } """ - item_tax_template = args.get("item_tax_template") - item_tax_template = _get_item_tax_template(args, item.taxes, out) + item_tax_template = None + if item.taxes: + item_tax_template = _get_item_tax_template(args, item.taxes, out) if not item_tax_template: item_group = item.item_group @@ -502,17 +509,17 @@ def _get_item_tax_template(args, taxes, out=None, for_validate=False): taxes_with_no_validity = [] for tax in taxes: - tax_company = frappe.get_value("Item Tax Template", tax.item_tax_template, 'company') - if (tax.valid_from or tax.maximum_net_rate) and tax_company == args['company']: - # In purchase Invoice first preference will be given to supplier invoice date - # if supplier date is not present then posting date - validation_date = args.get('transaction_date') or args.get('bill_date') or args.get('posting_date') + tax_company = frappe.get_cached_value("Item Tax Template", tax.item_tax_template, 'company') + if tax_company == args['company']: + if (tax.valid_from or tax.maximum_net_rate): + # In purchase Invoice first preference will be given to supplier invoice date + # if supplier date is not present then posting date + validation_date = args.get('transaction_date') or args.get('bill_date') or args.get('posting_date') - if getdate(tax.valid_from) <= getdate(validation_date) \ - and is_within_valid_range(args, tax): - taxes_with_validity.append(tax) - else: - if tax_company == args['company']: + if getdate(tax.valid_from) <= getdate(validation_date) \ + and is_within_valid_range(args, tax): + taxes_with_validity.append(tax) + else: taxes_with_no_validity.append(tax) if taxes_with_validity: @@ -890,8 +897,7 @@ def get_pos_profile_item_details(company, args, pos_profile=None, update_data=Fa res[fieldname] = pos_profile.get(fieldname) if res.get("warehouse"): - res.actual_qty = get_bin_details(args.item_code, - res.warehouse).get("actual_qty") + res.actual_qty = get_bin_details(args.item_code, res.warehouse).get("actual_qty") return res From b1efb7f80b4dfa682cbf6a695dcb451370459e31 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Tue, 12 Oct 2021 13:30:40 +0530 Subject: [PATCH 065/164] perf: skip insertion of stock ledger entry (cherry picked from commit c7fc609236aeaada0375e90b664c041bdbc52570) --- erpnext/stock/stock_ledger.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index e8768c4aed3..ccb7ee6da83 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -123,12 +123,11 @@ def set_as_cancel(voucher_type, voucher_no): (now(), frappe.session.user, voucher_type, voucher_no)) def make_entry(args, allow_negative_stock=False, via_landed_cost_voucher=False): - args.update({"doctype": "Stock Ledger Entry"}) + args["doctype"] = "Stock Ledger Entry" sle = frappe.get_doc(args) sle.flags.ignore_permissions = 1 sle.allow_negative_stock=allow_negative_stock sle.via_landed_cost_voucher = via_landed_cost_voucher - sle.insert() sle.submit() return sle @@ -232,8 +231,7 @@ class update_entries_after(object): self.verbose = verbose self.allow_zero_rate = allow_zero_rate self.via_landed_cost_voucher = via_landed_cost_voucher - self.allow_negative_stock = allow_negative_stock \ - or cint(frappe.db.get_single_value("Stock Settings", "allow_negative_stock")) + self.allow_negative_stock = True self.args = frappe._dict(args) self.item_code = args.get("item_code") From a2a462f2f57cf0276e97a4cdaa720874db7a5289 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 12 Oct 2021 20:39:10 +0530 Subject: [PATCH 066/164] fix: undo changes to allow negative stock flag (cherry picked from commit 7bafa11d5758c40496898cd0151efde4ff358946) --- erpnext/stock/stock_ledger.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index ccb7ee6da83..d903c0b8ece 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -231,7 +231,8 @@ class update_entries_after(object): self.verbose = verbose self.allow_zero_rate = allow_zero_rate self.via_landed_cost_voucher = via_landed_cost_voucher - self.allow_negative_stock = True + self.allow_negative_stock = allow_negative_stock \ + or cint(frappe.db.get_single_value("Stock Settings", "allow_negative_stock")) self.args = frappe._dict(args) self.item_code = args.get("item_code") From 5d4d6bb52bfa19cc35e9beb5de689fc423842905 Mon Sep 17 00:00:00 2001 From: Saqib Date: Mon, 18 Oct 2021 10:43:15 +0530 Subject: [PATCH 067/164] fix: sider (cherry picked from commit ac381d21fe94b99406b4d7d332a6356a41bd8e26) --- erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 77dc52b463d..aeab2e01c14 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -2072,8 +2072,7 @@ def get_mode_of_payments_info(mode_of_payments, company): mp.name in (%s) group by mp.name - """, - (company, mode_of_payments), as_dict=1) + """, (company, mode_of_payments), as_dict=1) return {row.get('mop'): row for row in data} From d4b4bd9d97a94d792529bd3fe8e42705d0438b76 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 27 Oct 2021 13:20:51 +0530 Subject: [PATCH 068/164] fix: update production plan status (backport #27567) * fix: update production plan status #27567 fix: update production plan status (cherry picked from commit 05831b18ad08ffcbc8a332ed74697efae1bd870a) # Conflicts: # erpnext/patches.txt * fix: resolve conflicts Co-authored-by: Alan <2.alan.tom@gmail.com> Co-authored-by: Ankush Menat --- .../production_plan/production_plan.py | 11 ++++++- erpnext/patches.txt | 1 + .../v12_0/update_production_plan_status.py | 31 +++++++++++++++++++ 3 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 erpnext/patches/v12_0/update_production_plan_status.py diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 7e6fc3c4a64..2424ef9a71c 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -311,7 +311,7 @@ class ProductionPlan(Document): if self.total_produced_qty > 0: self.status = "In Process" - if self.total_produced_qty >= self.total_planned_qty: + if self.check_have_work_orders_completed(): self.status = "Completed" if self.status != 'Completed': @@ -575,6 +575,15 @@ class ProductionPlan(Document): self.append("sub_assembly_items", data) + def check_have_work_orders_completed(self): + wo_status = frappe.db.get_list( + "Work Order", + filters={"production_plan": self.name}, + fields="status", + pluck="status" + ) + return all(s == "Completed" for s in wo_status) + @frappe.whitelist() def download_raw_materials(doc, warehouses=None): if isinstance(doc, str): diff --git a/erpnext/patches.txt b/erpnext/patches.txt index c8c7520260c..bc027520da2 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -329,3 +329,4 @@ erpnext.patches.v13_0.trim_sales_invoice_custom_field_length erpnext.patches.v13_0.enable_scheduler_job_for_item_reposting erpnext.patches.v13_0.requeue_failed_reposts erpnext.patches.v13_0.fetch_thumbnail_in_website_items +erpnext.patches.v12_0.update_production_plan_status diff --git a/erpnext/patches/v12_0/update_production_plan_status.py b/erpnext/patches/v12_0/update_production_plan_status.py new file mode 100644 index 00000000000..06fc503a33f --- /dev/null +++ b/erpnext/patches/v12_0/update_production_plan_status.py @@ -0,0 +1,31 @@ +# Copyright (c) 2021, Frappe and Contributors +# License: GNU General Public License v3. See license.txt + +import frappe + + +def execute(): + frappe.reload_doc("manufacturing", "doctype", "production_plan") + frappe.db.sql(""" + UPDATE `tabProduction Plan` ppl + SET status = "Completed" + WHERE ppl.name IN ( + SELECT ss.name FROM ( + SELECT + ( + count(wo.status = "Completed") = + count(pp.name) + ) = + ( + pp.status != "Completed" + AND pp.total_produced_qty >= pp.total_planned_qty + ) AS should_set, + pp.name AS name + FROM + `tabWork Order` wo INNER JOIN`tabProduction Plan` pp + ON wo.production_plan = pp.name + GROUP BY pp.name + HAVING should_set = 1 + ) ss + ) + """) From fb742476f8211812b3bd36c89fca9e9a018b52a2 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 27 Oct 2021 13:26:57 +0530 Subject: [PATCH 069/164] fix: Payment Terms validation precision (#28104) (cherry picked from commit 9c1705205f4597639c800b04eaf421bb7e987844) Co-authored-by: Deepesh Garg Co-authored-by: Afshan <33727827+AfshanKhan@users.noreply.github.com> --- erpnext/controllers/accounts_controller.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 3aa9fd6a7c6..5c5606c2140 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1359,8 +1359,8 @@ class AccountsController(TransactionBase): total = 0 base_total = 0 for d in self.get("payment_schedule"): - total += flt(d.payment_amount) - base_total += flt(d.base_payment_amount) + total += flt(d.payment_amount, d.precision("payment_amount")) + base_total += flt(d.base_payment_amount, d.precision("base_payment_amount")) base_grand_total = self.get("base_rounded_total") or self.base_grand_total grand_total = self.get("rounded_total") or self.grand_total @@ -1376,8 +1376,9 @@ class AccountsController(TransactionBase): else: grand_total -= self.get("total_advance") base_grand_total = flt(grand_total * self.get("conversion_rate"), self.precision("base_grand_total")) - if total != flt(grand_total, self.precision("grand_total")) or \ - base_total != flt(base_grand_total, self.precision("base_grand_total")): + + if flt(total, self.precision("grand_total")) != flt(grand_total, self.precision("grand_total")) or \ + flt(base_total, self.precision("base_grand_total")) != flt(base_grand_total, self.precision("base_grand_total")): frappe.throw(_("Total Payment Amount in Payment Schedule must be equal to Grand / Rounded Total")) def is_rounded_total_disabled(self): From f1b7bb88782e0fd8967ab88fc36e6708592514ab Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 27 Oct 2021 13:30:32 +0530 Subject: [PATCH 070/164] fix: Update receivable/payable account on company change (backport #28057) (#28087) * fix: Update receivable/payable account on company change (cherry picked from commit 65025fb628d55a97d3c2281760d7d5954ac44912) # Conflicts: # erpnext/accounts/doctype/sales_invoice/sales_invoice.js * fix: Update sales_invoice.js Co-authored-by: Deepesh Garg Co-authored-by: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Co-authored-by: Afshan <33727827+AfshanKhan@users.noreply.github.com> --- .../accounts/doctype/purchase_invoice/purchase_invoice.js | 6 ++++++ erpnext/accounts/doctype/sales_invoice/sales_invoice.js | 8 ++++++++ 2 files changed, 14 insertions(+) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index aef9243aad0..39bb3cdca6a 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -590,5 +590,11 @@ frappe.ui.form.on("Purchase Invoice", { company: function(frm) { erpnext.accounts.dimensions.update_dimension(frm, frm.doctype); + + if (frm.doc.company) { + frappe.db.get_value('Company', frm.doc.company, 'default_payable_account', (r) => { + frm.set_value('credit_to', r.default_payable_account); + }); + } }, }) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index 24928186b6b..9522c01f33a 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -10,9 +10,17 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte this.setup_posting_date_time_check(); this._super(doc); }, + company: function() { erpnext.accounts.dimensions.update_dimension(this.frm, this.frm.doctype); + let me = this; + if (this.frm.doc.company) { + frappe.db.get_value('Company', this.frm.doc.company, 'default_receivable_account', (r) => { + me.frm.set_value('debit_to', r.default_receivable_account); + }); + } }, + onload: function() { var me = this; this._super(); From e8b46bf3a361958e1480522a23f796b6ec617697 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 27 Oct 2021 14:03:24 +0530 Subject: [PATCH 071/164] fix(ux): add naming series to ERPNext setting workspace (backport #28090) (#28103) * fix(ux): add naming series to ERPNext setting workspace (#28090) * fix(ux): add naming series to setting workspace * fix: doctype link to naming series Co-authored-by: Ankush Menat Co-authored-by: Summayya Co-authored-by: Ankush Menat (cherry picked from commit 0806e32049fee67b731cb8c742fc6654faae6143) # Conflicts: # erpnext/setup/workspace/erpnext_settings/erpnext_settings.json * fix: conflicts Co-authored-by: Summayya Hashmani <58825865+sumaiya2908@users.noreply.github.com> Co-authored-by: Afshan <33727827+AfshanKhan@users.noreply.github.com> --- .../workspace/erpnext_settings/erpnext_settings.json | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/erpnext/setup/workspace/erpnext_settings/erpnext_settings.json b/erpnext/setup/workspace/erpnext_settings/erpnext_settings.json index 6ca3d637da4..db171fa9627 100644 --- a/erpnext/setup/workspace/erpnext_settings/erpnext_settings.json +++ b/erpnext/setup/workspace/erpnext_settings/erpnext_settings.json @@ -1,6 +1,7 @@ { "category": "Modules", "charts": [], + "content": "[{\"type\":\"header\",\"data\":{\"text\":\"Your Shortcuts\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\",\"level\":4,\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Projects Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Accounts Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Stock Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"HR Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Selling Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Buying Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Support Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Shopping Cart Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Portal Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Domain Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Products Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Naming Series\",\"col\":4}}]", "creation": "2020-03-12 14:47:51.166455", "developer_mode_only": 0, "disable_user_customization": 0, @@ -15,7 +16,7 @@ "is_standard": 1, "label": "ERPNext Settings", "links": [], - "modified": "2021-06-12 01:58:11.399566", + "modified": "2021-10-26 21:32:55.323591", "modified_by": "Administrator", "module": "Setup", "name": "ERPNext Settings", @@ -29,6 +30,14 @@ "link_to": "Projects Settings", "type": "DocType" }, + { + "color": "Grey", + "doc_view": "", + "icon": "dot-horizontal", + "label": "Naming Series", + "link_to": "Naming Series", + "type": "DocType" + }, { "icon": "accounting", "label": "Accounts Settings", From e757970db353e9908cefa235e58110eb63b85d45 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 27 Oct 2021 14:45:05 +0530 Subject: [PATCH 072/164] fix(general_ledger): Order by in case Group by Account (#28093) (#28106) * Update general_ledger.py Fix order_by_statement if filter group by: Group by Account * chore: whitespace Co-authored-by: Ankush Menat Co-authored-by: Afshan <33727827+AfshanKhan@users.noreply.github.com> (cherry picked from commit f24ed6723ebc16282b3968e9ef5d58e629f7deb1) Co-authored-by: hendrik --- erpnext/accounts/report/general_ledger/general_ledger.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index 0094bc2eebe..31416da4ac4 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -155,6 +155,8 @@ def get_gl_entries(filters, accounting_dimensions): if filters.get("group_by") == "Group by Voucher": order_by_statement = "order by posting_date, voucher_type, voucher_no" + if filters.get("group_by") == "Group by Account": + order_by_statement = "order by account, posting_date, creation" if filters.get("include_default_book_entries"): filters['company_fb'] = frappe.db.get_value("Company", From 5867d97e7ccb9e0931c5ab35b67a2fd4fde93260 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 27 Oct 2021 15:29:08 +0530 Subject: [PATCH 073/164] fix(ux): misleading label for image fields (#28107) (#28109) (cherry picked from commit e0cf45e7ec60649a35c591e3da44df4b47fb0cdd) Co-authored-by: Ankush Menat --- erpnext/manufacturing/doctype/bom/bom.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.json b/erpnext/manufacturing/doctype/bom/bom.json index 7e539183b0c..62187077f3d 100644 --- a/erpnext/manufacturing/doctype/bom/bom.json +++ b/erpnext/manufacturing/doctype/bom/bom.json @@ -436,7 +436,7 @@ "description": "Item Image (if not slideshow)", "fieldname": "website_image", "fieldtype": "Attach Image", - "label": "Image" + "label": "Website Image" }, { "allow_on_submit": 1, @@ -539,7 +539,7 @@ "image_field": "image", "is_submittable": 1, "links": [], - "modified": "2021-05-16 12:25:09.081968", + "modified": "2021-10-27 14:52:04.500251", "modified_by": "Administrator", "module": "Manufacturing", "name": "BOM", From 48acb56cb0bbc8560a941665f7153d6c25d09fde Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 27 Oct 2021 16:16:31 +0530 Subject: [PATCH 074/164] fix: Remove pointless buttons from Payment Order (#28108) fix: Remove pointless buttons from Payment Order (cherry picked from commit c9f3ea5fea6ee1d07897d8ae6496d9a26289f900) Co-authored-by: Ganga Manoj --- erpnext/accounts/doctype/payment_order/payment_order.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/accounts/doctype/payment_order/payment_order.js b/erpnext/accounts/doctype/payment_order/payment_order.js index aa373bc2fcc..9074defa577 100644 --- a/erpnext/accounts/doctype/payment_order/payment_order.js +++ b/erpnext/accounts/doctype/payment_order/payment_order.js @@ -10,6 +10,9 @@ frappe.ui.form.on('Payment Order', { } } }); + + frm.set_df_property('references', 'cannot_add_rows', true); + frm.set_df_property('references', 'cannot_delete_rows', true); }, refresh: function(frm) { if (frm.doc.docstatus == 0) { From 99a1eea982c5d895878755f6862c3ac2e91f05f5 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 27 Oct 2021 18:39:07 +0530 Subject: [PATCH 075/164] fix: remove bad hardcoded max value (cherry picked from commit 5f9bd9b8e9f0dca849f6ad78269cf4151181d5a5) --- erpnext/manufacturing/doctype/work_order/work_order.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index e282dd3ecba..f881e1bf16a 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -685,9 +685,7 @@ class WorkOrder(Document): if not d.operation: d.operation = operation else: - # Attribute a big number (999) to idx for sorting putpose in case idx is NULL - # For instance in BOM Explosion Item child table, the items coming from sub assembly items - for item in sorted(item_dict.values(), key=lambda d: d['idx'] or 9999): + for item in sorted(item_dict.values(), key=lambda d: d['idx'] or float('inf')): self.append('required_items', { 'rate': item.rate, 'amount': item.rate * item.qty, From 4bfacabf855a502dc87616f5a660764953f7ceaf Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 27 Oct 2021 18:55:54 +0530 Subject: [PATCH 076/164] fix(ux): alternative item two way validation (cherry picked from commit 5902762ec8e243c17116732d616426a17f49bc1c) --- .../item_alternative/item_alternative.py | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/erpnext/stock/doctype/item_alternative/item_alternative.py b/erpnext/stock/doctype/item_alternative/item_alternative.py index 6080fb4a5fa..6f2a389b707 100644 --- a/erpnext/stock/doctype/item_alternative/item_alternative.py +++ b/erpnext/stock/doctype/item_alternative/item_alternative.py @@ -25,19 +25,29 @@ class ItemAlternative(Document): frappe.throw(_("Alternative item must not be same as item code")) item_meta = frappe.get_meta("Item") - fields = ["is_stock_item", "include_item_in_manufacturing","has_serial_no","has_batch_no"] - item_data = frappe.db.get_values("Item", self.item_code, fields, as_dict=1) - alternative_item_data = frappe.db.get_values("Item", self.alternative_item_code, fields, as_dict=1) + fields = ["is_stock_item", "include_item_in_manufacturing","has_serial_no", "has_batch_no", "allow_alternative_item"] + item_data = frappe.db.get_value("Item", self.item_code, fields, as_dict=1) + alternative_item_data = frappe.db.get_value("Item", self.alternative_item_code, fields, as_dict=1) for field in fields: - if item_data[0].get(field) != alternative_item_data[0].get(field): + if item_data.get(field) != alternative_item_data.get(field): raise_exception, alert = [1, False] if field == "is_stock_item" else [0, True] frappe.msgprint(_("The value of {0} differs between Items {1} and {2}") \ .format(frappe.bold(item_meta.get_label(field)), frappe.bold(self.alternative_item_code), frappe.bold(self.item_code)), - alert=alert, raise_exception=raise_exception) + alert=alert, raise_exception=raise_exception, indicator="Orange") + + alternate_item_check_msg = _("Allow Alternative Item must be checked on Item {}") + + if not item_data.allow_alternative_item: + frappe.throw(alternate_item_check_msg.format(self.item_code)) + if self.two_way and not alternative_item_data.allow_alternative_item: + frappe.throw(alternate_item_check_msg.format(self.item_code)) + + + def validate_duplicate(self): if frappe.db.get_value("Item Alternative", {'item_code': self.item_code, From aa210ba6c83d56234d8b3ab9cdcfcd5fa73f945e Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 27 Oct 2021 19:15:44 +0530 Subject: [PATCH 077/164] fix: don't show blocked supplier in autocomplete (cherry picked from commit 2221c9ed89467ee7db7289da7b15778aed0c68a0) --- erpnext/controllers/queries.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index 9f28646a0b9..48552a972f0 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -132,7 +132,8 @@ def supplier_query(doctype, txt, searchfield, start, page_len, filters): return frappe.db.sql("""select {field} from `tabSupplier` where docstatus < 2 and ({key} like %(txt)s - or supplier_name like %(txt)s) and disabled=0 + or supplier_name like %(txt)s) and disabled=0 + and (on_hold = 0 or (on_hold = 1 and CURDATE() > release_date)) {mcond} order by if(locate(%(_txt)s, name), locate(%(_txt)s, name), 99999), From 56c98aa59e5122fb3225e1a8327d8d551d53ba06 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 27 Oct 2021 19:22:11 +0530 Subject: [PATCH 078/164] fix(ux): make qty 1 by default in WO (cherry picked from commit d81b87d9b3c83413adf4e1a87fccb95486b50931) --- erpnext/manufacturing/doctype/work_order/work_order.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.json b/erpnext/manufacturing/doctype/work_order/work_order.json index 913fc85af61..7f8e816a22a 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.json +++ b/erpnext/manufacturing/doctype/work_order/work_order.json @@ -182,6 +182,7 @@ "reqd": 1 }, { + "default": "1.0", "fieldname": "qty", "fieldtype": "Float", "label": "Qty To Manufacture", @@ -572,10 +573,11 @@ "image_field": "image", "is_submittable": 1, "links": [], - "modified": "2021-08-24 15:14:03.844937", + "modified": "2021-10-27 19:21:35.139888", "modified_by": "Administrator", "module": "Manufacturing", "name": "Work Order", + "naming_rule": "By \"Naming Series\" field", "nsm_parent_field": "parent_work_order", "owner": "Administrator", "permissions": [ From b34e737930b62f00368dff2282a9c396b72348bd Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 27 Oct 2021 19:39:18 +0530 Subject: [PATCH 079/164] fix: Autoemail report not showing dynamic report filters (cherry picked from commit 3a6894fb9c2803cdd411a577f05f1cbf76ac2b06) --- erpnext/public/js/financial_statements.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/erpnext/public/js/financial_statements.js b/erpnext/public/js/financial_statements.js index 0d79b10c041..1a309ba0156 100644 --- a/erpnext/public/js/financial_statements.js +++ b/erpnext/public/js/financial_statements.js @@ -113,15 +113,15 @@ function get_filters() { "fieldname":"period_start_date", "label": __("Start Date"), "fieldtype": "Date", - "hidden": 1, - "reqd": 1 + "reqd": 1, + "depends_on": "eval:doc.filter_based_on == 'Date Range'" }, { "fieldname":"period_end_date", "label": __("End Date"), "fieldtype": "Date", - "hidden": 1, - "reqd": 1 + "reqd": 1, + "depends_on": "eval:doc.filter_based_on == 'Date Range'" }, { "fieldname":"from_fiscal_year", @@ -129,7 +129,8 @@ function get_filters() { "fieldtype": "Link", "options": "Fiscal Year", "default": frappe.defaults.get_user_default("fiscal_year"), - "reqd": 1 + "reqd": 1, + "depends_on": "eval:doc.filter_based_on == 'Fiscal Year'" }, { "fieldname":"to_fiscal_year", @@ -137,7 +138,8 @@ function get_filters() { "fieldtype": "Link", "options": "Fiscal Year", "default": frappe.defaults.get_user_default("fiscal_year"), - "reqd": 1 + "reqd": 1, + "depends_on": "eval:doc.filter_based_on == 'Fiscal Year'" }, { "fieldname": "periodicity", From 9c7cdc10792e7cf6daaf12ca95e1c75bfda541c0 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 28 Oct 2021 11:48:19 +0530 Subject: [PATCH 080/164] fix: opportunity link is missign from customer (#28110) (#28121) (cherry picked from commit 4787a7520847456a510577b708acfc22b8e4840b) Co-authored-by: Anupam Kumar --- erpnext/crm/doctype/opportunity/opportunity.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/crm/doctype/opportunity/opportunity.py b/erpnext/crm/doctype/opportunity/opportunity.py index 8027cbc69cc..a304171ed49 100644 --- a/erpnext/crm/doctype/opportunity/opportunity.py +++ b/erpnext/crm/doctype/opportunity/opportunity.py @@ -305,6 +305,8 @@ def make_request_for_quotation(source_name, target_doc=None): @frappe.whitelist() def make_customer(source_name, target_doc=None): def set_missing_values(source, target): + target.opportunity_name = source.name + if source.opportunity_from == "Lead": target.lead_name = source.party_name From e03965afa512a4cd88dd4b98314e4d4d0b7782dc Mon Sep 17 00:00:00 2001 From: Ahmad Date: Thu, 26 Aug 2021 07:35:59 +0500 Subject: [PATCH 081/164] feat(Regional): KSA VAT Report --- .../workspace/accounting/accounting.json | 23 ++- .../ksa_vat_purchase_account/__init__.py | 0 .../ksa_vat_purchase_account.json | 49 +++++ .../ksa_vat_purchase_account.py | 9 + .../doctype/ksa_vat_sales_account/__init__.py | 0 .../ksa_vat_sales_account.js | 8 + .../ksa_vat_sales_account.json | 49 +++++ .../ksa_vat_sales_account.py | 9 + .../test_ksa_vat_sales_account.py | 9 + .../doctype/ksa_vat_setting/__init__.py | 0 .../ksa_vat_setting/ksa_vat_setting.js | 8 + .../ksa_vat_setting/ksa_vat_setting.json | 49 +++++ .../ksa_vat_setting/ksa_vat_setting.py | 9 + .../ksa_vat_setting/ksa_vat_setting_list.js | 5 + .../ksa_vat_setting/test_ksa_vat_setting.py | 9 + erpnext/regional/report/ksa_vat/__init__.py | 0 erpnext/regional/report/ksa_vat/ksa_vat.js | 60 ++++++ erpnext/regional/report/ksa_vat/ksa_vat.json | 32 ++++ erpnext/regional/report/ksa_vat/ksa_vat.py | 179 ++++++++++++++++++ erpnext/regional/saudi_arabia/setup.py | 19 +- .../regional/saudi_arabia/wizard/__init__.py | 0 .../saudi_arabia/wizard/data/__init__.py | 0 .../wizard/data/ksa_vat_settings.json | 47 +++++ .../wizard/operations/__init__.py | 0 .../operations/setup_ksa_vat_setting.py | 48 +++++ 25 files changed, 619 insertions(+), 2 deletions(-) create mode 100644 erpnext/regional/doctype/ksa_vat_purchase_account/__init__.py create mode 100644 erpnext/regional/doctype/ksa_vat_purchase_account/ksa_vat_purchase_account.json create mode 100644 erpnext/regional/doctype/ksa_vat_purchase_account/ksa_vat_purchase_account.py create mode 100644 erpnext/regional/doctype/ksa_vat_sales_account/__init__.py create mode 100644 erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.js create mode 100644 erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.json create mode 100644 erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.py create mode 100644 erpnext/regional/doctype/ksa_vat_sales_account/test_ksa_vat_sales_account.py create mode 100644 erpnext/regional/doctype/ksa_vat_setting/__init__.py create mode 100644 erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.js create mode 100644 erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.json create mode 100644 erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.py create mode 100644 erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting_list.js create mode 100644 erpnext/regional/doctype/ksa_vat_setting/test_ksa_vat_setting.py create mode 100644 erpnext/regional/report/ksa_vat/__init__.py create mode 100644 erpnext/regional/report/ksa_vat/ksa_vat.js create mode 100644 erpnext/regional/report/ksa_vat/ksa_vat.json create mode 100644 erpnext/regional/report/ksa_vat/ksa_vat.py create mode 100644 erpnext/regional/saudi_arabia/wizard/__init__.py create mode 100644 erpnext/regional/saudi_arabia/wizard/data/__init__.py create mode 100644 erpnext/regional/saudi_arabia/wizard/data/ksa_vat_settings.json create mode 100644 erpnext/regional/saudi_arabia/wizard/operations/__init__.py create mode 100644 erpnext/regional/saudi_arabia/wizard/operations/setup_ksa_vat_setting.py diff --git a/erpnext/accounts/workspace/accounting/accounting.json b/erpnext/accounts/workspace/accounting/accounting.json index 7e3ecaf3ab6..236e47594d1 100644 --- a/erpnext/accounts/workspace/accounting/accounting.json +++ b/erpnext/accounts/workspace/accounting/accounting.json @@ -454,6 +454,17 @@ "onboard": 0, "type": "Link" }, + { + "dependencies": "GL Entry", + "hidden": 0, + "is_query_report": 1, + "label": "KSA VAT Report", + "link_to": "KSA VAT", + "link_type": "Report", + "onboard": 0, + "only_for": "Saudi Arabia", + "type": "Link" + }, { "hidden": 0, "is_query_report": 0, @@ -1034,6 +1045,16 @@ "onboard": 0, "type": "Link" }, + { + "hidden": 0, + "is_query_report": 0, + "label": "KSA VAT Setting", + "link_to": "KSA VAT Setting", + "link_type": "DocType", + "onboard": 0, + "only_for": "Saudi Arabia", + "type": "Link" + }, { "hidden": 0, "is_query_report": 0, @@ -1082,7 +1103,7 @@ "type": "Link" } ], - "modified": "2021-08-23 16:06:34.167267", + "modified": "2021-08-26 13:15:52.872470", "modified_by": "Administrator", "module": "Accounts", "name": "Accounting", diff --git a/erpnext/regional/doctype/ksa_vat_purchase_account/__init__.py b/erpnext/regional/doctype/ksa_vat_purchase_account/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/regional/doctype/ksa_vat_purchase_account/ksa_vat_purchase_account.json b/erpnext/regional/doctype/ksa_vat_purchase_account/ksa_vat_purchase_account.json new file mode 100644 index 00000000000..89ba3e977af --- /dev/null +++ b/erpnext/regional/doctype/ksa_vat_purchase_account/ksa_vat_purchase_account.json @@ -0,0 +1,49 @@ +{ + "actions": [], + "creation": "2021-07-13 09:17:09.862163", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "title", + "item_tax_template", + "account" + ], + "fields": [ + { + "fieldname": "account", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Account", + "options": "Account", + "reqd": 1 + }, + { + "fieldname": "title", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Title", + "reqd": 1 + }, + { + "fieldname": "item_tax_template", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Item Tax Template", + "options": "Item Tax Template", + "reqd": 1 + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-08-04 06:42:38.205597", + "modified_by": "Administrator", + "module": "Regional", + "name": "KSA VAT Purchase Account", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/regional/doctype/ksa_vat_purchase_account/ksa_vat_purchase_account.py b/erpnext/regional/doctype/ksa_vat_purchase_account/ksa_vat_purchase_account.py new file mode 100644 index 00000000000..3920bc546c1 --- /dev/null +++ b/erpnext/regional/doctype/ksa_vat_purchase_account/ksa_vat_purchase_account.py @@ -0,0 +1,9 @@ +# Copyright (c) 2021, Havenir Solutions and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class KSAVATPurchaseAccount(Document): + pass diff --git a/erpnext/regional/doctype/ksa_vat_sales_account/__init__.py b/erpnext/regional/doctype/ksa_vat_sales_account/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.js b/erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.js new file mode 100644 index 00000000000..72613f4064f --- /dev/null +++ b/erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.js @@ -0,0 +1,8 @@ +// Copyright (c) 2021, Havenir Solutions and contributors +// For license information, please see license.txt + +frappe.ui.form.on('KSA VAT Sales Account', { + // refresh: function(frm) { + + // } +}); diff --git a/erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.json b/erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.json new file mode 100644 index 00000000000..df2747891dc --- /dev/null +++ b/erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.json @@ -0,0 +1,49 @@ +{ + "actions": [], + "creation": "2021-07-13 08:46:33.820968", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "title", + "item_tax_template", + "account" + ], + "fields": [ + { + "fieldname": "account", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Account", + "options": "Account", + "reqd": 1 + }, + { + "fieldname": "title", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Title", + "reqd": 1 + }, + { + "fieldname": "item_tax_template", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Item Tax Template", + "options": "Item Tax Template", + "reqd": 1 + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-08-04 06:42:00.081407", + "modified_by": "Administrator", + "module": "Regional", + "name": "KSA VAT Sales Account", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.py b/erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.py new file mode 100644 index 00000000000..7c2689f530e --- /dev/null +++ b/erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.py @@ -0,0 +1,9 @@ +# Copyright (c) 2021, Havenir Solutions and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class KSAVATSalesAccount(Document): + pass diff --git a/erpnext/regional/doctype/ksa_vat_sales_account/test_ksa_vat_sales_account.py b/erpnext/regional/doctype/ksa_vat_sales_account/test_ksa_vat_sales_account.py new file mode 100644 index 00000000000..1d6a6a793dc --- /dev/null +++ b/erpnext/regional/doctype/ksa_vat_sales_account/test_ksa_vat_sales_account.py @@ -0,0 +1,9 @@ +# Copyright (c) 2021, Havenir Solutions and Contributors +# See license.txt + +# import frappe +import unittest + + +class TestKSAVATSalesAccount(unittest.TestCase): + pass diff --git a/erpnext/regional/doctype/ksa_vat_setting/__init__.py b/erpnext/regional/doctype/ksa_vat_setting/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.js b/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.js new file mode 100644 index 00000000000..0238c7b3068 --- /dev/null +++ b/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.js @@ -0,0 +1,8 @@ +// Copyright (c) 2021, Havenir Solutions and contributors +// For license information, please see license.txt + +frappe.ui.form.on('KSA VAT Setting', { + onload: function(frm) { + frappe.breadcrumbs.add('Accounts', 'KSA VAT Setting'); + } +}); diff --git a/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.json b/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.json new file mode 100644 index 00000000000..33619467ed0 --- /dev/null +++ b/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.json @@ -0,0 +1,49 @@ +{ + "actions": [], + "autoname": "field:company", + "creation": "2021-07-13 08:49:01.100356", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "company", + "ksa_vat_sales_accounts", + "ksa_vat_purchase_accounts" + ], + "fields": [ + { + "fieldname": "company", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Company", + "options": "Company", + "reqd": 1, + "unique": 1 + }, + { + "fieldname": "ksa_vat_sales_accounts", + "fieldtype": "Table", + "label": "KSA VAT Sales Accounts", + "options": "KSA VAT Sales Account", + "reqd": 1 + }, + { + "fieldname": "ksa_vat_purchase_accounts", + "fieldtype": "Table", + "label": "KSA VAT Purchase Accounts", + "options": "KSA VAT Purchase Account", + "reqd": 1 + } + ], + "links": [], + "modified": "2021-08-26 04:29:06.499378", + "modified_by": "Administrator", + "module": "Regional", + "name": "KSA VAT Setting", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "company", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.py b/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.py new file mode 100644 index 00000000000..bdae1161fd7 --- /dev/null +++ b/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.py @@ -0,0 +1,9 @@ +# Copyright (c) 2021, Havenir Solutions and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class KSAVATSetting(Document): + pass diff --git a/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting_list.js b/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting_list.js new file mode 100644 index 00000000000..23d28b9e68b --- /dev/null +++ b/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting_list.js @@ -0,0 +1,5 @@ +frappe.listview_settings['KSA VAT Setting'] = { + onload(list) { + frappe.breadcrumbs.add('Accounts'); + } +} \ No newline at end of file diff --git a/erpnext/regional/doctype/ksa_vat_setting/test_ksa_vat_setting.py b/erpnext/regional/doctype/ksa_vat_setting/test_ksa_vat_setting.py new file mode 100644 index 00000000000..7207901fd43 --- /dev/null +++ b/erpnext/regional/doctype/ksa_vat_setting/test_ksa_vat_setting.py @@ -0,0 +1,9 @@ +# Copyright (c) 2021, Havenir Solutions and Contributors +# See license.txt + +# import frappe +import unittest + + +class TestKSAVATSetting(unittest.TestCase): + pass diff --git a/erpnext/regional/report/ksa_vat/__init__.py b/erpnext/regional/report/ksa_vat/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/regional/report/ksa_vat/ksa_vat.js b/erpnext/regional/report/ksa_vat/ksa_vat.js new file mode 100644 index 00000000000..d46d260ac1e --- /dev/null +++ b/erpnext/regional/report/ksa_vat/ksa_vat.js @@ -0,0 +1,60 @@ +// Copyright (c) 2016, Havenir Solutions and contributors +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports["KSA VAT"] = { + onload() { + frappe.breadcrumbs.add('Accounts'); + }, + "filters": [ + { + "fieldname": "company", + "label": __("Company"), + "fieldtype": "Link", + "options": "Company", + "reqd": 1, + "default": frappe.defaults.get_user_default("Company") + }, + { + "fieldname": "from_date", + "label": __("From Date"), + "fieldtype": "Date", + "reqd": 1, + "default": frappe.datetime.add_months(frappe.datetime.get_today(), -1), + }, + { + "fieldname": "to_date", + "label": __("To Date"), + "fieldtype": "Date", + "reqd": 1, + "default": frappe.datetime.get_today() + } + ], + "formatter": function(value, row, column, data, default_formatter) { + if (data + && (data.title=='VAT on Sales' || data.title=='VAT on Purchases') + && data.title==value) { + value = $(`${value}`); + var $value = $(value).css("font-weight", "bold"); + value = $value.wrap("

").parent().html(); + return value + }else if (data.title=='Grand Total'){ + if (data.title==value) { + value = $(`${value}`); + var $value = $(value).css("font-weight", "bold"); + value = $value.wrap("

").parent().html(); + return value + }else{ + value = default_formatter(value, row, column, data); + value = $(`${value}`); + var $value = $(value).css("font-weight", "bold"); + value = $value.wrap("

").parent().html(); + console.log($value) + return value + } + }else{ + value = default_formatter(value, row, column, data); + return value; + } + }, +}; diff --git a/erpnext/regional/report/ksa_vat/ksa_vat.json b/erpnext/regional/report/ksa_vat/ksa_vat.json new file mode 100644 index 00000000000..036e2603103 --- /dev/null +++ b/erpnext/regional/report/ksa_vat/ksa_vat.json @@ -0,0 +1,32 @@ +{ + "add_total_row": 0, + "columns": [], + "creation": "2021-07-13 08:54:38.000949", + "disable_prepared_report": 1, + "disabled": 1, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 0, + "is_standard": "Yes", + "modified": "2021-08-26 04:14:37.202594", + "modified_by": "Administrator", + "module": "Regional", + "name": "KSA VAT", + "owner": "Administrator", + "prepared_report": 1, + "ref_doctype": "GL Entry", + "report_name": "KSA VAT", + "report_type": "Script Report", + "roles": [ + { + "role": "System Manager" + }, + { + "role": "Accounts Manager" + }, + { + "role": "Accounts User" + } + ] +} \ No newline at end of file diff --git a/erpnext/regional/report/ksa_vat/ksa_vat.py b/erpnext/regional/report/ksa_vat/ksa_vat.py new file mode 100644 index 00000000000..b32dfc19811 --- /dev/null +++ b/erpnext/regional/report/ksa_vat/ksa_vat.py @@ -0,0 +1,179 @@ +# Copyright (c) 2013, Havenir Solutions and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals + +import json + +import frappe +from frappe import _ +from frappe.utils import get_url_to_list + + +def execute(filters=None): + columns = columns = get_columns() + data = get_data(filters) + return columns, data + +def get_columns(): + return [ + { + "fieldname": "title", + "label": _("Title"), + "fieldtype": "Data", + "width": 300 + }, + { + "fieldname": "amount", + "label": _("Amount (SAR)"), + "fieldtype": "Currency", + "width": 150, + }, + { + "fieldname": "adjustment_amount", + "label": _("Adjustment (SAR)"), + "fieldtype": "Currency", + "width": 150, + }, + { + "fieldname": "vat_amount", + "label": _("VAT Amount (SAR)"), + "fieldtype": "Currency", + "width": 150, + } + ] + +def get_data(filters): + data = [] + + # Validate if vat settings exist + company = filters.get('company') + if frappe.db.exists('KSA VAT Setting', company) is None: + url = get_url_to_list('KSA VAT Setting') + frappe.msgprint(f'Create KSA VAT Setting for this company') + return data + + ksa_vat_setting = frappe.get_doc('KSA VAT Setting', company) + + # Sales Heading + append_data(data, 'VAT on Sales', '', '', '') + + grand_total_taxable_amount = 0 + grand_total_taxable_adjustment_amount = 0 + grand_total_tax = 0 + + for vat_setting in ksa_vat_setting.ksa_vat_sales_accounts: + total_taxable_amount, total_taxable_adjustment_amount, \ + total_tax = get_tax_data_for_each_vat_setting(vat_setting, filters, 'Sales Invoice') + + # Adding results to data + append_data(data, vat_setting.title, total_taxable_amount, + total_taxable_adjustment_amount, total_tax) + + grand_total_taxable_amount += total_taxable_amount + grand_total_taxable_adjustment_amount += total_taxable_adjustment_amount + grand_total_tax += total_tax + + # Sales Grand Total + append_data(data, 'Grand Total', grand_total_taxable_amount, + grand_total_taxable_adjustment_amount, grand_total_tax ) + + # Blank Line + append_data(data, '', '', '', '') + + # Purchase Heading + append_data(data, 'VAT on Purchases', '', '', '') + + grand_total_taxable_amount = 0 + grand_total_taxable_adjustment_amount = 0 + grand_total_tax = 0 + + for vat_setting in ksa_vat_setting.ksa_vat_purchase_accounts: + total_taxable_amount, total_taxable_adjustment_amount, \ + total_tax = get_tax_data_for_each_vat_setting(vat_setting, filters, 'Purchase Invoice') + + # Adding results to data + append_data(data, vat_setting.title, total_taxable_amount, + total_taxable_adjustment_amount, total_tax) + + grand_total_taxable_amount += total_taxable_amount + grand_total_taxable_adjustment_amount += total_taxable_adjustment_amount + grand_total_tax += total_tax + + # Purchase Grand Total + append_data(data, 'Grand Total', grand_total_taxable_amount, + grand_total_taxable_adjustment_amount, grand_total_tax ) + + return data + +def get_tax_data_for_each_vat_setting(vat_setting, filters, doctype): + ''' + (KSA, {filters}, 'Sales Invoice') => 500, 153, 10 \n + calculates and returns \n + total_taxable_amount, total_taxable_adjustment_amount, total_tax''' + from_date = filters.get('from_date') + to_date = filters.get('to_date') + + # Initiate variables + total_taxable_amount = 0 + total_taxable_adjustment_amount = 0 + total_tax = 0 + # Fetch All Invoices + invoices = frappe.get_list(doctype, + filters ={ + 'docstatus': 1, + 'posting_date': ['between', [from_date, to_date]] + }, + fields =['name', 'is_return']) + + for invoice in invoices: + invoice_items = frappe.get_list(f'{doctype} Item', + filters ={ + 'docstatus': 1, + 'parent': invoice.name, + 'item_tax_template': vat_setting.item_tax_template + }, + fields =['item_code', 'net_amount']) + + + for item in invoice_items: + # Summing up total taxable amount + if invoice.is_return == 0: + total_taxable_amount += item.net_amount + + if invoice.is_return == 1: + total_taxable_adjustment_amount += item.net_amount + + # Summing up total tax + total_tax += get_tax_amount(item.item_code, vat_setting.account, doctype, invoice.name) + + return total_taxable_amount, total_taxable_adjustment_amount, total_tax + + + +def append_data(data, title, amount, adjustment_amount, vat_amount): + """Returns data with appended value.""" + data.append({"title":title, "amount": amount, "adjustment_amount": adjustment_amount, "vat_amount": vat_amount}) + +def get_tax_amount(item_code, account_head, doctype, parent): + if doctype == 'Sales Invoice': + tax_doctype = 'Sales Taxes and Charges' + + elif doctype == 'Purchase Invoice': + tax_doctype = 'Purchase Taxes and Charges' + + item_wise_tax_detail = frappe.get_value(tax_doctype, { + 'docstatus': 1, + 'parent': parent, + 'account_head': account_head + }, 'item_wise_tax_detail') + + tax_amount = 0 + if item_wise_tax_detail and len(item_wise_tax_detail) > 0: + item_wise_tax_detail = json.loads(item_wise_tax_detail) + for key, value in item_wise_tax_detail.items(): + if key == item_code: + tax_amount = value[1] + break + + return tax_amount \ No newline at end of file diff --git a/erpnext/regional/saudi_arabia/setup.py b/erpnext/regional/saudi_arabia/setup.py index 3ccaae9e6a5..3fc658b6eab 100644 --- a/erpnext/regional/saudi_arabia/setup.py +++ b/erpnext/regional/saudi_arabia/setup.py @@ -3,9 +3,26 @@ from __future__ import unicode_literals -from erpnext.regional.united_arab_emirates.setup import add_print_formats, make_custom_fields +import frappe +from frappe.permissions import add_permission, update_permission_property +from erpnext.regional.united_arab_emirates.setup import make_custom_fields, add_print_formats +from erpnext.regional.saudi_arabia.wizard.operations.setup_ksa_vat_setting import create_ksa_vat_setting def setup(company=None, patch=True): make_custom_fields() add_print_formats() + add_permissions() + create_ksa_vat_setting(company) + + +def add_permissions(): + """Add Permissions for KSA VAT Setting.""" + add_permission('KSA VAT Setting', 'All', 0) + for role in ('Accounts Manager', 'Accounts User', 'System Manager'): + add_permission('KSA VAT Setting', role, 0) + update_permission_property('KSA VAT Setting', role, 0, 'write', 1) + update_permission_property('KSA VAT Setting', role, 0, 'create', 1) + + """Enable KSA VAT Report""" + frappe.db.set_value('Report', 'KSA VAT', 'disabled', 0) diff --git a/erpnext/regional/saudi_arabia/wizard/__init__.py b/erpnext/regional/saudi_arabia/wizard/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/regional/saudi_arabia/wizard/data/__init__.py b/erpnext/regional/saudi_arabia/wizard/data/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/regional/saudi_arabia/wizard/data/ksa_vat_settings.json b/erpnext/regional/saudi_arabia/wizard/data/ksa_vat_settings.json new file mode 100644 index 00000000000..709d65be041 --- /dev/null +++ b/erpnext/regional/saudi_arabia/wizard/data/ksa_vat_settings.json @@ -0,0 +1,47 @@ +[ + { + "type": "Sales Account", + "accounts": [ + { + "title": "Standard rated Sales", + "item_tax_template": "KSA VAT 5%", + "account": "VAT 5%" + }, + { + "title": "Zero rated domestic sales", + "item_tax_template": "KSA VAT Zero", + "account": "VAT Zero" + }, + { + "title": "Exempted sales", + "item_tax_template": "KSA VAT Exempted", + "account": "VAT Zero" + } + ] + }, + { + "type": "Purchase Account", + "accounts": [ + { + "title": "Standard rated domestic purchases", + "item_tax_template": "KSA VAT 5%", + "account": "VAT 5%" + }, + { + "title": "Imports subject to VAT paid at customs", + "item_tax_template": "KSA Excise 50%", + "account": "Excise 50%" + }, + { + "title": "Zero rated purchases", + "item_tax_template": "KSA VAT Zero", + "account": "VAT Zero" + }, + { + "title": "Exempted purchases", + "item_tax_template": "KSA VAT Exempted", + "account": "VAT Zero" + } + ] + } +] \ No newline at end of file diff --git a/erpnext/regional/saudi_arabia/wizard/operations/__init__.py b/erpnext/regional/saudi_arabia/wizard/operations/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/regional/saudi_arabia/wizard/operations/setup_ksa_vat_setting.py b/erpnext/regional/saudi_arabia/wizard/operations/setup_ksa_vat_setting.py new file mode 100644 index 00000000000..ba137dada6a --- /dev/null +++ b/erpnext/regional/saudi_arabia/wizard/operations/setup_ksa_vat_setting.py @@ -0,0 +1,48 @@ +import json +import os + +import frappe + +from erpnext.setup.setup_wizard.operations.taxes_setup import setup_taxes_and_charges + + +def create_ksa_vat_setting(company): + """ + On creation of first company. Creates KSA VAT Setting""" + + company = frappe.get_doc('Company', company) + setup_taxes_and_charges(company.name, company.country) + + file_path = os.path.join(os.path.dirname(__file__), '..', 'data', 'ksa_vat_settings.json') + with open(file_path, 'r') as json_file: + account_data = json.load(json_file) + + + # Creating KSA VAT Setting + ksa_vat_setting = frappe.get_doc({ + 'doctype': 'KSA VAT Setting', + 'company': company.name + }) + + for data in account_data: + if data['type'] == 'Sales Account': + for row in data['accounts']: + item_tax_template = row['item_tax_template'] + account = row['account'] + ksa_vat_setting.append('ksa_vat_sales_accounts', { + 'title': row['title'], + 'item_tax_template': f'{item_tax_template} - {company.abbr}', + 'account': f'{account} - {company.abbr}' + }) + + elif data['type'] == 'Purchase Account': + for row in data['accounts']: + item_tax_template = row['item_tax_template'] + account = row['account'] + ksa_vat_setting.append('ksa_vat_purchase_accounts', { + 'title': row['title'], + 'item_tax_template': f'{item_tax_template} - {company.abbr}', + 'account': f'{account} - {company.abbr}' + }) + + ksa_vat_setting.save() \ No newline at end of file From 60fd96085c01f7f598814f4a85bfb6c99d47ea0f Mon Sep 17 00:00:00 2001 From: Ahmad Date: Mon, 6 Sep 2021 23:36:55 +0500 Subject: [PATCH 082/164] feat(regional): QR Code generation for Saudi Arabia Sales Invoices --- erpnext/hooks.py | 6 ++- erpnext/regional/__init__.py | 73 ++++++++++++++++++++++++++ erpnext/regional/saudi_arabia/setup.py | 17 ++++-- 3 files changed, 91 insertions(+), 5 deletions(-) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 3094deaafa7..894770c58a8 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -257,6 +257,7 @@ doc_events = { "validate": "erpnext.regional.india.utils.validate_tax_category" }, "Sales Invoice": { + "after_insert": "erpnext.regional.create_qr_code", "on_submit": [ "erpnext.regional.create_transaction_log", "erpnext.regional.italy.utils.sales_invoice_on_submit", @@ -266,7 +267,10 @@ doc_events = { "erpnext.regional.italy.utils.sales_invoice_on_cancel", "erpnext.erpnext_integrations.taxjar_integration.delete_transaction" ], - "on_trash": "erpnext.regional.check_deletion_permission", + "on_trash": [ + "erpnext.regional.check_deletion_permission", + "erpnext.regional.delete_qr_code_file" + ], "validate": [ "erpnext.regional.india.utils.validate_document_name", "erpnext.regional.india.utils.update_taxable_values" diff --git a/erpnext/regional/__init__.py b/erpnext/regional/__init__.py index 45a689efa8b..2fb68a6f529 100644 --- a/erpnext/regional/__init__.py +++ b/erpnext/regional/__init__.py @@ -3,8 +3,12 @@ from __future__ import unicode_literals +import io +import os + import frappe from frappe import _ +from pyqrcode import create as qr_create from erpnext import get_region @@ -31,3 +35,72 @@ def create_transaction_log(doc, method): "document_name": doc.name, "data": data }).insert(ignore_permissions=True) + + +def create_qr_code(doc, method): + """Create QR Code after inserting Sales Inv + """ + + region = get_region(doc.company) + if region not in ['Saudi Arabia']: + return + + # if QR Code field not present, do nothing + if not hasattr(doc, 'qr_code'): + return + + # Don't create QR Code if it already exists + qr_code = doc.get("qr_code") + if qr_code and frappe.db.exists({"doctype": "File", "file_url": qr_code}): + return + + fields = frappe.get_meta('Sales Invoice').fields + + for field in fields: + if field.fieldname == 'qr_code' and field.fieldtype == 'Attach Image': + # Creating public url to print format + default_print_format = frappe.db.get_value('Property Setter', dict(property='default_print_format', doc_type=doc.doctype), "value") + + # System Language + language = frappe.get_system_settings('language') + + # creating qr code for the url + url = f"{ frappe.utils.get_url() }/{ doc.doctype }/{ doc.name }?format={ default_print_format or 'Standard' }&_lang={ language }&key={ doc.get_signature() }" + qr_image = io.BytesIO() + url = qr_create(url, error='L') + url.png(qr_image, scale=2, quiet_zone=1) + + # making file + filename = f"QR-CODE-{doc.name}.png".replace(os.path.sep, "__") + _file = frappe.get_doc({ + "doctype": "File", + "file_name": filename, + "is_private": 0, + "content": qr_image.getvalue() + }) + + _file.save() + + # assigning to document + doc.db_set('qr_code', _file.file_url) + doc.notify_update() + + break + + else: + pass + +def delete_qr_code_file(doc, method): + """Delete QR Code on deleted sales invoice""" + + region = get_region(doc.company) + if region not in ['Saudi Arabia']: + return + + if hasattr(doc, 'qr_code'): + if doc.get('qr_code'): + file_doc = frappe.get_list('File', { + 'file_url': doc.qr_code + }) + if len(file_doc): + frappe.delete_doc('File', file_doc[0].name) \ No newline at end of file diff --git a/erpnext/regional/saudi_arabia/setup.py b/erpnext/regional/saudi_arabia/setup.py index 3fc658b6eab..fb0e3679f63 100644 --- a/erpnext/regional/saudi_arabia/setup.py +++ b/erpnext/regional/saudi_arabia/setup.py @@ -2,18 +2,18 @@ # License: GNU General Public License v3. See license.txt from __future__ import unicode_literals - import frappe from frappe.permissions import add_permission, update_permission_property -from erpnext.regional.united_arab_emirates.setup import make_custom_fields, add_print_formats +from erpnext.regional.united_arab_emirates.setup import make_custom_fields as uae_custom_fields, add_print_formats from erpnext.regional.saudi_arabia.wizard.operations.setup_ksa_vat_setting import create_ksa_vat_setting - +from frappe.custom.doctype.custom_field.custom_field import create_custom_field def setup(company=None, patch=True): - make_custom_fields() + uae_custom_fields() add_print_formats() add_permissions() create_ksa_vat_setting(company) + make_custom_fields() def add_permissions(): @@ -26,3 +26,12 @@ def add_permissions(): """Enable KSA VAT Report""" frappe.db.set_value('Report', 'KSA VAT', 'disabled', 0) + +def make_custom_fields(): + qr_code_field = dict( + fieldname='qr_code', + label='QR Code', + fieldtype='Attach Image', + read_only=1, no_copy=1, hidden=1) + + create_custom_field('Sales Invoice', qr_code_field) From a8ba52e8a5838362e6b5ba1663855570ea5a56ca Mon Sep 17 00:00:00 2001 From: Ahmad <7881486+ahmadpak@users.noreply.github.com> Date: Tue, 14 Sep 2021 14:26:44 +0500 Subject: [PATCH 083/164] Update erpnext/regional/report/ksa_vat/ksa_vat.py String made translatable Co-authored-by: Saqib --- erpnext/regional/report/ksa_vat/ksa_vat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/report/ksa_vat/ksa_vat.py b/erpnext/regional/report/ksa_vat/ksa_vat.py index b32dfc19811..3396d598e90 100644 --- a/erpnext/regional/report/ksa_vat/ksa_vat.py +++ b/erpnext/regional/report/ksa_vat/ksa_vat.py @@ -50,7 +50,7 @@ def get_data(filters): company = filters.get('company') if frappe.db.exists('KSA VAT Setting', company) is None: url = get_url_to_list('KSA VAT Setting') - frappe.msgprint(f'Create KSA VAT Setting for this company') + frappe.msgprint(_('Create KSA VAT Setting for this company').format(url)) return data ksa_vat_setting = frappe.get_doc('KSA VAT Setting', company) From 220ab16698050f9af82f795cf892f18dbc37a369 Mon Sep 17 00:00:00 2001 From: Ahmad <7881486+ahmadpak@users.noreply.github.com> Date: Tue, 14 Sep 2021 14:28:57 +0500 Subject: [PATCH 084/164] Refactor erpnext/regional/__init__.py Co-authored-by: Saqib --- erpnext/regional/__init__.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/erpnext/regional/__init__.py b/erpnext/regional/__init__.py index 2fb68a6f529..49fd80e28da 100644 --- a/erpnext/regional/__init__.py +++ b/erpnext/regional/__init__.py @@ -87,8 +87,6 @@ def create_qr_code(doc, method): break - else: - pass def delete_qr_code_file(doc, method): """Delete QR Code on deleted sales invoice""" From 3fca08e3e845eae61942056d7ecc9b30b7150054 Mon Sep 17 00:00:00 2001 From: Ahmad <7881486+ahmadpak@users.noreply.github.com> Date: Tue, 14 Sep 2021 14:29:33 +0500 Subject: [PATCH 085/164] Update erpnext/regional/saudi_arabia/setup.py Method name updated Co-authored-by: Saqib --- erpnext/regional/saudi_arabia/setup.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/erpnext/regional/saudi_arabia/setup.py b/erpnext/regional/saudi_arabia/setup.py index fb0e3679f63..a49490b6c42 100644 --- a/erpnext/regional/saudi_arabia/setup.py +++ b/erpnext/regional/saudi_arabia/setup.py @@ -13,8 +13,7 @@ def setup(company=None, patch=True): add_print_formats() add_permissions() create_ksa_vat_setting(company) - make_custom_fields() - + make_qrcode_field() def add_permissions(): """Add Permissions for KSA VAT Setting.""" @@ -27,7 +26,7 @@ def add_permissions(): """Enable KSA VAT Report""" frappe.db.set_value('Report', 'KSA VAT', 'disabled', 0) -def make_custom_fields(): +def make_qrcode_field(): qr_code_field = dict( fieldname='qr_code', label='QR Code', From 873ed1ae4f1ab75b29901927f8674be2d8a0e4e6 Mon Sep 17 00:00:00 2001 From: Ahmad Date: Tue, 14 Sep 2021 14:45:23 +0500 Subject: [PATCH 086/164] refactor(regional): moved methos to region specific utils for KSA --- erpnext/hooks.py | 4 +- erpnext/regional/__init__.py | 70 ------------------------ erpnext/regional/saudi_arabia/utils.py | 74 ++++++++++++++++++++++++++ 3 files changed, 76 insertions(+), 72 deletions(-) create mode 100644 erpnext/regional/saudi_arabia/utils.py diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 894770c58a8..3d29de8728b 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -257,7 +257,7 @@ doc_events = { "validate": "erpnext.regional.india.utils.validate_tax_category" }, "Sales Invoice": { - "after_insert": "erpnext.regional.create_qr_code", + "after_insert": "erpnext.regional.saudi_arabia.utils.create_qr_code", "on_submit": [ "erpnext.regional.create_transaction_log", "erpnext.regional.italy.utils.sales_invoice_on_submit", @@ -269,7 +269,7 @@ doc_events = { ], "on_trash": [ "erpnext.regional.check_deletion_permission", - "erpnext.regional.delete_qr_code_file" + "erpnext.regional.saudi_arabia.utils.delete_qr_code_file" ], "validate": [ "erpnext.regional.india.utils.validate_document_name", diff --git a/erpnext/regional/__init__.py b/erpnext/regional/__init__.py index 49fd80e28da..d7dcbf4fe18 100644 --- a/erpnext/regional/__init__.py +++ b/erpnext/regional/__init__.py @@ -3,12 +3,8 @@ from __future__ import unicode_literals -import io -import os - import frappe from frappe import _ -from pyqrcode import create as qr_create from erpnext import get_region @@ -36,69 +32,3 @@ def create_transaction_log(doc, method): "data": data }).insert(ignore_permissions=True) - -def create_qr_code(doc, method): - """Create QR Code after inserting Sales Inv - """ - - region = get_region(doc.company) - if region not in ['Saudi Arabia']: - return - - # if QR Code field not present, do nothing - if not hasattr(doc, 'qr_code'): - return - - # Don't create QR Code if it already exists - qr_code = doc.get("qr_code") - if qr_code and frappe.db.exists({"doctype": "File", "file_url": qr_code}): - return - - fields = frappe.get_meta('Sales Invoice').fields - - for field in fields: - if field.fieldname == 'qr_code' and field.fieldtype == 'Attach Image': - # Creating public url to print format - default_print_format = frappe.db.get_value('Property Setter', dict(property='default_print_format', doc_type=doc.doctype), "value") - - # System Language - language = frappe.get_system_settings('language') - - # creating qr code for the url - url = f"{ frappe.utils.get_url() }/{ doc.doctype }/{ doc.name }?format={ default_print_format or 'Standard' }&_lang={ language }&key={ doc.get_signature() }" - qr_image = io.BytesIO() - url = qr_create(url, error='L') - url.png(qr_image, scale=2, quiet_zone=1) - - # making file - filename = f"QR-CODE-{doc.name}.png".replace(os.path.sep, "__") - _file = frappe.get_doc({ - "doctype": "File", - "file_name": filename, - "is_private": 0, - "content": qr_image.getvalue() - }) - - _file.save() - - # assigning to document - doc.db_set('qr_code', _file.file_url) - doc.notify_update() - - break - - -def delete_qr_code_file(doc, method): - """Delete QR Code on deleted sales invoice""" - - region = get_region(doc.company) - if region not in ['Saudi Arabia']: - return - - if hasattr(doc, 'qr_code'): - if doc.get('qr_code'): - file_doc = frappe.get_list('File', { - 'file_url': doc.qr_code - }) - if len(file_doc): - frappe.delete_doc('File', file_doc[0].name) \ No newline at end of file diff --git a/erpnext/regional/saudi_arabia/utils.py b/erpnext/regional/saudi_arabia/utils.py new file mode 100644 index 00000000000..e83d8c72ad6 --- /dev/null +++ b/erpnext/regional/saudi_arabia/utils.py @@ -0,0 +1,74 @@ +import io +import os + +import frappe +from pyqrcode import create as qr_create + +from erpnext import get_region + + +def create_qr_code(doc, method): + """Create QR Code after inserting Sales Inv + """ + + region = get_region(doc.company) + if region not in ['Saudi Arabia']: + return + + # if QR Code field not present, do nothing + if not hasattr(doc, 'qr_code'): + return + + # Don't create QR Code if it already exists + qr_code = doc.get("qr_code") + if qr_code and frappe.db.exists({"doctype": "File", "file_url": qr_code}): + return + + fields = frappe.get_meta('Sales Invoice').fields + + for field in fields: + if field.fieldname == 'qr_code' and field.fieldtype == 'Attach Image': + # Creating public url to print format + default_print_format = frappe.db.get_value('Property Setter', dict(property='default_print_format', doc_type=doc.doctype), "value") + + # System Language + language = frappe.get_system_settings('language') + + # creating qr code for the url + url = f"{ frappe.utils.get_url() }/{ doc.doctype }/{ doc.name }?format={ default_print_format or 'Standard' }&_lang={ language }&key={ doc.get_signature() }" + qr_image = io.BytesIO() + url = qr_create(url, error='L') + url.png(qr_image, scale=2, quiet_zone=1) + + # making file + filename = f"QR-CODE-{doc.name}.png".replace(os.path.sep, "__") + _file = frappe.get_doc({ + "doctype": "File", + "file_name": filename, + "is_private": 0, + "content": qr_image.getvalue() + }) + + _file.save() + + # assigning to document + doc.db_set('qr_code', _file.file_url) + doc.notify_update() + + break + + +def delete_qr_code_file(doc, method): + """Delete QR Code on deleted sales invoice""" + + region = get_region(doc.company) + if region not in ['Saudi Arabia']: + return + + if hasattr(doc, 'qr_code'): + if doc.get('qr_code'): + file_doc = frappe.get_list('File', { + 'file_url': doc.qr_code + }) + if len(file_doc): + frappe.delete_doc('File', file_doc[0].name) \ No newline at end of file From 5f7c5bcac8d544f24816ffec96e621f4aa3eaa4d Mon Sep 17 00:00:00 2001 From: Ahmad Date: Fri, 17 Sep 2021 01:14:41 +0500 Subject: [PATCH 087/164] fix: indentations --- erpnext/hooks.py | 2 +- .../doctype/ksa_vat_setting/ksa_vat_setting.js | 2 +- .../doctype/ksa_vat_setting/ksa_vat_setting_list.js | 6 +++--- erpnext/regional/report/ksa_vat/ksa_vat.py | 11 ++++------- 4 files changed, 9 insertions(+), 12 deletions(-) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 3d29de8728b..6be3e25440a 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -270,7 +270,7 @@ doc_events = { "on_trash": [ "erpnext.regional.check_deletion_permission", "erpnext.regional.saudi_arabia.utils.delete_qr_code_file" - ], + ], "validate": [ "erpnext.regional.india.utils.validate_document_name", "erpnext.regional.india.utils.update_taxable_values" diff --git a/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.js b/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.js index 0238c7b3068..00b62b9adfb 100644 --- a/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.js +++ b/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.js @@ -2,7 +2,7 @@ // For license information, please see license.txt frappe.ui.form.on('KSA VAT Setting', { - onload: function(frm) { + onload: function () { frappe.breadcrumbs.add('Accounts', 'KSA VAT Setting'); } }); diff --git a/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting_list.js b/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting_list.js index 23d28b9e68b..269cbec5fb4 100644 --- a/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting_list.js +++ b/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting_list.js @@ -1,5 +1,5 @@ frappe.listview_settings['KSA VAT Setting'] = { - onload(list) { - frappe.breadcrumbs.add('Accounts'); - } + onload () { + frappe.breadcrumbs.add('Accounts'); + } } \ No newline at end of file diff --git a/erpnext/regional/report/ksa_vat/ksa_vat.py b/erpnext/regional/report/ksa_vat/ksa_vat.py index 3396d598e90..1697bbbd4d6 100644 --- a/erpnext/regional/report/ksa_vat/ksa_vat.py +++ b/erpnext/regional/report/ksa_vat/ksa_vat.py @@ -76,7 +76,7 @@ def get_data(filters): # Sales Grand Total append_data(data, 'Grand Total', grand_total_taxable_amount, - grand_total_taxable_adjustment_amount, grand_total_tax ) + grand_total_taxable_adjustment_amount, grand_total_tax) # Blank Line append_data(data, '', '', '', '') @@ -102,7 +102,7 @@ def get_data(filters): # Purchase Grand Total append_data(data, 'Grand Total', grand_total_taxable_amount, - grand_total_taxable_adjustment_amount, grand_total_tax ) + grand_total_taxable_adjustment_amount, grand_total_tax) return data @@ -123,8 +123,7 @@ def get_tax_data_for_each_vat_setting(vat_setting, filters, doctype): filters ={ 'docstatus': 1, 'posting_date': ['between', [from_date, to_date]] - }, - fields =['name', 'is_return']) + }, fields =['name', 'is_return']) for invoice in invoices: invoice_items = frappe.get_list(f'{doctype} Item', @@ -132,9 +131,7 @@ def get_tax_data_for_each_vat_setting(vat_setting, filters, doctype): 'docstatus': 1, 'parent': invoice.name, 'item_tax_template': vat_setting.item_tax_template - }, - fields =['item_code', 'net_amount']) - + }, fields =['item_code', 'net_amount']) for item in invoice_items: # Summing up total taxable amount From f9770b69b26e4ae77ec17cfcd441f06f3b3d3889 Mon Sep 17 00:00:00 2001 From: Ahmad <7881486+ahmadpak@users.noreply.github.com> Date: Fri, 17 Sep 2021 01:27:37 +0500 Subject: [PATCH 088/164] Update erpnext/regional/saudi_arabia/utils.py Co-authored-by: Saqib --- erpnext/regional/saudi_arabia/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/saudi_arabia/utils.py b/erpnext/regional/saudi_arabia/utils.py index e83d8c72ad6..5b8b7c13836 100644 --- a/erpnext/regional/saudi_arabia/utils.py +++ b/erpnext/regional/saudi_arabia/utils.py @@ -68,7 +68,7 @@ def delete_qr_code_file(doc, method): if hasattr(doc, 'qr_code'): if doc.get('qr_code'): file_doc = frappe.get_list('File', { - 'file_url': doc.qr_code + 'file_url': doc.get('qr_code') }) if len(file_doc): frappe.delete_doc('File', file_doc[0].name) \ No newline at end of file From 0313b44f1fac67df5521cba2647190882dae18c5 Mon Sep 17 00:00:00 2001 From: Ahmad <7881486+ahmadpak@users.noreply.github.com> Date: Fri, 17 Sep 2021 01:28:52 +0500 Subject: [PATCH 089/164] Update erpnext/regional/saudi_arabia/utils.py Co-authored-by: Saqib --- erpnext/regional/saudi_arabia/utils.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/regional/saudi_arabia/utils.py b/erpnext/regional/saudi_arabia/utils.py index 5b8b7c13836..79a29de9ac7 100644 --- a/erpnext/regional/saudi_arabia/utils.py +++ b/erpnext/regional/saudi_arabia/utils.py @@ -46,7 +46,10 @@ def create_qr_code(doc, method): "doctype": "File", "file_name": filename, "is_private": 0, - "content": qr_image.getvalue() + "content": qr_image.getvalue(), + "attached_to_doctype": doc.get("doctype"), + "attached_to_name": doc.get("name"), + "attached_to_field": "qr_code" }) _file.save() From 30f3b4804d7693c1eb9f1363c267eb9a45993680 Mon Sep 17 00:00:00 2001 From: Ahmad Date: Fri, 17 Sep 2021 01:33:32 +0500 Subject: [PATCH 090/164] refactor (regional): KSA utils --- erpnext/regional/saudi_arabia/utils.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/regional/saudi_arabia/utils.py b/erpnext/regional/saudi_arabia/utils.py index 79a29de9ac7..cc6c0af7a56 100644 --- a/erpnext/regional/saudi_arabia/utils.py +++ b/erpnext/regional/saudi_arabia/utils.py @@ -24,10 +24,10 @@ def create_qr_code(doc, method): if qr_code and frappe.db.exists({"doctype": "File", "file_url": qr_code}): return - fields = frappe.get_meta('Sales Invoice').fields + meta = frappe.get_meta('Sales Invoice') - for field in fields: - if field.fieldname == 'qr_code' and field.fieldtype == 'Attach Image': + for field in meta.get_image_fields(): + if field.fieldname == 'qr_code': # Creating public url to print format default_print_format = frappe.db.get_value('Property Setter', dict(property='default_print_format', doc_type=doc.doctype), "value") From 301b94e06acad96881c6e7d7c8a9501718a7c697 Mon Sep 17 00:00:00 2001 From: Ahmad Date: Fri, 17 Sep 2021 01:46:45 +0500 Subject: [PATCH 091/164] fix: pre-commit hooks --- .../saudi_arabia/wizard/operations/setup_ksa_vat_setting.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/regional/saudi_arabia/wizard/operations/setup_ksa_vat_setting.py b/erpnext/regional/saudi_arabia/wizard/operations/setup_ksa_vat_setting.py index ba137dada6a..c30fcffc37c 100644 --- a/erpnext/regional/saudi_arabia/wizard/operations/setup_ksa_vat_setting.py +++ b/erpnext/regional/saudi_arabia/wizard/operations/setup_ksa_vat_setting.py @@ -7,8 +7,7 @@ from erpnext.setup.setup_wizard.operations.taxes_setup import setup_taxes_and_ch def create_ksa_vat_setting(company): - """ - On creation of first company. Creates KSA VAT Setting""" + """On creation of first company. Creates KSA VAT Setting""" company = frappe.get_doc('Company', company) setup_taxes_and_charges(company.name, company.country) From b69381117fe8536d72f121e247224c49ab554264 Mon Sep 17 00:00:00 2001 From: Ahmad Date: Fri, 17 Sep 2021 01:51:32 +0500 Subject: [PATCH 092/164] fix: pre-commit hooks --- erpnext/regional/saudi_arabia/setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/regional/saudi_arabia/setup.py b/erpnext/regional/saudi_arabia/setup.py index a49490b6c42..6113f48d3f1 100644 --- a/erpnext/regional/saudi_arabia/setup.py +++ b/erpnext/regional/saudi_arabia/setup.py @@ -27,6 +27,7 @@ def add_permissions(): frappe.db.set_value('Report', 'KSA VAT', 'disabled', 0) def make_qrcode_field(): + """Created QR code Image file""" qr_code_field = dict( fieldname='qr_code', label='QR Code', From 6d39b90b8ed2413e3b96c02f0ae96a88ae73e3a4 Mon Sep 17 00:00:00 2001 From: Ahmad Date: Sun, 10 Oct 2021 21:17:59 +0200 Subject: [PATCH 093/164] refactor: make strings translate --- erpnext/regional/report/ksa_vat/ksa_vat.py | 4 ++-- .../saudi_arabia/wizard/operations/setup_ksa_vat_setting.py | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/erpnext/regional/report/ksa_vat/ksa_vat.py b/erpnext/regional/report/ksa_vat/ksa_vat.py index 1697bbbd4d6..a42ebc9f7e5 100644 --- a/erpnext/regional/report/ksa_vat/ksa_vat.py +++ b/erpnext/regional/report/ksa_vat/ksa_vat.py @@ -150,7 +150,7 @@ def get_tax_data_for_each_vat_setting(vat_setting, filters, doctype): def append_data(data, title, amount, adjustment_amount, vat_amount): """Returns data with appended value.""" - data.append({"title":title, "amount": amount, "adjustment_amount": adjustment_amount, "vat_amount": vat_amount}) + data.append({"title": _(title), "amount": amount, "adjustment_amount": adjustment_amount, "vat_amount": vat_amount}) def get_tax_amount(item_code, account_head, doctype, parent): if doctype == 'Sales Invoice': @@ -173,4 +173,4 @@ def get_tax_amount(item_code, account_head, doctype, parent): tax_amount = value[1] break - return tax_amount \ No newline at end of file + return tax_amount diff --git a/erpnext/regional/saudi_arabia/wizard/operations/setup_ksa_vat_setting.py b/erpnext/regional/saudi_arabia/wizard/operations/setup_ksa_vat_setting.py index c30fcffc37c..3c89edd37ed 100644 --- a/erpnext/regional/saudi_arabia/wizard/operations/setup_ksa_vat_setting.py +++ b/erpnext/regional/saudi_arabia/wizard/operations/setup_ksa_vat_setting.py @@ -16,7 +16,6 @@ def create_ksa_vat_setting(company): with open(file_path, 'r') as json_file: account_data = json.load(json_file) - # Creating KSA VAT Setting ksa_vat_setting = frappe.get_doc({ 'doctype': 'KSA VAT Setting', @@ -44,4 +43,4 @@ def create_ksa_vat_setting(company): 'account': f'{account} - {company.abbr}' }) - ksa_vat_setting.save() \ No newline at end of file + ksa_vat_setting.save() From e9c3d3ff0da524f88f2d8eb267259fdfc5c48d4b Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sat, 23 Oct 2021 18:58:41 +0530 Subject: [PATCH 094/164] fix: Get LTDS based on tax withholding category (cherry picked from commit e6e804e7d783a43d13dafe7b16f6e5d3b750f1d3) --- .../doctype/tax_withholding_category/tax_withholding_category.py | 1 + 1 file changed, 1 insertion(+) 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 c3cb8396d0d..54042fbe748 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -165,6 +165,7 @@ def get_lower_deduction_certificate(tax_details, pan_no): ldc_name = frappe.db.get_value('Lower Deduction Certificate', { 'pan_no': pan_no, + 'tax_withholding_category': tax_details.tax_withholding_category, 'valid_from': ('>=', tax_details.from_date), 'valid_upto': ('<=', tax_details.to_date) }, 'name') From 1ba0111e16889b8887dc934d9c18e86f3f10b297 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sat, 23 Oct 2021 19:00:03 +0530 Subject: [PATCH 095/164] fix: Replace section code with tax withholding category in LDC (cherry picked from commit bf13d183d8066219379a5e00c4ace4ca2f6c155b) # Conflicts: # erpnext/patches.txt --- erpnext/patches.txt | 6 ++++++ .../update_category_in_ltds_certificate.py | 10 ++++++++++ .../lower_deduction_certificate.json | 20 ++++++++++--------- .../lower_deduction_certificate.py | 12 ++++++----- 4 files changed, 34 insertions(+), 14 deletions(-) create mode 100644 erpnext/patches/v13_0/update_category_in_ltds_certificate.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index bc027520da2..fad36c7a83b 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -328,5 +328,11 @@ erpnext.patches.v13_0.add_default_interview_notification_templates erpnext.patches.v13_0.trim_sales_invoice_custom_field_length erpnext.patches.v13_0.enable_scheduler_job_for_item_reposting erpnext.patches.v13_0.requeue_failed_reposts +<<<<<<< HEAD erpnext.patches.v13_0.fetch_thumbnail_in_website_items erpnext.patches.v12_0.update_production_plan_status +======= +erpnext.patches.v13_0.healthcare_deprecation_warning +erpnext.patches.v14_0.delete_healthcare_doctypes +erpnext.patches.v13_0.update_category_in_ltds_certificate +>>>>>>> bf13d183d8 (fix: Replace section code with tax withholding category in LDC) diff --git a/erpnext/patches/v13_0/update_category_in_ltds_certificate.py b/erpnext/patches/v13_0/update_category_in_ltds_certificate.py new file mode 100644 index 00000000000..f8a0646fcbb --- /dev/null +++ b/erpnext/patches/v13_0/update_category_in_ltds_certificate.py @@ -0,0 +1,10 @@ +import frappe + + +def execute(): + + frappe.db.sql(""" + UPDATE `tabLower Deduction Certificate` l, `tabSupplier` s + SET l.tax_withholding_category = s.tax_withholding_category + WHERE l.supplier = s.name + """) \ No newline at end of file diff --git a/erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.json b/erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.json index f48fe6f4763..c32ab6bec24 100644 --- a/erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.json +++ b/erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.json @@ -7,7 +7,7 @@ "engine": "InnoDB", "field_order": [ "certificate_details_section", - "section_code", + "tax_withholding_category", "fiscal_year", "column_break_3", "certificate_no", @@ -33,13 +33,6 @@ "reqd": 1, "unique": 1 }, - { - "fieldname": "section_code", - "fieldtype": "Select", - "label": "Section Code", - "options": "192\n193\n194\n194A\n194C\n194D\n194H\n194I\n194J\n194LA\n194LBB\n194LBC\n195", - "reqd": 1 - }, { "fieldname": "section_break_3", "fieldtype": "Section Break", @@ -123,13 +116,22 @@ "label": "Fiscal Year", "options": "Fiscal Year", "reqd": 1 + }, + { + "fieldname": "tax_withholding_category", + "fieldtype": "Link", + "label": "Tax Withholding Category", + "options": "Tax Withholding Category", + "reqd": 1 } ], + "index_web_pages_for_search": 1, "links": [], - "modified": "2020-04-23 23:04:41.203721", + "modified": "2021-10-23 18:33:38.962622", "modified_by": "Administrator", "module": "Regional", "name": "Lower Deduction Certificate", + "naming_rule": "By fieldname", "owner": "Administrator", "permissions": [], "sort_field": "modified", diff --git a/erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.py b/erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.py index d8553f1d913..7afbc00980c 100644 --- a/erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.py +++ b/erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.py @@ -15,7 +15,7 @@ from erpnext.accounts.utils import get_fiscal_year class LowerDeductionCertificate(Document): def validate(self): self.validate_dates() - self.validate_supplier_against_section_code() + self.validate_supplier_against_tax_category() def validate_dates(self): if getdate(self.valid_upto) < getdate(self.valid_from): @@ -31,12 +31,14 @@ class LowerDeductionCertificate(Document): <= fiscal_year.year_end_date): frappe.throw(_("Valid Upto date not in Fiscal Year {0}").format(frappe.bold(self.fiscal_year))) - def validate_supplier_against_section_code(self): - duplicate_certificate = frappe.db.get_value('Lower Deduction Certificate', {'supplier': self.supplier, 'section_code': self.section_code}, ['name', 'valid_from', 'valid_upto'], as_dict=True) + def tax_withholding_category(self): + duplicate_certificate = frappe.db.get_value('Lower Deduction Certificate', + {'supplier': self.supplier, 'tax_withholding_category': self.tax_withholding_category, 'name': ("!=", self.name)}, + ['name', 'valid_from', 'valid_upto'], as_dict=True) if duplicate_certificate and self.are_dates_overlapping(duplicate_certificate): certificate_link = get_link_to_form('Lower Deduction Certificate', duplicate_certificate.name) - frappe.throw(_("There is already a valid Lower Deduction Certificate {0} for Supplier {1} against Section Code {2} for this time period.") - .format(certificate_link, frappe.bold(self.supplier), frappe.bold(self.section_code))) + frappe.throw(_("There is already a valid Lower Deduction Certificate {0} for Supplier {1} against category {2} for this time period.") + .format(certificate_link, frappe.bold(self.supplier), frappe.bold(self.tax_withholding_category))) def are_dates_overlapping(self,duplicate_certificate): valid_from = duplicate_certificate.valid_from From 7a25d5f2de0ab0aba3823517de3e702c7e00c431 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 28 Oct 2021 19:01:24 +0530 Subject: [PATCH 096/164] fix: Rewrite patch using query builder (cherry picked from commit 09a5616e2dce7d0d9962326781cd7c0a4bcaebbc) --- .../update_category_in_ltds_certificate.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/erpnext/patches/v13_0/update_category_in_ltds_certificate.py b/erpnext/patches/v13_0/update_category_in_ltds_certificate.py index f8a0646fcbb..4d4645269cb 100644 --- a/erpnext/patches/v13_0/update_category_in_ltds_certificate.py +++ b/erpnext/patches/v13_0/update_category_in_ltds_certificate.py @@ -2,9 +2,17 @@ import frappe def execute(): + company = frappe.get_all('Company', filters = {'country': 'India'}) + if not company: + return - frappe.db.sql(""" - UPDATE `tabLower Deduction Certificate` l, `tabSupplier` s - SET l.tax_withholding_category = s.tax_withholding_category - WHERE l.supplier = s.name - """) \ No newline at end of file + ldc = frappe.qb.DocType("Lower Deduction Certificate").as_("ldc") + supplier = frappe.qb.DocType("Supplier") + + frappe.qb.update(ldc).inner_join(supplier).on( + ldc.supplier == supplier.name + ).set( + ldc.tax_withholding_category, supplier.tax_withholding_category + ).where( + ldc.tax_withholding_category.isnull() + ).run() \ No newline at end of file From 4019148a424ca10e235024e3efd0e944facd8b36 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Thu, 28 Oct 2021 20:26:38 +0530 Subject: [PATCH 097/164] fix: Resolve conflicts --- erpnext/patches.txt | 5 ----- 1 file changed, 5 deletions(-) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index fad36c7a83b..2ad2fea6847 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -328,11 +328,6 @@ erpnext.patches.v13_0.add_default_interview_notification_templates erpnext.patches.v13_0.trim_sales_invoice_custom_field_length erpnext.patches.v13_0.enable_scheduler_job_for_item_reposting erpnext.patches.v13_0.requeue_failed_reposts -<<<<<<< HEAD erpnext.patches.v13_0.fetch_thumbnail_in_website_items erpnext.patches.v12_0.update_production_plan_status -======= -erpnext.patches.v13_0.healthcare_deprecation_warning -erpnext.patches.v14_0.delete_healthcare_doctypes erpnext.patches.v13_0.update_category_in_ltds_certificate ->>>>>>> bf13d183d8 (fix: Replace section code with tax withholding category in LDC) From 2fb18afea09639212631f65bc87560b89f7bdc8d Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Mon, 20 Sep 2021 19:01:46 +0530 Subject: [PATCH 098/164] fix: Unlink PO on cancelling SO (cherry picked from commit 8396f24e70520eed6c6b73a90a7b92349cbb2139) --- erpnext/controllers/accounts_controller.py | 49 ++++++++++++++++++- .../doctype/sales_order/sales_order.py | 6 +++ 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 5c5606c2140..098db3cf085 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -19,7 +19,8 @@ from frappe.utils import ( get_link_to_form, getdate, nowdate, - today, + now, + today ) from six import text_type @@ -820,6 +821,52 @@ class AccountsController(TransactionBase): if frappe.db.get_single_value('Accounts Settings', 'unlink_advance_payment_on_cancelation_of_order'): unlink_ref_doc_from_payment_entries(self) + if self.doctype == "Sales Order": + self.unlink_ref_doc_from_po() + + def unlink_ref_doc_from_po(self): + print("\n"*5, "*"*50, "\n"*5) + + so_items = [] + for item in self.items: + so_items.append(item.name) + + print("SO Items: ", so_items) + + linked_po = frappe.get_all( + 'Purchase Order Item', + filters = { + 'sales_order': self.name, + 'sales_order_item': ['in', so_items], + 'docstatus': ['<', 2] + }, + pluck='parent' + ) + + print("Before unlinking: ", linked_po) + + if linked_po: + frappe.db.sql("""update `tabPurchase Order Item` + set sales_order = null, sales_order_item = null, + modified = %s, modified_by = %s + where sales_order = %s and sales_order_item in %s + and docstatus < 2""", (now(), frappe.session.user, self.name, so_items)) + + frappe.msgprint(_("Purchase Orders {0} are un-linked").format("\n".join(linked_po))) + + linked_po = frappe.get_all( + 'Purchase Order Item', + filters = { + 'sales_order': self.name, + 'sales_order_item': ['in', so_items], + 'docstatus': ['<', 2] + }, + pluck='parent' + ) + print("After unlinking: ", linked_po) + + print("\n"*5, "*"*50, "\n"*5) + def get_tax_map(self): tax_map = {} for tax in self.get('taxes'): diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index dcf478bda6e..c9490fcae44 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -207,6 +207,12 @@ class SalesOrder(SellingController): from erpnext.accounts.doctype.pricing_rule.utils import update_coupon_code_count update_coupon_code_count(self.coupon_code,'cancelled') + def cancel(self): + import pdb + + pdb.set_trace() + super(SalesOrder, self).cancel() + def update_project(self): if frappe.db.get_single_value('Selling Settings', 'sales_update_frequency') != "Each Transaction": return From 7c42b8b7022160c7fe3fb777d7170078650196d8 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Mon, 20 Sep 2021 19:01:46 +0530 Subject: [PATCH 099/164] fix: Unlink PO on cancelling SO (cherry picked from commit e77534fe13a8384a066352f028eb26fb3520f26f) --- erpnext/controllers/accounts_controller.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 098db3cf085..0810939f8aa 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -846,11 +846,16 @@ class AccountsController(TransactionBase): print("Before unlinking: ", linked_po) if linked_po: - frappe.db.sql("""update `tabPurchase Order Item` - set sales_order = null, sales_order_item = null, - modified = %s, modified_by = %s - where sales_order = %s and sales_order_item in %s - and docstatus < 2""", (now(), frappe.session.user, self.name, so_items)) + frappe.db.set_value( + 'Purchase Order Item', { + 'sales_order': self.name, + 'sales_order_item': ['in', so_items], + 'docstatus': ['<', 2] + },{ + 'sales_order': None, + 'sales_order_item': None + } + ) frappe.msgprint(_("Purchase Orders {0} are un-linked").format("\n".join(linked_po))) From d47007adb201a38c67a7f2686b492f6a9e2a3f1d Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Thu, 28 Oct 2021 19:25:55 +0530 Subject: [PATCH 100/164] fix: Remove print statements (cherry picked from commit 8844fdbf230ef167c0051935a8f922c5c6bb13de) --- erpnext/controllers/accounts_controller.py | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 0810939f8aa..5acf6e9a68c 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -825,14 +825,10 @@ class AccountsController(TransactionBase): self.unlink_ref_doc_from_po() def unlink_ref_doc_from_po(self): - print("\n"*5, "*"*50, "\n"*5) - so_items = [] for item in self.items: so_items.append(item.name) - print("SO Items: ", so_items) - linked_po = frappe.get_all( 'Purchase Order Item', filters = { @@ -843,8 +839,6 @@ class AccountsController(TransactionBase): pluck='parent' ) - print("Before unlinking: ", linked_po) - if linked_po: frappe.db.set_value( 'Purchase Order Item', { @@ -859,19 +853,6 @@ class AccountsController(TransactionBase): frappe.msgprint(_("Purchase Orders {0} are un-linked").format("\n".join(linked_po))) - linked_po = frappe.get_all( - 'Purchase Order Item', - filters = { - 'sales_order': self.name, - 'sales_order_item': ['in', so_items], - 'docstatus': ['<', 2] - }, - pluck='parent' - ) - print("After unlinking: ", linked_po) - - print("\n"*5, "*"*50, "\n"*5) - def get_tax_map(self): tax_map = {} for tax in self.get('taxes'): From 305b9508e673101ef0fe1eae9d8a377c23dcf43a Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Thu, 28 Oct 2021 19:27:45 +0530 Subject: [PATCH 101/164] fix: Remove debugger (cherry picked from commit 1a9d2684e2c8e9f89ea82dbade5cbe9b72f9e364) --- erpnext/selling/doctype/sales_order/sales_order.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index c9490fcae44..dcf478bda6e 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -207,12 +207,6 @@ class SalesOrder(SellingController): from erpnext.accounts.doctype.pricing_rule.utils import update_coupon_code_count update_coupon_code_count(self.coupon_code,'cancelled') - def cancel(self): - import pdb - - pdb.set_trace() - super(SalesOrder, self).cancel() - def update_project(self): if frappe.db.get_single_value('Selling Settings', 'sales_update_frequency') != "Each Transaction": return From ed58f206fa8b21bfb917bae92eb8d730de348f5e Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Thu, 28 Oct 2021 20:06:48 +0530 Subject: [PATCH 102/164] fix: Remove unused imports (cherry picked from commit d24cfff3886b8502c8f98cb4421c5e37a46ec3f2) --- erpnext/controllers/accounts_controller.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 5acf6e9a68c..5999f2588a1 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -19,8 +19,7 @@ from frappe.utils import ( get_link_to_form, getdate, nowdate, - now, - today + today, ) from six import text_type From a60ecb95b0321861090687442fd2ac066a8d3bf6 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 29 Oct 2021 11:35:34 +0530 Subject: [PATCH 103/164] fix: Ignore PO on So cancel (cherry picked from commit 051aaa708d7ab8a0019e19c9c8b8371afa03abc6) --- erpnext/controllers/accounts_controller.py | 4 ++-- erpnext/selling/doctype/sales_order/sales_order.js | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 5999f2588a1..45574a6ba19 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -828,7 +828,7 @@ class AccountsController(TransactionBase): for item in self.items: so_items.append(item.name) - linked_po = frappe.get_all( + linked_po = list(set(frappe.get_all( 'Purchase Order Item', filters = { 'sales_order': self.name, @@ -836,7 +836,7 @@ class AccountsController(TransactionBase): 'docstatus': ['<', 2] }, pluck='parent' - ) + ))) if linked_po: frappe.db.set_value( diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js index 1961371d0b8..b8b023307f2 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.js +++ b/erpnext/selling/doctype/sales_order/sales_order.js @@ -78,6 +78,8 @@ frappe.ui.form.on("Sales Order", { }); erpnext.queries.setup_warehouse_query(frm); + + frm.ignore_doctypes_on_cancel_all = ['Purchase Order']; }, delivery_date: function(frm) { From 3fc24426223cceb52cad92a7bbd810f1255623aa Mon Sep 17 00:00:00 2001 From: Subin Tom Date: Thu, 28 Oct 2021 13:19:19 +0530 Subject: [PATCH 104/164] fix: Consolidated Financial Report throws error for empty equity data list (cherry picked from commit d786855d94a8d60be34ddd34e4b7a013bc7d4480) --- .../consolidated_financial_statement.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py index 0de2a9854d6..0475231a934 100644 --- a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py +++ b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py @@ -114,8 +114,9 @@ def prepare_companywise_opening_balance(asset_data, liability_data, equity_data, # opening_value = Aseet - liability - equity for data in [asset_data, liability_data, equity_data]: - account_name = get_root_account_name(data[0].root_type, company) - opening_value += (get_opening_balance(account_name, data, company) or 0.0) + if data: + account_name = get_root_account_name(data[0].root_type, company) + opening_value += (get_opening_balance(account_name, data, company) or 0.0) opening_balance[company] = opening_value From a33ebbdc54c8f8c445e58f7c0cac4eb1c020f223 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 29 Oct 2021 14:19:25 +0530 Subject: [PATCH 105/164] test: remove unnecessary creation of new company (#28137) (#28140) (cherry picked from commit afe09d4e80c3000ce866f0b43ecced878f715273) Co-authored-by: Ankush Menat --- erpnext/tests/test_woocommerce.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/erpnext/tests/test_woocommerce.py b/erpnext/tests/test_woocommerce.py index 881f286baeb..3ce68d89bcd 100644 --- a/erpnext/tests/test_woocommerce.py +++ b/erpnext/tests/test_woocommerce.py @@ -12,12 +12,6 @@ from erpnext.erpnext_integrations.connectors.woocommerce_connection import order class TestWoocommerce(unittest.TestCase): def setUp(self): - if not frappe.db.exists('Company', 'Woocommerce'): - company = frappe.new_doc("Company") - company.company_name = "Woocommerce" - company.abbr = "W" - company.default_currency = "INR" - company.save() woo_settings = frappe.get_doc("Woocommerce Settings") if not woo_settings.secret: @@ -26,14 +20,14 @@ class TestWoocommerce(unittest.TestCase): woo_settings.api_consumer_key = "ck_fd43ff5756a6abafd95fadb6677100ce95a758a1" woo_settings.api_consumer_secret = "cs_94360a1ad7bef7fa420a40cf284f7b3e0788454e" woo_settings.enable_sync = 1 - woo_settings.company = "Woocommerce" - woo_settings.tax_account = "Sales Expenses - W" - woo_settings.f_n_f_account = "Expenses - W" + woo_settings.company = "_Test Company" + woo_settings.tax_account = "Sales Expenses - _TC" + woo_settings.f_n_f_account = "Expenses - _TC" woo_settings.creation_user = "Administrator" woo_settings.save(ignore_permissions=True) def test_sales_order_for_woocommerce(self): - frappe.flags.woocomm_test_order_data = {"id":75,"parent_id":0,"number":"74","order_key":"wc_order_5aa1281c2dacb","created_via":"checkout","version":"3.3.3","status":"processing","currency":"INR","date_created":"2018-03-08T12:10:04","date_created_gmt":"2018-03-08T12:10:04","date_modified":"2018-03-08T12:10:04","date_modified_gmt":"2018-03-08T12:10:04","discount_total":"0.00","discount_tax":"0.00","shipping_total":"150.00","shipping_tax":"0.00","cart_tax":"0.00","total":"649.00","total_tax":"0.00","prices_include_tax":False,"customer_id":12,"customer_ip_address":"103.54.99.5","customer_user_agent":"mozilla\\/5.0 (x11; linux x86_64) applewebkit\\/537.36 (khtml, like gecko) chrome\\/64.0.3282.186 safari\\/537.36","customer_note":"","billing":{"first_name":"Tony","last_name":"Stark","company":"Woocommerce","address_1":"Mumbai","address_2":"","city":"Dadar","state":"MH","postcode":"123","country":"IN","email":"tony@gmail.com","phone":"123457890"},"shipping":{"first_name":"Tony","last_name":"Stark","company":"","address_1":"Mumbai","address_2":"","city":"Dadar","state":"MH","postcode":"123","country":"IN"},"payment_method":"cod","payment_method_title":"Cash on delivery","transaction_id":"","date_paid":"","date_paid_gmt":"","date_completed":"","date_completed_gmt":"","cart_hash":"8e76b020d5790066496f244860c4703f","meta_data":[],"line_items":[{"id":80,"name":"Marvel","product_id":56,"variation_id":0,"quantity":1,"tax_class":"","subtotal":"499.00","subtotal_tax":"0.00","total":"499.00","total_tax":"0.00","taxes":[],"meta_data":[],"sku":"","price":499}],"tax_lines":[],"shipping_lines":[{"id":81,"method_title":"Flat rate","method_id":"flat_rate:1","total":"150.00","total_tax":"0.00","taxes":[],"meta_data":[{"id":623,"key":"Items","value":"Marvel × 1"}]}],"fee_lines":[],"coupon_lines":[],"refunds":[]} + frappe.flags.woocomm_test_order_data = {"id":75,"parent_id":0,"number":"74","order_key":"wc_order_5aa1281c2dacb","created_via":"checkout","version":"3.3.3","status":"processing","currency":"INR","date_created":"2018-03-08T12:10:04","date_created_gmt":"2018-03-08T12:10:04","date_modified":"2018-03-08T12:10:04","date_modified_gmt":"2018-03-08T12:10:04","discount_total":"0.00","discount_tax":"0.00","shipping_total":"150.00","shipping_tax":"0.00","cart_tax":"0.00","total":"649.00","total_tax":"0.00","prices_include_tax":False,"customer_id":12,"customer_ip_address":"103.54.99.5","customer_user_agent":"mozilla\\/5.0 (x11; linux x86_64) applewebkit\\/537.36 (khtml, like gecko) chrome\\/64.0.3282.186 safari\\/537.36","customer_note":"","billing":{"first_name":"Tony","last_name":"Stark","company":"_Test Company","address_1":"Mumbai","address_2":"","city":"Dadar","state":"MH","postcode":"123","country":"IN","email":"tony@gmail.com","phone":"123457890"},"shipping":{"first_name":"Tony","last_name":"Stark","company":"","address_1":"Mumbai","address_2":"","city":"Dadar","state":"MH","postcode":"123","country":"IN"},"payment_method":"cod","payment_method_title":"Cash on delivery","transaction_id":"","date_paid":"","date_paid_gmt":"","date_completed":"","date_completed_gmt":"","cart_hash":"8e76b020d5790066496f244860c4703f","meta_data":[],"line_items":[{"id":80,"name":"Marvel","product_id":56,"variation_id":0,"quantity":1,"tax_class":"","subtotal":"499.00","subtotal_tax":"0.00","total":"499.00","total_tax":"0.00","taxes":[],"meta_data":[],"sku":"","price":499}],"tax_lines":[],"shipping_lines":[{"id":81,"method_title":"Flat rate","method_id":"flat_rate:1","total":"150.00","total_tax":"0.00","taxes":[],"meta_data":[{"id":623,"key":"Items","value":"Marvel × 1"}]}],"fee_lines":[],"coupon_lines":[],"refunds":[]} order() self.assertTrue(frappe.get_value("Customer",{"woocommerce_email":"tony@gmail.com"})) From 1454f8bc9678e32a2bd3927fa6c08936ff2dcde7 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 29 Oct 2021 14:19:51 +0530 Subject: [PATCH 106/164] fix: Skip empty rows while updating unsaved BOM cost (#28136) (#28142) - Dont try to get valuation rate if row has no item code - Dont try to add exploded items if row has no item code (cherry picked from commit 292419bc9ebc405ef8a4861125070abb0d321db1) Co-authored-by: Marica --- erpnext/manufacturing/doctype/bom/bom.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 3ea756eec97..0ac64c2cfca 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -308,6 +308,9 @@ class BOM(WebsiteGenerator): existing_bom_cost = self.total_cost for d in self.get("items"): + if not d.item_code: + continue + rate = self.get_rm_rate({ "company": self.company, "item_code": d.item_code, @@ -600,7 +603,7 @@ class BOM(WebsiteGenerator): for d in self.get('items'): if d.bom_no: self.get_child_exploded_items(d.bom_no, d.stock_qty) - else: + elif d.item_code: self.add_to_cur_exploded_items(frappe._dict({ 'item_code' : d.item_code, 'item_name' : d.item_name, From 7cbf577f1f980c9ad85d8d762233dbfcf516e9db Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 29 Oct 2021 12:45:19 +0530 Subject: [PATCH 107/164] fix: Accounting Dimension filters not honouring user permissions (cherry picked from commit aa9e78bed19b72584a5248afd83ca82011727e13) --- erpnext/controllers/queries.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index 48552a972f0..eab7a07be90 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -566,7 +566,7 @@ def get_filtered_dimensions(doctype, txt, searchfield, start, page_len, filters) query_filters.append(['name', query_selector, dimensions]) - output = frappe.get_all(doctype, filters=query_filters) + output = frappe.get_list(doctype, filters=query_filters) result = [d.name for d in output] return [(d,) for d in set(result)] From ff7423f15f71445470677d4f133c1319dd4dfdfa Mon Sep 17 00:00:00 2001 From: Dany Robert Date: Mon, 18 Oct 2021 18:46:45 +0530 Subject: [PATCH 108/164] fix: incorrect VAT Amount in UAT VAT 201 report (cherry picked from commit 1aa34d178051c9b1ec20bf45171bd12d6df4d151) --- erpnext/regional/report/uae_vat_201/uae_vat_201.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/report/uae_vat_201/uae_vat_201.py b/erpnext/regional/report/uae_vat_201/uae_vat_201.py index f4c049d1623..8507b0effb2 100644 --- a/erpnext/regional/report/uae_vat_201/uae_vat_201.py +++ b/erpnext/regional/report/uae_vat_201/uae_vat_201.py @@ -122,7 +122,7 @@ def get_total_emiratewise(filters): try: return frappe.db.sql(""" select - s.vat_emirate as emirate, sum(i.base_amount) as total, sum(s.total_taxes_and_charges) + s.vat_emirate as emirate, sum(i.base_amount) as total, s.total_taxes_and_charges from `tabSales Invoice Item` i inner join `tabSales Invoice` s on From 43ca66f46196c899f929a59a5804cbf4acd72de6 Mon Sep 17 00:00:00 2001 From: Dany Robert Date: Tue, 19 Oct 2021 14:12:59 +0530 Subject: [PATCH 109/164] fix: wrong vat amount (cherry picked from commit 4c499e804a29ac6639de6df9cfd6704f338a58c3) --- erpnext/regional/report/uae_vat_201/uae_vat_201.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/report/uae_vat_201/uae_vat_201.py b/erpnext/regional/report/uae_vat_201/uae_vat_201.py index 8507b0effb2..2b5ecc3b18c 100644 --- a/erpnext/regional/report/uae_vat_201/uae_vat_201.py +++ b/erpnext/regional/report/uae_vat_201/uae_vat_201.py @@ -122,7 +122,7 @@ def get_total_emiratewise(filters): try: return frappe.db.sql(""" select - s.vat_emirate as emirate, sum(i.base_amount) as total, s.total_taxes_and_charges + s.vat_emirate as emirate, sum(i.base_amount) as total, sum(i.tax_amount) from `tabSales Invoice Item` i inner join `tabSales Invoice` s on From 0664e64003908c88aec8cd634479fab7851d632b Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 29 Oct 2021 16:08:52 +0530 Subject: [PATCH 110/164] fix: COA importer importing all accounts as group (cherry picked from commit 8ccd3fee9e7092d6bb7d81e8ef6d59e25c13a29d) --- .../doctype/account/chart_of_accounts/chart_of_accounts.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py b/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py index 05caafe1c47..3596c340175 100644 --- a/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py +++ b/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py @@ -81,7 +81,7 @@ def add_suffix_if_duplicate(account_name, account_number, accounts): def identify_is_group(child): if child.get("is_group"): is_group = child.get("is_group") - elif len(set(child.keys()) - set(["account_type", "root_type", "is_group", "tax_rate", "account_number"])): + elif len(set(child.keys()) - set(["account_name", "account_type", "root_type", "is_group", "tax_rate", "account_number"])): is_group = 1 else: is_group = 0 From 835d96c7277b004de222cda8cbecfb0f7ae4a346 Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Wed, 20 Oct 2021 17:37:52 +0530 Subject: [PATCH 111/164] fix: incorrect amount of serial_nos fetched (cherry picked from commit b44945380dd0af192b12cdf96df7ac23b77a79c3) --- erpnext/stock/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py index c4a0497b744..b131125c670 100644 --- a/erpnext/stock/utils.py +++ b/erpnext/stock/utils.py @@ -104,7 +104,7 @@ def get_stock_balance(item_code, warehouse, posting_date=None, posting_time=None serial_nos = last_entry.get("serial_no") if (serial_nos and - len(get_serial_nos_data(serial_nos)) < last_entry.qty_after_transaction): + len(get_serial_nos_data(serial_nos)) <= last_entry.qty_after_transaction): serial_nos = get_serial_nos_data_after_transactions(args) return ((last_entry.qty_after_transaction, last_entry.valuation_rate, serial_nos) @@ -121,6 +121,7 @@ def get_serial_nos_data_after_transactions(args): WHERE item_code = %(item_code)s and warehouse = %(warehouse)s and timestamp(posting_date, posting_time) < timestamp(%(posting_date)s, %(posting_time)s) + and is_cancelled = 0 order by posting_date, posting_time asc """, args, as_dict=1) for d in data: From 7931cfd77be7938f8eed33ced448b9a3e2ad3389 Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Thu, 21 Oct 2021 17:17:11 +0530 Subject: [PATCH 112/164] refactor: replaced db.sql with qb (cherry picked from commit deb6b38fab47339098c903b253a5e478a2a63b65) --- erpnext/stock/utils.py | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py index b131125c670..518bdf11e3c 100644 --- a/erpnext/stock/utils.py +++ b/erpnext/stock/utils.py @@ -115,14 +115,25 @@ def get_stock_balance(item_code, warehouse, posting_date=None, posting_time=None return last_entry.qty_after_transaction if last_entry else 0.0 def get_serial_nos_data_after_transactions(args): + from pypika import CustomFunction + serial_nos = [] - data = frappe.db.sql(""" SELECT serial_no, actual_qty - FROM `tabStock Ledger Entry` - WHERE - item_code = %(item_code)s and warehouse = %(warehouse)s - and timestamp(posting_date, posting_time) < timestamp(%(posting_date)s, %(posting_time)s) - and is_cancelled = 0 - order by posting_date, posting_time asc """, args, as_dict=1) + args = frappe._dict(args) + sle = frappe.qb.DocType('Stock Ledger Entry') + Timestamp = CustomFunction('timestamp', ['date', 'time']) + + data = frappe.qb.from_( + sle + ).select( + 'serial_no','actual_qty' + ).where( + (sle.item_code == args.item_code) + & (sle.warehouse == args.warehouse) + & (Timestamp(sle.posting_date, sle.posting_time) < Timestamp(args.posting_date, args.posting_time)) + & (sle.is_cancelled == 0) + ).orderby( + sle.posting_date, sle.posting_time + ).run(as_dict=1) for d in data: if d.actual_qty > 0: From 42686f92e9fd7d1b62b88ad14ca4ab28e84b69c5 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 29 Oct 2021 14:32:13 +0530 Subject: [PATCH 113/164] fix: fetch serial nos from ledger unconditionally (cherry picked from commit 2aa019ae4c148a01ed57b8cffb461cb90748791a) --- erpnext/stock/utils.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py index 518bdf11e3c..463b314291a 100644 --- a/erpnext/stock/utils.py +++ b/erpnext/stock/utils.py @@ -101,11 +101,7 @@ def get_stock_balance(item_code, warehouse, posting_date=None, posting_time=None if with_valuation_rate: if with_serial_no: - serial_nos = last_entry.get("serial_no") - - if (serial_nos and - len(get_serial_nos_data(serial_nos)) <= last_entry.qty_after_transaction): - serial_nos = get_serial_nos_data_after_transactions(args) + serial_nos = get_serial_nos_data_after_transactions(args) return ((last_entry.qty_after_transaction, last_entry.valuation_rate, serial_nos) if last_entry else (0.0, 0.0, 0.0)) From 264ffe05b4616b9f17a77d07b4e5d08bdcbc5f16 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 29 Oct 2021 14:56:54 +0530 Subject: [PATCH 114/164] refactor: simplify sr no fetching (cherry picked from commit f4b60a48f5bb8b8b9e8446aa0ab441f84b321e55) --- erpnext/stock/utils.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py index 463b314291a..38ca25ee547 100644 --- a/erpnext/stock/utils.py +++ b/erpnext/stock/utils.py @@ -113,12 +113,12 @@ def get_stock_balance(item_code, warehouse, posting_date=None, posting_time=None def get_serial_nos_data_after_transactions(args): from pypika import CustomFunction - serial_nos = [] + serial_nos = set() args = frappe._dict(args) sle = frappe.qb.DocType('Stock Ledger Entry') Timestamp = CustomFunction('timestamp', ['date', 'time']) - data = frappe.qb.from_( + stock_ledger_entries = frappe.qb.from_( sle ).select( 'serial_no','actual_qty' @@ -131,11 +131,12 @@ def get_serial_nos_data_after_transactions(args): sle.posting_date, sle.posting_time ).run(as_dict=1) - for d in data: - if d.actual_qty > 0: - serial_nos.extend(get_serial_nos_data(d.serial_no)) + for stock_ledger_entry in stock_ledger_entries: + changed_serial_no = get_serial_nos_data(stock_ledger_entry.serial_no) + if stock_ledger_entry.actual_qty > 0: + serial_nos.update(changed_serial_no) else: - serial_nos = list(set(serial_nos) - set(get_serial_nos_data(d.serial_no))) + serial_nos.difference_update(changed_serial_no) return '\n'.join(serial_nos) From 9e9f730dd17510dfb6f53c0d488b96c9ee233359 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 29 Oct 2021 16:30:12 +0530 Subject: [PATCH 115/164] fix: sort by creation to break tie (cherry picked from commit ff9cfe0d14849d2103700ce84244c25b22075581) --- erpnext/stock/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py index 38ca25ee547..e1d5a89082e 100644 --- a/erpnext/stock/utils.py +++ b/erpnext/stock/utils.py @@ -128,7 +128,7 @@ def get_serial_nos_data_after_transactions(args): & (Timestamp(sle.posting_date, sle.posting_time) < Timestamp(args.posting_date, args.posting_time)) & (sle.is_cancelled == 0) ).orderby( - sle.posting_date, sle.posting_time + sle.posting_date, sle.posting_time, sle.creation ).run(as_dict=1) for stock_ledger_entry in stock_ledger_entries: From f16e0e17cc64ae42bdff9323c2387c420fd0ed84 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 29 Oct 2021 18:05:49 +0530 Subject: [PATCH 116/164] fix: POS consolidated invoice rounded total issue (#28146) --- .../pos_invoice_merge_log.py | 11 +++++ erpnext/controllers/taxes_and_totals.py | 40 ++++++++++++------- 2 files changed, 36 insertions(+), 15 deletions(-) diff --git a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py index 4f26ed43db7..28bd10283e7 100644 --- a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py +++ b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py @@ -114,6 +114,8 @@ class POSInvoiceMergeLog(Document): def merge_pos_invoice_into(self, invoice, data): items, payments, taxes = [], [], [] loyalty_amount_sum, loyalty_points_sum = 0, 0 + rounding_adjustment, base_rounding_adjustment = 0, 0 + rounded_total, base_rounded_total = 0, 0 for doc in data: map_doc(doc, invoice, table_map={ "doctype": invoice.doctype }) @@ -162,6 +164,11 @@ class POSInvoiceMergeLog(Document): found = True if not found: payments.append(payment) + rounding_adjustment += doc.rounding_adjustment + rounded_total += doc.rounded_total + base_rounding_adjustment += doc.rounding_adjustment + base_rounded_total += doc.rounded_total + if loyalty_points_sum: invoice.redeem_loyalty_points = 1 @@ -171,6 +178,10 @@ class POSInvoiceMergeLog(Document): invoice.set('items', items) invoice.set('payments', payments) invoice.set('taxes', taxes) + invoice.set('rounding_adjustment',rounding_adjustment) + invoice.set('rounding_adjustment',base_rounding_adjustment) + invoice.set('base_rounded_total',base_rounded_total) + invoice.set('rounded_total',rounded_total) invoice.additional_discount_percentage = 0 invoice.discount_amount = 0.0 invoice.taxes_and_charges = None diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index fbfdfdfac89..1c57a56371b 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -260,7 +260,9 @@ class calculate_taxes_and_totals(object): self.doc.round_floats_in(self.doc, ["total", "base_total", "net_total", "base_net_total"]) def calculate_taxes(self): - self.doc.rounding_adjustment = 0 + if not self.doc.get('is_consolidated'): + self.doc.rounding_adjustment = 0 + # maintain actual tax rate based on idx actual_tax_dict = dict([[tax.idx, flt(tax.tax_amount, tax.precision("tax_amount"))] for tax in self.doc.get("taxes") if tax.charge_type == "Actual"]) @@ -312,7 +314,9 @@ class calculate_taxes_and_totals(object): # adjust Discount Amount loss in last tax iteration if i == (len(self.doc.get("taxes")) - 1) and self.discount_amount_applied \ - and self.doc.discount_amount and self.doc.apply_discount_on == "Grand Total": + and self.doc.discount_amount \ + and self.doc.apply_discount_on == "Grand Total" \ + and not self.doc.get('is_consolidated'): self.doc.rounding_adjustment = flt(self.doc.grand_total - flt(self.doc.discount_amount) - tax.total, self.doc.precision("rounding_adjustment")) @@ -405,11 +409,16 @@ class calculate_taxes_and_totals(object): self.doc.rounding_adjustment = diff def calculate_totals(self): - self.doc.grand_total = flt(self.doc.get("taxes")[-1].total) + flt(self.doc.rounding_adjustment) \ - if self.doc.get("taxes") else flt(self.doc.net_total) + if self.doc.get("taxes"): + self.doc.grand_total = flt(self.doc.get("taxes")[-1].total) + flt(self.doc.rounding_adjustment) + else: + self.doc.grand_total = flt(self.doc.net_total) - self.doc.total_taxes_and_charges = flt(self.doc.grand_total - self.doc.net_total + if self.doc.get("taxes"): + self.doc.total_taxes_and_charges = flt(self.doc.grand_total - self.doc.net_total - flt(self.doc.rounding_adjustment), self.doc.precision("total_taxes_and_charges")) + else: + self.doc.total_taxes_and_charges = 0.0 self._set_in_company_currency(self.doc, ["total_taxes_and_charges", "rounding_adjustment"]) @@ -446,19 +455,20 @@ class calculate_taxes_and_totals(object): self.doc.total_net_weight += d.total_weight def set_rounded_total(self): - if self.doc.meta.get_field("rounded_total"): - if self.doc.is_rounded_total_disabled(): - self.doc.rounded_total = self.doc.base_rounded_total = 0 - return + if not self.doc.get('is_consolidated'): + if self.doc.meta.get_field("rounded_total"): + if self.doc.is_rounded_total_disabled(): + self.doc.rounded_total = self.doc.base_rounded_total = 0 + return - self.doc.rounded_total = round_based_on_smallest_currency_fraction(self.doc.grand_total, - self.doc.currency, self.doc.precision("rounded_total")) + self.doc.rounded_total = round_based_on_smallest_currency_fraction(self.doc.grand_total, + self.doc.currency, self.doc.precision("rounded_total")) - #if print_in_rate is set, we would have already calculated rounding adjustment - self.doc.rounding_adjustment += flt(self.doc.rounded_total - self.doc.grand_total, - self.doc.precision("rounding_adjustment")) + #if print_in_rate is set, we would have already calculated rounding adjustment + self.doc.rounding_adjustment += flt(self.doc.rounded_total - self.doc.grand_total, + self.doc.precision("rounding_adjustment")) - self._set_in_company_currency(self.doc, ["rounding_adjustment", "rounded_total"]) + self._set_in_company_currency(self.doc, ["rounding_adjustment", "rounded_total"]) def _cleanup(self): if not self.doc.get('is_consolidated'): From 9abb467127937ee5984c8263a1f44251af10f508 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 29 Oct 2021 18:16:23 +0530 Subject: [PATCH 117/164] fix: Make status filter optional (#28151) --- .../fixed_asset_register/fixed_asset_register.js | 5 ++--- .../fixed_asset_register/fixed_asset_register.py | 11 ++++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.js b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.js index 75f42a9f783..06989a95da7 100644 --- a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.js +++ b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.js @@ -16,9 +16,8 @@ frappe.query_reports["Fixed Asset Register"] = { fieldname:"status", label: __("Status"), fieldtype: "Select", - options: "In Location\nDisposed", - default: 'In Location', - reqd: 1 + options: "\nIn Location\nDisposed", + default: 'In Location' }, { "fieldname":"filter_based_on", diff --git a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py index e370b9d0cb3..63685fef465 100644 --- a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py +++ b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py @@ -45,12 +45,13 @@ def get_conditions(filters): if filters.get('cost_center'): conditions["cost_center"] = filters.get('cost_center') - # In Store assets are those that are not sold or scrapped - operand = 'not in' - if status not in 'In Location': - operand = 'in' + if status: + # In Store assets are those that are not sold or scrapped + operand = 'not in' + if status not in 'In Location': + operand = 'in' - conditions['status'] = (operand, ['Sold', 'Scrapped']) + conditions['status'] = (operand, ['Sold', 'Scrapped']) return conditions From aa464cc615ba4753a41cf4745d660e8903190be6 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sat, 30 Oct 2021 18:22:46 +0530 Subject: [PATCH 118/164] fix: Error for missing PAN no field (cherry picked from commit 541c892f9757f01a4ce7b72e5bc537260e65a3f8) --- .../tax_withholding_category.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) 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 c3cb8396d0d..20e93bc1e31 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -49,15 +49,24 @@ def get_party_tax_withholding_details(inv, tax_withholding_category=None): pan_no = '' parties = [] party_type, party = get_party_details(inv) + has_pan_field = frappe.get_meta(party_type).has_field("pan") if not tax_withholding_category: - tax_withholding_category, pan_no = frappe.db.get_value(party_type, party, ['tax_withholding_category', 'pan']) + if has_pan_field: + fields = ['tax_withholding_category', 'pan'] + else: + fields = ['tax_withholding_category'] + + tax_withholding_details = frappe.db.get_value(party_type, party, fields, as_dict=1) + + tax_withholding_category = tax_withholding_details.get('tax_withholding_category') + pan_no = tax_withholding_details.get('pan') if not tax_withholding_category: return # if tax_withholding_category passed as an argument but not pan_no - if not pan_no: + if not pan_no and has_pan_field: pan_no = frappe.db.get_value(party_type, party, 'pan') # Get others suppliers with the same PAN No From 060127663a8ca906372c30f5d89e58e16eb8c12d Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Sun, 31 Oct 2021 09:24:07 +0530 Subject: [PATCH 119/164] fix: patch update_category_in_ltds_certificate (cherry picked from commit cae29b71d863daf102fd002b7f32592817633026) --- erpnext/patches/v13_0/update_category_in_ltds_certificate.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/patches/v13_0/update_category_in_ltds_certificate.py b/erpnext/patches/v13_0/update_category_in_ltds_certificate.py index 4d4645269cb..a5f5a23449a 100644 --- a/erpnext/patches/v13_0/update_category_in_ltds_certificate.py +++ b/erpnext/patches/v13_0/update_category_in_ltds_certificate.py @@ -6,6 +6,8 @@ def execute(): if not company: return + frappe.reload_doc('regional', 'doctype', 'lower_deduction_certificate') + ldc = frappe.qb.DocType("Lower Deduction Certificate").as_("ldc") supplier = frappe.qb.DocType("Supplier") From 18dddfdea9ac971ee0df6d9265466711a86aa1dc Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 1 Nov 2021 11:11:40 +0530 Subject: [PATCH 120/164] fix: remove duplicate field from selling_settings (#28174) fix https://github.com/frappe/erpnext/issues/28155 (cherry picked from commit 1cc4eddacbb5eed63337d11e7b5764e34f1ff2b6) Co-authored-by: ahmadRagheb --- .../selling/doctype/selling_settings/selling_settings.json | 6 ------ 1 file changed, 6 deletions(-) diff --git a/erpnext/selling/doctype/selling_settings/selling_settings.json b/erpnext/selling/doctype/selling_settings/selling_settings.json index 419c8e237d0..a0c1c85dd52 100644 --- a/erpnext/selling/doctype/selling_settings/selling_settings.json +++ b/erpnext/selling/doctype/selling_settings/selling_settings.json @@ -193,12 +193,6 @@ "fieldname": "sales_transactions_settings_section", "fieldtype": "Section Break", "label": "Transaction Settings" - }, - { - "default": "0", - "fieldname": "editable_bundle_item_rates", - "fieldtype": "Check", - "label": "Calculate Product Bundle Price based on Child Items' Rates" } ], "icon": "fa fa-cog", From e9d1cbb3392494c141041efc40e5c33e004c4249 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 30 Jul 2021 12:36:35 +0530 Subject: [PATCH 121/164] fix: COGS account in purchase receipt (cherry picked from commit 2a14f255cfa8d85815cc22d0bcae76d7e4de8634) # Conflicts: # erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py --- .../doctype/pricing_rule/test_pricing_rule.py | 10 ++++++ erpnext/accounts/utils.py | 6 ++-- erpnext/controllers/accounts_controller.py | 4 +-- .../purchase_receipt/purchase_receipt.py | 2 +- .../purchase_receipt/test_purchase_receipt.py | 35 +++++++++++++++++++ 5 files changed, 51 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py index e2b23704c88..2e2d425dab7 100644 --- a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py @@ -19,6 +19,7 @@ from erpnext.stock.get_item_details import get_item_details class TestPricingRule(unittest.TestCase): def setUp(self): delete_existing_pricing_rules() + setup_pricing_rule_data() def tearDown(self): delete_existing_pricing_rules() @@ -561,6 +562,8 @@ class TestPricingRule(unittest.TestCase): for doc in [si, si1]: doc.delete() +test_dependencies = ["Campaign"] + def make_pricing_rule(**args): args = frappe._dict(args) @@ -607,6 +610,13 @@ def make_pricing_rule(**args): if args.get(applicable_for): doc.db_set(applicable_for, args.get(applicable_for)) +def setup_pricing_rule_data(): + if not frappe.db.exists('Campaign', '_Test Campaign'): + frappe.get_doc({ + 'doctype': 'Campaign', + 'campaign_name': '_Test Campaign', + 'name': '_Test Campaign' + }).insert() def delete_existing_pricing_rules(): for doctype in ["Pricing Rule", "Pricing Rule Item Code", diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index b425062dc6f..55ce70488d5 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -579,10 +579,10 @@ def remove_ref_doc_link_from_pe(ref_type, ref_no): frappe.msgprint(_("Payment Entries {0} are un-linked").format("\n".join(linked_pe))) @frappe.whitelist() -def get_company_default(company, fieldname): - value = frappe.get_cached_value('Company', company, fieldname) +def get_company_default(company, fieldname, ignore_validation=False): + value = frappe.get_cached_value('Company', company, fieldname) - if not value: + if not ignore_validation and not value: throw(_("Please set default {0} in Company {1}") .format(frappe.get_meta("Company").get_label(fieldname), company)) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 45574a6ba19..f2340795a1b 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1075,9 +1075,9 @@ class AccountsController(TransactionBase): 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)) - def get_company_default(self, fieldname): + def get_company_default(self, fieldname, ignore_validation=False): from erpnext.accounts.utils import get_company_default - return get_company_default(self.company, fieldname) + return get_company_default(self.company, fieldname, ignore_validation=ignore_validation) def get_stock_items(self): stock_items = [] diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index cd8b1bf48a0..233ea1f7045 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -362,7 +362,7 @@ class PurchaseReceipt(BuyingController): if self.is_return or flt(d.item_tax_amount): loss_account = expenses_included_in_valuation else: - loss_account = self.get_company_default("default_expense_account") + loss_account = self.get_company_default("default_expense_account", ignore_validation=True) or stock_rbnb cost_center = d.cost_center or frappe.get_cached_value("Company", self.company, "cost_center") diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 53e0e488f9e..b67ab2c5000 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -332,7 +332,25 @@ class TestPurchaseReceipt(ERPNextTestCase): pr1.submit() self.assertRaises(frappe.ValidationError, pr2.submit) +<<<<<<< HEAD frappe.db.rollback() +======= + + pr1.cancel() + se.cancel() + se1.cancel() + se2.cancel() + se3.cancel() + po.reload() + pr2.load_from_db() + + if pr2.docstatus == 1 and frappe.db.get_value('Stock Ledger Entry', + {'voucher_no': pr2.name, 'is_cancelled': 0}, 'name'): + pr2.cancel() + + po.load_from_db() + po.cancel() +>>>>>>> 2a14f255cf (fix: COGS account in purchase receipt) def test_serial_no_supplier(self): pr = make_purchase_receipt(item_code="_Test Serialized Item With Series", qty=1) @@ -1055,6 +1073,7 @@ class TestPurchaseReceipt(ERPNextTestCase): frappe.db.set_value('Company', company, 'enable_perpetual_inventory_for_non_stock_items', before_test_value) +<<<<<<< HEAD def test_payment_terms_are_fetched_when_creating_purchase_invoice(self): from erpnext.accounts.doctype.payment_entry.test_payment_entry import ( create_payment_terms_template, @@ -1070,6 +1089,22 @@ class TestPurchaseReceipt(ERPNextTestCase): ) automatically_fetch_payment_terms() +======= + def test_purchase_receipt_with_exchange_rate_difference(self): + from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice as create_purchase_invoice + from erpnext.accounts.doctype.purchase_invoice.purchase_invoice import make_purchase_receipt as create_purchase_receipt + + pi = create_purchase_invoice(company="_Test Company with perpetual inventory", + cost_center = "Main - TCP1", + warehouse = "Stores - TCP1", + expense_account ="_Test Account Cost for Goods Sold - TCP1", + currency = "USD", conversion_rate = 70) + + pr = create_purchase_receipt(pi.name) + pr.conversion_rate = 80 + pr.items[0].purchase_invoice = pi.name + pr.items[0].purchase_invoice_item = pi.items[0].name +>>>>>>> 2a14f255cf (fix: COGS account in purchase receipt) po = create_purchase_order(qty=10, rate=100, do_not_save=1) create_payment_terms_template() From 271dad941bcf6e2a63b1251f00b1dee505451ee3 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 1 Nov 2021 13:17:40 +0530 Subject: [PATCH 122/164] fix: pass company while fetching valuation rate If company is not supplied and valuation rate is 0, then default company is used for checking if perpetual inventory is enabled or not. This makes little sense as different companies can have different setting for perpetual inventory. (cherry picked from commit a0727b2e824e5299a893197624872122e3d6eb74) --- erpnext/stock/stock_ledger.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index d903c0b8ece..33ab54c0456 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -592,7 +592,7 @@ class update_entries_after(object): if not allow_zero_rate: self.wh_data.valuation_rate = get_valuation_rate(sle.item_code, sle.warehouse, sle.voucher_type, sle.voucher_no, self.allow_zero_rate, - currency=erpnext.get_company_currency(sle.company)) + currency=erpnext.get_company_currency(sle.company), company=sle.company) def get_incoming_value_for_serial_nos(self, sle, serial_nos): # get rate from serial nos within same company @@ -659,7 +659,7 @@ class update_entries_after(object): if not allow_zero_valuation_rate: self.wh_data.valuation_rate = get_valuation_rate(sle.item_code, sle.warehouse, sle.voucher_type, sle.voucher_no, self.allow_zero_rate, - currency=erpnext.get_company_currency(sle.company)) + currency=erpnext.get_company_currency(sle.company), company=sle.company) def get_fifo_values(self, sle): incoming_rate = flt(sle.incoming_rate) @@ -692,7 +692,7 @@ class update_entries_after(object): if not allow_zero_valuation_rate: _rate = get_valuation_rate(sle.item_code, sle.warehouse, sle.voucher_type, sle.voucher_no, self.allow_zero_rate, - currency=erpnext.get_company_currency(sle.company)) + currency=erpnext.get_company_currency(sle.company), company=sle.company) else: _rate = 0 From c6557a136c4ccbd0e684de244957620e4ea4240c Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 1 Nov 2021 13:21:14 +0530 Subject: [PATCH 123/164] fix: use warehouse to find company (cherry picked from commit f7ffe04a4b0a65b02f20f6ca180a86c1d5a3874f) --- erpnext/stock/stock_ledger.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 33ab54c0456..29f14111e02 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -903,10 +903,11 @@ def get_sle_by_voucher_detail_no(voucher_detail_no, excluded_sle=None): def get_valuation_rate(item_code, warehouse, voucher_type, voucher_no, allow_zero_rate=False, currency=None, company=None, raise_error_if_no_rate=True): - # Get valuation rate from last sle for the same item and warehouse - if not company: - company = erpnext.get_default_company() + if not company: + company = frappe.get_cached_value("Warehouse", warehouse, "company") + + # Get valuation rate from last sle for the same item and warehouse last_valuation_rate = frappe.db.sql("""select valuation_rate from `tabStock Ledger Entry` force index (item_warehouse) where From bb7d661a04616c011332d0f142316a29a84e4757 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Mon, 1 Nov 2021 16:23:39 +0530 Subject: [PATCH 124/164] fix: Resolve conflicts --- .../purchase_receipt/test_purchase_receipt.py | 21 ------------------- 1 file changed, 21 deletions(-) diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index b67ab2c5000..f06c12359da 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -332,9 +332,6 @@ class TestPurchaseReceipt(ERPNextTestCase): pr1.submit() self.assertRaises(frappe.ValidationError, pr2.submit) -<<<<<<< HEAD - frappe.db.rollback() -======= pr1.cancel() se.cancel() @@ -350,7 +347,6 @@ class TestPurchaseReceipt(ERPNextTestCase): po.load_from_db() po.cancel() ->>>>>>> 2a14f255cf (fix: COGS account in purchase receipt) def test_serial_no_supplier(self): pr = make_purchase_receipt(item_code="_Test Serialized Item With Series", qty=1) @@ -1073,7 +1069,6 @@ class TestPurchaseReceipt(ERPNextTestCase): frappe.db.set_value('Company', company, 'enable_perpetual_inventory_for_non_stock_items', before_test_value) -<<<<<<< HEAD def test_payment_terms_are_fetched_when_creating_purchase_invoice(self): from erpnext.accounts.doctype.payment_entry.test_payment_entry import ( create_payment_terms_template, @@ -1089,22 +1084,6 @@ class TestPurchaseReceipt(ERPNextTestCase): ) automatically_fetch_payment_terms() -======= - def test_purchase_receipt_with_exchange_rate_difference(self): - from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice as create_purchase_invoice - from erpnext.accounts.doctype.purchase_invoice.purchase_invoice import make_purchase_receipt as create_purchase_receipt - - pi = create_purchase_invoice(company="_Test Company with perpetual inventory", - cost_center = "Main - TCP1", - warehouse = "Stores - TCP1", - expense_account ="_Test Account Cost for Goods Sold - TCP1", - currency = "USD", conversion_rate = 70) - - pr = create_purchase_receipt(pi.name) - pr.conversion_rate = 80 - pr.items[0].purchase_invoice = pi.name - pr.items[0].purchase_invoice_item = pi.items[0].name ->>>>>>> 2a14f255cf (fix: COGS account in purchase receipt) po = create_purchase_order(qty=10, rate=100, do_not_save=1) create_payment_terms_template() From 68809159ba04f870a279bf662a8f941a77e8c8fc Mon Sep 17 00:00:00 2001 From: marination Date: Tue, 2 Nov 2021 15:49:58 +0530 Subject: [PATCH 125/164] fix: Remove warehouse filter on Batch field for Material Receipt (cherry picked from commit 048210a8f67fe0544216361d7342619581318570) --- erpnext/stock/doctype/stock_entry/stock_entry.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index dfd827b62d5..d05368d79c7 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -88,7 +88,9 @@ frappe.ui.form.on('Stock Entry', { } } - filters["warehouse"] = item.s_warehouse || item.t_warehouse; + if (frm.doc.purpose != "Material Receipt") { + filters["warehouse"] = item.s_warehouse || item.t_warehouse; + } return { query : "erpnext.controllers.queries.get_batch_no", From b81dfdcd26cd31c60fab3e26610d8c899615d42c Mon Sep 17 00:00:00 2001 From: marination Date: Tue, 2 Nov 2021 15:57:41 +0530 Subject: [PATCH 126/164] chore: Add comment above fix for future reference (cherry picked from commit 48886ee70524132d1f4cf6751b56ccbc96d765a5) --- erpnext/stock/doctype/stock_entry/stock_entry.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index d05368d79c7..fc9d1ed98f7 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -88,6 +88,8 @@ frappe.ui.form.on('Stock Entry', { } } + // User could want to select a manually created empty batch (no warehouse) + // or a pre-existing batch if (frm.doc.purpose != "Material Receipt") { filters["warehouse"] = item.s_warehouse || item.t_warehouse; } From 50fbc167f38a82e91eb5f9bae61c3374bb80d63c Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 2 Nov 2021 17:12:11 +0530 Subject: [PATCH 127/164] fix(Payment Reconciliation): clear child tables on company/party change (#28194) --- .../payment_reconciliation.js | 132 +++++++++--------- erpnext/accounts/utils.py | 3 +- 2 files changed, 70 insertions(+), 65 deletions(-) diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js index 74531879e95..648d2da754e 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js @@ -4,9 +4,14 @@ frappe.provide("erpnext.accounts"); erpnext.accounts.PaymentReconciliationController = frappe.ui.form.Controller.extend({ onload: function() { - var me = this; + const default_company = frappe.defaults.get_default('company'); + this.frm.set_value('company', default_company); - this.frm.set_query("party_type", function() { + this.frm.set_value('party_type', ''); + this.frm.set_value('party', ''); + this.frm.set_value('receivable_payable_account', ''); + + this.frm.set_query("party_type", () => { return { "filters": { "name": ["in", Object.keys(frappe.boot.party_account_types)], @@ -14,44 +19,30 @@ erpnext.accounts.PaymentReconciliationController = frappe.ui.form.Controller.ext } }); - this.frm.set_query('receivable_payable_account', function() { - check_mandatory(me.frm); + this.frm.set_query('receivable_payable_account', () => { return { filters: { - "company": me.frm.doc.company, + "company": this.frm.doc.company, "is_group": 0, - "account_type": frappe.boot.party_account_types[me.frm.doc.party_type] + "account_type": frappe.boot.party_account_types[this.frm.doc.party_type] } }; }); - this.frm.set_query('bank_cash_account', function() { - check_mandatory(me.frm, true); + this.frm.set_query('bank_cash_account', () => { return { filters:[ - ['Account', 'company', '=', me.frm.doc.company], + ['Account', 'company', '=', this.frm.doc.company], ['Account', 'is_group', '=', 0], ['Account', 'account_type', 'in', ['Bank', 'Cash']] ] }; }); - - this.frm.set_value('party_type', ''); - this.frm.set_value('party', ''); - this.frm.set_value('receivable_payable_account', ''); - - var check_mandatory = (frm, only_company=false) => { - var title = __("Mandatory"); - if (only_company && !frm.doc.company) { - frappe.throw({message: __("Please Select a Company First"), title: title}); - } else if (!frm.doc.company || !frm.doc.party_type) { - frappe.throw({message: __("Please Select Both Company and Party Type First"), title: title}); - } - }; }, refresh: function() { this.frm.disable_save(); + this.frm.set_df_property('invoices', 'cannot_delete_rows', true); this.frm.set_df_property('payments', 'cannot_delete_rows', true); this.frm.set_df_property('allocation', 'cannot_delete_rows', true); @@ -85,76 +76,92 @@ erpnext.accounts.PaymentReconciliationController = frappe.ui.form.Controller.ext }, company: function() { - var me = this; + this.frm.set_value('party', ''); this.frm.set_value('receivable_payable_account', ''); - me.frm.clear_table("allocation"); - me.frm.clear_table("invoices"); - me.frm.clear_table("payments"); - me.frm.refresh_fields(); - me.frm.trigger('party'); + }, + + party_type: function() { + this.frm.set_value('party', ''); }, party: function() { - var me = this; - if (!me.frm.doc.receivable_payable_account && me.frm.doc.party_type && me.frm.doc.party) { + this.frm.set_value('receivable_payable_account', ''); + this.frm.trigger("clear_child_tables"); + + if (!this.frm.doc.receivable_payable_account && this.frm.doc.party_type && this.frm.doc.party) { return frappe.call({ method: "erpnext.accounts.party.get_party_account", args: { - company: me.frm.doc.company, - party_type: me.frm.doc.party_type, - party: me.frm.doc.party + company: this.frm.doc.company, + party_type: this.frm.doc.party_type, + party: this.frm.doc.party }, - callback: function(r) { + callback: (r) => { if (!r.exc && r.message) { - me.frm.set_value("receivable_payable_account", r.message); + this.frm.set_value("receivable_payable_account", r.message); } - me.frm.refresh(); + this.frm.refresh(); + } }); } }, + receivable_payable_account: function() { + this.frm.trigger("clear_child_tables"); + this.frm.refresh(); + }, + + clear_child_tables: function() { + this.frm.clear_table("invoices"); + this.frm.clear_table("payments"); + this.frm.clear_table("allocation"); + this.frm.refresh_fields(); + }, + get_unreconciled_entries: function() { - var me = this; + this.frm.clear_table("allocation"); return this.frm.call({ - doc: me.frm.doc, + doc: this.frm.doc, method: 'get_unreconciled_entries', - callback: function(r, rt) { - if (!(me.frm.doc.payments.length || me.frm.doc.invoices.length)) { - frappe.throw({message: __("No invoice and payment records found for this party")}); + callback: () => { + if (!(this.frm.doc.payments.length || this.frm.doc.invoices.length)) { + frappe.throw({message: __("No Unreconciled Invoices and Payments found for this party and account")}); + } else if (!(this.frm.doc.invoices.length)) { + frappe.throw({message: __("No Outstanding Invoices found for this party")}); + } else if (!(this.frm.doc.payments.length)) { + frappe.throw({message: __("No Unreconciled Payments found for this party")}); } - me.frm.refresh(); + this.frm.refresh(); } }); }, allocate: function() { - var me = this; - let payments = me.frm.fields_dict.payments.grid.get_selected_children(); + let payments = this.frm.fields_dict.payments.grid.get_selected_children(); if (!(payments.length)) { - payments = me.frm.doc.payments; + payments = this.frm.doc.payments; } - let invoices = me.frm.fields_dict.invoices.grid.get_selected_children(); + let invoices = this.frm.fields_dict.invoices.grid.get_selected_children(); if (!(invoices.length)) { - invoices = me.frm.doc.invoices; + invoices = this.frm.doc.invoices; } - return me.frm.call({ - doc: me.frm.doc, + return this.frm.call({ + doc: this.frm.doc, method: 'allocate_entries', args: { payments: payments, invoices: invoices }, - callback: function() { - me.frm.refresh(); + callback: () => { + this.frm.refresh(); } }); }, reconcile: function() { - var me = this; - var show_dialog = me.frm.doc.allocation.filter(d => d.difference_amount && !d.difference_account); + var show_dialog = this.frm.doc.allocation.filter(d => d.difference_amount && !d.difference_account); if (show_dialog && show_dialog.length) { @@ -186,10 +193,10 @@ erpnext.accounts.PaymentReconciliationController = frappe.ui.form.Controller.ext label: __("Difference Account"), fieldname: 'difference_account', reqd: 1, - get_query: function() { + get_query: () => { return { filters: { - company: me.frm.doc.company, + company: this.frm.doc.company, is_group: 0 } } @@ -203,7 +210,7 @@ erpnext.accounts.PaymentReconciliationController = frappe.ui.form.Controller.ext }] }, ], - primary_action: function() { + primary_action: () => { const args = dialog.get_values()["allocation"]; args.forEach(d => { @@ -211,7 +218,7 @@ erpnext.accounts.PaymentReconciliationController = frappe.ui.form.Controller.ext "difference_account", d.difference_account); }); - me.reconcile_payment_entries(); + this.reconcile_payment_entries(); dialog.hide(); }, primary_action_label: __('Reconcile Entries') @@ -237,15 +244,12 @@ erpnext.accounts.PaymentReconciliationController = frappe.ui.form.Controller.ext }, reconcile_payment_entries: function() { - var me = this; - return this.frm.call({ - doc: me.frm.doc, + doc: this.frm.doc, method: 'reconcile', - callback: function(r, rt) { - me.frm.clear_table("allocation"); - me.frm.refresh_fields(); - me.frm.refresh(); + callback: () => { + this.frm.clear_table("allocation"); + this.frm.refresh(); } }); } diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 55ce70488d5..da49ed9f4e0 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -450,7 +450,8 @@ def update_reference_in_journal_entry(d, journal_entry, do_not_save=False): # new row with references new_row = journal_entry.append("accounts") - new_row.update(jv_detail.as_dict().copy()) + + new_row.update((frappe.copy_doc(jv_detail)).as_dict()) new_row.set(d["dr_or_cr"], d["allocated_amount"]) new_row.set('debit' if d['dr_or_cr'] == 'debit_in_account_currency' else 'credit', From 093d9f4a70b78dcc8798256bc67e9f0b9589e312 Mon Sep 17 00:00:00 2001 From: marination Date: Tue, 2 Nov 2021 18:34:55 +0530 Subject: [PATCH 128/164] fix: Serial Nos not set in the row after scanning in popup - Avoid whitspaces while calculating length of serial nos (cherry picked from commit 734b57deec069fc3f573604c60a3157da81eff45) --- erpnext/selling/sales_common.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/selling/sales_common.js b/erpnext/selling/sales_common.js index d982dfe27a3..fdc55f3ab5e 100644 --- a/erpnext/selling/sales_common.js +++ b/erpnext/selling/sales_common.js @@ -202,8 +202,10 @@ erpnext.selling.SellingController = erpnext.TransactionController.extend({ var me = this; var item = frappe.get_doc(cdt, cdn); - if (item.serial_no && item.qty === item.serial_no.split(`\n`).length) { - return; + // check if serial nos entered are as much as qty in row + if (item.serial_no) { + let serial_nos = item.serial_no.split(`\n`).filter(sn => sn.trim()); // filter out whitespaces + if (item.qty === serial_nos.length) return; } if (item.serial_no && !item.batch_no) { From e358d34c4f4c759becac80cdc83135da20d20b5a Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 2 Nov 2021 20:26:40 +0530 Subject: [PATCH 129/164] fix: Error on LDC creation (cherry picked from commit 66348e1a035d98e91cb7228e9bd2b7286c5cc274) --- .../lower_deduction_certificate/lower_deduction_certificate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.py b/erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.py index 7afbc00980c..821e0171bbd 100644 --- a/erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.py +++ b/erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.py @@ -31,7 +31,7 @@ class LowerDeductionCertificate(Document): <= fiscal_year.year_end_date): frappe.throw(_("Valid Upto date not in Fiscal Year {0}").format(frappe.bold(self.fiscal_year))) - def tax_withholding_category(self): + def validate_supplier_against_tax_category(self): duplicate_certificate = frappe.db.get_value('Lower Deduction Certificate', {'supplier': self.supplier, 'tax_withholding_category': self.tax_withholding_category, 'name': ("!=", self.name)}, ['name', 'valid_from', 'valid_upto'], as_dict=True) From 94905df7744244131b4ea630b725821bd948b473 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 3 Nov 2021 09:47:00 +0530 Subject: [PATCH 130/164] fix: ignore unsupported methods while resyncing (#28210) (#28214) (cherry picked from commit 72a050fb0b5289f25f0a6a3ebc314e1671e56c11) Co-authored-by: Ankush Menat --- .../erpnext_integrations/doctype/shopify_log/shopify_log.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/erpnext_integrations/doctype/shopify_log/shopify_log.py b/erpnext/erpnext_integrations/doctype/shopify_log/shopify_log.py index b000c831947..69ebaabc00b 100644 --- a/erpnext/erpnext_integrations/doctype/shopify_log/shopify_log.py +++ b/erpnext/erpnext_integrations/doctype/shopify_log/shopify_log.py @@ -68,5 +68,8 @@ def dump_request_data(data, event="create/order"): @frappe.whitelist() def resync(method, name, request_data): frappe.db.set_value("Shopify Log", name, "status", "Queued", update_modified=False) + if not method.startswith("erpnext.erpnext_integrations.connectors.shopify_connection"): + return + frappe.enqueue(method=method, queue='short', timeout=300, is_async=True, **{"order": json.loads(request_data), "request_id": name}) From 2c9266807a277642c25caad3b466bec4f8ac3a85 Mon Sep 17 00:00:00 2001 From: Anuja Pawar <60467153+Anuja-pawar@users.noreply.github.com> Date: Tue, 2 Nov 2021 16:28:46 +0530 Subject: [PATCH 131/164] fix(Payment Entry): splitting outstanding rows as per payment terms (#27946) (cherry picked from commit d72709dd817b879fa3bac2387689e0a9d4728e8f) --- .../doctype/payment_entry/payment_entry.py | 44 +++++++++++-------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 9b4a91d4e96..8bbe3db914f 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -389,7 +389,7 @@ class PaymentEntry(AccountsController): invoice_paid_amount_map[invoice_key]['outstanding'] = term.outstanding invoice_paid_amount_map[invoice_key]['discounted_amt'] = ref.total_amount * (term.discount / 100) - for key, allocated_amount in iteritems(invoice_payment_amount_map): + for idx, (key, allocated_amount) in enumerate(iteritems(invoice_payment_amount_map), 1): if not invoice_paid_amount_map.get(key): frappe.throw(_('Payment term {0} not used in {1}').format(key[0], key[1])) @@ -407,7 +407,7 @@ class PaymentEntry(AccountsController): (allocated_amount - discounted_amt, discounted_amt, allocated_amount, key[1], key[0])) else: if allocated_amount > outstanding: - frappe.throw(_('Cannot allocate more than {0} against payment term {1}').format(outstanding, key[0])) + frappe.throw(_('Row #{0}: Cannot allocate more than {1} against payment term {2}').format(idx, outstanding, key[0])) if allocated_amount and outstanding: frappe.db.sql(""" @@ -1053,12 +1053,6 @@ def get_outstanding_reference_documents(args): party_account_currency = get_account_currency(args.get("party_account")) company_currency = frappe.get_cached_value('Company', args.get("company"), "default_currency") - # Get negative outstanding sales /purchase invoices - negative_outstanding_invoices = [] - if args.get("party_type") not in ["Student", "Employee"] and not args.get("voucher_no"): - negative_outstanding_invoices = get_negative_outstanding_invoices(args.get("party_type"), args.get("party"), - args.get("party_account"), args.get("company"), party_account_currency, company_currency) - # Get positive outstanding sales /purchase invoices/ Fees condition = "" if args.get("voucher_type") and args.get("voucher_no"): @@ -1105,6 +1099,12 @@ def get_outstanding_reference_documents(args): orders_to_be_billed = get_orders_to_be_billed(args.get("posting_date"),args.get("party_type"), args.get("party"), args.get("company"), party_account_currency, company_currency, filters=args) + # Get negative outstanding sales /purchase invoices + negative_outstanding_invoices = [] + if args.get("party_type") not in ["Student", "Employee"] and not args.get("voucher_no"): + negative_outstanding_invoices = get_negative_outstanding_invoices(args.get("party_type"), args.get("party"), + args.get("party_account"), party_account_currency, company_currency, condition=condition) + data = negative_outstanding_invoices + outstanding_invoices + orders_to_be_billed if not data: @@ -1137,22 +1137,26 @@ def split_invoices_based_on_payment_terms(outstanding_invoices): 'invoice_amount': flt(d.invoice_amount), 'outstanding_amount': flt(d.outstanding_amount), 'payment_amount': payment_term.payment_amount, - 'payment_term': payment_term.payment_term, - 'allocated_amount': payment_term.outstanding + 'payment_term': payment_term.payment_term })) + outstanding_invoices_after_split = [] if invoice_ref_based_on_payment_terms: for idx, ref in invoice_ref_based_on_payment_terms.items(): - voucher_no = outstanding_invoices[idx]['voucher_no'] - voucher_type = outstanding_invoices[idx]['voucher_type'] + voucher_no = ref[0]['voucher_no'] + voucher_type = ref[0]['voucher_type'] - frappe.msgprint(_("Spliting {} {} into {} rows as per payment terms").format( + frappe.msgprint(_("Spliting {} {} into {} row(s) as per Payment Terms").format( voucher_type, voucher_no, len(ref)), alert=True) - outstanding_invoices.pop(idx - 1) - outstanding_invoices += invoice_ref_based_on_payment_terms[idx] + outstanding_invoices_after_split += invoice_ref_based_on_payment_terms[idx] - return outstanding_invoices + existing_row = list(filter(lambda x: x.get('voucher_no') == voucher_no, outstanding_invoices)) + index = outstanding_invoices.index(existing_row[0]) + outstanding_invoices.pop(index) + + outstanding_invoices_after_split += outstanding_invoices + return outstanding_invoices_after_split def get_orders_to_be_billed(posting_date, party_type, party, company, party_account_currency, company_currency, cost_center=None, filters=None): @@ -1219,7 +1223,7 @@ def get_orders_to_be_billed(posting_date, party_type, party, return order_list def get_negative_outstanding_invoices(party_type, party, party_account, - company, party_account_currency, company_currency, cost_center=None): + party_account_currency, company_currency, cost_center=None, condition=None): voucher_type = "Sales Invoice" if party_type == "Customer" else "Purchase Invoice" supplier_condition = "" if voucher_type == "Purchase Invoice": @@ -1241,19 +1245,21 @@ def get_negative_outstanding_invoices(party_type, party, party_account, `tab{voucher_type}` where {party_type} = %s and {party_account} = %s and docstatus = 1 and - company = %s and outstanding_amount < 0 + outstanding_amount < 0 {supplier_condition} + {condition} order by posting_date, name """.format(**{ "supplier_condition": supplier_condition, + "condition": condition, "rounded_total_field": rounded_total_field, "grand_total_field": grand_total_field, "voucher_type": voucher_type, "party_type": scrub(party_type), "party_account": "debit_to" if party_type == "Customer" else "credit_to", "cost_center": cost_center - }), (party, party_account, company), as_dict=True) + }), (party, party_account), as_dict=True) @frappe.whitelist() From 79be341ea468476226e52385b6ec4df20e17b397 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 5 Nov 2021 11:15:18 +0530 Subject: [PATCH 132/164] feat: replace newline in remarks (DATEV report) (#28152) (#28248) (cherry picked from commit 114028e473caff6e766347d377cf41c5572c3827) Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com> --- erpnext/regional/report/datev/datev.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/report/datev/datev.py b/erpnext/regional/report/datev/datev.py index dcabe368aa5..54c3526d2e3 100644 --- a/erpnext/regional/report/datev/datev.py +++ b/erpnext/regional/report/datev/datev.py @@ -351,7 +351,7 @@ def run_query(filters, extra_fields, extra_joins, extra_filters, as_dict=1): gl.posting_date as 'Belegdatum', gl.voucher_no as 'Belegfeld 1', - LEFT(gl.remarks, 60) as 'Buchungstext', + REPLACE(LEFT(gl.remarks, 60), '\n', ' ') as 'Buchungstext', gl.voucher_type as 'Beleginfo - Art 1', gl.voucher_no as 'Beleginfo - Inhalt 1', gl.against_voucher_type as 'Beleginfo - Art 2', From 9f6b0dbfdff9783768ceb1a0464a0d5f04d11fb9 Mon Sep 17 00:00:00 2001 From: ahmadpak Date: Sat, 30 Oct 2021 17:18:45 +0300 Subject: [PATCH 133/164] new (Print Format): KSA VAT Invoice (cherry picked from commit 2a5beec885a40c6dd210ad11280ac46bc1867feb) --- .../print_format/ksa_vat_invoice/__init__.py | 0 .../ksa_vat_invoice/ksa_vat_invoice.json | 32 +++++++++++++++++++ erpnext/regional/saudi_arabia/setup.py | 32 ++++++++++++++++--- 3 files changed, 59 insertions(+), 5 deletions(-) create mode 100644 erpnext/regional/print_format/ksa_vat_invoice/__init__.py create mode 100644 erpnext/regional/print_format/ksa_vat_invoice/ksa_vat_invoice.json diff --git a/erpnext/regional/print_format/ksa_vat_invoice/__init__.py b/erpnext/regional/print_format/ksa_vat_invoice/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/regional/print_format/ksa_vat_invoice/ksa_vat_invoice.json b/erpnext/regional/print_format/ksa_vat_invoice/ksa_vat_invoice.json new file mode 100644 index 00000000000..edb107adef4 --- /dev/null +++ b/erpnext/regional/print_format/ksa_vat_invoice/ksa_vat_invoice.json @@ -0,0 +1,32 @@ +{ + "absolute_value": 0, + "align_labels_right": 0, + "creation": "2021-10-29 22:46:26.039023", + "css": ".qr-code{\n float:right;\n}\n\n.invoice-heading {\n margin: 0;\n}\n\n.ksa-invoice-table {\n border: 1px solid #888a8e;\n border-collapse: collapse;\n width: 100%;\n margin: 20px 0;\n color: #888a8e;\n font-size: 16px;\n}\n\n.ksa-invoice-table.two-columns td:nth-child(2) {\n direction: rtl;\n}\n\n.ksa-invoice-table th {\n border: 1px solid #888a8e;\n max-width: 50%;\n background-color: #265e4a !important;\n color: #fff;\n padding: 8px;\n}\n\n.ksa-invoice-table td {\n padding: 5px;\n border: 1px solid #888a8e;\n max-width: 50%;\n}\n\n.ksa-invoice-table thead,\n.ksa-invoice-table tfoot {\n text-transform: uppercase;\n}\n\n.qr-rtl {\n direction: rtl;\n}\n\n.qr-flex{\n display: flex;\n justify-content: space-between;\n}", + "custom_format": 1, + "default_print_language": "en", + "disabled": 0, + "doc_type": "Sales Invoice", + "docstatus": 0, + "doctype": "Print Format", + "font_size": 14, + "html": "
\n
\n
\n

TAX INVOICE

\n

\u0641\u0627\u062a\u0648\u0631\u0629 \u0636\u0631\u064a\u0628\u064a\u0629

\n
\n \n \n
\n {% set company = frappe.get_doc(\"Company\", doc.company)%}\n {% if (doc.company_address) %}\n {% set supplier_address_doc = frappe.get_doc('Address', doc.company_address) %}\n {% endif %}\n \n {% if(doc.customer_address) %}\n {% set customer_address = frappe.get_doc('Address', doc.customer_address ) %}\n {% endif %}\n \n {% if(doc.shipping_address_name) %}\n {% set customer_shipping_address = frappe.get_doc('Address', doc.shipping_address_name ) %}\n {% endif %} \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n {% if(supplier_address_doc) %}\n \n \n \n \n \n \n \n \n \n \n \n \n {% endif %}\n \n {% if (company.tax_id) %}\n \n \n \n \n \n \n \n \n {% endif %}\n \n \n \n \n \n \n \n \n \n \n \n {% if(customer_address) %}\n \n \n \n \n {% endif %}\n \n {% if(customer_shipping_address) %}\n \n \n \n \n \n \n \n \n \n {% endif %}\n \n \n \n \n \n \n {% if(doc.po_no) %}\n \n \n \n \n {% endif %}\n \n \n \n \n \n \n
{{ company.name }}{{ company.company_name_in_arabic }}
Invoice#: {{doc.name}}\u0631\u0642\u0645 \u0627\u0644\u0641\u0627\u062a\u0648\u0631\u0629: {{doc.name}}
Invoice Date: {{doc.posting_date}}\u062a\u0627\u0631\u064a\u062e \u0627\u0644\u0641\u0627\u062a\u0648\u0631\u0629: {{doc.posting_date}}
Date of Supply:{{doc.posting_date}}\u062a\u0627\u0631\u064a\u062e \u0627\u0644\u062a\u0648\u0631\u064a\u062f: {{doc.posting_date}}
Supplier:\u0627\u0644\u0645\u0648\u0631\u062f:
{{ company.name }}{{ company.company_name_in_arabic }}
{{ supplier_address_doc.address_line1}} {{ supplier_address_doc.address_in_arabic}}
Phone: {{ supplier_address_doc.phone }}\u0647\u0627\u062a\u0641: {{ supplier_address_doc.phone }}
Email: {{ supplier_address_doc.email_id }}\u0628\u0631\u064a\u062f \u0627\u0644\u0643\u062a\u0631\u0648\u0646\u064a: {{ supplier_address_doc.email_id }}
Supplier Tax Identification Number:\u0631\u0642\u0645 \u0627\u0644\u062a\u0639\u0631\u064a\u0641 \u0627\u0644\u0636\u0631\u064a\u0628\u064a \u0644\u0644\u0645\u0648\u0631\u062f:
{{ company.tax_id }}{{ company.tax_id }}
CUSTOMER:\u0639\u0645\u064a\u0644:
{{ doc.customer }} {{ doc.customer_name_in_arabic }}
{{ customer_address.address_line1}} {{ customer_address.address_in_arabic}}
SHIPPING ADDRESS:\u0639\u0646\u0648\u0627\u0646 \u0627\u0644\u0634\u062d\u0646:
{{ customer_shipping_address.address_line1}} {{ customer_shipping_address.address_in_arabic}}
OTHER INFORMATION\u0645\u0639\u0644\u0648\u0645\u0627\u062a \u0623\u062e\u0631\u0649
Purchase Order Number: {{ doc.po_no }}\u0631\u0642\u0645 \u0623\u0645\u0631 \u0627\u0644\u0634\u0631\u0627\u0621: {{ doc.po_no }}
Payment Due Date: {{ doc.due_date}} \u062a\u0627\u0631\u064a\u062e \u0627\u0633\u062a\u062d\u0642\u0627\u0642 \u0627\u0644\u062f\u0641\u0639: {{ doc.due_date}}
\n\n \n {% set col = namespace(one = 2, two = 1) %}\n {% set length = doc.taxes | length %}\n {% set length = length / 2 | round %}\n {% set col.one = col.one + length %}\n {% set col.two = col.two + length %}\n \n {%- if(doc.taxes | length % 2 > 0 ) -%}\n {% set col.two = col.two + 1 %}\n {% endif %}\n \n \n {% set total = namespace(amount = 0) %}\n \n \n \n \n \n \n \n \n {% for row in doc.taxes %}\n \n {% endfor %}\n \n \n \n \n \n {%- for item in doc.items -%}\n {% set total.amount = item.amount %}\n \n \n \n \n \n {% for row in doc.taxes %}\n {% set data_object = json.loads(row.item_wise_tax_detail) %}\n \n {% endfor %}\n \n \n {%- endfor -%}\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
Nature of goods or services
\u0637\u0628\u064a\u0639\u0629 \u0627\u0644\u0633\u0644\u0639 \u0623\u0648 \u0627\u0644\u062e\u062f\u0645\u0627\u062a
\n Unit price
\n \u0633\u0639\u0631 \u0627\u0644\u0648\u062d\u062f\u0629\n
\n Quantity
\n \u0627\u0644\u0643\u0645\u064a\u0629\n
\n Taxable Amount
\n \u0627\u0644\u0645\u0628\u0644\u063a \u0627\u0644\u062e\u0627\u0636\u0639 \u0644\u0644\u0636\u0631\u064a\u0628\u0629\n
{{row.description}}\n Total
\n \u0627\u0644\u0645\u062c\u0645\u0648\u0639\n
{{ item.item_code }}{{ item.get_formatted(\"rate\") }}{{ item.qty }}{{ item.get_formatted(\"amount\") }}\n
\n {%- if(data_object[item.item_code][0])-%}\n {{ frappe.format(data_object[item.item_code][0], {'fieldtype': 'Percent'}) }}\n {%- endif -%}\n \n {%- if(data_object[item.item_code][1])-%}\n {{ frappe.format(data_object[item.item_code][1], {'fieldtype': 'Currency'}) }}\n {% set total.amount = total.amount + data_object[item.item_code][1] %}\n {%- endif -%}\n
\n
{{ frappe.format(total.amount, {'fieldtype': 'Currency'}) }}
\n {{ doc.get_formatted(\"total\") }}
\n {{ doc.get_formatted(\"total_taxes_and_charges\") }}\n
\n \u0627\u0644\u0625\u062c\u0645\u0627\u0644\u064a \u0628\u0627\u0633\u062a\u062b\u0646\u0627\u0621 \u0636\u0631\u064a\u0628\u0629 \u0627\u0644\u0642\u064a\u0645\u0629 \u0627\u0644\u0645\u0636\u0627\u0641\u0629\n
\n \u0625\u062c\u0645\u0627\u0644\u064a \u0636\u0631\u064a\u0628\u0629 \u0627\u0644\u0642\u064a\u0645\u0629 \u0627\u0644\u0645\u0636\u0627\u0641\u0629\n
\n Total (Excluding VAT)\n
\n Total VAT\n
\n {{ doc.get_formatted(\"total\") }}
\n {{ doc.get_formatted(\"total_taxes_and_charges\") }}\n
{{ doc.get_formatted(\"grand_total\") }}\n \u0625\u062c\u0645\u0627\u0644\u064a \u0627\u0644\u0645\u0628\u0644\u063a \u0627\u0644\u0645\u0633\u062a\u062d\u0642Total Amount Due{{ doc.get_formatted(\"grand_total\") }}
\n\n\t{%- if doc.terms -%}\n

\n {{doc.terms}}\n

\n\t{%- endif -%}\n
\n", + "idx": 0, + "line_breaks": 0, + "margin_bottom": 15.0, + "margin_left": 15.0, + "margin_right": 15.0, + "margin_top": 15.0, + "modified": "2021-10-30 17:12:49.496507", + "modified_by": "Administrator", + "module": "Regional", + "name": "KSA VAT Invoice", + "owner": "Administrator", + "page_number": "Hide", + "print_format_builder": 0, + "print_format_builder_beta": 0, + "print_format_type": "Jinja", + "raw_printing": 0, + "show_section_headings": 0, + "standard": "Yes" +} \ No newline at end of file diff --git a/erpnext/regional/saudi_arabia/setup.py b/erpnext/regional/saudi_arabia/setup.py index 6113f48d3f1..2e139e6eb86 100644 --- a/erpnext/regional/saudi_arabia/setup.py +++ b/erpnext/regional/saudi_arabia/setup.py @@ -13,7 +13,7 @@ def setup(company=None, patch=True): add_print_formats() add_permissions() create_ksa_vat_setting(company) - make_qrcode_field() + make_custom_fields() def add_permissions(): """Add Permissions for KSA VAT Setting.""" @@ -26,12 +26,34 @@ def add_permissions(): """Enable KSA VAT Report""" frappe.db.set_value('Report', 'KSA VAT', 'disabled', 0) -def make_qrcode_field(): - """Created QR code Image file""" - qr_code_field = dict( +def make_custom_fields(): + """Create Custom fields + - QR code Image file + - Company Name in Arabic + - Address in Arabic + """ + qr_code = dict( fieldname='qr_code', label='QR Code', fieldtype='Attach Image', read_only=1, no_copy=1, hidden=1) - create_custom_field('Sales Invoice', qr_code_field) + create_custom_field('Sales Invoice', qr_code) + + company_name_in_arabic = dict( + fieldname='company_name_in_arabic', + label='Company Name In Arabic', + fieldtype='Data', + insert_after='company_name' + ) + + create_custom_field('Company', company_name_in_arabic) + + address_in_arabic = dict( + fieldname='address_in_arabic', + label='Address in Arabic', + fieldtype='Data', + insert_after='address_line2' + ) + + create_custom_field('Address', address_in_arabic) From 0ba59049f9b34236c8f898232f09885433d4dd92 Mon Sep 17 00:00:00 2001 From: ahmadpak Date: Mon, 8 Nov 2021 09:29:33 +0300 Subject: [PATCH 134/164] update(Print Format): Sales Invoice - KSA VAT Invoice Customer Identification Number Added (cherry picked from commit 8df50cf1e0a7f58fbb9af94c2598e91d82f3faf5) --- .../print_format/ksa_vat_invoice/ksa_vat_invoice.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/regional/print_format/ksa_vat_invoice/ksa_vat_invoice.json b/erpnext/regional/print_format/ksa_vat_invoice/ksa_vat_invoice.json index edb107adef4..36d653616ba 100644 --- a/erpnext/regional/print_format/ksa_vat_invoice/ksa_vat_invoice.json +++ b/erpnext/regional/print_format/ksa_vat_invoice/ksa_vat_invoice.json @@ -10,14 +10,14 @@ "docstatus": 0, "doctype": "Print Format", "font_size": 14, - "html": "
\n
\n
\n

TAX INVOICE

\n

\u0641\u0627\u062a\u0648\u0631\u0629 \u0636\u0631\u064a\u0628\u064a\u0629

\n
\n \n \n
\n {% set company = frappe.get_doc(\"Company\", doc.company)%}\n {% if (doc.company_address) %}\n {% set supplier_address_doc = frappe.get_doc('Address', doc.company_address) %}\n {% endif %}\n \n {% if(doc.customer_address) %}\n {% set customer_address = frappe.get_doc('Address', doc.customer_address ) %}\n {% endif %}\n \n {% if(doc.shipping_address_name) %}\n {% set customer_shipping_address = frappe.get_doc('Address', doc.shipping_address_name ) %}\n {% endif %} \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n {% if(supplier_address_doc) %}\n \n \n \n \n \n \n \n \n \n \n \n \n {% endif %}\n \n {% if (company.tax_id) %}\n \n \n \n \n \n \n \n \n {% endif %}\n \n \n \n \n \n \n \n \n \n \n \n {% if(customer_address) %}\n \n \n \n \n {% endif %}\n \n {% if(customer_shipping_address) %}\n \n \n \n \n \n \n \n \n \n {% endif %}\n \n \n \n \n \n \n {% if(doc.po_no) %}\n \n \n \n \n {% endif %}\n \n \n \n \n \n \n
{{ company.name }}{{ company.company_name_in_arabic }}
Invoice#: {{doc.name}}\u0631\u0642\u0645 \u0627\u0644\u0641\u0627\u062a\u0648\u0631\u0629: {{doc.name}}
Invoice Date: {{doc.posting_date}}\u062a\u0627\u0631\u064a\u062e \u0627\u0644\u0641\u0627\u062a\u0648\u0631\u0629: {{doc.posting_date}}
Date of Supply:{{doc.posting_date}}\u062a\u0627\u0631\u064a\u062e \u0627\u0644\u062a\u0648\u0631\u064a\u062f: {{doc.posting_date}}
Supplier:\u0627\u0644\u0645\u0648\u0631\u062f:
{{ company.name }}{{ company.company_name_in_arabic }}
{{ supplier_address_doc.address_line1}} {{ supplier_address_doc.address_in_arabic}}
Phone: {{ supplier_address_doc.phone }}\u0647\u0627\u062a\u0641: {{ supplier_address_doc.phone }}
Email: {{ supplier_address_doc.email_id }}\u0628\u0631\u064a\u062f \u0627\u0644\u0643\u062a\u0631\u0648\u0646\u064a: {{ supplier_address_doc.email_id }}
Supplier Tax Identification Number:\u0631\u0642\u0645 \u0627\u0644\u062a\u0639\u0631\u064a\u0641 \u0627\u0644\u0636\u0631\u064a\u0628\u064a \u0644\u0644\u0645\u0648\u0631\u062f:
{{ company.tax_id }}{{ company.tax_id }}
CUSTOMER:\u0639\u0645\u064a\u0644:
{{ doc.customer }} {{ doc.customer_name_in_arabic }}
{{ customer_address.address_line1}} {{ customer_address.address_in_arabic}}
SHIPPING ADDRESS:\u0639\u0646\u0648\u0627\u0646 \u0627\u0644\u0634\u062d\u0646:
{{ customer_shipping_address.address_line1}} {{ customer_shipping_address.address_in_arabic}}
OTHER INFORMATION\u0645\u0639\u0644\u0648\u0645\u0627\u062a \u0623\u062e\u0631\u0649
Purchase Order Number: {{ doc.po_no }}\u0631\u0642\u0645 \u0623\u0645\u0631 \u0627\u0644\u0634\u0631\u0627\u0621: {{ doc.po_no }}
Payment Due Date: {{ doc.due_date}} \u062a\u0627\u0631\u064a\u062e \u0627\u0633\u062a\u062d\u0642\u0627\u0642 \u0627\u0644\u062f\u0641\u0639: {{ doc.due_date}}
\n\n \n {% set col = namespace(one = 2, two = 1) %}\n {% set length = doc.taxes | length %}\n {% set length = length / 2 | round %}\n {% set col.one = col.one + length %}\n {% set col.two = col.two + length %}\n \n {%- if(doc.taxes | length % 2 > 0 ) -%}\n {% set col.two = col.two + 1 %}\n {% endif %}\n \n \n {% set total = namespace(amount = 0) %}\n \n \n \n \n \n \n \n \n {% for row in doc.taxes %}\n \n {% endfor %}\n \n \n \n \n \n {%- for item in doc.items -%}\n {% set total.amount = item.amount %}\n \n \n \n \n \n {% for row in doc.taxes %}\n {% set data_object = json.loads(row.item_wise_tax_detail) %}\n \n {% endfor %}\n \n \n {%- endfor -%}\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
Nature of goods or services
\u0637\u0628\u064a\u0639\u0629 \u0627\u0644\u0633\u0644\u0639 \u0623\u0648 \u0627\u0644\u062e\u062f\u0645\u0627\u062a
\n Unit price
\n \u0633\u0639\u0631 \u0627\u0644\u0648\u062d\u062f\u0629\n
\n Quantity
\n \u0627\u0644\u0643\u0645\u064a\u0629\n
\n Taxable Amount
\n \u0627\u0644\u0645\u0628\u0644\u063a \u0627\u0644\u062e\u0627\u0636\u0639 \u0644\u0644\u0636\u0631\u064a\u0628\u0629\n
{{row.description}}\n Total
\n \u0627\u0644\u0645\u062c\u0645\u0648\u0639\n
{{ item.item_code }}{{ item.get_formatted(\"rate\") }}{{ item.qty }}{{ item.get_formatted(\"amount\") }}\n
\n {%- if(data_object[item.item_code][0])-%}\n {{ frappe.format(data_object[item.item_code][0], {'fieldtype': 'Percent'}) }}\n {%- endif -%}\n \n {%- if(data_object[item.item_code][1])-%}\n {{ frappe.format(data_object[item.item_code][1], {'fieldtype': 'Currency'}) }}\n {% set total.amount = total.amount + data_object[item.item_code][1] %}\n {%- endif -%}\n
\n
{{ frappe.format(total.amount, {'fieldtype': 'Currency'}) }}
\n {{ doc.get_formatted(\"total\") }}
\n {{ doc.get_formatted(\"total_taxes_and_charges\") }}\n
\n \u0627\u0644\u0625\u062c\u0645\u0627\u0644\u064a \u0628\u0627\u0633\u062a\u062b\u0646\u0627\u0621 \u0636\u0631\u064a\u0628\u0629 \u0627\u0644\u0642\u064a\u0645\u0629 \u0627\u0644\u0645\u0636\u0627\u0641\u0629\n
\n \u0625\u062c\u0645\u0627\u0644\u064a \u0636\u0631\u064a\u0628\u0629 \u0627\u0644\u0642\u064a\u0645\u0629 \u0627\u0644\u0645\u0636\u0627\u0641\u0629\n
\n Total (Excluding VAT)\n
\n Total VAT\n
\n {{ doc.get_formatted(\"total\") }}
\n {{ doc.get_formatted(\"total_taxes_and_charges\") }}\n
{{ doc.get_formatted(\"grand_total\") }}\n \u0625\u062c\u0645\u0627\u0644\u064a \u0627\u0644\u0645\u0628\u0644\u063a \u0627\u0644\u0645\u0633\u062a\u062d\u0642Total Amount Due{{ doc.get_formatted(\"grand_total\") }}
\n\n\t{%- if doc.terms -%}\n

\n {{doc.terms}}\n

\n\t{%- endif -%}\n
\n", + "html": "
\n
\n
\n

TAX INVOICE

\n

\u0641\u0627\u062a\u0648\u0631\u0629 \u0636\u0631\u064a\u0628\u064a\u0629

\n
\n \n \n
\n {% set company = frappe.get_doc(\"Company\", doc.company)%}\n {% if (doc.company_address) %}\n {% set supplier_address_doc = frappe.get_doc('Address', doc.company_address) %}\n {% endif %}\n \n {% if(doc.customer_address) %}\n {% set customer_address = frappe.get_doc('Address', doc.customer_address ) %}\n {% endif %}\n \n {% if(doc.shipping_address_name) %}\n {% set customer_shipping_address = frappe.get_doc('Address', doc.shipping_address_name ) %}\n {% endif %} \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\t\t{% if (company.tax_id) %}\n \n \n \n \n \n \n \n \n {% endif %}\n \n \n \n \n \n \n {% if(supplier_address_doc) %}\n \n \n \n \n \n \n \n \n \n \n \n \n {% endif %}\n \n \n \n \n \n \n\t\t{% set customer_tax_id = frappe.db.get_value('Customer', doc.customer, 'tax_id') %}\n\t\t{% if customer_tax_id %}\n \n \n \n \n \n \n \n \n {% endif %}\n \n \n \n \n \n {% if(customer_address) %}\n \n \n \n \n {% endif %}\n \n {% if(customer_shipping_address) %}\n \n \n \n \n \n \n \n \n \n {% endif %}\n \n\t\t{% if(doc.po_no) %}\n \n \n \n \n \n \n \n \n \n {% endif %}\n \n \n \n \n \n \n
{{ company.name }}{{ company.company_name_in_arabic }}
Invoice#: {{doc.name}}\u0631\u0642\u0645 \u0627\u0644\u0641\u0627\u062a\u0648\u0631\u0629: {{doc.name}}
Invoice Date: {{doc.posting_date}}\u062a\u0627\u0631\u064a\u062e \u0627\u0644\u0641\u0627\u062a\u0648\u0631\u0629: {{doc.posting_date}}
Date of Supply:{{doc.posting_date}}\u062a\u0627\u0631\u064a\u062e \u0627\u0644\u062a\u0648\u0631\u064a\u062f: {{doc.posting_date}}
Supplier:\u0627\u0644\u0645\u0648\u0631\u062f:
Supplier Tax Identification Number:\u0631\u0642\u0645 \u0627\u0644\u062a\u0639\u0631\u064a\u0641 \u0627\u0644\u0636\u0631\u064a\u0628\u064a \u0644\u0644\u0645\u0648\u0631\u062f:
{{ company.tax_id }}{{ company.tax_id }}
{{ company.name }}{{ company.company_name_in_arabic }}
{{ supplier_address_doc.address_line1}} {{ supplier_address_doc.address_in_arabic}}
Phone: {{ supplier_address_doc.phone }}\u0647\u0627\u062a\u0641: {{ supplier_address_doc.phone }}
Email: {{ supplier_address_doc.email_id }}\u0628\u0631\u064a\u062f \u0627\u0644\u0643\u062a\u0631\u0648\u0646\u064a: {{ supplier_address_doc.email_id }}
CUSTOMER:\u0639\u0645\u064a\u0644:
Customer Tax Identification Number:\u0631\u0642\u0645 \u0627\u0644\u062a\u0639\u0631\u064a\u0641 \u0627\u0644\u0636\u0631\u064a\u0628\u064a \u0644\u0644\u0639\u0645\u064a\u0644:
{{ customer_tax_id }}{{ customer_tax_id }}
{{ doc.customer }} {{ doc.customer_name_in_arabic }}
{{ customer_address.address_line1}} {{ customer_address.address_in_arabic}}
SHIPPING ADDRESS:\u0639\u0646\u0648\u0627\u0646 \u0627\u0644\u0634\u062d\u0646:
{{ customer_shipping_address.address_line1}} {{ customer_shipping_address.address_in_arabic}}
OTHER INFORMATION\u0645\u0639\u0644\u0648\u0645\u0627\u062a \u0623\u062e\u0631\u0649
Purchase Order Number: {{ doc.po_no }}\u0631\u0642\u0645 \u0623\u0645\u0631 \u0627\u0644\u0634\u0631\u0627\u0621: {{ doc.po_no }}
Payment Due Date: {{ doc.due_date}} \u062a\u0627\u0631\u064a\u062e \u0627\u0633\u062a\u062d\u0642\u0627\u0642 \u0627\u0644\u062f\u0641\u0639: {{ doc.due_date}}
\n\n \n {% set col = namespace(one = 2, two = 1) %}\n {% set length = doc.taxes | length %}\n {% set length = length / 2 | round %}\n {% set col.one = col.one + length %}\n {% set col.two = col.two + length %}\n \n {%- if(doc.taxes | length % 2 > 0 ) -%}\n {% set col.two = col.two + 1 %}\n {% endif %}\n \n \n {% set total = namespace(amount = 0) %}\n \n \n \n \n \n \n \n \n {% for row in doc.taxes %}\n \n {% endfor %}\n \n \n \n \n \n {%- for item in doc.items -%}\n {% set total.amount = item.amount %}\n \n \n \n \n \n {% for row in doc.taxes %}\n {% set data_object = json.loads(row.item_wise_tax_detail) %}\n \n {% endfor %}\n \n \n {%- endfor -%}\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
Nature of goods or services
\u0637\u0628\u064a\u0639\u0629 \u0627\u0644\u0633\u0644\u0639 \u0623\u0648 \u0627\u0644\u062e\u062f\u0645\u0627\u062a
\n Unit price
\n \u0633\u0639\u0631 \u0627\u0644\u0648\u062d\u062f\u0629\n
\n Quantity
\n \u0627\u0644\u0643\u0645\u064a\u0629\n
\n Taxable Amount
\n \u0627\u0644\u0645\u0628\u0644\u063a \u0627\u0644\u062e\u0627\u0636\u0639 \u0644\u0644\u0636\u0631\u064a\u0628\u0629\n
{{row.description}}\n Total
\n \u0627\u0644\u0645\u062c\u0645\u0648\u0639\n
{{ item.item_code }}{{ item.get_formatted(\"rate\") }}{{ item.qty }}{{ item.get_formatted(\"amount\") }}\n
\n {%- if(data_object[item.item_code][0])-%}\n {{ frappe.format(data_object[item.item_code][0], {'fieldtype': 'Percent'}) }}\n {%- endif -%}\n \n {%- if(data_object[item.item_code][1])-%}\n {{ frappe.format(data_object[item.item_code][1], {'fieldtype': 'Currency'}) }}\n {% set total.amount = total.amount + data_object[item.item_code][1] %}\n {%- endif -%}\n
\n
{{ frappe.format(total.amount, {'fieldtype': 'Currency'}) }}
\n {{ doc.get_formatted(\"total\") }}
\n {{ doc.get_formatted(\"total_taxes_and_charges\") }}\n
\n \u0627\u0644\u0625\u062c\u0645\u0627\u0644\u064a \u0628\u0627\u0633\u062a\u062b\u0646\u0627\u0621 \u0636\u0631\u064a\u0628\u0629 \u0627\u0644\u0642\u064a\u0645\u0629 \u0627\u0644\u0645\u0636\u0627\u0641\u0629\n
\n \u0625\u062c\u0645\u0627\u0644\u064a \u0636\u0631\u064a\u0628\u0629 \u0627\u0644\u0642\u064a\u0645\u0629 \u0627\u0644\u0645\u0636\u0627\u0641\u0629\n
\n Total (Excluding VAT)\n
\n Total VAT\n
\n {{ doc.get_formatted(\"total\") }}
\n {{ doc.get_formatted(\"total_taxes_and_charges\") }}\n
{{ doc.get_formatted(\"grand_total\") }}\n \u0625\u062c\u0645\u0627\u0644\u064a \u0627\u0644\u0645\u0628\u0644\u063a \u0627\u0644\u0645\u0633\u062a\u062d\u0642Total Amount Due{{ doc.get_formatted(\"grand_total\") }}
\n\n\t{%- if doc.terms -%}\n

\n {{doc.terms}}\n

\n\t{%- endif -%}\n
\n", "idx": 0, "line_breaks": 0, "margin_bottom": 15.0, "margin_left": 15.0, "margin_right": 15.0, "margin_top": 15.0, - "modified": "2021-10-30 17:12:49.496507", + "modified": "2021-11-08 09:19:18.660806", "modified_by": "Administrator", "module": "Regional", "name": "KSA VAT Invoice", From aa6caa00bf02584f5196694ea0ab0af9f6793b3c Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 8 Nov 2021 17:14:03 +0530 Subject: [PATCH 135/164] fix: KSA VAT setup issues (cherry picked from commit c81d4734c48656b7ca9766a01ce33ed4d2ac00ed) --- erpnext/regional/report/ksa_vat/ksa_vat.js | 1 - erpnext/regional/report/ksa_vat/ksa_vat.py | 4 +- erpnext/regional/saudi_arabia/setup.py | 55 ++++++++++--------- .../wizard/data/ksa_vat_settings.json | 4 +- .../operations/setup_ksa_vat_setting.py | 3 - .../setup_wizard/data/country_wise_tax.json | 4 ++ 6 files changed, 38 insertions(+), 33 deletions(-) diff --git a/erpnext/regional/report/ksa_vat/ksa_vat.js b/erpnext/regional/report/ksa_vat/ksa_vat.js index d46d260ac1e..59e72c3e638 100644 --- a/erpnext/regional/report/ksa_vat/ksa_vat.js +++ b/erpnext/regional/report/ksa_vat/ksa_vat.js @@ -49,7 +49,6 @@ frappe.query_reports["KSA VAT"] = { value = $(`${value}`); var $value = $(value).css("font-weight", "bold"); value = $value.wrap("

").parent().html(); - console.log($value) return value } }else{ diff --git a/erpnext/regional/report/ksa_vat/ksa_vat.py b/erpnext/regional/report/ksa_vat/ksa_vat.py index a42ebc9f7e5..9a0e7b1cd20 100644 --- a/erpnext/regional/report/ksa_vat/ksa_vat.py +++ b/erpnext/regional/report/ksa_vat/ksa_vat.py @@ -119,14 +119,14 @@ def get_tax_data_for_each_vat_setting(vat_setting, filters, doctype): total_taxable_adjustment_amount = 0 total_tax = 0 # Fetch All Invoices - invoices = frappe.get_list(doctype, + invoices = frappe.get_all(doctype, filters ={ 'docstatus': 1, 'posting_date': ['between', [from_date, to_date]] }, fields =['name', 'is_return']) for invoice in invoices: - invoice_items = frappe.get_list(f'{doctype} Item', + invoice_items = frappe.get_all(f'{doctype} Item', filters ={ 'docstatus': 1, 'parent': invoice.name, diff --git a/erpnext/regional/saudi_arabia/setup.py b/erpnext/regional/saudi_arabia/setup.py index 2e139e6eb86..41142bd59fb 100644 --- a/erpnext/regional/saudi_arabia/setup.py +++ b/erpnext/regional/saudi_arabia/setup.py @@ -6,13 +6,12 @@ import frappe from frappe.permissions import add_permission, update_permission_property from erpnext.regional.united_arab_emirates.setup import make_custom_fields as uae_custom_fields, add_print_formats from erpnext.regional.saudi_arabia.wizard.operations.setup_ksa_vat_setting import create_ksa_vat_setting -from frappe.custom.doctype.custom_field.custom_field import create_custom_field +from frappe.custom.doctype.custom_field.custom_field import create_custom_fields def setup(company=None, patch=True): uae_custom_fields() add_print_formats() add_permissions() - create_ksa_vat_setting(company) make_custom_fields() def add_permissions(): @@ -32,28 +31,34 @@ def make_custom_fields(): - Company Name in Arabic - Address in Arabic """ - qr_code = dict( - fieldname='qr_code', - label='QR Code', - fieldtype='Attach Image', - read_only=1, no_copy=1, hidden=1) + custom_fields = { + 'Sales Invoice': [ + dict( + fieldname='qr_code', + label='QR Code', + fieldtype='Attach Image', + read_only=1, no_copy=1, hidden=1 + ) + ], + 'Address': [ + dict( + fieldname='address_in_arabic', + label='Address in Arabic', + fieldtype='Data', + insert_after='address_line2' + ) + ], + 'Company': [ + dict( + fieldname='company_name_in_arabic', + label='Company Name In Arabic', + fieldtype='Data', + insert_after='company_name' + ) + ] + } - create_custom_field('Sales Invoice', qr_code) + create_custom_fields(custom_fields, update=True) - company_name_in_arabic = dict( - fieldname='company_name_in_arabic', - label='Company Name In Arabic', - fieldtype='Data', - insert_after='company_name' - ) - - create_custom_field('Company', company_name_in_arabic) - - address_in_arabic = dict( - fieldname='address_in_arabic', - label='Address in Arabic', - fieldtype='Data', - insert_after='address_line2' - ) - - create_custom_field('Address', address_in_arabic) +def update_regional_tax_settings(country, company): + create_ksa_vat_setting(company) diff --git a/erpnext/regional/saudi_arabia/wizard/data/ksa_vat_settings.json b/erpnext/regional/saudi_arabia/wizard/data/ksa_vat_settings.json index 709d65be041..60951a9ceca 100644 --- a/erpnext/regional/saudi_arabia/wizard/data/ksa_vat_settings.json +++ b/erpnext/regional/saudi_arabia/wizard/data/ksa_vat_settings.json @@ -15,7 +15,7 @@ { "title": "Exempted sales", "item_tax_template": "KSA VAT Exempted", - "account": "VAT Zero" + "account": "VAT Exempted" } ] }, @@ -40,7 +40,7 @@ { "title": "Exempted purchases", "item_tax_template": "KSA VAT Exempted", - "account": "VAT Zero" + "account": "VAT Exempted" } ] } diff --git a/erpnext/regional/saudi_arabia/wizard/operations/setup_ksa_vat_setting.py b/erpnext/regional/saudi_arabia/wizard/operations/setup_ksa_vat_setting.py index 3c89edd37ed..97300dc3782 100644 --- a/erpnext/regional/saudi_arabia/wizard/operations/setup_ksa_vat_setting.py +++ b/erpnext/regional/saudi_arabia/wizard/operations/setup_ksa_vat_setting.py @@ -3,14 +3,11 @@ import os import frappe -from erpnext.setup.setup_wizard.operations.taxes_setup import setup_taxes_and_charges - def create_ksa_vat_setting(company): """On creation of first company. Creates KSA VAT Setting""" company = frappe.get_doc('Company', company) - setup_taxes_and_charges(company.name, company.country) file_path = os.path.join(os.path.dirname(__file__), '..', 'data', 'ksa_vat_settings.json') with open(file_path, 'r') as json_file: diff --git a/erpnext/setup/setup_wizard/data/country_wise_tax.json b/erpnext/setup/setup_wizard/data/country_wise_tax.json index b7e895db363..08fef32d849 100644 --- a/erpnext/setup/setup_wizard/data/country_wise_tax.json +++ b/erpnext/setup/setup_wizard/data/country_wise_tax.json @@ -2120,6 +2120,10 @@ "account_name": "VAT 15%", "tax_rate": 15.00 }, + "KSA VAT 5%": { + "account_name": "VAT 5%", + "tax_rate": 5.00 + }, "KSA VAT Zero": { "account_name": "VAT Zero", "tax_rate": 0.00 From f5251b5a86fd5aadc9396e409385fe261935ed49 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 8 Nov 2021 17:46:24 +0530 Subject: [PATCH 136/164] fix: Add patch to make custom fields (cherry picked from commit 508832e90a3863e0a2030b999a6772d291afd84b) # Conflicts: # erpnext/patches.txt --- erpnext/patches.txt | 6 ++++++ .../patches/v13_0/create_ksa_vat_custom_fields.py | 12 ++++++++++++ 2 files changed, 18 insertions(+) create mode 100644 erpnext/patches/v13_0/create_ksa_vat_custom_fields.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 2ad2fea6847..f65374abc35 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -331,3 +331,9 @@ erpnext.patches.v13_0.requeue_failed_reposts erpnext.patches.v13_0.fetch_thumbnail_in_website_items erpnext.patches.v12_0.update_production_plan_status erpnext.patches.v13_0.update_category_in_ltds_certificate +<<<<<<< HEAD +======= +erpnext.patches.v13_0.create_pan_field_for_india #2 +erpnext.patches.v14_0.delete_hub_doctypes +erpnext.patches.v13_0.create_ksa_vat_custom_fields +>>>>>>> 508832e90a (fix: Add patch to make custom fields) diff --git a/erpnext/patches/v13_0/create_ksa_vat_custom_fields.py b/erpnext/patches/v13_0/create_ksa_vat_custom_fields.py new file mode 100644 index 00000000000..f33b4b3ea0d --- /dev/null +++ b/erpnext/patches/v13_0/create_ksa_vat_custom_fields.py @@ -0,0 +1,12 @@ +import frappe + +from erpnext.regional.saudi_arabia.setup import make_custom_fields + + +def execute(): + company = frappe.get_all('Company', filters = {'country': 'Saudi Arabia'}) + if not company: + return + + make_custom_fields() + From 2dc12505b26e5a780b85cced95a8e511cdd08a06 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Mon, 8 Nov 2021 18:16:46 +0530 Subject: [PATCH 137/164] fix: Resolve conflicts --- erpnext/patches.txt | 5 ----- 1 file changed, 5 deletions(-) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index f65374abc35..3f12fcdf2aa 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -331,9 +331,4 @@ erpnext.patches.v13_0.requeue_failed_reposts erpnext.patches.v13_0.fetch_thumbnail_in_website_items erpnext.patches.v12_0.update_production_plan_status erpnext.patches.v13_0.update_category_in_ltds_certificate -<<<<<<< HEAD -======= -erpnext.patches.v13_0.create_pan_field_for_india #2 -erpnext.patches.v14_0.delete_hub_doctypes erpnext.patches.v13_0.create_ksa_vat_custom_fields ->>>>>>> 508832e90a (fix: Add patch to make custom fields) From 3d1b833f355ebf48ea9987f49471021098cd8322 Mon Sep 17 00:00:00 2001 From: Subin Tom Date: Mon, 8 Nov 2021 09:49:11 +0530 Subject: [PATCH 138/164] fix: Fixed customer address variable, sales invoice item field currency issue (cherry picked from commit 904010ab645fdb16f09072c062269945f7747712) --- .../doctype/taxjar_settings/taxjar_settings.py | 4 ++-- erpnext/erpnext_integrations/taxjar_integration.py | 2 +- erpnext/patches/v13_0/custom_fields_for_taxjar_integration.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.py b/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.py index f430a9e9bae..edd4eacaf0c 100644 --- a/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.py +++ b/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.py @@ -82,9 +82,9 @@ def make_custom_fields(update=True): dict(fieldname='product_tax_category', fieldtype='Link', insert_after='description', options='Product Tax Category', label='Product Tax Category', fetch_from='item_code.product_tax_category'), dict(fieldname='tax_collectable', fieldtype='Currency', insert_after='net_amount', - label='Tax Collectable', read_only=1), + label='Tax Collectable', read_only=1, options='currency'), dict(fieldname='taxable_amount', fieldtype='Currency', insert_after='tax_collectable', - label='Taxable Amount', read_only=1) + label='Taxable Amount', read_only=1, options='currency') ], 'Item': [ dict(fieldname='product_tax_category', fieldtype='Link', insert_after='item_group', options='Product Tax Category', diff --git a/erpnext/erpnext_integrations/taxjar_integration.py b/erpnext/erpnext_integrations/taxjar_integration.py index 2a7243c2430..39aac0c4073 100644 --- a/erpnext/erpnext_integrations/taxjar_integration.py +++ b/erpnext/erpnext_integrations/taxjar_integration.py @@ -262,7 +262,7 @@ def get_shipping_address_details(doc): if doc.shipping_address_name: shipping_address = frappe.get_doc("Address", doc.shipping_address_name) elif doc.customer_address: - shipping_address = frappe.get_doc("Address", doc.customer_address_name) + shipping_address = frappe.get_doc("Address", doc.customer_address) else: shipping_address = get_company_address_details(doc) diff --git a/erpnext/patches/v13_0/custom_fields_for_taxjar_integration.py b/erpnext/patches/v13_0/custom_fields_for_taxjar_integration.py index e136d64bb56..fdec87c7cc8 100644 --- a/erpnext/patches/v13_0/custom_fields_for_taxjar_integration.py +++ b/erpnext/patches/v13_0/custom_fields_for_taxjar_integration.py @@ -23,9 +23,9 @@ def execute(): dict(fieldname='product_tax_category', fieldtype='Link', insert_after='description', options='Product Tax Category', label='Product Tax Category', fetch_from='item_code.product_tax_category'), dict(fieldname='tax_collectable', fieldtype='Currency', insert_after='net_amount', - label='Tax Collectable', read_only=1), + label='Tax Collectable', read_only=1, options='currency'), dict(fieldname='taxable_amount', fieldtype='Currency', insert_after='tax_collectable', - label='Taxable Amount', read_only=1) + label='Taxable Amount', read_only=1, options='currency') ], 'Item': [ dict(fieldname='product_tax_category', fieldtype='Link', insert_after='item_group', options='Product Tax Category', From abe6ef4cd46eaec01a19dff4aa7b7afac2cbd9f2 Mon Sep 17 00:00:00 2001 From: Subin Tom Date: Mon, 8 Nov 2021 15:16:20 +0530 Subject: [PATCH 139/164] fix: Added company field, filtered account heads (cherry picked from commit 902c03cd375c7b1b2f35cff4dfbc5f1ff3e4cd56) --- .../taxjar_settings/taxjar_settings.js | 17 ++++++++++++++ .../taxjar_settings/taxjar_settings.json | 22 ++++++++++++++----- .../taxjar_integration.py | 6 +++++ .../custom_fields_for_taxjar_integration.py | 4 ++++ 4 files changed, 44 insertions(+), 5 deletions(-) diff --git a/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.js b/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.js index d49598932fe..a362ba1f0b4 100644 --- a/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.js +++ b/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.js @@ -7,6 +7,23 @@ frappe.ui.form.on('TaxJar Settings', { frm.toggle_reqd("sandbox_api_key", frm.doc.is_sandbox); }, + on_load: (frm) => { + frm.set_query('shipping_account_head', function() { + return { + filters: { + 'company': frm.doc.current_company + } + }; + }); + frm.set_query('tax_account_head', function() { + return { + filters: { + 'company': frm.doc.current_company + } + }; + }); + }, + refresh: (frm) => { frm.add_custom_button(__('Update Nexus List'), function() { frm.call({ diff --git a/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.json b/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.json index 2d17f2ed832..1fda8929e90 100644 --- a/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.json +++ b/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.json @@ -14,6 +14,8 @@ "cb_keys", "sandbox_api_key", "configuration", + "current_company", + "column_break_10", "tax_account_head", "configuration_cb", "shipping_account_head", @@ -67,10 +69,6 @@ "fieldtype": "Password", "label": "Sandbox API Key" }, - { - "fieldname": "configuration_cb", - "fieldtype": "Column Break" - }, { "default": "0", "depends_on": "taxjar_calculate_tax", @@ -104,11 +102,25 @@ "label": "Nexus", "options": "TaxJar Nexus", "read_only": 1 + }, + { + "fieldname": "current_company", + "fieldtype": "Link", + "label": "Company", + "options": "Company" + }, + { + "fieldname": "configuration_cb", + "fieldtype": "Column Break" + }, + { + "fieldname": "column_break_10", + "fieldtype": "Column Break" } ], "issingle": 1, "links": [], - "modified": "2021-10-06 10:59:13.475442", + "modified": "2021-11-08 14:40:15.684224", "modified_by": "Administrator", "module": "ERPNext Integrations", "name": "TaxJar Settings", diff --git a/erpnext/erpnext_integrations/taxjar_integration.py b/erpnext/erpnext_integrations/taxjar_integration.py index 39aac0c4073..794b281c55a 100644 --- a/erpnext/erpnext_integrations/taxjar_integration.py +++ b/erpnext/erpnext_integrations/taxjar_integration.py @@ -19,6 +19,8 @@ SUPPORTED_STATE_CODES = ['AL', 'AK', 'AZ', 'AR', 'CA', 'CO', 'CT', 'DE', 'DC', ' 'IN', 'IA', 'KS', 'KY', 'LA', 'ME', 'MD', 'MA', 'MI', 'MN', 'MS', 'MO', 'MT', 'NE', 'NV', 'NH', 'NJ', 'NM', 'NY', 'NC', 'ND', 'OH', 'OK', 'OR', 'PA', 'RI', 'SC', 'SD', 'TN', 'TX', 'UT', 'VT', 'VA', 'WA', 'WV', 'WI', 'WY'] +USA_COMPANIES = frappe.get_all('Company', filters = {'country': 'United States'}, fields=['name'], pluck='name') + def get_client(): @@ -158,6 +160,10 @@ def set_sales_tax(doc, method): if not TAXJAR_CALCULATE_TAX: return + taxjar_settings_company = frappe.db.get_single_value('TaxJar Settings','current_company') + if taxjar_settings_company not in USA_COMPANIES: + return + if not doc.items: return diff --git a/erpnext/patches/v13_0/custom_fields_for_taxjar_integration.py b/erpnext/patches/v13_0/custom_fields_for_taxjar_integration.py index fdec87c7cc8..3ef36873aa1 100644 --- a/erpnext/patches/v13_0/custom_fields_for_taxjar_integration.py +++ b/erpnext/patches/v13_0/custom_fields_for_taxjar_integration.py @@ -30,6 +30,10 @@ def execute(): 'Item': [ dict(fieldname='product_tax_category', fieldtype='Link', insert_after='item_group', options='Product Tax Category', label='Product Tax Category') + ], + 'TaxJar Settings': [ + dict(fieldname='current_company', fieldtype='Link', insert_after='configuration', options='Company', + label='Company') ] } create_custom_fields(custom_fields, update=True) From 1dc7cb8ad196a547b11c01bf1c664cb12e7676cb Mon Sep 17 00:00:00 2001 From: Subin Tom Date: Mon, 8 Nov 2021 17:59:03 +0530 Subject: [PATCH 140/164] fix: fixed company field, updated patch (cherry picked from commit 7f2d304f32fdf580a354c1dbba58189cdd4f35f2) --- .../doctype/taxjar_settings/taxjar_settings.js | 4 ++-- .../doctype/taxjar_settings/taxjar_settings.json | 9 +-------- erpnext/erpnext_integrations/taxjar_integration.py | 6 ++---- erpnext/patches.txt | 2 +- .../v13_0/custom_fields_for_taxjar_integration.py | 2 +- 5 files changed, 7 insertions(+), 16 deletions(-) diff --git a/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.js b/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.js index a362ba1f0b4..2925db82e33 100644 --- a/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.js +++ b/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.js @@ -11,14 +11,14 @@ frappe.ui.form.on('TaxJar Settings', { frm.set_query('shipping_account_head', function() { return { filters: { - 'company': frm.doc.current_company + 'company': frm.doc.company } }; }); frm.set_query('tax_account_head', function() { return { filters: { - 'company': frm.doc.current_company + 'company': frm.doc.company } }; }); diff --git a/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.json b/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.json index 1fda8929e90..d9ae5b601cc 100644 --- a/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.json +++ b/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.json @@ -14,7 +14,6 @@ "cb_keys", "sandbox_api_key", "configuration", - "current_company", "column_break_10", "tax_account_head", "configuration_cb", @@ -103,12 +102,6 @@ "options": "TaxJar Nexus", "read_only": 1 }, - { - "fieldname": "current_company", - "fieldtype": "Link", - "label": "Company", - "options": "Company" - }, { "fieldname": "configuration_cb", "fieldtype": "Column Break" @@ -120,7 +113,7 @@ ], "issingle": 1, "links": [], - "modified": "2021-11-08 14:40:15.684224", + "modified": "2021-11-08 17:57:17.323275", "modified_by": "Administrator", "module": "ERPNext Integrations", "name": "TaxJar Settings", diff --git a/erpnext/erpnext_integrations/taxjar_integration.py b/erpnext/erpnext_integrations/taxjar_integration.py index 794b281c55a..1adc1ddb140 100644 --- a/erpnext/erpnext_integrations/taxjar_integration.py +++ b/erpnext/erpnext_integrations/taxjar_integration.py @@ -6,7 +6,7 @@ from frappe import _ from frappe.contacts.doctype.address.address import get_company_address from frappe.utils import cint, flt -from erpnext import get_default_company +from erpnext import get_default_company, get_region TAX_ACCOUNT_HEAD = frappe.db.get_single_value("TaxJar Settings", "tax_account_head") SHIP_ACCOUNT_HEAD = frappe.db.get_single_value("TaxJar Settings", "shipping_account_head") @@ -19,7 +19,6 @@ SUPPORTED_STATE_CODES = ['AL', 'AK', 'AZ', 'AR', 'CA', 'CO', 'CT', 'DE', 'DC', ' 'IN', 'IA', 'KS', 'KY', 'LA', 'ME', 'MD', 'MA', 'MI', 'MN', 'MS', 'MO', 'MT', 'NE', 'NV', 'NH', 'NJ', 'NM', 'NY', 'NC', 'ND', 'OH', 'OK', 'OR', 'PA', 'RI', 'SC', 'SD', 'TN', 'TX', 'UT', 'VT', 'VA', 'WA', 'WV', 'WI', 'WY'] -USA_COMPANIES = frappe.get_all('Company', filters = {'country': 'United States'}, fields=['name'], pluck='name') @@ -160,8 +159,7 @@ def set_sales_tax(doc, method): if not TAXJAR_CALCULATE_TAX: return - taxjar_settings_company = frappe.db.get_single_value('TaxJar Settings','current_company') - if taxjar_settings_company not in USA_COMPANIES: + if get_region(doc.company): return if not doc.items: diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 2ad2fea6847..e6fefaaa8d5 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -308,7 +308,7 @@ erpnext.patches.v13_0.migrate_stripe_api erpnext.patches.v13_0.reset_clearance_date_for_intracompany_payment_entries execute:frappe.reload_doc("erpnext_integrations", "doctype", "TaxJar Settings") execute:frappe.reload_doc("erpnext_integrations", "doctype", "Product Tax Category") -erpnext.patches.v13_0.custom_fields_for_taxjar_integration +erpnext.patches.v13_0.custom_fields_for_taxjar_integration #08-11-2021 erpnext.patches.v13_0.set_operation_time_based_on_operating_cost erpnext.patches.v13_0.validate_options_for_data_field erpnext.patches.v13_0.create_website_items #30-09-2021 diff --git a/erpnext/patches/v13_0/custom_fields_for_taxjar_integration.py b/erpnext/patches/v13_0/custom_fields_for_taxjar_integration.py index 3ef36873aa1..f8b145c6d02 100644 --- a/erpnext/patches/v13_0/custom_fields_for_taxjar_integration.py +++ b/erpnext/patches/v13_0/custom_fields_for_taxjar_integration.py @@ -32,7 +32,7 @@ def execute(): label='Product Tax Category') ], 'TaxJar Settings': [ - dict(fieldname='current_company', fieldtype='Link', insert_after='configuration', options='Company', + dict(fieldname='company', fieldtype='Link', insert_after='configuration', options='Company', label='Company') ] } From a403200516e4aac32e3a983fc3a483d3bb57025c Mon Sep 17 00:00:00 2001 From: Subin Tom Date: Mon, 8 Nov 2021 18:03:44 +0530 Subject: [PATCH 141/164] fix: company condition fix, added company field (cherry picked from commit 7ad2717accea61883f98d418b4abed47ca3937a8) --- .../doctype/taxjar_settings/taxjar_settings.json | 9 ++++++++- erpnext/erpnext_integrations/taxjar_integration.py | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.json b/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.json index d9ae5b601cc..23ccb7e4dac 100644 --- a/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.json +++ b/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.json @@ -14,6 +14,7 @@ "cb_keys", "sandbox_api_key", "configuration", + "company", "column_break_10", "tax_account_head", "configuration_cb", @@ -109,11 +110,17 @@ { "fieldname": "column_break_10", "fieldtype": "Column Break" + }, + { + "fieldname": "company", + "fieldtype": "Link", + "label": "Company", + "options": "Company" } ], "issingle": 1, "links": [], - "modified": "2021-11-08 17:57:17.323275", + "modified": "2021-11-08 18:02:29.232090", "modified_by": "Administrator", "module": "ERPNext Integrations", "name": "TaxJar Settings", diff --git a/erpnext/erpnext_integrations/taxjar_integration.py b/erpnext/erpnext_integrations/taxjar_integration.py index 1adc1ddb140..a4e21579e32 100644 --- a/erpnext/erpnext_integrations/taxjar_integration.py +++ b/erpnext/erpnext_integrations/taxjar_integration.py @@ -159,7 +159,7 @@ def set_sales_tax(doc, method): if not TAXJAR_CALCULATE_TAX: return - if get_region(doc.company): + if get_region(doc.company) != 'United States': return if not doc.items: From 40b5bb1d159e5f8aec1ac53ecc1f100ed4155266 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 8 Nov 2021 20:07:57 +0530 Subject: [PATCH 142/164] fix: do not generate multiple invoices (#28216) --- .../doctype/subscription/subscription.py | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/erpnext/accounts/doctype/subscription/subscription.py b/erpnext/accounts/doctype/subscription/subscription.py index ab5c46d2f85..1c441a9e2bb 100644 --- a/erpnext/accounts/doctype/subscription/subscription.py +++ b/erpnext/accounts/doctype/subscription/subscription.py @@ -498,9 +498,11 @@ class Subscription(Document): # Check invoice dates and make sure it doesn't have outstanding invoices return getdate() >= getdate(self.current_invoice_start) - def is_current_invoice_generated(self): + def is_current_invoice_generated(self, _current_start_date=None, _current_end_date=None): invoice = self.get_current_invoice() - _current_start_date, _current_end_date = self.update_subscription_period(date=add_days(self.current_invoice_end, 1), return_date=True) + + if not (_current_start_date and _current_end_date): + _current_start_date, _current_end_date = self.update_subscription_period(date=add_days(self.current_invoice_end, 1), return_date=True) if invoice and getdate(_current_start_date) <= getdate(invoice.posting_date) <= getdate(_current_end_date): return True @@ -516,13 +518,16 @@ class Subscription(Document): 2. Change the `Subscription` status to 'Past Due Date' 3. Change the `Subscription` status to 'Cancelled' """ - if getdate() > getdate(self.current_invoice_end) and self.is_prepaid_to_invoice(): - self.update_subscription_period(add_days(self.current_invoice_end, 1)) - if not self.is_current_invoice_generated() and (self.is_postpaid_to_invoice() or self.is_prepaid_to_invoice()): + if not self.is_current_invoice_generated(self.current_invoice_start, self.current_invoice_end) \ + and (self.is_postpaid_to_invoice() or self.is_prepaid_to_invoice()): + prorate = frappe.db.get_single_value('Subscription Settings', 'prorate') self.generate_invoice(prorate) + if getdate() > getdate(self.current_invoice_end) and self.is_prepaid_to_invoice(): + self.update_subscription_period(add_days(self.current_invoice_end, 1)) + if self.cancel_at_period_end and getdate() > getdate(self.current_invoice_end): self.cancel_subscription_at_period_end() @@ -556,8 +561,10 @@ class Subscription(Document): self.set_status_grace_period() # Generate invoices periodically even if current invoice are unpaid - if self.generate_new_invoices_past_due_date and not self.is_current_invoice_generated() and (self.is_postpaid_to_invoice() - or self.is_prepaid_to_invoice()): + if self.generate_new_invoices_past_due_date and not \ + self.is_current_invoice_generated(self.current_invoice_start, self.current_invoice_end) \ + and (self.is_postpaid_to_invoice() or self.is_prepaid_to_invoice()): + prorate = frappe.db.get_single_value('Subscription Settings', 'prorate') self.generate_invoice(prorate) From eacf26d6187b3840762ce22d3329f0d254e7fb24 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 9 Nov 2021 12:14:54 +0530 Subject: [PATCH 143/164] fix: show full item name in search widget (#28283) (#28285) (cherry picked from commit 34f5283c1773b79463fcf77a037c6ae258e329dc) Co-authored-by: Ankush Menat --- erpnext/controllers/queries.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index eab7a07be90..680b2554e9f 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -211,12 +211,15 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals meta = frappe.get_meta("Item", cached=True) searchfields = meta.get_search_fields() - if "description" in searchfields: - searchfields.remove("description") + # these are handled separately + ignored_search_fields = ("item_name", "description") + for ignored_field in ignored_search_fields: + if ignored_field in searchfields: + searchfields.remove(ignored_field) columns = '' extra_searchfields = [field for field in searchfields - if not field in ["name", "item_group", "description"]] + if not field in ["name", "item_group", "description", "item_name"]] if extra_searchfields: columns = ", " + ", ".join(extra_searchfields) @@ -253,10 +256,8 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals if frappe.db.count('Item', cache=True) < 50000: # scan description only if items are less than 50000 description_cond = 'or tabItem.description LIKE %(txt)s' - return frappe.db.sql("""select tabItem.name, - if(length(tabItem.item_name) > 40, - concat(substr(tabItem.item_name, 1, 40), "..."), item_name) as item_name, - tabItem.item_group, + return frappe.db.sql("""select + tabItem.name, tabItem.item_name, tabItem.item_group, if(length(tabItem.description) > 40, \ concat(substr(tabItem.description, 1, 40), "..."), description) as description {columns} From e8ef54f4d7ac861fdc50fc8d5fc8ac7ed742066f Mon Sep 17 00:00:00 2001 From: Anupam Date: Fri, 29 Oct 2021 17:27:17 +0530 Subject: [PATCH 144/164] feat: provision to close the Work Order (cherry picked from commit 23af036894ef61f590278fa7e84402f0be3ff50d) --- .../doctype/work_order/work_order.js | 48 +++++++++++-------- .../doctype/work_order/work_order.json | 5 +- .../doctype/work_order/work_order.py | 31 +++++++++++- 3 files changed, 60 insertions(+), 24 deletions(-) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js index 512048512ed..aac0c6f731d 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.js +++ b/erpnext/manufacturing/doctype/work_order/work_order.js @@ -135,24 +135,26 @@ frappe.ui.form.on("Work Order", { frm.set_intro(__("Submit this Work Order for further processing.")); } - if (frm.doc.docstatus===1) { - frm.trigger('show_progress_for_items'); - frm.trigger('show_progress_for_operations'); - } + if (frm.doc.status != "Closed") { + if (frm.doc.docstatus===1) { + frm.trigger('show_progress_for_items'); + frm.trigger('show_progress_for_operations'); + } - if (frm.doc.docstatus === 1 - && frm.doc.operations && frm.doc.operations.length) { + if (frm.doc.docstatus === 1 + && frm.doc.operations && frm.doc.operations.length) { - const not_completed = frm.doc.operations.filter(d => { - if(d.status != 'Completed') { - return true; + const not_completed = frm.doc.operations.filter(d => { + if(d.status != 'Completed') { + return true; + } + }); + + if(not_completed && not_completed.length) { + frm.add_custom_button(__('Create Job Card'), () => { + frm.trigger("make_job_card"); + }).addClass('btn-primary'); } - }); - - if(not_completed && not_completed.length) { - frm.add_custom_button(__('Create Job Card'), () => { - frm.trigger("make_job_card"); - }).addClass('btn-primary'); } } @@ -517,14 +519,19 @@ frappe.ui.form.on("Work Order Operation", { erpnext.work_order = { set_custom_buttons: function(frm) { var doc = frm.doc; - if (doc.docstatus === 1) { + if (doc.docstatus === 1 && doc.status != "Closed") { + console.log("check"); + frm.add_custom_button(__('Close'), function() { + erpnext.work_order.change_work_order_status(frm, "Closed"); + }, __("Status")); + if (doc.status != 'Stopped' && doc.status != 'Completed') { frm.add_custom_button(__('Stop'), function() { - erpnext.work_order.stop_work_order(frm, "Stopped"); + erpnext.work_order.change_work_order_status(frm, "Stopped"); }, __("Status")); } else if (doc.status == 'Stopped') { frm.add_custom_button(__('Re-open'), function() { - erpnext.work_order.stop_work_order(frm, "Resumed"); + erpnext.work_order.change_work_order_status(frm, "Resumed"); }, __("Status")); } @@ -713,9 +720,10 @@ erpnext.work_order = { }); }, - stop_work_order: function(frm, status) { + change_work_order_status: function(frm, status) { + let method_name = status=="Closed" ? "close_work_order" : "stop_unstop"; frappe.call({ - method: "erpnext.manufacturing.doctype.work_order.work_order.stop_unstop", + method: `erpnext.manufacturing.doctype.work_order.work_order.${method_name}`, freeze: true, freeze_message: __("Updating Work Order status"), args: { diff --git a/erpnext/manufacturing/doctype/work_order/work_order.json b/erpnext/manufacturing/doctype/work_order/work_order.json index 7f8e816a22a..df7ee53b92d 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.json +++ b/erpnext/manufacturing/doctype/work_order/work_order.json @@ -99,7 +99,7 @@ "no_copy": 1, "oldfieldname": "status", "oldfieldtype": "Select", - "options": "\nDraft\nSubmitted\nNot Started\nIn Process\nCompleted\nStopped\nCancelled", + "options": "\nDraft\nSubmitted\nNot Started\nIn Process\nCompleted\nStopped\nClosed\nCancelled", "read_only": 1, "reqd": 1, "search_index": 1 @@ -573,7 +573,8 @@ "image_field": "image", "is_submittable": 1, "links": [], - "modified": "2021-10-27 19:21:35.139888", + "migration_hash": "a18118963f4fcdb7f9d326de5f4063ba", + "modified": "2021-10-29 15:12:32.203605", "modified_by": "Administrator", "module": "Manufacturing", "name": "Work Order", diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index f881e1bf16a..6901d71ad4a 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -175,7 +175,7 @@ class WorkOrder(Document): def update_status(self, status=None): '''Update status of work order if unknown''' - if status != "Stopped": + if status != "Stopped" and status != "Closed": status = self.get_status(status) if status != self.status: @@ -624,7 +624,6 @@ class WorkOrder(Document): def validate_operation_time(self): for d in self.operations: if not d.time_in_mins > 0: - print(self.bom_no, self.production_item) frappe.throw(_("Operation Time must be greater than 0 for Operation {0}").format(d.operation)) def update_required_items(self): @@ -967,6 +966,10 @@ def stop_unstop(work_order, status): frappe.throw(_("Not permitted"), frappe.PermissionError) pro_order = frappe.get_doc("Work Order", work_order) + + if pro_order.status == "Closed": + frappe.throw(_("Closed Work Order can not be stopped or Re-opened")) + pro_order.update_status(status) pro_order.update_planned_qty() frappe.msgprint(_("Work Order has been {0}").format(status)) @@ -1001,6 +1004,30 @@ def make_job_card(work_order, operations): if row.job_card_qty > 0: create_job_card(work_order, row, auto_create=True) +@frappe.whitelist() +def close_work_order(work_order, status): + if not frappe.has_permission("Work Order", "write"): + frappe.throw(_("Not permitted"), frappe.PermissionError) + + work_order = frappe.get_doc("Work Order", work_order) + if work_order.get("operations"): + job_cards = frappe.get_list("Job Card", + filters={ + "work_order": work_order.name, + "status": "Work In Progress" + }, + pluck='name') + + if job_cards: + job_cards = ", ".join(job_cards) + frappe.throw(_("Can not close Work Order. Since {0} Job Cards are in Work In Progress state.").format(job_cards)) + + work_order.update_status(status) + work_order.update_planned_qty() + frappe.msgprint(_("Work Order has been {0}").format(status)) + work_order.notify_update() + return work_order.status + def split_qty_based_on_batch_size(wo_doc, row, qty): if not cint(frappe.db.get_value("Operation", row.operation, "create_job_card_based_on_batch_size")): From d43aecbe134439458e1ea46df18627bcc4565d1c Mon Sep 17 00:00:00 2001 From: Anupam Date: Sun, 31 Oct 2021 14:20:03 +0530 Subject: [PATCH 145/164] feat: added confirm dialog on closing of workorder (cherry picked from commit 5d4c5652aff59d6f051adf34dd60094cce96af07) --- erpnext/manufacturing/doctype/work_order/work_order.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js index aac0c6f731d..9146e48b926 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.js +++ b/erpnext/manufacturing/doctype/work_order/work_order.js @@ -520,9 +520,12 @@ erpnext.work_order = { set_custom_buttons: function(frm) { var doc = frm.doc; if (doc.docstatus === 1 && doc.status != "Closed") { - console.log("check"); frm.add_custom_button(__('Close'), function() { - erpnext.work_order.change_work_order_status(frm, "Closed"); + frappe.confirm(__("Once the Work Order is Closed. It can't be resumed."), + () => { + erpnext.work_order.change_work_order_status(frm, "Closed"); + } + ); }, __("Status")); if (doc.status != 'Stopped' && doc.status != 'Completed') { From 44c53deb908cf3c7a56276ea58ecbdeca60f5f75 Mon Sep 17 00:00:00 2001 From: Anupam Date: Sun, 31 Oct 2021 14:42:10 +0530 Subject: [PATCH 146/164] fix: sider issue (cherry picked from commit e290fe0721a2db994171e0be5a98d7fce272a3c7) --- erpnext/manufacturing/doctype/work_order/work_order.js | 4 ++-- erpnext/manufacturing/doctype/work_order/work_order.py | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js index 9146e48b926..21209cab962 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.js +++ b/erpnext/manufacturing/doctype/work_order/work_order.js @@ -145,12 +145,12 @@ frappe.ui.form.on("Work Order", { && frm.doc.operations && frm.doc.operations.length) { const not_completed = frm.doc.operations.filter(d => { - if(d.status != 'Completed') { + if (d.status != 'Completed') { return true; } }); - if(not_completed && not_completed.length) { + if (not_completed && not_completed.length) { frm.add_custom_button(__('Create Job Card'), () => { frm.trigger("make_job_card"); }).addClass('btn-primary'); diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index 6901d71ad4a..af80c4ef0be 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -1015,8 +1015,7 @@ def close_work_order(work_order, status): filters={ "work_order": work_order.name, "status": "Work In Progress" - }, - pluck='name') + }, pluck='name') if job_cards: job_cards = ", ".join(job_cards) From 47f8f3d9dd457fcd631444f53df079f14930bf11 Mon Sep 17 00:00:00 2001 From: Anupam Date: Sun, 31 Oct 2021 14:45:36 +0530 Subject: [PATCH 147/164] fix: sider issue (cherry picked from commit 55e97dce8a7cffd12180b36586ee277b3188cda5) --- erpnext/manufacturing/doctype/work_order/work_order.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index af80c4ef0be..117e2a227a8 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -1012,10 +1012,10 @@ def close_work_order(work_order, status): work_order = frappe.get_doc("Work Order", work_order) if work_order.get("operations"): job_cards = frappe.get_list("Job Card", - filters={ - "work_order": work_order.name, - "status": "Work In Progress" - }, pluck='name') + filters={ + "work_order": work_order.name, + "status": "Work In Progress" + }, pluck='name') if job_cards: job_cards = ", ".join(job_cards) From 8ddeb8319159ec4a7b7b815dbc65649d1ad62b67 Mon Sep 17 00:00:00 2001 From: Anupam Date: Sun, 31 Oct 2021 16:11:11 +0530 Subject: [PATCH 148/164] fix: sider issue (cherry picked from commit 264b0df9ffe5074542c28b46c6ead2c3436e27ed) --- erpnext/manufacturing/doctype/work_order/work_order.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index 117e2a227a8..48703bc621b 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -1015,7 +1015,7 @@ def close_work_order(work_order, status): filters={ "work_order": work_order.name, "status": "Work In Progress" - }, pluck='name') + }, pluck='name') if job_cards: job_cards = ", ".join(job_cards) From f3da88e301ac403372cc59af62dbd3846455d7bd Mon Sep 17 00:00:00 2001 From: Anupam Date: Tue, 2 Nov 2021 12:39:13 +0530 Subject: [PATCH 149/164] fix: added testcase (cherry picked from commit 530a0f481e8901d2b525a54c663251a477cc3758) --- erpnext/manufacturing/doctype/work_order/test_work_order.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index 85b5bfb9bfc..dc817296816 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -14,6 +14,7 @@ from erpnext.manufacturing.doctype.work_order.work_order import ( StockOverProductionError, make_stock_entry, stop_unstop, + close_work_order, ) from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order from erpnext.stock.doctype.item.test_item import create_item, make_item @@ -800,6 +801,10 @@ class TestWorkOrder(unittest.TestCase): if row.is_scrap_item: self.assertEqual(row.qty, 1) + def test_close_work_order(self): + close_work_order(self.wo_order.name, "Stopped") + self.assertEqual(self.wo_order.status, "Closed") + def update_job_card(job_card): job_card_doc = frappe.get_doc('Job Card', job_card) job_card_doc.set('scrap_items', [ From 7c7493c91d7afb3d626cdc39680e52721e5e3ae2 Mon Sep 17 00:00:00 2001 From: Anupam Date: Tue, 2 Nov 2021 12:46:00 +0530 Subject: [PATCH 150/164] fix: linter issues (cherry picked from commit 59e4fd980c1c411f82df1f588bc2c80e17d8e317) --- erpnext/manufacturing/doctype/work_order/test_work_order.py | 2 +- erpnext/manufacturing/doctype/work_order/work_order.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index dc817296816..f5851dae36c 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -12,9 +12,9 @@ from erpnext.manufacturing.doctype.work_order.work_order import ( ItemHasVariantError, OverProductionError, StockOverProductionError, + close_work_order, make_stock_entry, stop_unstop, - close_work_order, ) from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order from erpnext.stock.doctype.item.test_item import create_item, make_item diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index 48703bc621b..36dae99a691 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -1011,6 +1011,7 @@ def close_work_order(work_order, status): work_order = frappe.get_doc("Work Order", work_order) if work_order.get("operations"): + job_cards = frappe.get_list("Job Card", filters={ "work_order": work_order.name, From cb209d9b576cbeb216ba781c62c61b8f571f2038 Mon Sep 17 00:00:00 2001 From: Anupam Date: Tue, 2 Nov 2021 12:53:00 +0530 Subject: [PATCH 151/164] fix: linter issues (cherry picked from commit e36da4d13749afd0fb0e1523aa6d2ecc33ab730c) --- erpnext/manufacturing/doctype/work_order/work_order.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index 36dae99a691..0090f4d04ee 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -1011,8 +1011,7 @@ def close_work_order(work_order, status): work_order = frappe.get_doc("Work Order", work_order) if work_order.get("operations"): - - job_cards = frappe.get_list("Job Card", + job_cards = frappe.get_list("Job Card", filters={ "work_order": work_order.name, "status": "Work In Progress" From dce943b41fabf9a5191a8d672d5ac7eb220578a3 Mon Sep 17 00:00:00 2001 From: Anupam Date: Tue, 2 Nov 2021 20:43:00 +0530 Subject: [PATCH 152/164] fix: test cases (cherry picked from commit 9c0906f1b51431f67c3bc009a1410e1129a80744) --- erpnext/manufacturing/doctype/work_order/test_work_order.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index f5851dae36c..b6a8700baca 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -802,7 +802,7 @@ class TestWorkOrder(unittest.TestCase): self.assertEqual(row.qty, 1) def test_close_work_order(self): - close_work_order(self.wo_order.name, "Stopped") + close_work_order(self.wo_order.name, "Closed") self.assertEqual(self.wo_order.status, "Closed") def update_job_card(job_card): From 116210dcea0d73a3a0a6bc775494cfda7710579d Mon Sep 17 00:00:00 2001 From: Anupam Date: Wed, 3 Nov 2021 13:27:50 +0530 Subject: [PATCH 153/164] fix: validate job card (cherry picked from commit 9b4c7e479650851228181cdf562647364ba51cf9) --- .../doctype/job_card/job_card.js | 5 +++ .../doctype/job_card/job_card.py | 14 +++++++ .../doctype/work_order/test_work_order.py | 39 ++++++++++++++++++- 3 files changed, 56 insertions(+), 2 deletions(-) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.js b/erpnext/manufacturing/doctype/job_card/job_card.js index 35be38813e5..df35028ca72 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.js +++ b/erpnext/manufacturing/doctype/job_card/job_card.js @@ -28,6 +28,11 @@ frappe.ui.form.on('Job Card', { frappe.flags.resume_job = 0; let has_items = frm.doc.items && frm.doc.items.length; + if (frm.doc.__onload.work_order_stopped) { + frm.disable_save(); + return + } + if (!frm.doc.__islocal && has_items && frm.doc.docstatus < 2) { let to_request = frm.doc.for_quantity > frm.doc.transferred_qty; let excess_transfer_allowed = frm.doc.__onload.job_card_excess_transfer; diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index e1d79be81c4..dd1df20216b 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -37,6 +37,7 @@ class JobCard(Document): def onload(self): excess_transfer = frappe.db.get_single_value("Manufacturing Settings", "job_card_excess_transfer") self.set_onload("job_card_excess_transfer", excess_transfer) + self.set_onload("work_order_stopped", self.is_work_order_stopped()) def validate(self): self.validate_time_logs() @@ -45,6 +46,7 @@ class JobCard(Document): self.validate_sequence_id() self.set_sub_operations() self.update_sub_operation_status() + self.validate_work_order() def set_sub_operations(self): if self.operation: @@ -549,6 +551,18 @@ class JobCard(Document): frappe.throw(_("{0}, complete the operation {1} before the operation {2}.") .format(message, bold(row.operation), bold(self.operation)), OperationSequenceError) + def validate_work_order(self): + if self.is_work_order_stopped(): + frappe.throw(_("You can't make any changes to Job Card since Work Order is stopped.")) + + def is_work_order_stopped(self): + if self.work_order: + status = frappe.get_value('Work Order', self.work_order) + + if status == "Closed": + return True + + return False @frappe.whitelist() def make_time_log(args): diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index b6a8700baca..3dbbf317576 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -802,8 +802,43 @@ class TestWorkOrder(unittest.TestCase): self.assertEqual(row.qty, 1) def test_close_work_order(self): - close_work_order(self.wo_order.name, "Closed") - self.assertEqual(self.wo_order.status, "Closed") + items = ['Test FG Item for Closed WO', 'Test RM Item 1 for Closed WO', + 'Test RM Item 2 for Closed WO'] + + company = '_Test Company with perpetual inventory' + for item_code in items: + create_item(item_code = item_code, is_stock_item = 1, + is_purchase_item=1, opening_stock=100, valuation_rate=10, company=company, warehouse='Stores - TCP1') + + item = 'Test FG Item for Closed WO' + raw_materials = ['Test RM Item 1 for Closed WO', 'Test RM Item 2 for Closed WO'] + if not frappe.db.get_value('BOM', {'item': item}): + bom = make_bom(item=item, source_warehouse='Stores - TCP1', raw_materials=raw_materials, do_not_save=True) + bom.with_operations = 1 + bom.append('operations', { + 'operation': '_Test Operation 1', + 'workstation': '_Test Workstation 1', + 'hour_rate': 20, + 'time_in_mins': 60 + }) + + bom.submit() + + wo_order = make_wo_order_test_record(item=item, company=company, planned_start_date=now(), qty=20, skip_transfer=1) + job_cards = frappe.db.get_value('Job Card', {'work_order': wo_order.name}, 'name') + + for jc in job_cards: + job_card_doc = frappe.get_doc('Job Card', jc) + job_card_doc.append('time_logs', { + 'from_time': now(), + 'time_in_mins': 60, + 'completed_qty': job_card_doc.for_quantity + }) + + job_card_doc.submit() + + close_work_order(wo_order, "Closed") + self.assertEqual(wo_order.get('status'), "Closed") def update_job_card(job_card): job_card_doc = frappe.get_doc('Job Card', job_card) From 8851b7adcfe4fe6f867ea4956f4d67fde28c6082 Mon Sep 17 00:00:00 2001 From: Anupam Date: Wed, 3 Nov 2021 13:41:22 +0530 Subject: [PATCH 154/164] fix: sider isuue (cherry picked from commit ba47bd02b6999411736aa864303a181addc225f3) --- erpnext/manufacturing/doctype/job_card/job_card.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.js b/erpnext/manufacturing/doctype/job_card/job_card.js index df35028ca72..e3eed92d7e1 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.js +++ b/erpnext/manufacturing/doctype/job_card/job_card.js @@ -30,7 +30,7 @@ frappe.ui.form.on('Job Card', { if (frm.doc.__onload.work_order_stopped) { frm.disable_save(); - return + return; } if (!frm.doc.__islocal && has_items && frm.doc.docstatus < 2) { From 029e66709d01ea18386da0f2d86dade8ffe98131 Mon Sep 17 00:00:00 2001 From: Anupam Date: Wed, 3 Nov 2021 13:54:46 +0530 Subject: [PATCH 155/164] fix: linter isuue (cherry picked from commit cc15cf6ae2915ba01188a36c427ab19d7fd89e2b) --- erpnext/manufacturing/doctype/work_order/test_work_order.py | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index 3dbbf317576..4f675077553 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -826,7 +826,6 @@ class TestWorkOrder(unittest.TestCase): wo_order = make_wo_order_test_record(item=item, company=company, planned_start_date=now(), qty=20, skip_transfer=1) job_cards = frappe.db.get_value('Job Card', {'work_order': wo_order.name}, 'name') - for jc in job_cards: job_card_doc = frappe.get_doc('Job Card', jc) job_card_doc.append('time_logs', { From 365572f5ea48a30875eecd2c54efdefafd39b76f Mon Sep 17 00:00:00 2001 From: Anupam Date: Wed, 3 Nov 2021 17:41:04 +0530 Subject: [PATCH 156/164] fix: testcases: (cherry picked from commit 7044ae5e39669a0edf59f3abd19b5b52e1de233b) --- erpnext/manufacturing/doctype/work_order/test_work_order.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index 4f675077553..c3f3917b5c8 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -826,6 +826,7 @@ class TestWorkOrder(unittest.TestCase): wo_order = make_wo_order_test_record(item=item, company=company, planned_start_date=now(), qty=20, skip_transfer=1) job_cards = frappe.db.get_value('Job Card', {'work_order': wo_order.name}, 'name') + self.assertEqual(len(job_cards), len(bom.operations)) for jc in job_cards: job_card_doc = frappe.get_doc('Job Card', jc) job_card_doc.append('time_logs', { From dac0d87e16f58787c00d7a356c6bc73a492a86fd Mon Sep 17 00:00:00 2001 From: Anupam Date: Tue, 9 Nov 2021 09:56:58 +0530 Subject: [PATCH 157/164] fix: test cases (cherry picked from commit 27709a1c71a9943cb95deb8ef1b563c465489dcb) --- .../doctype/work_order/test_work_order.py | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index c3f3917b5c8..f4a88dc4598 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -826,19 +826,20 @@ class TestWorkOrder(unittest.TestCase): wo_order = make_wo_order_test_record(item=item, company=company, planned_start_date=now(), qty=20, skip_transfer=1) job_cards = frappe.db.get_value('Job Card', {'work_order': wo_order.name}, 'name') - self.assertEqual(len(job_cards), len(bom.operations)) - for jc in job_cards: - job_card_doc = frappe.get_doc('Job Card', jc) - job_card_doc.append('time_logs', { - 'from_time': now(), - 'time_in_mins': 60, - 'completed_qty': job_card_doc.for_quantity - }) - job_card_doc.submit() + if len(job_cards) == len(bom.operations): + for jc in job_cards: + job_card_doc = frappe.get_doc('Job Card', jc) + job_card_doc.append('time_logs', { + 'from_time': now(), + 'time_in_mins': 60, + 'completed_qty': job_card_doc.for_quantity + }) - close_work_order(wo_order, "Closed") - self.assertEqual(wo_order.get('status'), "Closed") + job_card_doc.submit() + + close_work_order(wo_order, "Closed") + self.assertEqual(wo_order.get('status'), "Closed") def update_job_card(job_card): job_card_doc = frappe.get_doc('Job Card', job_card) From 515b8dd8ca96411025083bd4bdfc4901482cbb2e Mon Sep 17 00:00:00 2001 From: Anupam Date: Tue, 9 Nov 2021 10:50:38 +0530 Subject: [PATCH 158/164] fix: get_planned_qty chnages (cherry picked from commit 663a7afe4df81fe3956af4eb18ca1eaa0614b626) --- erpnext/stock/stock_balance.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/stock_balance.py b/erpnext/stock/stock_balance.py index 1cb0f0d0a49..51c20b02506 100644 --- a/erpnext/stock/stock_balance.py +++ b/erpnext/stock/stock_balance.py @@ -161,7 +161,7 @@ def get_ordered_qty(item_code, warehouse): def get_planned_qty(item_code, warehouse): planned_qty = frappe.db.sql(""" select sum(qty - produced_qty) from `tabWork Order` - where production_item = %s and fg_warehouse = %s and status not in ("Stopped", "Completed") + where production_item = %s and fg_warehouse = %s and status not in ("Stopped", "Completed", "Closed") and docstatus=1 and qty > produced_qty""", (item_code, warehouse)) return flt(planned_qty[0][0]) if planned_qty else 0 From 3c78ac21d90fb98f97f15d4d1c6086dd87f7ee4d Mon Sep 17 00:00:00 2001 From: Diksha Jadhav Date: Mon, 1 Nov 2021 17:19:07 +0530 Subject: [PATCH 159/164] feat(manufacturing): add link field for quality inspection template on operation (cherry picked from commit 281a9cc749fdf2170f58da9be19ea628cebe56ba) --- erpnext/manufacturing/doctype/operation/operation.json | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/operation/operation.json b/erpnext/manufacturing/doctype/operation/operation.json index 10a97eda763..2b2a81703f8 100644 --- a/erpnext/manufacturing/doctype/operation/operation.json +++ b/erpnext/manufacturing/doctype/operation/operation.json @@ -9,6 +9,7 @@ "engine": "InnoDB", "field_order": [ "workstation", + "quality_inspection_template", "data_2", "is_corrective_operation", "job_card_section", @@ -92,12 +93,18 @@ "fieldname": "is_corrective_operation", "fieldtype": "Check", "label": "Is Corrective Operation" + }, + { + "fieldname": "quality_inspection_template", + "fieldtype": "Link", + "label": "Quality Inspection Template", + "options": "Quality Inspection Template" } ], "icon": "fa fa-wrench", "index_web_pages_for_search": 1, "links": [], - "modified": "2021-01-12 15:09:23.593338", + "modified": "2021-11-01 15:13:35.911144", "modified_by": "Administrator", "module": "Manufacturing", "name": "Operation", From ac54e80856f3d365d81ae863abab6226b603e972 Mon Sep 17 00:00:00 2001 From: Diksha Jadhav Date: Wed, 3 Nov 2021 14:07:45 +0530 Subject: [PATCH 160/164] feat: provision to have limited parameters for in-process quality inspection (cherry picked from commit 6c45f8b2c46adb8a19123f4b65f566cb63f6eb2f) --- .../manufacturing/doctype/job_card/job_card.js | 17 +++++++++++++++++ .../doctype/job_card/job_card.json | 9 ++++++++- .../manufacturing/doctype/job_card/job_card.py | 5 +++++ .../doctype/operation/operation.json | 4 ++-- .../quality_inspection/quality_inspection.js | 2 +- .../quality_inspection/quality_inspection.py | 9 +++++++++ 6 files changed, 42 insertions(+), 4 deletions(-) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.js b/erpnext/manufacturing/doctype/job_card/job_card.js index 35be38813e5..b036e972a74 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.js +++ b/erpnext/manufacturing/doctype/job_card/job_card.js @@ -70,6 +70,23 @@ frappe.ui.form.on('Job Card', { && (frm.doc.items || !frm.doc.items.length || frm.doc.for_quantity == frm.doc.transferred_qty)) { frm.trigger("prepare_timer_buttons"); } + frm.trigger("setup_quality_inspection"); + }, + + setup_quality_inspection: function(frm) { + let quality_inspection_field = frm.get_docfield("quality_inspection"); + quality_inspection_field.get_route_options_for_new_doc = function(frm) { + return { + "inspection_type": "In Process", + "reference_type": "Job Card", + "reference_name": frm.doc.name, + "item_code": frm.doc.production_item, + "item_name": frm.doc.item_name, + "item_serial_no": frm.doc.serial_no, + "batch_no": frm.doc.batch_no, + "quality_inspection_template": frm.doc.quality_inspection_template, + }; + }; }, setup_corrective_job_card: function(frm) { diff --git a/erpnext/manufacturing/doctype/job_card/job_card.json b/erpnext/manufacturing/doctype/job_card/job_card.json index 7dd38f4673d..0682144e4a0 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.json +++ b/erpnext/manufacturing/doctype/job_card/job_card.json @@ -19,6 +19,7 @@ "serial_no", "column_break_12", "wip_warehouse", + "quality_inspection_template", "quality_inspection", "project", "batch_no", @@ -407,11 +408,17 @@ "no_copy": 1, "options": "Job Card Scrap Item", "print_hide": 1 + }, + { + "fieldname": "quality_inspection_template", + "fieldtype": "Link", + "label": "Quality Inspection Template", + "options": "Quality Inspection Template" } ], "is_submittable": 1, "links": [], - "modified": "2021-09-14 00:38:46.873105", + "modified": "2021-11-03 13:46:06.557348", "modified_by": "Administrator", "module": "Manufacturing", "name": "Job Card", diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index e1d79be81c4..22e8b54eb91 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -45,6 +45,7 @@ class JobCard(Document): self.validate_sequence_id() self.set_sub_operations() self.update_sub_operation_status() + self.set_quality_inspection_template() def set_sub_operations(self): if self.operation: @@ -292,6 +293,10 @@ class JobCard(Document): row.completed_time = 0.0 row.completed_qty = 0.0 + def set_quality_inspection_template(self): + qi_template = frappe.db.get_value('Operation', self.operation, 'quality_inspection_template') + self.quality_inspection_template = qi_template + def update_time_logs(self, row): self.append("time_logs", { "from_time": row.planned_start_time, diff --git a/erpnext/manufacturing/doctype/operation/operation.json b/erpnext/manufacturing/doctype/operation/operation.json index 2b2a81703f8..3da3f03b3f1 100644 --- a/erpnext/manufacturing/doctype/operation/operation.json +++ b/erpnext/manufacturing/doctype/operation/operation.json @@ -9,11 +9,11 @@ "engine": "InnoDB", "field_order": [ "workstation", - "quality_inspection_template", "data_2", "is_corrective_operation", "job_card_section", "create_job_card_based_on_batch_size", + "quality_inspection_template", "column_break_6", "batch_size", "sub_operations_section", @@ -104,7 +104,7 @@ "icon": "fa fa-wrench", "index_web_pages_for_search": 1, "links": [], - "modified": "2021-11-01 15:13:35.911144", + "modified": "2021-11-03 13:49:39.114976", "modified_by": "Administrator", "module": "Manufacturing", "name": "Operation", diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.js b/erpnext/stock/doctype/quality_inspection/quality_inspection.js index d08dc3e8b76..eea28791a9f 100644 --- a/erpnext/stock/doctype/quality_inspection/quality_inspection.js +++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.js @@ -59,7 +59,7 @@ frappe.ui.form.on("Quality Inspection", { }, item_code: function(frm) { - if (frm.doc.item_code) { + if (frm.doc.item_code && !frm.doc.quality_inspection_template) { return frm.call({ method: "get_quality_inspection_template", doc: frm.doc, diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.py b/erpnext/stock/doctype/quality_inspection/quality_inspection.py index 8b2f8da9dfd..2fa5b72f431 100644 --- a/erpnext/stock/doctype/quality_inspection/quality_inspection.py +++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.py @@ -19,6 +19,15 @@ class QualityInspection(Document): if not self.readings and self.item_code: self.get_item_specification_details() + if self.inspection_type=="In Process" and self.reference_type=="Job Card": + item_qi_template = frappe.db.get_value("Item", self.item_code, 'quality_inspection_template') + parameters = get_template_details(item_qi_template) + for reading in self.readings: + for d in parameters: + if reading.specification == d.specification: + reading.update(d) + reading.status = "Accepted" + if self.readings: self.inspect_and_set_status() From bb9713a7b24d8828407a0c1ac1007d196be50c64 Mon Sep 17 00:00:00 2001 From: Diksha Jadhav Date: Tue, 9 Nov 2021 14:21:34 +0530 Subject: [PATCH 161/164] refactor: use fetch from property for qi template on job card (cherry picked from commit 467324c87f407f66b20d8182039fa902b6ffc82f) --- erpnext/manufacturing/doctype/job_card/job_card.json | 3 ++- erpnext/manufacturing/doctype/job_card/job_card.py | 5 ----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.json b/erpnext/manufacturing/doctype/job_card/job_card.json index 0682144e4a0..8095e66eac0 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.json +++ b/erpnext/manufacturing/doctype/job_card/job_card.json @@ -410,6 +410,7 @@ "print_hide": 1 }, { + "fetch_from": "operation.quality_inspection_template", "fieldname": "quality_inspection_template", "fieldtype": "Link", "label": "Quality Inspection Template", @@ -418,7 +419,7 @@ ], "is_submittable": 1, "links": [], - "modified": "2021-11-03 13:46:06.557348", + "modified": "2021-11-09 14:07:20.290306", "modified_by": "Administrator", "module": "Manufacturing", "name": "Job Card", diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index 22e8b54eb91..e1d79be81c4 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -45,7 +45,6 @@ class JobCard(Document): self.validate_sequence_id() self.set_sub_operations() self.update_sub_operation_status() - self.set_quality_inspection_template() def set_sub_operations(self): if self.operation: @@ -293,10 +292,6 @@ class JobCard(Document): row.completed_time = 0.0 row.completed_qty = 0.0 - def set_quality_inspection_template(self): - qi_template = frappe.db.get_value('Operation', self.operation, 'quality_inspection_template') - self.quality_inspection_template = qi_template - def update_time_logs(self, row): self.append("time_logs", { "from_time": row.planned_start_time, From fa2f1348ec8872ca79fdaa44a5fbb42c7e7f46d9 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 9 Nov 2021 19:50:14 +0530 Subject: [PATCH 162/164] fix(India setup): setup company independent fixtures for patch (#28304) (cherry picked from commit 88b5bda34b80fb83e71f94a984184304e5b4c853) Co-authored-by: Rucha Mahabal --- 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 09207c17744..58280070795 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -18,7 +18,7 @@ from erpnext.regional.india import states def setup(company=None, patch=True): # Company independent fixtures should be called only once at the first company setup - if frappe.db.count('Company', {'country': 'India'}) <=1: + if patch or frappe.db.count('Company', {'country': 'India'}) <=1: setup_company_independent_fixtures(patch=patch) if not patch: From f3cfb93a5f5d49716e4fc42a91de802e27436536 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 9 Nov 2021 20:08:55 +0530 Subject: [PATCH 163/164] chore: Added change log for version 13.14.0 --- erpnext/change_log/v13/v13_14_0.md | 42 ++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 erpnext/change_log/v13/v13_14_0.md diff --git a/erpnext/change_log/v13/v13_14_0.md b/erpnext/change_log/v13/v13_14_0.md new file mode 100644 index 00000000000..3769c7984a7 --- /dev/null +++ b/erpnext/change_log/v13/v13_14_0.md @@ -0,0 +1,42 @@ +# Version 13.14.0 Release Notes + +### Features & Enhancements + +- KSA E-Invoicing and VAT Report ([#27369](https://github.com/frappe/erpnext/pull/27369)) + - Added KSA VAT settings to setup KSA VAT accounts + - New report KSA VAT to check the vat amounts + - Print format for KSA VAT Invoice ([#28166](https://github.com/frappe/erpnext/pull/28166)) + +- Provision to setup tax for recurring additional salary in Salary Slip ([#27459](https://github.com/frappe/erpnext/pull/27459)) +- Add dispatch address in E-invoicing for India localization ([#28084](https://github.com/frappe/erpnext/pull/28084)) +- Employee initial work history updated when transfer is performed ([#27768](https://github.com/frappe/erpnext/pull/27768)) +- Provision to setup quality inspection teamplte in the operation which will be use in the Job Card([#28219](https://github.com/frappe/erpnext/pull/28219)) +- Improved sales invoice submission performance ([#27916](https://github.com/frappe/erpnext/pull/27916)) + + +### Fixes + +- Splitting outstanding rows as per payment terms ([#27946](https://github.com/frappe/erpnext/pull/27946)) + +- Make status filter in Fixed Asset Register optional ([#28126](https://github.com/frappe/erpnext/pull/28126)) +- Skip empty rows while updating unsaved BOM cost ([#28136](https://github.com/frappe/erpnext/pull/28136)) +- TDS round off not working from second transaction ([#27934](https://github.com/frappe/erpnext/pull/27934)) +- Update receivable/payable account on company change in the Sales / Purchase Invoice ([#28057](https://github.com/frappe/erpnext/pull/28057)) +- Changes in Maintenance Schedule gets overwritten on save ([#27990](https://github.com/frappe/erpnext/pull/27990)) +- Fetch thumbnail from Item master instead of regenerating ([#28005](https://github.com/frappe/erpnext/pull/28005)) +- Serial Nos not set in the row after scanning in popup ([#28202](https://github.com/frappe/erpnext/pull/28202)) +- Taxjar customer_address fix, currency fix ([#28262](https://github.com/frappe/erpnext/pull/28262)) +- TaxJar update - added nexus list, making api call only for nexus ([#27497](https://github.com/frappe/erpnext/pull/27497)) +- Don't reset rates in Timesheet Detail when Activity Type is cleared ([#28056](https://github.com/frappe/erpnext/pull/28056)) +- Show full item name in search widget ([#28283](https://github.com/frappe/erpnext/pull/28283)) +- Avoid automatic customer creation on website user login ([#27914](https://github.com/frappe/erpnext/pull/27914)) +- POS Closing Entry without linked invoices ([#28042](https://github.com/frappe/erpnext/pull/28042)) +- Added patch to fix production plan status ([#27567](https://github.com/frappe/erpnext/pull/27567)) +- Interstate internal transfer invoices was not displying in the GSTR-1 report ([#27970](https://github.com/frappe/erpnext/pull/27970)) +- Shows opening balance from filtered from date in the stock balance and stock ledger report ([#26877](https://github.com/frappe/erpnext/pull/26877)) +- Employee filter in YTD and MTD in salary slip ([#27997](https://github.com/frappe/erpnext/pull/27997)) +- Removed warehouse filter on Batch field for Material Receipt ([#28195](https://github.com/frappe/erpnext/pull/28195)) +- Account number and name incorrectly imported using COA importer ([#27967](https://github.com/frappe/erpnext/pull/27967)) +- Autoemail report not showing dynamic report filters ([#28114](https://github.com/frappe/erpnext/pull/28114)) +- Incorrect VAT Amount in UAE VAT 201 report ([#27994](https://github.com/frappe/erpnext/pull/27994)) +- Employee Leave Balance report should only consider ledgers of transaction type Leave Allocation([#27728](https://github.com/frappe/erpnext/pull/27728)) \ No newline at end of file From 6b77bac7da95421e80653d9f7e7eff079d71122a Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 9 Nov 2021 20:36:00 +0550 Subject: [PATCH 164/164] bumped to version 13.14.0 --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 89d12e005c8..3862a5475d4 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -7,7 +7,7 @@ import frappe from erpnext.hooks import regional_overrides -__version__ = '13.13.0' +__version__ = '13.14.0' def get_default_company(user=None): '''Get default company for user'''