From f68205468c9443a061e66fd0166a28923dd07a2d Mon Sep 17 00:00:00 2001 From: m1ngaa <77512195+m1ngaa@users.noreply.github.com> Date: Wed, 14 Apr 2021 05:50:31 +0800 Subject: [PATCH 01/60] Delete accounts (an empty file) This file has no purpose, and must've been included as a tiny mistake..? --- erpnext/accounts/accounts | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 erpnext/accounts/accounts diff --git a/erpnext/accounts/accounts b/erpnext/accounts/accounts deleted file mode 100644 index e69de29bb2d..00000000000 From e379f083bbc5b40faa12c8adf41aedccb8b56f48 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 20 Apr 2021 23:04:39 +0530 Subject: [PATCH 02/60] feat(India): Separate Input and Output GST tax accounts --- .../setup_wizard/data/country_wise_tax.json | 49 ++++++++++++++++--- 1 file changed, 42 insertions(+), 7 deletions(-) diff --git a/erpnext/setup/setup_wizard/data/country_wise_tax.json b/erpnext/setup/setup_wizard/data/country_wise_tax.json index 58764880330..9db2122be2e 100644 --- a/erpnext/setup/setup_wizard/data/country_wise_tax.json +++ b/erpnext/setup/setup_wizard/data/country_wise_tax.json @@ -881,35 +881,70 @@ ] } ], - "*": [ + "sales_tax_templates": [ { - "title": "In State GST", + "title": "Output GST In-state", "taxes": [ { "account_head": { - "account_name": "SGST", + "account_name": "Output Tax SGST", "tax_rate": 9.00 } }, { "account_head": { - "account_name": "CGST", + "account_name": "Output Tax CGST", "tax_rate": 9.00 } } ] }, { - "title": "Out of State GST", + "title": "Output GST Out-state", "taxes": [ { "account_head": { - "account_name": "IGST", + "account_name": "Output Tax IGST", "tax_rate": 18.00 } } ] + } + ], + "purchase_tax_templates": [ + { + "title": "Input GST In-state", + "taxes": [ + { + "account_head": { + "account_name": "Input Tax SGST", + "tax_rate": 9.00, + "root_type": "Asset" + } + }, + { + "account_head": { + "account_name": "Input Tax CGST", + "tax_rate": 9.00, + "root_type": "Asset" + } + } + ] }, + { + "title": "Input GST Out-state", + "taxes": [ + { + "account_head": { + "account_name": "Input Tax IGST", + "tax_rate": 18.00, + "root_type": "Asset" + } + } + ] + } + ], + "*": [ { "title": "VAT 5%", "taxes": [ @@ -1001,7 +1036,7 @@ "Italy VAT 4%":{ "account_name": "IVA 4%", "tax_rate": 4.00 - } + } }, "Ivory Coast": { From 3130ed52ff0625c150b1c0b8492adc4474ddbf60 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 20 Apr 2021 23:23:07 +0530 Subject: [PATCH 03/60] fix: Item Tax templates for GST --- .../setup_wizard/data/country_wise_tax.json | 148 +++++++++++++++++- 1 file changed, 142 insertions(+), 6 deletions(-) diff --git a/erpnext/setup/setup_wizard/data/country_wise_tax.json b/erpnext/setup/setup_wizard/data/country_wise_tax.json index 9db2122be2e..3119eeec498 100644 --- a/erpnext/setup/setup_wizard/data/country_wise_tax.json +++ b/erpnext/setup/setup_wizard/data/country_wise_tax.json @@ -820,29 +820,165 @@ "*": { "item_tax_templates": [ { - "title": "In State GST", + "title": "GST 9%", "taxes": [ { "tax_type": { - "account_name": "SGST", + "account_name": "Output Tax SGST", "tax_rate": 9.00 } }, { "tax_type": { - "account_name": "CGST", + "account_name": "Output Tax CGST", "tax_rate": 9.00 } + }, + { + "tax_type": { + "account_name": "Output Tax IGST", + "tax_rate": 18.00 + } + }, + { + "tax_type": { + "account_name": "Input Tax SGST", + "tax_rate": 9.00 + } + }, + { + "tax_type": { + "account_name": "Input Tax CGST", + "tax_rate": 9.00 + } + }, + { + "tax_type": { + "account_name": "Input Tax IGST", + "tax_rate": 18.00 + } } ] }, { - "title": "Out of State GST", + "title": "GST 5%", "taxes": [ { "tax_type": { - "account_name": "IGST", - "tax_rate": 18.00 + "account_name": "Output Tax SGST", + "tax_rate": 2.5 + } + }, + { + "tax_type": { + "account_name": "Output Tax CGST", + "tax_rate": 2.5 + } + }, + { + "tax_type": { + "account_name": "Output Tax IGST", + "tax_rate": 5.0 + } + }, + { + "tax_type": { + "account_name": "Input Tax SGST", + "tax_rate": 2.5 + } + }, + { + "tax_type": { + "account_name": "Input Tax CGST", + "tax_rate": 2.5 + } + }, + { + "tax_type": { + "account_name": "Input Tax IGST", + "tax_rate": 5.0 + } + } + ] + }, + { + "title": "GST 12%", + "taxes": [ + { + "tax_type": { + "account_name": "Output Tax SGST", + "tax_rate": 6.0 + } + }, + { + "tax_type": { + "account_name": "Output Tax CGST", + "tax_rate": 6.0 + } + }, + { + "tax_type": { + "account_name": "Output Tax IGST", + "tax_rate": 12.0 + } + }, + { + "tax_type": { + "account_name": "Input Tax SGST", + "tax_rate": 6.0 + } + }, + { + "tax_type": { + "account_name": "Input Tax CGST", + "tax_rate": 6.0 + } + }, + { + "tax_type": { + "account_name": "Input Tax IGST", + "tax_rate": 12.0 + } + } + ] + }, + { + "title": "GST 28%", + "taxes": [ + { + "tax_type": { + "account_name": "Output Tax SGST", + "tax_rate": 14.0 + } + }, + { + "tax_type": { + "account_name": "Output Tax CGST", + "tax_rate": 14.0 + } + }, + { + "tax_type": { + "account_name": "Output Tax IGST", + "tax_rate": 28.0 + } + }, + { + "tax_type": { + "account_name": "Input Tax SGST", + "tax_rate": 14.0 + } + }, + { + "tax_type": { + "account_name": "Input Tax CGST", + "tax_rate": 14.0 + } + }, + { + "tax_type": { + "account_name": "Input Tax IGST", + "tax_rate": 28.0 } } ] From 2acc66db2cc55208c73a045a4f5cd64693d06606 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 21 Apr 2021 11:49:02 +0530 Subject: [PATCH 04/60] fix: Add tax categories on company setup --- .../setup_wizard/data/country_wise_tax.json | 22 +++++++++++++++++++ .../setup_wizard/operations/taxes_setup.py | 10 +++++++++ 2 files changed, 32 insertions(+) diff --git a/erpnext/setup/setup_wizard/data/country_wise_tax.json b/erpnext/setup/setup_wizard/data/country_wise_tax.json index 3119eeec498..e3fde60ef36 100644 --- a/erpnext/setup/setup_wizard/data/country_wise_tax.json +++ b/erpnext/setup/setup_wizard/data/country_wise_tax.json @@ -818,6 +818,28 @@ "India": { "chart_of_accounts": { "*": { + "tax_categories": [ + { + "title": "In-Sate", + "is_inter_state": 0, + "gst_state": "" + }, + { + "title": "Out-Sate", + "is_inter_state": 1, + "gst_state": "" + }, + { + "title": "Reverse Charge", + "is_inter_state": 0, + "gst_state": "" + }, + { + "title": "Registered Composition", + "is_inter_state": 0, + "gst_state": "" + } + ], "item_tax_templates": [ { "title": "GST 9%", diff --git a/erpnext/setup/setup_wizard/operations/taxes_setup.py b/erpnext/setup/setup_wizard/operations/taxes_setup.py index 429a558c589..578a270dd9e 100644 --- a/erpnext/setup/setup_wizard/operations/taxes_setup.py +++ b/erpnext/setup/setup_wizard/operations/taxes_setup.py @@ -78,6 +78,7 @@ def from_detailed_data(company_name, data): sales_tax_templates = tax_templates.get('sales_tax_templates') or tax_templates.get('*') purchase_tax_templates = tax_templates.get('purchase_tax_templates') or tax_templates.get('*') item_tax_templates = tax_templates.get('item_tax_templates') or tax_templates.get('*') + tax_categories = tax_templates.get('tax_categories') if sales_tax_templates: for template in sales_tax_templates: @@ -91,6 +92,10 @@ def from_detailed_data(company_name, data): for template in item_tax_templates: make_item_tax_template(company_name, template) + if tax_categories: + for tax_category in tax_categories: + make_tax_category(tax_category) + def make_taxes_and_charges_template(company_name, doctype, template): template['company'] = company_name @@ -146,6 +151,11 @@ def make_item_tax_template(company_name, template): return frappe.get_doc(template).insert(ignore_permissions=True) +def make_tax_category(tax_category): + """ Make tax category based on title if not already created """ + doctype = 'Tax Category' + if not frappe.db.exists(doctype, tax_category) + frappe.get_doc(tax_category).insert(ignore_permissions=True) def get_or_create_account(company_name, account): """ From 50997709d5f714ed59517177954f0c624a8f7473 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 21 Apr 2021 20:41:57 +0530 Subject: [PATCH 05/60] fix: Update country-wise-tax JSON and tax setup --- .../setup_wizard/data/country_wise_tax.json | 34 ++++++++++++------- .../setup_wizard/operations/taxes_setup.py | 22 ++++++------ 2 files changed, 34 insertions(+), 22 deletions(-) diff --git a/erpnext/setup/setup_wizard/data/country_wise_tax.json b/erpnext/setup/setup_wizard/data/country_wise_tax.json index e3fde60ef36..d21ef03e195 100644 --- a/erpnext/setup/setup_wizard/data/country_wise_tax.json +++ b/erpnext/setup/setup_wizard/data/country_wise_tax.json @@ -820,12 +820,12 @@ "*": { "tax_categories": [ { - "title": "In-Sate", + "title": "In-State", "is_inter_state": 0, "gst_state": "" }, { - "title": "Out-Sate", + "title": "Out-State", "is_inter_state": 1, "gst_state": "" }, @@ -1046,16 +1046,19 @@ { "account_head": { "account_name": "Output Tax SGST", - "tax_rate": 9.00 + "tax_rate": 9.00, + "account_type": "Tax" } }, { "account_head": { "account_name": "Output Tax CGST", - "tax_rate": 9.00 + "tax_rate": 9.00, + "account_type": "Tax" } } - ] + ], + "tax_category": "In-State" }, { "title": "Output GST Out-state", @@ -1063,10 +1066,12 @@ { "account_head": { "account_name": "Output Tax IGST", - "tax_rate": 18.00 + "tax_rate": 18.00, + "account_type": "Tax" } } - ] + ], + "tax_category": "Out-State" } ], "purchase_tax_templates": [ @@ -1077,17 +1082,20 @@ "account_head": { "account_name": "Input Tax SGST", "tax_rate": 9.00, - "root_type": "Asset" + "root_type": "Asset", + "account_type": "Tax" } }, { "account_head": { "account_name": "Input Tax CGST", "tax_rate": 9.00, - "root_type": "Asset" + "root_type": "Asset", + "account_type": "Tax" } } - ] + ], + "tax_category": "In-State" }, { "title": "Input GST Out-state", @@ -1096,10 +1104,12 @@ "account_head": { "account_name": "Input Tax IGST", "tax_rate": 18.00, - "root_type": "Asset" + "root_type": "Asset", + "account_type": "Tax" } } - ] + ], + "tax_category": "Out-State" } ], "*": [ diff --git a/erpnext/setup/setup_wizard/operations/taxes_setup.py b/erpnext/setup/setup_wizard/operations/taxes_setup.py index 578a270dd9e..bbe301bb373 100644 --- a/erpnext/setup/setup_wizard/operations/taxes_setup.py +++ b/erpnext/setup/setup_wizard/operations/taxes_setup.py @@ -80,6 +80,10 @@ def from_detailed_data(company_name, data): item_tax_templates = tax_templates.get('item_tax_templates') or tax_templates.get('*') tax_categories = tax_templates.get('tax_categories') + if tax_categories: + for tax_category in tax_categories: + make_tax_category(tax_category) + if sales_tax_templates: for template in sales_tax_templates: make_taxes_and_charges_template(company_name, 'Sales Taxes and Charges Template', template) @@ -92,10 +96,6 @@ def from_detailed_data(company_name, data): for template in item_tax_templates: make_item_tax_template(company_name, template) - if tax_categories: - for tax_category in tax_categories: - make_tax_category(tax_category) - def make_taxes_and_charges_template(company_name, doctype, template): template['company'] = company_name @@ -154,8 +154,9 @@ def make_item_tax_template(company_name, template): def make_tax_category(tax_category): """ Make tax category based on title if not already created """ doctype = 'Tax Category' - if not frappe.db.exists(doctype, tax_category) - frappe.get_doc(tax_category).insert(ignore_permissions=True) + if not frappe.db.exists(doctype, tax_category): + tax_category['doctype'] = doctype + frappe.get_doc(tax_category).insert(ignore_permissions=True) def get_or_create_account(company_name, account): """ @@ -167,12 +168,13 @@ def get_or_create_account(company_name, account): existing_accounts = frappe.get_list('Account', filters={ - 'company': company_name, - 'root_type': root_type + 'account_name': account.get('account_name'), + 'account_number': account.get('account_number', '') }, or_filters={ - 'account_name': account.get('account_name'), - 'account_number': account.get('account_number') + 'company': company_name, + 'root_type': root_type, + 'is_group': 0 } ) From 66a71bdd1a78c4b9253080ed229a8dc25995c53c Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 21 Apr 2021 20:42:20 +0530 Subject: [PATCH 06/60] fix: Issues on new company setup --- erpnext/regional/india/setup.py | 10 +++++----- .../report/e_invoice_summary/e_invoice_summary.json | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index 9ded8dab5bc..f70ba905068 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -427,13 +427,13 @@ def make_custom_fields(update=True): dict(fieldname='einvoice_section', label='E-Invoice Fields', fieldtype='Section Break', insert_after='gst_vehicle_type', print_hide=1, hidden=1), - + dict(fieldname='ack_no', label='Ack. No.', fieldtype='Data', read_only=1, hidden=1, insert_after='einvoice_section', no_copy=1, print_hide=1), - + dict(fieldname='ack_date', label='Ack. Date', fieldtype='Data', read_only=1, hidden=1, insert_after='ack_no', no_copy=1, print_hide=1), - dict(fieldname='irn_cancel_date', label='Cancel Date', fieldtype='Data', read_only=1, hidden=1, insert_after='ack_date', + dict(fieldname='irn_cancel_date', label='Cancel Date', fieldtype='Data', read_only=1, hidden=1, insert_after='ack_date', no_copy=1, print_hide=1), dict(fieldname='signed_einvoice', label='Signed E-Invoice', fieldtype='Code', options='JSON', hidden=1, insert_after='irn_cancel_date', @@ -696,12 +696,12 @@ def set_tax_withholding_category(company): docs = get_tds_details(accounts, fiscal_year) for d in docs: - try: + if not frappe.db.exists("Tax Withholding Category", d.get("name")): doc = frappe.get_doc(d) doc.flags.ignore_permissions = True doc.flags.ignore_mandatory = True doc.insert() - except frappe.DuplicateEntryError: + else: doc = frappe.get_doc("Tax Withholding Category", d.get("name")) if accounts: diff --git a/erpnext/regional/report/e_invoice_summary/e_invoice_summary.json b/erpnext/regional/report/e_invoice_summary/e_invoice_summary.json index 4deb073a53d..d0000ad50df 100644 --- a/erpnext/regional/report/e_invoice_summary/e_invoice_summary.json +++ b/erpnext/regional/report/e_invoice_summary/e_invoice_summary.json @@ -11,7 +11,7 @@ "is_standard": "Yes", "json": "{}", "letter_head": "Logo", - "modified": "2021-03-12 12:36:48.689413", + "modified": "2021-03-13 12:36:48.689413", "modified_by": "Administrator", "module": "Regional", "name": "E-Invoice Summary", From 204ea1027f84190b5a26e127b88be19f2945337c Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 30 Apr 2021 16:35:52 +0530 Subject: [PATCH 07/60] fix: Ignore validations for Tax Setup --- erpnext/regional/india/setup.py | 10 ++++-- erpnext/setup/doctype/company/company.py | 2 +- .../setup_wizard/operations/taxes_setup.py | 34 ++++++++++++++----- 3 files changed, 35 insertions(+), 11 deletions(-) diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index f70ba905068..aedd6c88aba 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -25,6 +25,7 @@ def setup_company_independent_fixtures(patch=False): frappe.enqueue('erpnext.regional.india.setup.add_hsn_sac_codes', now=frappe.flags.in_test) create_gratuity_rule() add_print_formats() + update_accounts_settings_for_taxes() def add_hsn_sac_codes(): # HSN codes @@ -698,6 +699,7 @@ def set_tax_withholding_category(company): for d in docs: if not frappe.db.exists("Tax Withholding Category", d.get("name")): doc = frappe.get_doc(d) + doc.flags.ignore_validate = True doc.flags.ignore_permissions = True doc.flags.ignore_mandatory = True doc.insert() @@ -714,11 +716,12 @@ def set_tax_withholding_category(company): doc.append("rates", d.get('rates')[0]) doc.flags.ignore_permissions = True + doc.flags.ignore_validdate = True doc.flags.ignore_mandatory = True + doc.flags.ignore_links = True doc.save() def set_tds_account(docs, company): - abbr = frappe.get_value("Company", company, "abbr") parent_account = frappe.db.get_value("Account", filters = {"account_name": "Duties and Taxes", "company": company}) if parent_account: docs.extend([ @@ -877,7 +880,6 @@ def get_tds_details(accounts, fiscal_year): ] def create_gratuity_rule(): - # Standard Indain Gratuity Rule if not frappe.db.exists("Gratuity Rule", "Indian Standard Gratuity Rule"): rule = frappe.new_doc("Gratuity Rule") @@ -895,3 +897,7 @@ def create_gratuity_rule(): rule.flags.ignore_mandatory = True rule.save() + +def update_accounts_settings_for_taxes(): + if frappe.db.count('Company') == 1: + frappe.db.set_value('Accounts Settings', None, "add_taxes_from_item_tax_template", 0) \ No newline at end of file diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py index 64e027dd28b..aa4bc98875f 100644 --- a/erpnext/setup/doctype/company/company.py +++ b/erpnext/setup/doctype/company/company.py @@ -110,7 +110,7 @@ class Company(NestedSet): self.create_default_warehouses() if frappe.flags.country_change: - install_country_fixtures(self.name) + install_country_fixtures(self.name, self.country) self.create_default_tax_template() if not frappe.db.get_value("Department", {"company": self.name}): diff --git a/erpnext/setup/setup_wizard/operations/taxes_setup.py b/erpnext/setup/setup_wizard/operations/taxes_setup.py index bbe301bb373..a644da92925 100644 --- a/erpnext/setup/setup_wizard/operations/taxes_setup.py +++ b/erpnext/setup/setup_wizard/operations/taxes_setup.py @@ -123,8 +123,11 @@ def make_taxes_and_charges_template(company_name, doctype, template): if fieldname not in tax_row: tax_row[fieldname] = default_value - return frappe.get_doc(template).insert(ignore_permissions=True) - + doc = frappe.get_doc(template) + doc.flags.ignore_links = True + doc.flags.ignore_validate = True + doc.insert(ignore_permissions=True) + return doc def make_item_tax_template(company_name, template): """Create an Item Tax Template. @@ -149,14 +152,21 @@ def make_item_tax_template(company_name, template): if 'tax_rate' not in tax_row: tax_row['tax_rate'] = account_data.get('tax_rate') - return frappe.get_doc(template).insert(ignore_permissions=True) + doc = frappe.get_doc(template) + doc.flags.ignore_links = True + doc.flags.ignore_validate = True + doc.insert(ignore_permissions=True) + return doc def make_tax_category(tax_category): """ Make tax category based on title if not already created """ doctype = 'Tax Category' if not frappe.db.exists(doctype, tax_category): tax_category['doctype'] = doctype - frappe.get_doc(tax_category).insert(ignore_permissions=True) + doc = frappe.get_doc(tax_category) + doc.flags.ignore_links = True + doc.flags.ignore_validate = True + doc.insert(ignore_permissions=True) def get_or_create_account(company_name, account): """ @@ -169,7 +179,8 @@ def get_or_create_account(company_name, account): existing_accounts = frappe.get_list('Account', filters={ 'account_name': account.get('account_name'), - 'account_number': account.get('account_number', '') + 'account_number': account.get('account_number', ''), + 'company': company_name }, or_filters={ 'company': company_name, @@ -191,8 +202,11 @@ def get_or_create_account(company_name, account): account['root_type'] = root_type account['is_group'] = 0 - return frappe.get_doc(account).insert(ignore_permissions=True, ignore_mandatory=True) - + doc = frappe.get_doc(account) + doc.flags.ignore_links = True + doc.flags.ignore_validate = True + doc.insert(ignore_permissions=True, ignore_mandatory=True) + return doc def get_or_create_tax_group(company_name, root_type): # Look for a group account of type 'Tax' @@ -237,7 +251,11 @@ def get_or_create_tax_group(company_name, root_type): 'account_type': 'Tax', 'account_name': account_name, 'parent_account': root_account.name - }).insert(ignore_permissions=True) + }) + + tax_group_account.flags.ignore_links = True + tax_group_account.flags.ignore_validate = True + tax_group_account.insert(ignore_permissions=True) tax_group_name = tax_group_account.name From a66184fe3cd9527b8b13c9b4897b46ec0dcbed8c Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 2 May 2021 12:22:16 +0530 Subject: [PATCH 08/60] fix: Remove redundant get_doc --- erpnext/regional/india/setup.py | 2 +- erpnext/setup/doctype/company/company.py | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index aedd6c88aba..71b5661a4e1 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -646,7 +646,7 @@ def make_custom_fields(update=True): def make_fixtures(company=None): docs = [] - company = company.name if company else frappe.db.get_value("Global Defaults", None, "default_company") + company = company or frappe.db.get_value("Global Defaults", None, "default_company") set_salary_components(docs) set_tds_account(docs, company) diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py index aa4bc98875f..779e976181c 100644 --- a/erpnext/setup/doctype/company/company.py +++ b/erpnext/setup/doctype/company/company.py @@ -435,13 +435,12 @@ def get_name_with_abbr(name, company): return " - ".join(parts) -def install_country_fixtures(company): - company_doc = frappe.get_doc("Company", company) - path = frappe.get_app_path('erpnext', 'regional', frappe.scrub(company_doc.country)) +def install_country_fixtures(company, country): + path = frappe.get_app_path('erpnext', 'regional', frappe.scrub(country)) if os.path.exists(path.encode("utf-8")): try: - module_name = "erpnext.regional.{0}.setup.setup".format(frappe.scrub(company_doc.country)) - frappe.get_attr(module_name)(company_doc, False) + module_name = "erpnext.regional.{0}.setup.setup".format(frappe.scrub(country)) + frappe.get_attr(module_name)(company, False) except Exception as e: frappe.log_error() frappe.throw(_("Failed to setup defaults for country {0}. Please contact support@erpnext.com").format(frappe.bold(company_doc.country))) From f6610c96dd6cf3585dcbadfaf90bc973a60bb40f Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 2 May 2021 12:23:11 +0530 Subject: [PATCH 09/60] fix: Gracefully handle duplicate bank account name to make setup faster --- erpnext/accounts/doctype/account/account.py | 6 +++++ .../chart_of_accounts/chart_of_accounts.py | 18 ------------- erpnext/public/js/setup_wizard.js | 26 ------------------- 3 files changed, 6 insertions(+), 44 deletions(-) diff --git a/erpnext/accounts/doctype/account/account.py b/erpnext/accounts/doctype/account/account.py index 06068238213..60269943cf7 100644 --- a/erpnext/accounts/doctype/account/account.py +++ b/erpnext/accounts/doctype/account/account.py @@ -28,6 +28,12 @@ class Account(NestedSet): from erpnext.accounts.utils import get_autoname_with_number self.name = get_autoname_with_number(self.account_number, self.account_name, None, self.company) + def before_insert(self): + # Update Bank account name if conflicting with any other account + if frappe.flags.in_install and self.account_type == 'Bank': + if frappe.db.get_value('Account', {'account_name': self.account_name}): + self.account_name = self.account_name + '-1' + def validate(self): from erpnext.accounts.utils import validate_field_number if frappe.local.flags.allow_unverified_charts: 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 0e3b24cda3d..9d3174204bd 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 @@ -188,24 +188,6 @@ def build_account_tree(tree, parent, all_accounts): # call recursively to build a subtree for current account build_account_tree(tree[child.account_name], child, all_accounts) -@frappe.whitelist() -def validate_bank_account(coa, bank_account): - accounts = [] - chart = get_chart(coa) - - if chart: - def _get_account_names(account_master): - for account_name, child in iteritems(account_master): - if account_name not in ["account_number", "account_type", - "root_type", "is_group", "tax_rate"]: - accounts.append(account_name) - - _get_account_names(child) - - _get_account_names(chart) - - return (bank_account in accounts) - @frappe.whitelist() def build_tree_from_json(chart_template, chart_data=None): ''' get chart template from its folder and parse the json to be rendered as tree ''' diff --git a/erpnext/public/js/setup_wizard.js b/erpnext/public/js/setup_wizard.js index ef03b01698c..a3045724fee 100644 --- a/erpnext/public/js/setup_wizard.js +++ b/erpnext/public/js/setup_wizard.js @@ -139,36 +139,10 @@ erpnext.setup.slides_settings = [ }, validate: function () { - let me = this; - let exist; - if (!this.validate_fy_dates()) { return false; } - // Validate bank name - if(me.values.bank_account){ - frappe.call({ - async: false, - method: "erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts.validate_bank_account", - args: { - "coa": me.values.chart_of_accounts, - "bank_account": me.values.bank_account - }, - callback: function (r) { - if(r.message){ - exist = r.message; - me.get_field("bank_account").set_value(""); - let message = __('Account {0} already exists. Please enter a different name for your bank account.', - [me.values.bank_account] - ); - frappe.msgprint(message); - } - } - }); - return !exist; // Return False if exist = true - } - return true; }, From 72e602ae548a4dd16ace8788f574c09374941b68 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 2 May 2021 18:22:29 +0530 Subject: [PATCH 10/60] fix: Add validation for GST Settings --- .../regional/doctype/gst_settings/gst_settings.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/erpnext/regional/doctype/gst_settings/gst_settings.py b/erpnext/regional/doctype/gst_settings/gst_settings.py index bc956e9fa88..af3d92e59a7 100644 --- a/erpnext/regional/doctype/gst_settings/gst_settings.py +++ b/erpnext/regional/doctype/gst_settings/gst_settings.py @@ -19,6 +19,21 @@ class GSTSettings(Document): from tabAddress where country = "India" and ifnull(gstin, '')!='' ''') self.set_onload('data', data) + def validate(self): + # Validate duplicate accounts + self.validate_duplicate_accounts() + + def validate_duplicate_accounts(self): + account_list = [] + for account in self.get('gst_accounts'): + for fieldname in ['cgst_account', 'sgst_account', 'igst_account', 'cess_account']: + if account.get(fieldname) in account_list: + frappe.throw(_("Account {0} appears multiple times").format( + frappe.bold(account.get(fieldname)))) + + if account.get(fieldname): + account_list.append(account.get(fieldname)) + @frappe.whitelist() def send_reminder(): frappe.has_permission('GST Settings', throw=True) From 1bac72b74d41a7284ca5028015a386b6cdaa61b5 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 2 May 2021 22:29:48 +0530 Subject: [PATCH 11/60] fix: Add GST accounts to GST Settings --- erpnext/regional/india/setup.py | 52 ++++++++++++++++++++++++ erpnext/setup/doctype/company/company.py | 2 +- 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index 71b5661a4e1..2c0a285546c 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -15,6 +15,7 @@ def setup(company=None, patch=True): setup_company_independent_fixtures(patch=patch) if not patch: make_fixtures(company) + setup_gst_settings(company) # TODO: for all countries def setup_company_independent_fixtures(patch=False): @@ -664,6 +665,57 @@ def make_fixtures(company=None): # create records for Tax Withholding Category set_tax_withholding_category(company) +def setup_gst_settings(company): + # Will only add default GST accounts if present + input_account_names = ['Input Tax CGST', 'Input Tax SGST', 'Input Tax IGST'] + output_account_names = ['Output Tax CGST', 'Output Tax SGST', 'Output Tax IGST'] + gst_settings = frappe.get_single('GST Settings') + existing_account_list = [] + + for account in gst_settings.get('gst_accounts'): + for key in ['cgst_account', 'sgst_account', 'igst_account']: + existing_account_list.append(account.get(key)) + + gst_accounts = frappe._dict(frappe.get_all("Account", + {'company': company, 'name': ('like', "%GST%")}, ['account_name', 'name'], as_list=1)) + + all_input_account_exists = 0 + all_output_account_exists = 0 + + for account in input_account_names: + if not gst_accounts.get(account): + all_input_account_exists = 1 + + # Check if already added in GST Settings + if gst_accounts.get(account) in existing_account_list: + all_input_account_exists = 1 + + for account in output_account_names: + if not gst_accounts.get(account): + all_output_account_exists = 1 + + # Check if already added in GST Settings + if gst_accounts.get(account) in existing_account_list: + all_output_account_exists = 1 + + if not all_input_account_exists: + gst_settings.append('gst_accounts', { + 'company': company, + 'cgst_account': gst_accounts.get(input_account_names[0]), + 'sgst_account': gst_accounts.get(input_account_names[1]), + 'igst_account': gst_accounts.get(input_account_names[2]) + }) + + if not all_output_account_exists: + gst_settings.append('gst_accounts', { + 'company': company, + 'cgst_account': gst_accounts.get(output_account_names[0]), + 'sgst_account': gst_accounts.get(output_account_names[1]), + 'igst_account': gst_accounts.get(output_account_names[2]) + }) + + gst_settings.save() + def set_salary_components(docs): docs.extend([ {'doctype': 'Salary Component', 'salary_component': 'Professional Tax', diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py index 779e976181c..9a74a2e68ef 100644 --- a/erpnext/setup/doctype/company/company.py +++ b/erpnext/setup/doctype/company/company.py @@ -443,7 +443,7 @@ def install_country_fixtures(company, country): frappe.get_attr(module_name)(company, False) except Exception as e: frappe.log_error() - frappe.throw(_("Failed to setup defaults for country {0}. Please contact support@erpnext.com").format(frappe.bold(company_doc.country))) + frappe.throw(_("Failed to setup defaults for country {0}. Please contact support@erpnext.com").format(frappe.bold(country))) def update_company_current_month_sales(company): From 48b1a82fa1d873470c6b5513742b104ef3f3573e Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sat, 29 May 2021 23:54:51 +0530 Subject: [PATCH 12/60] fix: Add accounts and templates for reverse charge --- erpnext/accounts/doctype/account/account.py | 6 - erpnext/accounts/utils.py | 2 +- erpnext/regional/india/setup.py | 68 +++++------ erpnext/setup/doctype/company/company.py | 2 +- .../setup_wizard/data/country_wise_tax.json | 115 +++++++++++++++++- .../setup_wizard/operations/company_setup.py | 23 ---- 6 files changed, 148 insertions(+), 68 deletions(-) diff --git a/erpnext/accounts/doctype/account/account.py b/erpnext/accounts/doctype/account/account.py index 645f49bcdc4..1be2fbf5c81 100644 --- a/erpnext/accounts/doctype/account/account.py +++ b/erpnext/accounts/doctype/account/account.py @@ -28,12 +28,6 @@ class Account(NestedSet): from erpnext.accounts.utils import get_autoname_with_number self.name = get_autoname_with_number(self.account_number, self.account_name, None, self.company) - def before_insert(self): - # Update Bank account name if conflicting with any other account - if frappe.flags.in_install and self.account_type == 'Bank': - if frappe.db.get_value('Account', {'account_name': self.account_name}): - self.account_name = self.account_name + '-1' - def validate(self): from erpnext.accounts.utils import validate_field_number if frappe.local.flags.allow_unverified_charts: diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 5a64e27ccb7..121589930b3 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -784,7 +784,7 @@ def get_children(doctype, parent, company, is_root=False): return acc def create_payment_gateway_account(gateway, payment_channel="Email"): - from erpnext.setup.setup_wizard.operations.company_setup import create_bank_account + from erpnext.setup.setup_wizard.operations.install_fixtures import create_bank_account company = frappe.db.get_value("Global Defaults", None, "default_company") if not company: diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index 28f49e0a7e5..d79ce64fb35 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -698,6 +698,7 @@ def setup_gst_settings(company): # Will only add default GST accounts if present input_account_names = ['Input Tax CGST', 'Input Tax SGST', 'Input Tax IGST'] output_account_names = ['Output Tax CGST', 'Output Tax SGST', 'Output Tax IGST'] + rcm_accounts = ['Input Tax CGST RCM', 'Input Tax SGST RCM', 'Input Tax IGST RCM'] gst_settings = frappe.get_single('GST Settings') existing_account_list = [] @@ -706,45 +707,40 @@ def setup_gst_settings(company): existing_account_list.append(account.get(key)) gst_accounts = frappe._dict(frappe.get_all("Account", - {'company': company, 'name': ('like', "%GST%")}, ['account_name', 'name'], as_list=1)) + {'company': company, 'account_name': ('in', input_account_names + + output_account_names + rcm_accounts)}, ['account_name', 'name'], as_list=1)) - all_input_account_exists = 0 - all_output_account_exists = 0 - - for account in input_account_names: - if not gst_accounts.get(account): - all_input_account_exists = 1 - - # Check if already added in GST Settings - if gst_accounts.get(account) in existing_account_list: - all_input_account_exists = 1 - - for account in output_account_names: - if not gst_accounts.get(account): - all_output_account_exists = 1 - - # Check if already added in GST Settings - if gst_accounts.get(account) in existing_account_list: - all_output_account_exists = 1 - - if not all_input_account_exists: - gst_settings.append('gst_accounts', { - 'company': company, - 'cgst_account': gst_accounts.get(input_account_names[0]), - 'sgst_account': gst_accounts.get(input_account_names[1]), - 'igst_account': gst_accounts.get(input_account_names[2]) - }) - - if not all_output_account_exists: - gst_settings.append('gst_accounts', { - 'company': company, - 'cgst_account': gst_accounts.get(output_account_names[0]), - 'sgst_account': gst_accounts.get(output_account_names[1]), - 'igst_account': gst_accounts.get(output_account_names[2]) - }) + add_accounts_in_gst_settings(company, input_account_names, gst_accounts, + existing_account_list, gst_settings) + add_accounts_in_gst_settings(company, output_account_names, gst_accounts, + existing_account_list, gst_settings) + add_accounts_in_gst_settings(company, rcm_accounts, gst_accounts, + existing_account_list, gst_settings, is_reverse_charge=1) gst_settings.save() +def add_accounts_in_gst_settings(company, account_names, gst_accounts, + existing_account_list, gst_settings, is_reverse_charge=0): + accounts_not_added = 1 + + for account in account_names: + # Default Account Added does not exists + if not gst_accounts.get(account): + accounts_not_added = 0 + + # Check if already added in GST Settings + if gst_accounts.get(account) in existing_account_list: + accounts_not_added = 0 + + if accounts_not_added: + gst_settings.append('gst_accounts', { + 'company': company, + 'cgst_account': gst_accounts.get(account_names[0]), + 'sgst_account': gst_accounts.get(account_names[1]), + 'igst_account': gst_accounts.get(account_names[2]), + 'is_reverse_charge_account': is_reverse_charge + }) + def set_salary_components(docs): docs.extend([ {'doctype': 'Salary Component', 'salary_component': 'Professional Tax', @@ -797,7 +793,7 @@ def set_tax_withholding_category(company): doc.append("rates", d.get('rates')[0]) doc.flags.ignore_permissions = True - doc.flags.ignore_validdate = True + doc.flags.ignore_validate = True doc.flags.ignore_mandatory = True doc.flags.ignore_links = True doc.save() diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py index 61d63a3f29d..ff96c0b4c47 100644 --- a/erpnext/setup/doctype/company/company.py +++ b/erpnext/setup/doctype/company/company.py @@ -110,8 +110,8 @@ class Company(NestedSet): self.create_default_warehouses() if frappe.flags.country_change: - install_country_fixtures(self.name, self.country) self.create_default_tax_template() + install_country_fixtures(self.name, self.country) if not frappe.db.get_value("Department", {"company": self.name}): from erpnext.setup.setup_wizard.operations.install_fixtures import install_post_company_fixtures diff --git a/erpnext/setup/setup_wizard/data/country_wise_tax.json b/erpnext/setup/setup_wizard/data/country_wise_tax.json index d21ef03e195..ee42a87320e 100644 --- a/erpnext/setup/setup_wizard/data/country_wise_tax.json +++ b/erpnext/setup/setup_wizard/data/country_wise_tax.json @@ -830,7 +830,12 @@ "gst_state": "" }, { - "title": "Reverse Charge", + "title": "Reverse Charge In-State", + "is_inter_state": 0, + "gst_state": "" + }, + { + "title": "Reverse Charge Out-State", "is_inter_state": 0, "gst_state": "" }, @@ -879,6 +884,24 @@ "account_name": "Input Tax IGST", "tax_rate": 18.00 } + }, + { + "tax_type": { + "account_name": "Input Tax SGST RCM", + "tax_rate": 9.00 + } + }, + { + "tax_type": { + "account_name": "Input Tax CGST RCM", + "tax_rate": 9.00 + } + }, + { + "tax_type": { + "account_name": "Input Tax IGST RCM", + "tax_rate": 18.00 + } } ] }, @@ -920,6 +943,24 @@ "account_name": "Input Tax IGST", "tax_rate": 5.0 } + }, + { + "tax_type": { + "account_name": "Input Tax SGST RCM", + "tax_rate": 2.50 + } + }, + { + "tax_type": { + "account_name": "Input Tax CGST RCM", + "tax_rate": 2.50 + } + }, + { + "tax_type": { + "account_name": "Input Tax IGST RCM", + "tax_rate": 5.00 + } } ] }, @@ -961,6 +1002,24 @@ "account_name": "Input Tax IGST", "tax_rate": 12.0 } + }, + { + "tax_type": { + "account_name": "Input Tax SGST RCM", + "tax_rate": 6.00 + } + }, + { + "tax_type": { + "account_name": "Input Tax CGST RCM", + "tax_rate": 6.00 + } + }, + { + "tax_type": { + "account_name": "Input Tax IGST RCM", + "tax_rate": 12.00 + } } ] }, @@ -1002,6 +1061,24 @@ "account_name": "Input Tax IGST", "tax_rate": 28.0 } + }, + { + "tax_type": { + "account_name": "Input Tax SGST RCM", + "tax_rate": 14.00 + } + }, + { + "tax_type": { + "account_name": "Input Tax CGST RCM", + "tax_rate": 14.00 + } + }, + { + "tax_type": { + "account_name": "Input Tax IGST RCM", + "tax_rate": 28.00 + } } ] }, @@ -1110,6 +1187,42 @@ } ], "tax_category": "Out-State" + }, + { + "title": "Input GST RCM In-state", + "taxes": [ + { + "account_head": { + "account_name": "Input Tax SGST RCM", + "tax_rate": 9.00, + "root_type": "Asset", + "account_type": "Tax" + } + }, + { + "account_head": { + "account_name": "Input Tax CGST RCM", + "tax_rate": 9.00, + "root_type": "Asset", + "account_type": "Tax" + } + } + ], + "tax_category": "Reverse Charge In-State" + }, + { + "title": "Input GST RCM Out-state", + "taxes": [ + { + "account_head": { + "account_name": "Input Tax IGST RCM", + "tax_rate": 18.00, + "root_type": "Asset", + "account_type": "Tax" + } + } + ], + "tax_category": "Reverse Charge Out-State" } ], "*": [ diff --git a/erpnext/setup/setup_wizard/operations/company_setup.py b/erpnext/setup/setup_wizard/operations/company_setup.py index 3f0bb146499..4edf9485dc1 100644 --- a/erpnext/setup/setup_wizard/operations/company_setup.py +++ b/erpnext/setup/setup_wizard/operations/company_setup.py @@ -42,29 +42,6 @@ def enable_shopping_cart(args): 'quotation_series': "QTN-", }).insert() -def create_bank_account(args): - if args.get("bank_account"): - company_name = args.get('company_name') - bank_account_group = frappe.db.get_value("Account", - {"account_type": "Bank", "is_group": 1, "root_type": "Asset", - "company": company_name}) - if bank_account_group: - bank_account = frappe.get_doc({ - "doctype": "Account", - 'account_name': args.get("bank_account"), - 'parent_account': bank_account_group, - 'is_group':0, - 'company': company_name, - "account_type": "Bank", - }) - try: - return bank_account.insert() - except RootNotEditable: - frappe.throw(_("Bank account cannot be named as {0}").format(args.get("bank_account"))) - except frappe.DuplicateEntryError: - # bank account same as a CoA entry - pass - def create_email_digest(): from frappe.utils.user import get_system_managers system_managers = get_system_managers(only_name=True) From b3ed807b70d5bd1b1b94442fd0c4f1d774846d9d Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 2 Jun 2021 13:26:21 +0530 Subject: [PATCH 13/60] fix: Regional settings setup --- erpnext/regional/india/setup.py | 3 +-- erpnext/setup/doctype/company/company.py | 2 +- erpnext/setup/setup_wizard/operations/taxes_setup.py | 12 ++++++++++++ 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index d79ce64fb35..fa9f1b7e9ef 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -15,7 +15,6 @@ def setup(company=None, patch=True): setup_company_independent_fixtures(patch=patch) if not patch: make_fixtures(company) - setup_gst_settings(company) # TODO: for all countries def setup_company_independent_fixtures(patch=False): @@ -694,7 +693,7 @@ def make_fixtures(company=None): # create records for Tax Withholding Category set_tax_withholding_category(company) -def setup_gst_settings(company): +def update_regional_tax_settings(country, company): # Will only add default GST accounts if present input_account_names = ['Input Tax CGST', 'Input Tax SGST', 'Input Tax IGST'] output_account_names = ['Output Tax CGST', 'Output Tax SGST', 'Output Tax IGST'] diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py index ff96c0b4c47..61d63a3f29d 100644 --- a/erpnext/setup/doctype/company/company.py +++ b/erpnext/setup/doctype/company/company.py @@ -110,8 +110,8 @@ class Company(NestedSet): self.create_default_warehouses() if frappe.flags.country_change: - self.create_default_tax_template() install_country_fixtures(self.name, self.country) + self.create_default_tax_template() if not frappe.db.get_value("Department", {"company": self.name}): from erpnext.setup.setup_wizard.operations.install_fixtures import install_post_company_fixtures diff --git a/erpnext/setup/setup_wizard/operations/taxes_setup.py b/erpnext/setup/setup_wizard/operations/taxes_setup.py index a644da92925..6ac561223ab 100644 --- a/erpnext/setup/setup_wizard/operations/taxes_setup.py +++ b/erpnext/setup/setup_wizard/operations/taxes_setup.py @@ -24,6 +24,7 @@ def setup_taxes_and_charges(company_name: str, country: str): country_wise_tax = simple_to_detailed(country_wise_tax) from_detailed_data(company_name, country_wise_tax.get('chart_of_accounts')) + update_regional_tax_settings(country, company_name) def simple_to_detailed(templates): @@ -97,6 +98,17 @@ def from_detailed_data(company_name, data): make_item_tax_template(company_name, template) +def update_regional_tax_settings(country, company): + path = frappe.get_app_path('erpnext', 'regional', frappe.scrub(country)) + if os.path.exists(path.encode("utf-8")): + try: + module_name = "erpnext.regional.{0}.setup.update_regional_tax_settings".format(frappe.scrub(country)) + frappe.get_attr(module_name)(country, company) + except Exception as e: + # Log error and ignore if failed to setup regional tax settings + frappe.log_error() + pass + def make_taxes_and_charges_template(company_name, doctype, template): template['company'] = company_name template['doctype'] = doctype From 7c7c084159b68f99927cc28b4b3b88fa8c415b80 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 2 Jun 2021 14:12:28 +0530 Subject: [PATCH 14/60] fix: Check for tax category --- erpnext/setup/setup_wizard/operations/taxes_setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/setup/setup_wizard/operations/taxes_setup.py b/erpnext/setup/setup_wizard/operations/taxes_setup.py index 4c496090391..9a830d4f2e2 100644 --- a/erpnext/setup/setup_wizard/operations/taxes_setup.py +++ b/erpnext/setup/setup_wizard/operations/taxes_setup.py @@ -176,7 +176,7 @@ def make_item_tax_template(company_name, template): def make_tax_category(tax_category): """ Make tax category based on title if not already created """ doctype = 'Tax Category' - if not frappe.db.exists(doctype, tax_category): + if not frappe.db.exists(doctype, tax_category['title']): tax_category['doctype'] = doctype doc = frappe.get_doc(tax_category) doc.flags.ignore_links = True From ea2c02738d55d17c2d8161bdae5ce6454f7d4fa9 Mon Sep 17 00:00:00 2001 From: marination Date: Tue, 22 Jun 2021 21:35:25 +0530 Subject: [PATCH 15/60] fix: Include Stock Reco logic in update_qty_in_future_sle --- erpnext/stock/stock_ledger.py | 75 ++++++++++++++++++++++------------- 1 file changed, 48 insertions(+), 27 deletions(-) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 9fe89c3fa59..94bd3077a73 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -215,7 +215,7 @@ class update_entries_after(object): """ self.data.setdefault(args.warehouse, frappe._dict()) warehouse_dict = self.data[args.warehouse] - previous_sle = self.get_previous_sle_of_current_voucher(args) + previous_sle = get_previous_sle_of_current_voucher(args) warehouse_dict.previous_sle = previous_sle for key in ("qty_after_transaction", "valuation_rate", "stock_value"): @@ -227,29 +227,6 @@ class update_entries_after(object): "stock_value_difference": 0.0 }) - def get_previous_sle_of_current_voucher(self, args): - """get stock ledger entries filtered by specific posting datetime conditions""" - - args['time_format'] = '%H:%i:%s' - if not args.get("posting_date"): - args["posting_date"] = "1900-01-01" - if not args.get("posting_time"): - args["posting_time"] = "00:00" - - sle = frappe.db.sql(""" - select *, timestamp(posting_date, posting_time) as "timestamp" - from `tabStock Ledger Entry` - where item_code = %(item_code)s - and warehouse = %(warehouse)s - and is_cancelled = 0 - and timestamp(posting_date, time_format(posting_time, %(time_format)s)) < timestamp(%(posting_date)s, time_format(%(posting_time)s, %(time_format)s)) - order by timestamp(posting_date, posting_time) desc, creation desc - limit 1 - for update""", args, as_dict=1) - - return sle[0] if sle else frappe._dict() - - def build(self): from erpnext.controllers.stock_controller import future_sle_exists @@ -734,6 +711,35 @@ class update_entries_after(object): bin_doc.flags.via_stock_ledger_entry = True bin_doc.save(ignore_permissions=True) + +def get_previous_sle_of_current_voucher(args, exclude_current_voucher=False): + """get stock ledger entries filtered by specific posting datetime conditions""" + + args['time_format'] = '%H:%i:%s' + if not args.get("posting_date"): + args["posting_date"] = "1900-01-01" + if not args.get("posting_time"): + args["posting_time"] = "00:00" + + voucher_condition = "" + if exclude_current_voucher: + voucher_no = args.get("voucher_no") + voucher_condition = f"and voucher_no != '{voucher_no}'" + + sle = frappe.db.sql(""" + select *, timestamp(posting_date, posting_time) as "timestamp" + from `tabStock Ledger Entry` + where item_code = %(item_code)s + and warehouse = %(warehouse)s + and is_cancelled = 0 + {voucher_condition} + and timestamp(posting_date, time_format(posting_time, %(time_format)s)) < timestamp(%(posting_date)s, time_format(%(posting_time)s, %(time_format)s)) + order by timestamp(posting_date, posting_time) desc, creation desc + limit 1 + for update""".format(voucher_condition=voucher_condition), args, as_dict=1) + + return sle[0] if sle else frappe._dict() + def get_previous_sle(args, for_update=False): """ get the last sle on or before the current time-bucket, @@ -862,9 +868,24 @@ def get_valuation_rate(item_code, warehouse, voucher_type, voucher_no, return valuation_rate def update_qty_in_future_sle(args, allow_negative_stock=None): + """Recalculate Qty after Transaction in future SLEs based on current SLE.""" + qty_shift = args.actual_qty + + # find difference/shift in qty caused by stock reconciliation + if args.voucher_type == "Stock Reconciliation": + last_balance = get_previous_sle_of_current_voucher( + args, + exclude_current_voucher=True + ).get("qty_after_transaction") + if last_balance is not None: + stock_reco_qty_shift = flt(args.qty_after_transaction) - flt(last_balance) + else: + stock_reco_qty_shift = args.qty_after_transaction + qty_shift = stock_reco_qty_shift + frappe.db.sql(""" update `tabStock Ledger Entry` - set qty_after_transaction = qty_after_transaction + {qty} + set qty_after_transaction = qty_after_transaction + {qty_shift} where item_code = %(item_code)s and warehouse = %(warehouse)s @@ -876,7 +897,7 @@ def update_qty_in_future_sle(args, allow_negative_stock=None): and creation > %(creation)s ) ) - """.format(qty=args.actual_qty), args) + """.format(qty_shift=qty_shift), args) validate_negative_qty_in_future_sle(args, allow_negative_stock) @@ -884,7 +905,7 @@ def validate_negative_qty_in_future_sle(args, allow_negative_stock=None): allow_negative_stock = allow_negative_stock \ or cint(frappe.db.get_single_value("Stock Settings", "allow_negative_stock")) - if args.actual_qty < 0 and not allow_negative_stock: + if (args.actual_qty < 0 or args.voucher_type == "Stock Reconciliation") and not allow_negative_stock: sle = get_future_sle_with_negative_qty(args) if sle: message = _("{0} units of {1} needed in {2} on {3} {4} for {5} to complete this transaction.").format( From 40a4330ec1240492484d67bbdd0eeb777440c34f Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 23 Jun 2021 12:38:37 +0530 Subject: [PATCH 16/60] fix: Move tax categories up in country wise json --- .../setup_wizard/data/country_wise_tax.json | 54 +++++++++---------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/erpnext/setup/setup_wizard/data/country_wise_tax.json b/erpnext/setup/setup_wizard/data/country_wise_tax.json index 6df57f17380..e36bf5cbe0e 100644 --- a/erpnext/setup/setup_wizard/data/country_wise_tax.json +++ b/erpnext/setup/setup_wizard/data/country_wise_tax.json @@ -1164,35 +1164,35 @@ }, "India": { + "tax_categories": [ + { + "title": "In-State", + "is_inter_state": 0, + "gst_state": "" + }, + { + "title": "Out-State", + "is_inter_state": 1, + "gst_state": "" + }, + { + "title": "Reverse Charge In-State", + "is_inter_state": 0, + "gst_state": "" + }, + { + "title": "Reverse Charge Out-State", + "is_inter_state": 1, + "gst_state": "" + }, + { + "title": "Registered Composition", + "is_inter_state": 0, + "gst_state": "" + } + ], "chart_of_accounts": { "*": { - "tax_categories": [ - { - "title": "In-State", - "is_inter_state": 0, - "gst_state": "" - }, - { - "title": "Out-State", - "is_inter_state": 1, - "gst_state": "" - }, - { - "title": "Reverse Charge In-State", - "is_inter_state": 0, - "gst_state": "" - }, - { - "title": "Reverse Charge Out-State", - "is_inter_state": 0, - "gst_state": "" - }, - { - "title": "Registered Composition", - "is_inter_state": 0, - "gst_state": "" - } - ], "item_tax_templates": [ { "title": "GST 9%", From 208b5f9e7303bd89b694aa569c6143d4656c4c6b Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 23 Jun 2021 12:44:56 +0530 Subject: [PATCH 17/60] chore: Add comments --- erpnext/setup/setup_wizard/operations/taxes_setup.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/erpnext/setup/setup_wizard/operations/taxes_setup.py b/erpnext/setup/setup_wizard/operations/taxes_setup.py index a664914f08e..d5682b6f4ca 100644 --- a/erpnext/setup/setup_wizard/operations/taxes_setup.py +++ b/erpnext/setup/setup_wizard/operations/taxes_setup.py @@ -143,6 +143,9 @@ def make_taxes_and_charges_template(company_name, doctype, template): tax_row[fieldname] = default_value doc = frappe.get_doc(template) + + # Data in country wise json is already pre validated, hence validations can be ignored + # Ingone validations to make doctypes faster doc.flags.ignore_links = True doc.flags.ignore_validate = True doc.insert(ignore_permissions=True) @@ -172,6 +175,9 @@ def make_item_tax_template(company_name, template): tax_row['tax_rate'] = account_data.get('tax_rate') doc = frappe.get_doc(template) + + # Data in country wise json is already pre validated, hence validations can be ignored + # Ingone validations to make doctypes faster doc.flags.ignore_links = True doc.flags.ignore_validate = True doc.insert(ignore_permissions=True) From 35e11fbea6baeb140a08bee8a30a2d156f20e7b1 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 23 Jun 2021 13:17:01 +0530 Subject: [PATCH 18/60] fix: Tests --- erpnext/setup/setup_wizard/operations/install_fixtures.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/setup/setup_wizard/operations/install_fixtures.py b/erpnext/setup/setup_wizard/operations/install_fixtures.py index 5c725d332de..9c7d9e5259d 100644 --- a/erpnext/setup/setup_wizard/operations/install_fixtures.py +++ b/erpnext/setup/setup_wizard/operations/install_fixtures.py @@ -480,7 +480,7 @@ def update_stock_settings(): stock_settings.save() def create_bank_account(args): - if not args.bank_account: + if not args.get('bank_account'): return company_name = args.company_name From 0bfffddac470cb003efba73d4ee6c1bca2620609 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 23 Jun 2021 15:37:17 +0530 Subject: [PATCH 19/60] fix: Test Cases --- erpnext/setup/setup_wizard/operations/install_fixtures.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/setup/setup_wizard/operations/install_fixtures.py b/erpnext/setup/setup_wizard/operations/install_fixtures.py index 9c7d9e5259d..7dfb9f4d3c2 100644 --- a/erpnext/setup/setup_wizard/operations/install_fixtures.py +++ b/erpnext/setup/setup_wizard/operations/install_fixtures.py @@ -483,14 +483,14 @@ def create_bank_account(args): if not args.get('bank_account'): return - company_name = args.company_name + company_name = args.get('company_name') bank_account_group = frappe.db.get_value("Account", {"account_type": "Bank", "is_group": 1, "root_type": "Asset", "company": company_name}) if bank_account_group: bank_account = frappe.get_doc({ "doctype": "Account", - 'account_name': args.bank_account, + 'account_name': args.get('bank_account'), 'parent_account': bank_account_group, 'is_group':0, 'company': company_name, @@ -499,10 +499,10 @@ def create_bank_account(args): try: doc = bank_account.insert() - frappe.db.set_value("Company", args.company_name, "default_bank_account", bank_account.name, update_modified=False) + frappe.db.set_value("Company", args.get('company_name'), "default_bank_account", bank_account.name, update_modified=False) except RootNotEditable: - frappe.throw(_("Bank account cannot be named as {0}").format(args.bank_account)) + frappe.throw(_("Bank account cannot be named as {0}").format(args.get('bank_account'))) except frappe.DuplicateEntryError: # bank account same as a CoA entry pass From 1e5482cedd605f2398f6dbeff4efe40a8cbc1848 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sat, 26 Jun 2021 23:49:32 +0530 Subject: [PATCH 20/60] fix: Revert Changes --- erpnext/public/js/setup_wizard.js | 26 +++++++ .../setup_wizard/data/country_wise_tax.json | 72 ++++++++++++------- .../setup_wizard/operations/taxes_setup.py | 15 ++-- 3 files changed, 81 insertions(+), 32 deletions(-) diff --git a/erpnext/public/js/setup_wizard.js b/erpnext/public/js/setup_wizard.js index a3045724fee..6f5d67c7462 100644 --- a/erpnext/public/js/setup_wizard.js +++ b/erpnext/public/js/setup_wizard.js @@ -139,10 +139,36 @@ erpnext.setup.slides_settings = [ }, validate: function () { + let me = this; + let exist; + if (!this.validate_fy_dates()) { return false; } + // Validate bank name + if(me.values.bank_account) { + frappe.call({ + async: false, + method: "erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts.validate_bank_account", + args: { + "coa": me.values.chart_of_accounts, + "bank_account": me.values.bank_account + }, + callback: function (r) { + if(r.message){ + exist = r.message; + me.get_field("bank_account").set_value(""); + let message = __('Account {0} already exists. Please enter a different name for your bank account.', + [me.values.bank_account] + ); + frappe.msgprint(message); + } + } + }); + return !exist; // Return False if exist = true + } + return true; }, diff --git a/erpnext/setup/setup_wizard/data/country_wise_tax.json b/erpnext/setup/setup_wizard/data/country_wise_tax.json index e36bf5cbe0e..34af093a231 100644 --- a/erpnext/setup/setup_wizard/data/country_wise_tax.json +++ b/erpnext/setup/setup_wizard/data/country_wise_tax.json @@ -1218,37 +1218,43 @@ { "tax_type": { "account_name": "Input Tax SGST", - "tax_rate": 9.00 + "tax_rate": 9.00, + "root_type": "Asset" } }, { "tax_type": { "account_name": "Input Tax CGST", - "tax_rate": 9.00 + "tax_rate": 9.00, + "root_type": "Asset" } }, { "tax_type": { "account_name": "Input Tax IGST", - "tax_rate": 18.00 + "tax_rate": 18.00, + "root_type": "Asset" } }, { "tax_type": { "account_name": "Input Tax SGST RCM", - "tax_rate": 9.00 + "tax_rate": 9.00, + "root_type": "Asset" } }, { "tax_type": { "account_name": "Input Tax CGST RCM", - "tax_rate": 9.00 + "tax_rate": 9.00, + "root_type": "Asset" } }, { "tax_type": { "account_name": "Input Tax IGST RCM", - "tax_rate": 18.00 + "tax_rate": 18.00, + "root_type": "Asset" } } ] @@ -1277,37 +1283,43 @@ { "tax_type": { "account_name": "Input Tax SGST", - "tax_rate": 2.5 + "tax_rate": 2.5, + "root_type": "Asset" } }, { "tax_type": { "account_name": "Input Tax CGST", - "tax_rate": 2.5 + "tax_rate": 2.5, + "root_type": "Asset" } }, { "tax_type": { "account_name": "Input Tax IGST", - "tax_rate": 5.0 + "tax_rate": 5.0, + "root_type": "Asset" } }, { "tax_type": { "account_name": "Input Tax SGST RCM", - "tax_rate": 2.50 + "tax_rate": 2.50, + "root_type": "Asset" } }, { "tax_type": { "account_name": "Input Tax CGST RCM", - "tax_rate": 2.50 + "tax_rate": 2.50, + "root_type": "Asset" } }, { "tax_type": { "account_name": "Input Tax IGST RCM", - "tax_rate": 5.00 + "tax_rate": 5.00, + "root_type": "Asset" } } ] @@ -1336,37 +1348,43 @@ { "tax_type": { "account_name": "Input Tax SGST", - "tax_rate": 6.0 + "tax_rate": 6.0, + "root_type": "Asset" } }, { "tax_type": { "account_name": "Input Tax CGST", - "tax_rate": 6.0 + "tax_rate": 6.0, + "root_type": "Asset" } }, { "tax_type": { "account_name": "Input Tax IGST", - "tax_rate": 12.0 + "tax_rate": 12.0, + "root_type": "Asset" } }, { "tax_type": { "account_name": "Input Tax SGST RCM", - "tax_rate": 6.00 + "tax_rate": 6.00, + "root_type": "Asset" } }, { "tax_type": { "account_name": "Input Tax CGST RCM", - "tax_rate": 6.00 + "tax_rate": 6.00, + "root_type": "Asset" } }, { "tax_type": { "account_name": "Input Tax IGST RCM", - "tax_rate": 12.00 + "tax_rate": 12.00, + "root_type": "Asset" } } ] @@ -1395,37 +1413,43 @@ { "tax_type": { "account_name": "Input Tax SGST", - "tax_rate": 14.0 + "tax_rate": 14.0, + "root_type": "Asset" } }, { "tax_type": { "account_name": "Input Tax CGST", - "tax_rate": 14.0 + "tax_rate": 14.0, + "root_type": "Asset" } }, { "tax_type": { "account_name": "Input Tax IGST", - "tax_rate": 28.0 + "tax_rate": 28.0, + "root_type": "Asset" } }, { "tax_type": { "account_name": "Input Tax SGST RCM", - "tax_rate": 14.00 + "tax_rate": 14.00, + "root_type": "Asset" } }, { "tax_type": { "account_name": "Input Tax CGST RCM", - "tax_rate": 14.00 + "tax_rate": 14.00, + "root_type": "Asset" } }, { "tax_type": { "account_name": "Input Tax IGST RCM", - "tax_rate": 28.00 + "tax_rate": 28.00, + "root_type": "Asset" } } ] diff --git a/erpnext/setup/setup_wizard/operations/taxes_setup.py b/erpnext/setup/setup_wizard/operations/taxes_setup.py index d5682b6f4ca..9f73f214bd1 100644 --- a/erpnext/setup/setup_wizard/operations/taxes_setup.py +++ b/erpnext/setup/setup_wizard/operations/taxes_setup.py @@ -203,16 +203,15 @@ def get_or_create_account(company_name, account): existing_accounts = frappe.get_list('Account', filters={ - 'account_name': account.get('account_name'), - 'account_number': account.get('account_number', ''), - 'company': company_name + 'company': company_name, + 'root_type': root_type }, or_filters={ - 'company': company_name, - 'root_type': root_type, - 'is_group': 0 - } - ) + 'account_name': account.get('account_name'), + 'account_number': account.get('account_number') + }) + + print(company_name, account, existing_accounts) if existing_accounts: return frappe.get_doc('Account', existing_accounts[0].name) From cf445eb7b4f057dfb57c0861745051d5e65dccb9 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sat, 26 Jun 2021 23:59:47 +0530 Subject: [PATCH 21/60] fix: Add validate bank account method back --- .../chart_of_accounts/chart_of_accounts.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) 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 9b6842d896c..927adc7086c 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 @@ -188,6 +188,24 @@ def build_account_tree(tree, parent, all_accounts): # call recursively to build a subtree for current account build_account_tree(tree[child.account_name], child, all_accounts) +@frappe.whitelist() +def validate_bank_account(coa, bank_account): + accounts = [] + chart = get_chart(coa) + + if chart: + def _get_account_names(account_master): + for account_name, child in iteritems(account_master): + if account_name not in ["account_number", "account_type", + "root_type", "is_group", "tax_rate"]: + accounts.append(account_name) + + _get_account_names(child) + + _get_account_names(chart) + + return (bank_account in accounts) + @frappe.whitelist() def build_tree_from_json(chart_template, chart_data=None): ''' get chart template from its folder and parse the json to be rendered as tree ''' From ebc46c1e09b26fc6a26ad386272affd78fa2948e Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 28 Jun 2021 10:52:38 +0530 Subject: [PATCH 22/60] fix: Update account heads in GST test cases --- .../sales_invoice/test_sales_invoice.py | 63 ++++++++++--------- .../gstr_3b_report/test_gstr_3b_report.py | 28 ++++----- .../setup_wizard/operations/taxes_setup.py | 2 - 3 files changed, 46 insertions(+), 47 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 114b7d2d352..dbc7f8632fc 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -1957,6 +1957,33 @@ class TestSalesInvoice(unittest.TestCase): einvoice = make_einvoice(si) validate_totals(einvoice) + def test_item_tax_net_range(self): + item = create_item("T Shirt") + + item.set('taxes', []) + item.append("taxes", { + "item_tax_template": "_Test Account Excise Duty @ 10 - _TC", + "minimum_net_rate": 0, + "maximum_net_rate": 500 + }) + + item.append("taxes", { + "item_tax_template": "_Test Account Excise Duty @ 12 - _TC", + "minimum_net_rate": 501, + "maximum_net_rate": 1000 + }) + + item.save() + + sales_invoice = create_sales_invoice(item = "T Shirt", rate=700, do_not_submit=True) + self.assertEqual(sales_invoice.items[0].item_tax_template, "_Test Account Excise Duty @ 12 - _TC") + + # Apply discount + sales_invoice.apply_discount_on = 'Net Total' + sales_invoice.discount_amount = 300 + sales_invoice.save() + self.assertEqual(sales_invoice.items[0].item_tax_template, "_Test Account Excise Duty @ 10 - _TC") + def get_sales_invoice_for_e_invoice(): si = make_sales_invoice_for_ewaybill() si.naming_series = 'INV-2020-.#####' @@ -1985,32 +2012,6 @@ def get_sales_invoice_for_e_invoice(): return si - def test_item_tax_net_range(self): - item = create_item("T Shirt") - - item.set('taxes', []) - item.append("taxes", { - "item_tax_template": "_Test Account Excise Duty @ 10 - _TC", - "minimum_net_rate": 0, - "maximum_net_rate": 500 - }) - - item.append("taxes", { - "item_tax_template": "_Test Account Excise Duty @ 12 - _TC", - "minimum_net_rate": 501, - "maximum_net_rate": 1000 - }) - - item.save() - - sales_invoice = create_sales_invoice(item = "T Shirt", rate=700, do_not_submit=True) - self.assertEqual(sales_invoice.items[0].item_tax_template, "_Test Account Excise Duty @ 12 - _TC") - - # Apply discount - sales_invoice.apply_discount_on = 'Net Total' - sales_invoice.discount_amount = 300 - sales_invoice.save() - self.assertEqual(sales_invoice.items[0].item_tax_template, "_Test Account Excise Duty @ 10 - _TC") def make_test_address_for_ewaybill(): if not frappe.db.exists('Address', '_Test Address for Eway bill-Billing'): @@ -2087,9 +2088,9 @@ def make_sales_invoice_for_ewaybill(): if not gst_account: gst_settings.append("gst_accounts", { "company": "_Test Company", - "cgst_account": "CGST - _TC", - "sgst_account": "SGST - _TC", - "igst_account": "IGST - _TC", + "cgst_account": "Output Tax CGST - _TC", + "sgst_account": "Output Tax SGST - _TC", + "igst_account": "Output Tax IGST - _TC", }) gst_settings.save() @@ -2106,7 +2107,7 @@ def make_sales_invoice_for_ewaybill(): si.append("taxes", { "charge_type": "On Net Total", - "account_head": "CGST - _TC", + "account_head": "Output Tax CGST - _TC", "cost_center": "Main - _TC", "description": "CGST @ 9.0", "rate": 9 @@ -2114,7 +2115,7 @@ def make_sales_invoice_for_ewaybill(): si.append("taxes", { "charge_type": "On Net Total", - "account_head": "SGST - _TC", + "account_head": "Output Tax SGST - _TC", "cost_center": "Main - _TC", "description": "SGST @ 9.0", "rate": 9 diff --git a/erpnext/regional/doctype/gstr_3b_report/test_gstr_3b_report.py b/erpnext/regional/doctype/gstr_3b_report/test_gstr_3b_report.py index 3857ce1cdb8..065f80d610a 100644 --- a/erpnext/regional/doctype/gstr_3b_report/test_gstr_3b_report.py +++ b/erpnext/regional/doctype/gstr_3b_report/test_gstr_3b_report.py @@ -46,14 +46,14 @@ class TestGSTR3BReport(unittest.TestCase): make_sales_invoice() create_purchase_invoices() - if frappe.db.exists("GSTR 3B Report", "GSTR3B-March-2019-_Test Address-Billing"): - report = frappe.get_doc("GSTR 3B Report", "GSTR3B-March-2019-_Test Address-Billing") + if frappe.db.exists("GSTR 3B Report", "GSTR3B-March-2019-_Test Address GST-Billing"): + report = frappe.get_doc("GSTR 3B Report", "GSTR3B-March-2019-_Test Address GST-Billing") report.save() else: report = frappe.get_doc({ "doctype": "GSTR 3B Report", "company": "_Test Company GST", - "company_address": "_Test Address-Billing", + "company_address": "_Test Address GST-Billing", "year": getdate().year, "month": month_number_mapping.get(getdate().month) }).insert() @@ -89,7 +89,7 @@ class TestGSTR3BReport(unittest.TestCase): si.append("taxes", { "charge_type": "On Net Total", - "account_head": "IGST - _GST", + "account_head": "Output Tax IGST - _GST", "cost_center": "Main - _GST", "description": "IGST @ 18.0", "rate": 18 @@ -117,7 +117,7 @@ def make_sales_invoice(): si.append("taxes", { "charge_type": "On Net Total", - "account_head": "IGST - _GST", + "account_head": "Output Tax IGST - _GST", "cost_center": "Main - _GST", "description": "IGST @ 18.0", "rate": 18 @@ -138,7 +138,7 @@ def make_sales_invoice(): si1.append("taxes", { "charge_type": "On Net Total", - "account_head": "IGST - _GST", + "account_head": "Output Tax IGST - _GST", "cost_center": "Main - _GST", "description": "IGST @ 18.0", "rate": 18 @@ -159,7 +159,7 @@ def make_sales_invoice(): si2.append("taxes", { "charge_type": "On Net Total", - "account_head": "IGST - _GST", + "account_head": "Output Tax IGST - _GST", "cost_center": "Main - _GST", "description": "IGST @ 18.0", "rate": 18 @@ -195,7 +195,7 @@ def create_purchase_invoices(): pi.append("taxes", { "charge_type": "On Net Total", - "account_head": "CGST - _GST", + "account_head": "Input Tax CGST - _GST", "cost_center": "Main - _GST", "description": "CGST @ 9.0", "rate": 9 @@ -203,7 +203,7 @@ def create_purchase_invoices(): pi.append("taxes", { "charge_type": "On Net Total", - "account_head": "SGST - _GST", + "account_head": "Input Tax SGST - _GST", "cost_center": "Main - _GST", "description": "SGST @ 9.0", "rate": 9 @@ -410,10 +410,10 @@ def make_company(): company.country = "India" company.insert() - if not frappe.db.exists('Address', '_Test Address-Billing'): + if not frappe.db.exists('Address', '_Test Address GST-Billing'): address = frappe.get_doc({ + "address_title": "_Test Address GST", "address_line1": "_Test Address Line 1", - "address_title": "_Test Address", "address_type": "Billing", "city": "_Test City", "state": "Test State", @@ -444,9 +444,9 @@ def set_account_heads(): if not gst_account: gst_settings.append("gst_accounts", { "company": "_Test Company GST", - "cgst_account": "CGST - _GST", - "sgst_account": "SGST - _GST", - "igst_account": "IGST - _GST", + "cgst_account": "Output Tax CGST - _GST", + "sgst_account": "Output Tax SGST - _GST", + "igst_account": "Output Tax IGST - _GST" }) gst_settings.save() diff --git a/erpnext/setup/setup_wizard/operations/taxes_setup.py b/erpnext/setup/setup_wizard/operations/taxes_setup.py index 9f73f214bd1..c7cc0005186 100644 --- a/erpnext/setup/setup_wizard/operations/taxes_setup.py +++ b/erpnext/setup/setup_wizard/operations/taxes_setup.py @@ -211,8 +211,6 @@ def get_or_create_account(company_name, account): 'account_number': account.get('account_number') }) - print(company_name, account, existing_accounts) - if existing_accounts: return frappe.get_doc('Account', existing_accounts[0].name) From 7d7d797ffc80956e13493d98d92097cc8d280863 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 28 Jun 2021 11:24:32 +0530 Subject: [PATCH 23/60] fix: Do not consider cancelled entries in party dashboard --- erpnext/accounts/party.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index e025fc69054..b97dc401e6a 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -542,6 +542,7 @@ def get_dashboard_info(party_type, party, loyalty_program=None): select company, sum(debit_in_account_currency) - sum(credit_in_account_currency) from `tabGL Entry` where party_type = %s and party=%s + and is_cancelled = 0 group by company""", (party_type, party))) for d in companies: From 2d1c4fee1d79e06e90effe283cdf8ab47c98d9de Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 29 Jun 2021 00:31:14 +0530 Subject: [PATCH 24/60] fix: allow to changes to date in the blanket order --- .../manufacturing/doctype/blanket_order/blanket_order.js | 2 +- .../manufacturing/doctype/blanket_order/blanket_order.json | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/erpnext/manufacturing/doctype/blanket_order/blanket_order.js b/erpnext/manufacturing/doctype/blanket_order/blanket_order.js index 4c31bd0b7d0..f19a1b08681 100644 --- a/erpnext/manufacturing/doctype/blanket_order/blanket_order.js +++ b/erpnext/manufacturing/doctype/blanket_order/blanket_order.js @@ -13,7 +13,7 @@ frappe.ui.form.on('Blanket Order', { refresh: function(frm) { erpnext.hide_company(); - if (frm.doc.customer && frm.doc.docstatus === 1) { + if (frm.doc.customer && frm.doc.docstatus === 1 && frm.doc.to_date > frappe.datetime.get_today()) { frm.add_custom_button(__("Sales Order"), function() { frappe.model.open_mapped_doc({ method: "erpnext.manufacturing.doctype.blanket_order.blanket_order.make_order", diff --git a/erpnext/manufacturing/doctype/blanket_order/blanket_order.json b/erpnext/manufacturing/doctype/blanket_order/blanket_order.json index 0330e5c85c9..a63fc4da69a 100644 --- a/erpnext/manufacturing/doctype/blanket_order/blanket_order.json +++ b/erpnext/manufacturing/doctype/blanket_order/blanket_order.json @@ -1,4 +1,5 @@ { + "actions": [], "autoname": "naming_series:", "creation": "2018-05-24 07:18:08.256060", "doctype": "DocType", @@ -79,6 +80,7 @@ "reqd": 1 }, { + "allow_on_submit": 1, "fieldname": "to_date", "fieldtype": "Date", "label": "To Date", @@ -129,8 +131,10 @@ "label": "Terms and Conditions Details" } ], + "index_web_pages_for_search": 1, "is_submittable": 1, - "modified": "2019-11-18 19:37:37.151686", + "links": [], + "modified": "2021-06-29 00:30:30.621636", "modified_by": "Administrator", "module": "Manufacturing", "name": "Blanket Order", From 91dcc07e26c647708b5317623ec984d9fa1671d1 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 29 Jun 2021 15:58:46 +0530 Subject: [PATCH 25/60] fix: Employee Inactive status implications (#26244) --- erpnext/hr/doctype/attendance/attendance.js | 2 +- erpnext/hr/doctype/attendance/attendance.py | 5 +++++ erpnext/hr/doctype/attendance/attendance_list.js | 3 +++ erpnext/payroll/doctype/payroll_entry/payroll_entry.py | 1 + 4 files changed, 10 insertions(+), 1 deletion(-) diff --git a/erpnext/hr/doctype/attendance/attendance.js b/erpnext/hr/doctype/attendance/attendance.js index c3c3cb82f94..7964078c7f0 100644 --- a/erpnext/hr/doctype/attendance/attendance.js +++ b/erpnext/hr/doctype/attendance/attendance.js @@ -11,5 +11,5 @@ cur_frm.cscript.onload = function(doc, cdt, cdn) { cur_frm.fields_dict.employee.get_query = function(doc,cdt,cdn) { return{ query: "erpnext.controllers.queries.employee_query" - } + } } diff --git a/erpnext/hr/doctype/attendance/attendance.py b/erpnext/hr/doctype/attendance/attendance.py index f3b8a799b3c..3412675d811 100644 --- a/erpnext/hr/doctype/attendance/attendance.py +++ b/erpnext/hr/doctype/attendance/attendance.py @@ -15,6 +15,7 @@ class Attendance(Document): validate_status(self.status, ["Present", "Absent", "On Leave", "Half Day", "Work From Home"]) self.validate_attendance_date() self.validate_duplicate_record() + self.validate_employee_status() self.check_leave_record() def validate_attendance_date(self): @@ -38,6 +39,10 @@ class Attendance(Document): frappe.throw(_("Attendance for employee {0} is already marked for the date {1}").format( frappe.bold(self.employee), frappe.bold(self.attendance_date))) + def validate_employee_status(self): + if frappe.db.get_value("Employee", self.employee, "status") == "Inactive": + frappe.throw(_("Cannot mark attendance for an Inactive employee {0}").format(self.employee)) + def check_leave_record(self): leave_record = frappe.db.sql(""" select leave_type, half_day, half_day_date diff --git a/erpnext/hr/doctype/attendance/attendance_list.js b/erpnext/hr/doctype/attendance/attendance_list.js index 0c7eafe9c61..9a3bac0eb23 100644 --- a/erpnext/hr/doctype/attendance/attendance_list.js +++ b/erpnext/hr/doctype/attendance/attendance_list.js @@ -21,6 +21,9 @@ frappe.listview_settings['Attendance'] = { label: __('For Employee'), fieldtype: 'Link', options: 'Employee', + get_query: () => { + return {query: "erpnext.controllers.queries.employee_query"} + }, reqd: 1, onchange: function() { dialog.set_df_property("unmarked_days", "hidden", 1); diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py index e71d81f323a..5c7c0a3b092 100644 --- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py +++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py @@ -459,6 +459,7 @@ def get_emp_list(sal_struct, cond, end_date, payroll_payable_account): where t1.name = t2.employee and t2.docstatus = 1 + and t1.status != 'Inactive' %s order by t2.from_date desc """ % cond, {"sal_struct": tuple(sal_struct), "from_date": end_date, "payroll_payable_account": payroll_payable_account}, as_dict=True) From 9181dde86a9ccabdfb2c075383dc1daaa8f4662f Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 29 Jun 2021 17:18:39 +0530 Subject: [PATCH 26/60] fix: Cancelation of Loan Security Pledges --- .../loan_security_pledge.json | 84 ++++++++++++++----- .../loan_security_pledge.py | 18 +++- 2 files changed, 76 insertions(+), 26 deletions(-) diff --git a/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.json b/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.json index 18bd4aea78b..68bac8ed8cd 100644 --- a/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.json +++ b/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.json @@ -35,7 +35,9 @@ "no_copy": 1, "options": "Loan Security Pledge", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fetch_from": "loan_application.applicant", @@ -45,47 +47,63 @@ "in_standard_filter": 1, "label": "Applicant", "options": "applicant_type", - "reqd": 1 + "reqd": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "loan_security_details_section", "fieldtype": "Section Break", - "label": "Loan Security Details" + "label": "Loan Security Details", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_3", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "loan", "fieldtype": "Link", "label": "Loan", - "options": "Loan" + "options": "Loan", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "loan_application", "fieldtype": "Link", "label": "Loan Application", - "options": "Loan Application" + "options": "Loan Application", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "total_security_value", "fieldtype": "Currency", "label": "Total Security Value", "options": "Company:company:default_currency", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "maximum_loan_value", "fieldtype": "Currency", "label": "Maximum Loan Value", "options": "Company:company:default_currency", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "loan_details_section", "fieldtype": "Section Break", - "label": "Loan Details" + "label": "Loan Details", + "show_days": 1, + "show_seconds": 1 }, { "default": "Requested", @@ -94,37 +112,49 @@ "in_list_view": 1, "in_standard_filter": 1, "label": "Status", - "options": "Requested\nUnpledged\nPledged\nPartially Pledged", - "read_only": 1 + "options": "Requested\nUnpledged\nPledged\nPartially Pledged\nCancelled", + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "pledge_time", "fieldtype": "Datetime", "label": "Pledge Time", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "securities", "fieldtype": "Table", "label": "Securities", "options": "Pledge", - "reqd": 1 + "reqd": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_11", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "section_break_10", "fieldtype": "Section Break", - "label": "Totals" + "label": "Totals", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "company", "fieldtype": "Link", "label": "Company", "options": "Company", - "reqd": 1 + "reqd": 1, + "show_days": 1, + "show_seconds": 1 }, { "fetch_from": "loan.applicant_type", @@ -132,35 +162,45 @@ "fieldtype": "Select", "label": "Applicant Type", "options": "Employee\nMember\nCustomer", - "reqd": 1 + "reqd": 1, + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, "fieldname": "more_information_section", "fieldtype": "Section Break", - "label": "More Information" + "label": "More Information", + "show_days": 1, + "show_seconds": 1 }, { "allow_on_submit": 1, "fieldname": "reference_no", "fieldtype": "Data", - "label": "Reference No" + "label": "Reference No", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_18", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "allow_on_submit": 1, "fieldname": "description", "fieldtype": "Text", - "label": "Description" + "label": "Description", + "show_days": 1, + "show_seconds": 1 } ], "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2021-04-19 18:23:16.953305", + "modified": "2021-06-29 17:15:16.082256", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan Security Pledge", diff --git a/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.py b/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.py index cbc8376aa56..c390b6c526d 100644 --- a/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.py +++ b/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.py @@ -23,6 +23,12 @@ class LoanSecurityPledge(Document): update_shortfall_status(self.loan, self.total_security_value) update_loan(self.loan, self.maximum_loan_value) + def on_cancel(self): + if self.loan: + self.db_set("status", "Cancelled") + self.db_set("pledge_time", None) + update_loan(self.loan, self.maximum_loan_value, cancel=1) + def validate_duplicate_securities(self): security_list = [] for security in self.securities: @@ -36,7 +42,7 @@ class LoanSecurityPledge(Document): existing_pledge = '' if self.loan: - existing_pledge = frappe.db.get_value('Loan Security Pledge', {'loan': self.loan}, ['name']) + existing_pledge = frappe.db.get_value('Loan Security Pledge', {'loan': self.loan, 'docstatus': 1}, ['name']) if existing_pledge: loan_security_type = frappe.db.get_value('Pledge', {'parent': existing_pledge}, ['loan_security_type']) @@ -77,8 +83,12 @@ class LoanSecurityPledge(Document): self.total_security_value = total_security_value self.maximum_loan_value = maximum_loan_value -def update_loan(loan, maximum_value_against_pledge): +def update_loan(loan, maximum_value_against_pledge, cancel=0): maximum_loan_value = frappe.db.get_value('Loan', {'name': loan}, ['maximum_loan_amount']) - frappe.db.sql(""" UPDATE `tabLoan` SET maximum_loan_amount=%s, is_secured_loan=1 - WHERE name=%s""", (maximum_loan_value + maximum_value_against_pledge, loan)) + if cancel: + frappe.db.sql(""" UPDATE `tabLoan` SET maximum_loan_amount=%s + WHERE name=%s""", (maximum_loan_value - maximum_value_against_pledge, loan)) + else: + frappe.db.sql(""" UPDATE `tabLoan` SET maximum_loan_amount=%s, is_secured_loan=1 + WHERE name=%s""", (maximum_loan_value + maximum_value_against_pledge, loan)) From 61690775a836be80d19d842b7cf7a6e6d56e1dba Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 28 Jun 2021 15:42:39 +0530 Subject: [PATCH 27/60] feat: provision to make subcontracted purchase order from the production plan --- .../purchase_order_item.json | 29 ++- erpnext/manufacturing/doctype/bom/bom.json | 21 +- erpnext/manufacturing/doctype/bom/bom.py | 19 +- .../doctype/bom/bom_item_preview.html | 36 +++- erpnext/manufacturing/doctype/bom/bom_tree.js | 2 +- .../production_plan/production_plan.js | 41 +++- .../production_plan/production_plan.json | 31 ++- .../production_plan/production_plan.py | 180 +++++++++++----- .../production_plan_dashboard.py | 4 + .../production_plan/test_production_plan.py | 10 +- .../production_plan_item.json | 19 +- .../__init__.py | 0 .../production_plan_sub_assembly_item.json | 202 ++++++++++++++++++ .../production_plan_sub_assembly_item.py | 10 + .../doctype/work_order/work_order.json | 18 +- .../doctype/work_order/work_order.py | 3 +- .../work_order/work_order_preview.html | 33 +++ .../report/bom_explorer/bom_explorer.py | 11 +- .../job_card_summary/job_card_summary.js | 12 ++ .../job_card_summary/job_card_summary.json | 8 +- .../production_plan_summary/__init__.py | 0 .../production_plan_summary.js | 32 +++ .../production_plan_summary.json | 26 +++ .../production_plan_summary.py | 136 ++++++++++++ .../work_order_summary/work_order_summary.py | 5 +- erpnext/patches.txt | 1 + erpnext/patches/v13_0/update_level_in_bom.py | 30 +++ .../stock/doctype/stock_entry/stock_entry.py | 2 +- 28 files changed, 809 insertions(+), 112 deletions(-) create mode 100644 erpnext/manufacturing/doctype/production_plan_sub_assembly_item/__init__.py create mode 100644 erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.json create mode 100644 erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.py create mode 100644 erpnext/manufacturing/doctype/work_order/work_order_preview.html create mode 100644 erpnext/manufacturing/report/production_plan_summary/__init__.py create mode 100644 erpnext/manufacturing/report/production_plan_summary/production_plan_summary.js create mode 100644 erpnext/manufacturing/report/production_plan_summary/production_plan_summary.json create mode 100644 erpnext/manufacturing/report/production_plan_summary/production_plan_summary.py create mode 100644 erpnext/patches/v13_0/update_level_in_bom.py diff --git a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json index 1dbd7c60c3e..132dd1769c5 100644 --- a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json +++ b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json @@ -97,6 +97,9 @@ "is_fixed_asset", "item_tax_rate", "section_break_72", + "production_plan", + "production_plan_item", + "production_plan_sub_assembly_item", "page_break" ], "fields": [ @@ -803,13 +806,37 @@ "options": "Company:company:default_currency", "print_hide": 1, "read_only": 1 + }, + { + "fieldname": "production_plan", + "fieldtype": "Link", + "label": "Production Plan", + "options": "Production Plan", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "production_plan_item", + "fieldtype": "Data", + "hidden": 1, + "label": "Production Plan Item", + "no_copy": 1, + "read_only": 1 + }, + { + "fieldname": "production_plan_sub_assembly_item", + "fieldtype": "Data", + "hidden": 1, + "label": "Production Plan Sub Assembly Item", + "no_copy": 1, + "read_only": 1 } ], "idx": 1, "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-03-22 11:46:12.357435", + "modified": "2021-06-28 19:22:22.715365", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order Item", diff --git a/erpnext/manufacturing/doctype/bom/bom.json b/erpnext/manufacturing/doctype/bom/bom.json index f38d1b98922..7e539183b0c 100644 --- a/erpnext/manufacturing/doctype/bom/bom.json +++ b/erpnext/manufacturing/doctype/bom/bom.json @@ -36,6 +36,9 @@ "materials_section", "inspection_required", "quality_inspection_template", + "column_break_31", + "bom_level", + "section_break_33", "items", "scrap_section", "scrap_items", @@ -513,6 +516,22 @@ "no_copy": 1, "print_hide": 1, "read_only": 1 + }, + { + "fieldname": "column_break_31", + "fieldtype": "Column Break" + }, + { + "default": "0", + "fieldname": "bom_level", + "fieldtype": "Int", + "label": "BOM Level", + "read_only": 1 + }, + { + "fieldname": "section_break_33", + "fieldtype": "Section Break", + "hide_border": 1 } ], "icon": "fa fa-sitemap", @@ -520,7 +539,7 @@ "image_field": "image", "is_submittable": 1, "links": [], - "modified": "2021-03-16 12:25:09.081968", + "modified": "2021-05-16 12:25:09.081968", "modified_by": "Administrator", "module": "Manufacturing", "name": "BOM", diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index c58f017258e..c31b1bd3e9c 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -154,6 +154,7 @@ class BOM(WebsiteGenerator): self.calculate_cost() self.update_stock_qty() self.update_cost(update_parent=False, from_child_bom=True, update_hour_rate = False, save=False) + self.set_bom_level() def get_context(self, context): context.parents = [{'name': 'boms', 'title': _('All BOMs') }] @@ -676,6 +677,19 @@ class BOM(WebsiteGenerator): """Get a complete tree representation preserving order of child items.""" return BOMTree(self.name) + def set_bom_level(self, update=False): + levels = [] + + self.bom_level = 0 + for row in self.items: + if row.bom_no: + levels.append(frappe.get_cached_value("BOM", row.bom_no, "bom_level") or 0) + + if levels: + self.bom_level = max(levels) + 1 + + if update: + self.db_set("bom_level", self.bom_level) def get_bom_item_rate(args, bom_doc): if bom_doc.rm_cost_as_per == 'Valuation Rate': @@ -860,7 +874,7 @@ def get_children(doctype, parent=None, is_root=False, **filters): frappe.form_dict.parent = parent if frappe.form_dict.parent: - bom_doc = frappe.get_doc("BOM", frappe.form_dict.parent) + bom_doc = frappe.get_cached_doc("BOM", frappe.form_dict.parent) frappe.has_permission("BOM", doc=bom_doc, throw=True) bom_items = frappe.get_all('BOM Item', @@ -871,7 +885,7 @@ def get_children(doctype, parent=None, is_root=False, **filters): item_names = tuple(d.get('item_code') for d in bom_items) items = frappe.get_list('Item', - fields=['image', 'description', 'name', 'stock_uom', 'item_name'], + fields=['image', 'description', 'name', 'stock_uom', 'item_name', 'is_sub_contracted_item'], filters=[['name', 'in', item_names]]) # to get only required item dicts for bom_item in bom_items: @@ -884,6 +898,7 @@ def get_children(doctype, parent=None, is_root=False, **filters): bom_item.parent_bom_qty = bom_doc.quantity bom_item.expandable = 0 if bom_item.value in ('', None) else 1 + bom_item.image = frappe.db.escape(bom_item.image) return bom_items diff --git a/erpnext/manufacturing/doctype/bom/bom_item_preview.html b/erpnext/manufacturing/doctype/bom/bom_item_preview.html index 6cd5f8cb3cf..6088e46265b 100644 --- a/erpnext/manufacturing/doctype/bom/bom_item_preview.html +++ b/erpnext/manufacturing/doctype/bom/bom_item_preview.html @@ -1,13 +1,31 @@
- {% if data.image %} - -
- {% endif %} -

- {{ __("Description") }} -

-
- {{ data.description }} +
+
+ {% if data.image %} +
+ +
+ {% endif %} +
+
+

+ {{ __("Description") }} +

+
+ {{ data.description }} +
+
+

+ {% if data.value %} + + {{ __("Open BOM {0}", [data.value.bold()]) }} + {% endif %} + {% if data.item_code %} + + {{ __("Open Item {0}", [data.item_code.bold()]) }} + {% endif %} +

+

diff --git a/erpnext/manufacturing/doctype/bom/bom_tree.js b/erpnext/manufacturing/doctype/bom/bom_tree.js index 185b9ed4bcf..60fb377f476 100644 --- a/erpnext/manufacturing/doctype/bom/bom_tree.js +++ b/erpnext/manufacturing/doctype/bom/bom_tree.js @@ -64,7 +64,7 @@ frappe.treeview_settings["BOM"] = { if(node.is_root && node.data.value!="BOM") { frappe.model.with_doc("BOM", node.data.value, function() { var bom = frappe.model.get_doc("BOM", node.data.value); - node.data.image = bom.image || ""; + node.data.image = escape(bom.image) || ""; node.data.description = bom.description || ""; }); } diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.js b/erpnext/manufacturing/doctype/production_plan/production_plan.js index 450aa04a73d..d198a6962a5 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.js +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.js @@ -4,7 +4,7 @@ frappe.ui.form.on('Production Plan', { setup: function(frm) { frm.custom_make_buttons = { - 'Work Order': 'Work Order', + 'Work Order': 'Work Order / Subcontract PO', 'Material Request': 'Material Request', }; @@ -68,17 +68,13 @@ frappe.ui.form.on('Production Plan', { frm.trigger("show_progress"); if (frm.doc.status !== "Completed") { - if (frm.doc.po_items && frm.doc.status !== "Closed") { - frm.add_custom_button(__("Work Order"), ()=> { - frm.trigger("make_work_order"); - }, __('Create')); - } + frm.add_custom_button(__("Work Order Tree"), ()=> { + frappe.set_route('Tree', 'Work Order', {production_plan: frm.doc.name}); + }, __('View')); - if (frm.doc.mr_items && !in_list(['Material Requested', 'Closed'], frm.doc.status)) { - frm.add_custom_button(__("Material Request"), ()=> { - frm.trigger("make_material_request"); - }, __('Create')); - } + frm.add_custom_button(__("Production Plan Summary"), ()=> { + frappe.set_route('query-report', 'Production Plan Summary', {production_plan: frm.doc.name}); + }, __('View')); if (frm.doc.status === "Closed") { frm.add_custom_button(__("Re-open"), function() { @@ -89,6 +85,18 @@ frappe.ui.form.on('Production Plan', { frm.events.close_open_production_plan(frm, true); }, __("Status")); } + + if (frm.doc.po_items && frm.doc.status !== "Closed") { + frm.add_custom_button(__("Work Order / Subcontract PO"), ()=> { + frm.trigger("make_work_order"); + }, __('Create')); + } + + if (frm.doc.mr_items && !in_list(['Material Requested', 'Closed'], frm.doc.status)) { + frm.add_custom_button(__("Material Request"), ()=> { + frm.trigger("make_material_request"); + }, __('Create')); + } } } @@ -233,6 +241,17 @@ frappe.ui.form.on('Production Plan', { }); }, + get_sub_assembly_items: function(frm) { + frappe.call({ + method: "get_sub_assembly_items", + freeze: true, + doc: frm.doc, + callback: function() { + refresh_field("sub_assembly_items"); + } + }); + }, + get_items_for_mr: function(frm) { if (!frm.doc.for_warehouse) { frappe.throw(__("Select warehouse for material requests")); diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.json b/erpnext/manufacturing/doctype/production_plan/production_plan.json index 1c0dde227c5..84378956c61 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.json +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.json @@ -32,6 +32,9 @@ "po_items", "section_break_25", "prod_plan_references", + "section_break_24", + "get_sub_assembly_items", + "sub_assembly_items", "material_request_planning", "include_non_stock_items", "include_subcontracted_items", @@ -187,7 +190,7 @@ "depends_on": "get_items_from", "fieldname": "get_items", "fieldtype": "Button", - "label": "Get Items For Work Order" + "label": "Get Finished Goods for Manufacture" }, { "fieldname": "po_items", @@ -199,7 +202,7 @@ { "fieldname": "material_request_planning", "fieldtype": "Section Break", - "label": "Material Request Planning" + "label": "Material Requirement Planning" }, { "default": "1", @@ -237,12 +240,13 @@ }, { "fieldname": "section_break_27", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "hide_border": 1 }, { "fieldname": "mr_items", "fieldtype": "Table", - "label": "Material Request Plan Item", + "label": "Raw Materials", "no_copy": 1, "options": "Material Request Plan Item" }, @@ -337,13 +341,30 @@ "hidden": 1, "label": "Production Plan Item Reference", "options": "Production Plan Item Reference" + }, + { + "fieldname": "section_break_24", + "fieldtype": "Section Break", + "hide_border": 1 + }, + { + "fieldname": "sub_assembly_items", + "fieldtype": "Table", + "label": "Sub Assembly Items", + "no_copy": 1, + "options": "Production Plan Sub Assembly Item" + }, + { + "fieldname": "get_sub_assembly_items", + "fieldtype": "Button", + "label": "Get Sub Assembly Items" } ], "icon": "fa fa-calendar", "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2021-05-24 16:59:03.643211", + "modified": "2021-06-28 20:00:33.905114", "modified_by": "Administrator", "module": "Manufacturing", "name": "Production Plan", diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 0ede1bd4ab3..38a0ee77ad7 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -5,10 +5,11 @@ from __future__ import unicode_literals import frappe, json, copy from frappe import msgprint, _ -from six import string_types, iteritems +from six import iteritems from frappe.model.document import Document -from frappe.utils import cstr, flt, cint, nowdate, add_days, comma_and, now_datetime, ceil +from frappe.utils import (flt, cint, nowdate, add_days, comma_and, now_datetime, + ceil, get_link_to_form, getdate) from frappe.utils.csvutils import build_csv_response from erpnext.manufacturing.doctype.bom.bom import validate_bom_no, get_children from erpnext.manufacturing.doctype.work_order.work_order import get_item_details @@ -349,49 +350,88 @@ class ProductionPlan(Document): @frappe.whitelist() def make_work_order(self): - wo_list = [] + wo_list, po_list = [], [] + subcontracted_po = {} + self.validate_data() + self.make_work_order_for_finished_goods(wo_list) + self.make_work_order_for_subassembly_items(wo_list, subcontracted_po) + self.make_subcontracted_purchase_order(subcontracted_po, po_list) + self.show_list_created_message('Work Order', wo_list) + self.show_list_created_message('Purchase Order', po_list) + + def make_work_order_for_finished_goods(self, wo_list): items_data = self.get_production_items() for key, item in items_data.items(): + if self.sub_assembly_items: + item['use_multi_level_bom'] = 0 + work_order = self.create_work_order(item) if work_order: wo_list.append(work_order) - if item.get("make_work_order_for_sub_assembly_items"): - work_orders = self.make_work_order_for_sub_assembly_items(item) - wo_list.extend(work_orders) + def make_work_order_for_subassembly_items(self, wo_list, subcontracted_po): + for row in self.sub_assembly_items: + if row.type_of_manufacturing == 'Subcontract': + subcontracted_po.setdefault(row.supplier, []).append(row) + continue + + args = {} + self.prepare_args_for_sub_assembly_items(row, args) + work_order = self.create_work_order(args) + if work_order: + wo_list.append(work_order) + + def make_subcontracted_purchase_order(self, subcontracted_po, purchase_orders): + if not subcontracted_po: + return + + for supplier, po_list in subcontracted_po.items(): + 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' + for row in po_list: + args = { + 'item_code': row.production_item, + 'warehouse': row.fg_warehouse, + 'production_plan_sub_assembly_item': row.name, + 'bom': row.bom_no, + 'production_plan': self.name + } + + for field in ['schedule_date', 'qty', 'uom', 'stock_uom', 'item_name', + 'description', 'production_plan_item']: + args[field] = row.get(field) + + po.append('items', args) + + po.set_missing_values() + po.flags.ignore_mandatory = True + po.flags.ignore_validate = True + po.insert() + purchase_orders.append(po.name) + + def show_list_created_message(self, doctype, doc_list=None): + if not doc_list: + return frappe.flags.mute_messages = False + if doc_list: + doc_list = [get_link_to_form(doctype, p) for p in doc_list] + msgprint(_("{0} created").format(comma_and(doc_list))) - if wo_list: - wo_list = ["""%s""" % \ - (p, p) for p in wo_list] - msgprint(_("{0} created").format(comma_and(wo_list))) - else : - msgprint(_("No Work Orders created")) + def prepare_args_for_sub_assembly_items(self, row, args): + for field in ["production_item", "item_name", "qty", "fg_warehouse", + "description", "bom_no", "stock_uom", "bom_level", "production_plan_item"]: + args[field] = row.get(field) - def make_work_order_for_sub_assembly_items(self, item): - work_orders = [] - bom_data = {} - - get_sub_assembly_items(item.get("bom_no"), bom_data, item.get("qty")) - - for key, data in bom_data.items(): - data.update({ - 'qty': data.get("stock_qty"), - 'production_plan': self.name, - 'use_multi_level_bom': item.get("use_multi_level_bom"), - 'company': self.company, - 'fg_warehouse': item.get("fg_warehouse"), - 'update_consumed_material_cost_in_project': 0 - }) - - work_order = self.create_work_order(data) - if work_order: - work_orders.append(work_order) - - return work_orders + args.update({ + "use_multi_level_bom": 0, + "production_plan": self.name, + "production_plan_sub_assembly_item": row.name + }) def create_work_order(self, item): from erpnext.manufacturing.doctype.work_order.work_order import OverProductionError, get_default_warehouse @@ -476,9 +516,32 @@ class ProductionPlan(Document): else : msgprint(_("No material request created")) + @frappe.whitelist() + def get_sub_assembly_items(self, manufacturing_type=None): + self.sub_assembly_items = [] + for row in self.po_items: + bom_data = [] + get_sub_assembly_items(row.bom_no, bom_data, row.planned_qty) + self.set_sub_assembly_items_based_on_level(row, bom_data, manufacturing_type) + + self.save() + + def set_sub_assembly_items_based_on_level(self, row, bom_data, manufacturing_type=None): + bom_data = sorted(bom_data, key = lambda i: i.bom_level) + + for data in bom_data: + data.qty = data.stock_qty + data.production_plan_item = row.name + data.fg_warehouse = row.warehouse + data.schedule_date = row.planned_start_date + data.type_of_manufacturing = manufacturing_type or ("Subcontract" if data.is_sub_contracted_item + else "In House") + + self.append("sub_assembly_items", data) + @frappe.whitelist() def download_raw_materials(doc, warehouses=None): - if isinstance(doc, string_types): + if isinstance(doc, str): doc = frappe._dict(json.loads(doc)) item_list = [['Item Code', 'Description', 'Stock UOM', 'Warehouse', 'Required Qty as per BOM', @@ -660,7 +723,7 @@ def get_sales_orders(self): @frappe.whitelist() def get_bin_details(row, company, for_warehouse=None, all_warehouse=False): - if isinstance(row, string_types): + if isinstance(row, str): row = frappe._dict(json.loads(row)) company = frappe.db.escape(company) @@ -684,8 +747,11 @@ def get_bin_details(row, company, for_warehouse=None, all_warehouse=False): group by item_code, warehouse """.format(conditions=conditions), { "item_code": row['item_code'] }, as_dict=1) -def get_warehouse_list(warehouses, warehouse_list=[]): - if isinstance(warehouses, string_types): +def get_warehouse_list(warehouses, warehouse_list=None): + if not warehouse_list: + warehouse_list = [] + + if isinstance(warehouses, str): warehouses = json.loads(warehouses) for row in warehouses: @@ -697,7 +763,7 @@ def get_warehouse_list(warehouses, warehouse_list=[]): @frappe.whitelist() def get_items_for_material_requests(doc, warehouses=None, get_parent_warehouse_data=None): - if isinstance(doc, string_types): + if isinstance(doc, str): doc = frappe._dict(json.loads(doc)) warehouse_list = [] @@ -726,6 +792,9 @@ def get_items_for_material_requests(doc, warehouses=None, get_parent_warehouse_d so_item_details = frappe._dict() for data in po_items: + if not data.get("include_exploded_items") and doc.get("sub_assembly_items"): + data["include_exploded_items"] = 1 + planned_qty = data.get('required_qty') or data.get('planned_qty') ignore_existing_ordered_qty = data.get('ignore_existing_ordered_qty') or ignore_existing_ordered_qty warehouse = doc.get('for_warehouse') @@ -857,23 +926,28 @@ def get_item_data(item_code): # "description": item_details.get("description") } -def get_sub_assembly_items(bom_no, bom_data, to_produce_qty): +def get_sub_assembly_items(bom_no, bom_data, to_produce_qty, indent=0): data = get_children('BOM', parent = bom_no) for d in data: if d.expandable: - key = (d.name, d.value) - if key not in bom_data: - bom_data.setdefault(key, { - 'stock_qty': 0, - 'description': d.description, - 'production_item': d.item_code, - 'item_name': d.item_name, - 'stock_uom': d.stock_uom, - 'uom': d.stock_uom, - 'bom_no': d.value - }) + parent_item_code = frappe.get_cached_value("BOM", bom_no, "item") + bom_level = (frappe.get_cached_value("BOM", d.value, "bom_level") + if d.value else 0) - bom_item = bom_data.get(key) - bom_item["stock_qty"] += (d.stock_qty / d.parent_bom_qty) * flt(to_produce_qty) + stock_qty = (d.stock_qty / d.parent_bom_qty) * flt(to_produce_qty) + bom_data.append(frappe._dict({ + 'parent_item_code': parent_item_code, + 'description': d.description, + 'production_item': d.item_code, + 'item_name': d.item_name, + 'stock_uom': d.stock_uom, + 'uom': d.stock_uom, + 'bom_no': d.value, + 'is_sub_contracted_item': d.is_sub_contracted_item, + 'bom_level': bom_level, + 'indent': indent, + 'stock_qty': stock_qty + })) - get_sub_assembly_items(bom_item.get("bom_no"), bom_data, bom_item["stock_qty"]) + if d.value: + get_sub_assembly_items(d.value, bom_data, stock_qty, indent=indent+1) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan_dashboard.py b/erpnext/manufacturing/doctype/production_plan/production_plan_dashboard.py index 09ec24a67a2..ca597f63278 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan_dashboard.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan_dashboard.py @@ -9,5 +9,9 @@ def get_data(): 'label': _('Transactions'), 'items': ['Work Order', 'Material Request'] }, + { + 'label': _('Subcontract'), + 'items': ['Purchase Order'] + }, ] } \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py index 768f99eb431..cce1bb61b6b 100644 --- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py @@ -169,7 +169,7 @@ class TestProductionPlan(unittest.TestCase): pln.get_items() pln.submit() - self.assertTrue(pln.po_items[0].planned_qty, 3) + self.assertTrue(pln.po_items[0].planned_qty, 3) pln.make_work_order() work_order = frappe.db.get_value('Work Order', { @@ -193,10 +193,10 @@ class TestProductionPlan(unittest.TestCase): for so_item in so_items: so_wo_qty = frappe.db.get_value('Sales Order Item', so_item, 'work_order_qty') self.assertEqual(so_wo_qty, 0.0) - + latest_plan = frappe.get_doc('Production Plan', pln.name) latest_plan.cancel() - + def test_pp_to_mr_customer_provided(self): #Material Request from Production Plan for Customer Provided create_item('CUST-0987', is_customer_provided_item = 1, customer = '_Test Customer', is_purchase_item = 0) @@ -236,10 +236,10 @@ class TestProductionPlan(unittest.TestCase): pln.append("po_items", { "item_code": item_code, "bom_no": frappe.db.get_value('BOM', {'item': "Test BOM 1"}), - "planned_qty": 3, - "make_work_order_for_sub_assembly_items": 1 + "planned_qty": 3 }) + pln.get_sub_assembly_items('In House') pln.submit() pln.make_work_order() diff --git a/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json b/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json index 89ab7aa0a06..f829d57475a 100644 --- a/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json +++ b/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json @@ -9,18 +9,17 @@ "include_exploded_items", "item_code", "bom_no", - "planned_qty", "column_break_6", - "make_work_order_for_sub_assembly_items", + "planned_qty", "warehouse", "planned_start_date", "section_break_9", "pending_qty", "ordered_qty", - "produced_qty", "column_break_17", "description", "stock_uom", + "produced_qty", "reference_section", "sales_order", "sales_order_item", @@ -32,11 +31,10 @@ ], "fields": [ { - "columns": 2, - "default": "0", + "columns": 1, + "default": "1", "fieldname": "include_exploded_items", "fieldtype": "Check", - "in_list_view": 1, "label": "Include Exploded Items" }, { @@ -80,13 +78,6 @@ "fieldname": "column_break_6", "fieldtype": "Column Break" }, - { - "default": "0", - "description": "If enabled, system will create the work order for the exploded items against which BOM is available.", - "fieldname": "make_work_order_for_sub_assembly_items", - "fieldtype": "Check", - "label": "Make Work Order for Sub Assembly Items" - }, { "fieldname": "warehouse", "fieldtype": "Link", @@ -218,7 +209,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2021-04-28 19:14:57.772123", + "modified": "2021-06-28 18:31:06.822168", "modified_by": "Administrator", "module": "Manufacturing", "name": "Production Plan Item", diff --git a/erpnext/manufacturing/doctype/production_plan_sub_assembly_item/__init__.py b/erpnext/manufacturing/doctype/production_plan_sub_assembly_item/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.json b/erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.json new file mode 100644 index 00000000000..657ee35a852 --- /dev/null +++ b/erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.json @@ -0,0 +1,202 @@ +{ + "actions": [], + "creation": "2020-12-27 16:08:36.127199", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "production_item", + "item_name", + "fg_warehouse", + "parent_item_code", + "schedule_date", + "column_break_3", + "qty", + "bom_no", + "bom_level", + "type_of_manufacturing", + "supplier", + "work_order_details_section", + "work_order", + "purchase_order", + "production_plan_item", + "column_break_7", + "produced_qty", + "received_qty", + "indent", + "section_break_19", + "uom", + "stock_uom", + "column_break_22", + "description" + ], + "fields": [ + { + "fetch_from": "sub_assembly_item_code.item_name", + "fieldname": "item_name", + "fieldtype": "Data", + "label": "Item Name", + "read_only": 1 + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "depends_on": "eval:doc.type_of_manufacturing == \"In House\"", + "fieldname": "work_order_details_section", + "fieldtype": "Section Break", + "label": "Reference" + }, + { + "fieldname": "work_order", + "fieldtype": "Link", + "label": "Work Order", + "options": "Work Order", + "read_only": 1 + }, + { + "fieldname": "column_break_7", + "fieldtype": "Column Break" + }, + { + "columns": 1, + "fieldname": "qty", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Required Qty", + "read_only": 1 + }, + { + "fieldname": "purchase_order", + "fieldtype": "Link", + "label": "Purchase Order", + "options": "Purchase Order", + "read_only": 1 + }, + { + "fieldname": "received_qty", + "fieldtype": "Float", + "label": "Received Qty" + }, + { + "fieldname": "bom_no", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Bom No", + "options": "BOM" + }, + { + "fieldname": "production_plan_item", + "fieldtype": "Data", + "hidden": 1, + "label": "Production Plan Item", + "read_only": 1 + }, + { + "fieldname": "parent_item_code", + "fieldtype": "Link", + "label": "Finished Good", + "options": "Item", + "read_only": 1 + }, + { + "columns": 1, + "fetch_from": "bom_no.bom_level", + "fieldname": "bom_level", + "fieldtype": "Int", + "in_list_view": 1, + "label": "Level (BOM)", + "read_only": 1 + }, + { + "collapsible": 1, + "fieldname": "section_break_19", + "fieldtype": "Section Break", + "label": "Item Details" + }, + { + "fieldname": "uom", + "fieldtype": "Link", + "label": "UOM", + "options": "UOM", + "read_only": 1 + }, + { + "fieldname": "stock_uom", + "fieldtype": "Link", + "label": "Stock UOM", + "options": "UOM", + "read_only": 1 + }, + { + "fieldname": "column_break_22", + "fieldtype": "Column Break" + }, + { + "fieldname": "description", + "fieldtype": "Small Text", + "label": "description", + "read_only": 1 + }, + { + "fieldname": "production_item", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Sub Assembly Item Code", + "options": "Item", + "read_only": 1 + }, + { + "fieldname": "indent", + "fieldtype": "Int", + "label": "Indent" + }, + { + "fieldname": "fg_warehouse", + "fieldtype": "Link", + "label": "Target Warehouse", + "options": "Warehouse" + }, + { + "fieldname": "produced_qty", + "fieldtype": "Data", + "label": "Produced Quantity", + "read_only": 1 + }, + { + "default": "In House", + "fieldname": "type_of_manufacturing", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Manufacturing Type", + "options": "In House\nSubcontract" + }, + { + "fieldname": "supplier", + "fieldtype": "Link", + "label": "Supplier", + "mandatory_depends_on": "eval:doc.type_of_manufacturing == 'Subcontract'", + "options": "Supplier" + }, + { + "fieldname": "schedule_date", + "fieldtype": "Datetime", + "in_list_view": 1, + "label": "Schedule Date" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-06-28 20:10:56.296410", + "modified_by": "Administrator", + "module": "Manufacturing", + "name": "Production Plan Sub Assembly Item", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.py b/erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.py new file mode 100644 index 00000000000..6850a2eb4ed --- /dev/null +++ b/erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document + +class ProductionPlanSubAssemblyItem(Document): + pass diff --git a/erpnext/manufacturing/doctype/work_order/work_order.json b/erpnext/manufacturing/doctype/work_order/work_order.json index 44d76d2b01c..3b56854aaf3 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.json +++ b/erpnext/manufacturing/doctype/work_order/work_order.json @@ -64,11 +64,16 @@ "description", "stock_uom", "column_break2", + "references_section", "material_request", "material_request_item", "sales_order_item", + "column_break_61", "production_plan", "production_plan_item", + "production_plan_sub_assembly_item", + "parent_work_order", + "bom_level", "product_bundle_item", "amended_from" ], @@ -546,17 +551,26 @@ "no_copy": 1, "print_hide": 1, "read_only": 1 - } + }, + { + "fieldname": "production_plan_sub_assembly_item", + "fieldtype": "Data", + "label": "Production Plan Sub-assembly Item", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 + } ], "icon": "fa fa-cogs", "idx": 1, "image_field": "image", "is_submittable": 1, "links": [], - "modified": "2021-06-20 15:19:14.902699", + "modified": "2021-06-28 16:19:14.902699", "modified_by": "Administrator", "module": "Manufacturing", "name": "Work Order", + "nsm_parent_field": "parent_work_order", "owner": "Administrator", "permissions": [ { diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index 180815d80e4..779ae42d653 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -483,7 +483,7 @@ class WorkOrder(Document): self.set('operations', []) - if not self.bom_no: + if not self.bom_no or not frappe.get_cached_value('BOM', self.bom_no, 'with_operations'): return operations = [] @@ -590,6 +590,7 @@ 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): diff --git a/erpnext/manufacturing/doctype/work_order/work_order_preview.html b/erpnext/manufacturing/doctype/work_order/work_order_preview.html new file mode 100644 index 00000000000..a4bf93edef1 --- /dev/null +++ b/erpnext/manufacturing/doctype/work_order/work_order_preview.html @@ -0,0 +1,33 @@ +

+
+
+ {% if data.image %} +
+ +
+ {% endif %} +
+
+
+ Status {{ data.status }} +
+
+ Qty to Produce {{ data.qty }} +
+
+ Produced Qty {{ data.produced_qty }} +
+
+

+ {% if data.value %} + + {{ __("Open Work Order {0}", [data.value.bold()]) }} + {% endif %} + {% if data.item_code %} + + {{ __("Open Item {0}", [data.item_code.bold()]) }} + {% endif %} +

+
+
+
\ No newline at end of file diff --git a/erpnext/manufacturing/report/bom_explorer/bom_explorer.py b/erpnext/manufacturing/report/bom_explorer/bom_explorer.py index 48907adc5f3..858b5546b02 100644 --- a/erpnext/manufacturing/report/bom_explorer/bom_explorer.py +++ b/erpnext/manufacturing/report/bom_explorer/bom_explorer.py @@ -20,17 +20,20 @@ def get_exploded_items(bom, data, indent=0, qty=1): fields= ['qty','bom_no','qty','scrap','item_code','item_name','description','uom']) for item in exploded_items: + print(item.bom_no, indent) item["indent"] = indent data.append({ 'item_code': item.item_code, 'item_name': item.item_name, 'indent': indent, + 'bom_level': (frappe.get_cached_value("BOM", item.bom_no, "bom_level") + if item.bom_no else ""), 'bom': item.bom_no, 'qty': item.qty * qty, 'uom': item.uom, 'description': item.description, 'scrap': item.scrap - }) + }) if item.bom_no: get_exploded_items(item.bom_no, data, indent=indent+1, qty=item.qty) @@ -68,6 +71,12 @@ def get_columns(): "fieldname": "uom", "width": 100 }, + { + "label": "BOM Level", + "fieldtype": "Data", + "fieldname": "bom_level", + "width": 100 + }, { "label": "Standard Description", "fieldtype": "data", diff --git a/erpnext/manufacturing/report/job_card_summary/job_card_summary.js b/erpnext/manufacturing/report/job_card_summary/job_card_summary.js index bd68db190e7..cb771e49941 100644 --- a/erpnext/manufacturing/report/job_card_summary/job_card_summary.js +++ b/erpnext/manufacturing/report/job_card_summary/job_card_summary.js @@ -68,6 +68,18 @@ frappe.query_reports["Job Card Summary"] = { get_data: function(txt) { return frappe.db.get_link_options('Item', txt); } + }, + { + label: __("Workstation"), + fieldname: "workstation", + fieldtype: "Link", + options: "Workstation" + }, + { + label: __("Operation"), + fieldname: "operation", + fieldtype: "Link", + options: "Operation" } ] }; diff --git a/erpnext/manufacturing/report/job_card_summary/job_card_summary.json b/erpnext/manufacturing/report/job_card_summary/job_card_summary.json index 9f08fc34cb8..ecf2b74bbed 100644 --- a/erpnext/manufacturing/report/job_card_summary/job_card_summary.json +++ b/erpnext/manufacturing/report/job_card_summary/job_card_summary.json @@ -1,14 +1,16 @@ { - "add_total_row": 0, + "add_total_row": 1, + "columns": [], "creation": "2020-04-20 12:00:21.436619", "disable_prepared_report": 0, "disabled": 0, "docstatus": 0, "doctype": "Report", + "filters": [], "idx": 0, "is_standard": "Yes", - "letter_head": "Gadgets International", - "modified": "2020-04-20 12:00:21.436619", + "letter_head": "", + "modified": "2020-12-30 11:49:21.713561", "modified_by": "Administrator", "module": "Manufacturing", "name": "Job Card Summary", diff --git a/erpnext/manufacturing/report/production_plan_summary/__init__.py b/erpnext/manufacturing/report/production_plan_summary/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/manufacturing/report/production_plan_summary/production_plan_summary.js b/erpnext/manufacturing/report/production_plan_summary/production_plan_summary.js new file mode 100644 index 00000000000..59396fef16e --- /dev/null +++ b/erpnext/manufacturing/report/production_plan_summary/production_plan_summary.js @@ -0,0 +1,32 @@ +// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports["Production Plan Summary"] = { + "filters": [ + { + fieldname: "production_plan", + label: __("Production Plan"), + fieldtype: "Link", + options: "Production Plan", + reqd: 1, + get_query: function() { + return { + filters: { + "docstatus": 1 + } + }; + } + } + ], + "formatter": function(value, row, column, data, default_formatter) { + value = default_formatter(value, row, column, data); + + if (column.fieldname == "document_name") { + var color = data.pending_qty > 0 ? 'red': 'green'; + value = `${data['document_name']}`; + } + + return value; + }, +}; diff --git a/erpnext/manufacturing/report/production_plan_summary/production_plan_summary.json b/erpnext/manufacturing/report/production_plan_summary/production_plan_summary.json new file mode 100644 index 00000000000..33aca21a6ea --- /dev/null +++ b/erpnext/manufacturing/report/production_plan_summary/production_plan_summary.json @@ -0,0 +1,26 @@ +{ + "add_total_row": 0, + "columns": [], + "creation": "2020-12-27 11:43:39.781793", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 0, + "is_standard": "Yes", + "modified": "2020-12-27 11:43:42.677584", + "modified_by": "Administrator", + "module": "Manufacturing", + "name": "Production Plan Summary", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Production Plan", + "report_name": "Production Plan Summary", + "report_type": "Script Report", + "roles": [ + { + "role": "Manufacturing User" + } + ] +} \ No newline at end of file diff --git a/erpnext/manufacturing/report/production_plan_summary/production_plan_summary.py b/erpnext/manufacturing/report/production_plan_summary/production_plan_summary.py new file mode 100644 index 00000000000..81b1791ae81 --- /dev/null +++ b/erpnext/manufacturing/report/production_plan_summary/production_plan_summary.py @@ -0,0 +1,136 @@ +# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.utils import flt + +def execute(filters=None): + columns, data = [], [] + data = get_data(filters) + columns = get_column(filters) + + return columns, data + +def get_data(filters): + data = [] + + order_details = {} + get_work_order_details(filters, order_details) + get_purchase_order_details(filters, order_details) + get_production_plan_item_details(filters, data, order_details) + + return data + +def get_production_plan_item_details(filters, data, order_details): + itemwise_indent = {} + + production_plan_doc = frappe.get_cached_doc("Production Plan", filters.get("production_plan")) + for row in production_plan_doc.po_items: + work_order = frappe.get_cached_value("Work Order", {"production_plan_item": row.name, + "bom_no": row.bom_no, "production_item": row.item_code}, "name") + + if row.item_code not in itemwise_indent: + itemwise_indent.setdefault(row.item_code, {}) + + data.append({ + "indent": 0, + "item_code": row.item_code, + "item_name": frappe.get_cached_value("Item", row.item_code, "item_name"), + "qty": row.planned_qty, + "document_type": "Work Order", + "document_name": work_order, + "bom_level": frappe.get_cached_value("BOM", row.bom_no, "bom_level"), + "produced_qty": order_details.get((work_order, row.item_code)).get("produced_qty"), + "pending_qty": flt(row.planned_qty) - flt(order_details.get((work_order, row.item_code)).get("produced_qty")) + }) + + get_production_plan_sub_assembly_item_details(filters, row, production_plan_doc, data, order_details) + +def get_production_plan_sub_assembly_item_details(filters, row, production_plan_doc, data, order_details): + for item in production_plan_doc.sub_assembly_items: + if row.name == item.production_plan_item: + subcontracted_item = (item.type_of_manufacturing == 'Subcontract') + + if subcontracted_item: + docname = frappe.get_cached_value("Purchase Order Item", + {"production_plan_sub_assembly_item": item.name, "docstatus": ("<", 2)}, "parent") + else: + docname = frappe.get_cached_value("Work Order", + {"production_plan_sub_assembly_item": item.name, "docstatus": ("<", 2)}, "name") + + data.append({ + "indent": 1, + "item_code": item.production_item, + "item_name": item.item_name, + "qty": item.qty, + "document_type": "Work Order" if not subcontracted_item else "Purchase Order", + "document_name": docname, + "bom_level": item.bom_level, + "produced_qty": order_details.get((docname, item.production_item)).get("produced_qty"), + "pending_qty": flt(item.qty) - flt(order_details.get((docname, item.production_item)).get("produced_qty")) + }) + +def get_work_order_details(filters, order_details): + for row in frappe.get_all("Work Order", filters = {"production_plan": filters.get("production_plan")}, + fields=["name", "produced_qty", "production_plan", "production_item"]): + order_details.setdefault((row.name, row.production_item), row) + +def get_purchase_order_details(filters, order_details): + for row in frappe.get_all("Purchase Order Item", filters = {"production_plan": filters.get("production_plan")}, + fields=["parent", "received_qty as produced_qty", "item_code"]): + order_details.setdefault((row.parent, row.item_code), row) + +def get_column(filters): + return [ + { + "label": "Finished Good", + "fieldtype": "Link", + "fieldname": "item_code", + "width": 300, + "options": "Item" + }, + { + "label": "Item Name", + "fieldtype": "data", + "fieldname": "item_name", + "width": 100 + }, + { + "label": "Document Type", + "fieldtype": "Link", + "fieldname": "document_type", + "width": 150, + "options": "DocType" + }, + { + "label": "Document Name", + "fieldtype": "Dynamic Link", + "fieldname": "document_name", + "width": 150 + }, + { + "label": "BOM Level", + "fieldtype": "Int", + "fieldname": "bom_level", + "width": 100 + }, + { + "label": "Order Qty", + "fieldtype": "Float", + "fieldname": "qty", + "width": 120 + }, + { + "label": "Received Qty", + "fieldtype": "Float", + "fieldname": "produced_qty", + "width": 160 + }, + { + "label": "Pending Qty", + "fieldtype": "Float", + "fieldname": "pending_qty", + "width": 110 + } + ] diff --git a/erpnext/manufacturing/report/work_order_summary/work_order_summary.py b/erpnext/manufacturing/report/work_order_summary/work_order_summary.py index fb047b230ce..612dad0bf51 100644 --- a/erpnext/manufacturing/report/work_order_summary/work_order_summary.py +++ b/erpnext/manufacturing/report/work_order_summary/work_order_summary.py @@ -19,7 +19,7 @@ def execute(filters=None): return columns, data, None, chart_data def get_data(filters): - query_filters = {"docstatus": 1} + query_filters = {"docstatus": ("<", 2)} fields = ["name", "status", "sales_order", "production_item", "qty", "produced_qty", "planned_start_date", "planned_end_date", "actual_start_date", "actual_end_date", "lead_time"] @@ -62,7 +62,8 @@ def get_chart_based_on_status(data): "Not Started": 0, "In Process": 0, "Stopped": 0, - "Completed": 0 + "Completed": 0, + "Draft": 0 } for d in data: diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 2b1fc43a1c0..29376f00a1c 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -290,3 +290,4 @@ erpnext.patches.v13_0.set_training_event_attendance erpnext.patches.v13_0.rename_issue_status_hold_to_on_hold erpnext.patches.v13_0.bill_for_rejected_quantity_in_purchase_invoice erpnext.patches.v13_0.update_job_card_details +erpnext.patches.v13_0.update_level_in_bom #1234sswef diff --git a/erpnext/patches/v13_0/update_level_in_bom.py b/erpnext/patches/v13_0/update_level_in_bom.py new file mode 100644 index 00000000000..0d03c42e980 --- /dev/null +++ b/erpnext/patches/v13_0/update_level_in_bom.py @@ -0,0 +1,30 @@ +# Copyright (c) 2020, Frappe and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals +import frappe + +def execute(): + for document in ["bom", "bom_item", "bom_explosion_item"]: + frappe.reload_doc('manufacturing', 'doctype', document) + + frappe.db.sql(" update `tabBOM` set bom_level = 0 where docstatus = 1") + + bom_list = frappe.db.sql_list("""select name from `tabBOM` bom + where docstatus=1 and is_active=1 and not exists(select bom_no from `tabBOM Item` + where parent=bom.name and ifnull(bom_no, '')!='')""") + + count = 0 + while(count < len(bom_list)): + for parent_bom in get_parent_boms(bom_list[count]): + bom_doc = frappe.get_cached_doc("BOM", parent_bom) + bom_doc.set_bom_level(update=True) + bom_list.append(parent_bom) + count += 1 + +def get_parent_boms(bom_no): + return frappe.db.sql_list(""" + select distinct bom_item.parent from `tabBOM Item` bom_item + where bom_item.bom_no = %s and bom_item.docstatus=1 and bom_item.parenttype='BOM' + and exists(select bom.name from `tabBOM` bom where bom.name=bom_item.parent and bom.is_active=1) + """, bom_no) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 8f27ef4356c..e21a80030ab 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -72,7 +72,7 @@ class StockEntry(StockController): self.validate_with_material_request() self.validate_batch() self.validate_inspection() - self.validate_fg_completed_qty() + # self.validate_fg_completed_qty() self.validate_difference_account() self.set_job_card_data() self.set_purpose_for_stock_entry() From 46b67b901b55f296bef953f85059bfc9c788ddbb Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 30 Jun 2021 00:03:32 +0530 Subject: [PATCH 28/60] fix: incorrect valuation rate in stock reconciliation --- .../stock_reconciliation.py | 7 +-- .../test_stock_reconciliation.py | 44 ++++++++++++++++++- 2 files changed, 47 insertions(+), 4 deletions(-) diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index e646600752c..dd94e7c7522 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -404,17 +404,18 @@ class StockReconciliation(StockController): key = (d.item_code, d.warehouse) if key not in merge_similar_entries: + d.total_amount = (d.actual_qty * d.valuation_rate) merge_similar_entries[key] = d elif d.serial_no: data = merge_similar_entries[key] data.actual_qty += d.actual_qty data.qty_after_transaction += d.qty_after_transaction - data.valuation_rate = (data.valuation_rate + d.valuation_rate) / data.actual_qty + data.total_amount += (d.actual_qty * d.valuation_rate) + data.valuation_rate = (data.total_amount) / data.actual_qty data.serial_no += '\n' + d.serial_no - if data.incoming_rate: - data.incoming_rate = (data.incoming_rate + d.incoming_rate) / data.actual_qty + data.incoming_rate = (data.total_amount) / data.actual_qty for key, value in merge_similar_entries.items(): new_sl_entries.append(value) diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py index 36380b838b1..ce4cbd259c4 100644 --- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py @@ -6,7 +6,7 @@ from __future__ import unicode_literals import frappe, unittest -from frappe.utils import flt, nowdate, nowtime +from frappe.utils import flt, nowdate, nowtime, random_string from erpnext.accounts.utils import get_stock_and_account_balance from erpnext.stock.stock_ledger import get_previous_sle, update_entries_after from erpnext.stock.doctype.stock_reconciliation.stock_reconciliation import EmptyStockReconciliationItemsError, get_items @@ -150,6 +150,42 @@ class TestStockReconciliation(unittest.TestCase): stock_doc = frappe.get_doc("Stock Reconciliation", d) stock_doc.cancel() + + def test_stock_reco_for_merge_serialized_item(self): + to_delete_records = [] + + # Add new serial nos + serial_item_code = "Stock-Reco-Serial-Item-2" + serial_warehouse = "_Test Warehouse for Stock Reco1 - _TC" + + sr = create_stock_reconciliation(item_code=serial_item_code, serial_no=random_string(6), + warehouse = serial_warehouse, qty=1, rate=100, do_not_submit=True, purpose='Opening Stock') + + for i in range(3): + sr.append('items', { + 'item_code': serial_item_code, + 'warehouse': serial_warehouse, + 'qty': 1, + 'valuation_rate': 100, + 'serial_no': random_string(6) + }) + + sr.save() + sr.submit() + + sle_entries = frappe.get_all('Stock Ledger Entry', filters= {'voucher_no': sr.name}, + fields = ['name', 'incoming_rate']) + + self.assertEqual(len(sle_entries), 1) + self.assertEqual(sle_entries[0].incoming_rate, 100) + + to_delete_records.append(sr.name) + to_delete_records.reverse() + + for d in to_delete_records: + stock_doc = frappe.get_doc("Stock Reconciliation", d) + stock_doc.cancel() + def test_stock_reco_for_batch_item(self): to_delete_records = [] to_delete_serial_nos = [] @@ -231,6 +267,12 @@ def create_batch_or_serial_no_items(): serial_item_doc.serial_no_series = "SRSI.####" serial_item_doc.save(ignore_permissions=True) + serial_item_doc = create_item("Stock-Reco-Serial-Item-2", is_stock_item=1) + if not serial_item_doc.has_serial_no: + serial_item_doc.has_serial_no = 1 + serial_item_doc.serial_no_series = "SRSII.####" + serial_item_doc.save(ignore_permissions=True) + batch_item_doc = create_item("Stock-Reco-batch-Item-1", is_stock_item=1) if not batch_item_doc.has_batch_no: batch_item_doc.has_batch_no = 1 From 815e6ec846799449bf15b4c70b95bf3532e34a4f Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 30 Jun 2021 11:35:50 +0530 Subject: [PATCH 29/60] fix: minor removed unused file --- .../work_order/work_order_preview.html | 33 ------------------- .../stock/doctype/stock_entry/stock_entry.py | 2 +- 2 files changed, 1 insertion(+), 34 deletions(-) delete mode 100644 erpnext/manufacturing/doctype/work_order/work_order_preview.html diff --git a/erpnext/manufacturing/doctype/work_order/work_order_preview.html b/erpnext/manufacturing/doctype/work_order/work_order_preview.html deleted file mode 100644 index a4bf93edef1..00000000000 --- a/erpnext/manufacturing/doctype/work_order/work_order_preview.html +++ /dev/null @@ -1,33 +0,0 @@ -
-
-
- {% if data.image %} -
- -
- {% endif %} -
-
-
- Status {{ data.status }} -
-
- Qty to Produce {{ data.qty }} -
-
- Produced Qty {{ data.produced_qty }} -
-
-

- {% if data.value %} - - {{ __("Open Work Order {0}", [data.value.bold()]) }} - {% endif %} - {% if data.item_code %} - - {{ __("Open Item {0}", [data.item_code.bold()]) }} - {% endif %} -

-
-
-
\ No newline at end of file diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index e21a80030ab..8f27ef4356c 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -72,7 +72,7 @@ class StockEntry(StockController): self.validate_with_material_request() self.validate_batch() self.validate_inspection() - # self.validate_fg_completed_qty() + self.validate_fg_completed_qty() self.validate_difference_account() self.set_job_card_data() self.set_purpose_for_stock_entry() From 68c697b354367e394e00a451487cd24528514d40 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 1 Jul 2021 09:31:31 +0530 Subject: [PATCH 30/60] fix: Auto process deferred accounting for multi-company setup --- erpnext/accounts/deferred_revenue.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/erpnext/accounts/deferred_revenue.py b/erpnext/accounts/deferred_revenue.py index 2f86c6c1de2..335e8a15ab0 100644 --- a/erpnext/accounts/deferred_revenue.py +++ b/erpnext/accounts/deferred_revenue.py @@ -301,17 +301,21 @@ def process_deferred_accounting(posting_date=None): start_date = add_months(today(), -1) end_date = add_days(today(), -1) - for record_type in ('Income', 'Expense'): - doc = frappe.get_doc(dict( - doctype='Process Deferred Accounting', - posting_date=posting_date, - start_date=start_date, - end_date=end_date, - type=record_type - )) + companies = frappe.get_all('Company') - doc.insert() - doc.submit() + for company in companies: + for record_type in ('Income', 'Expense'): + doc = frappe.get_doc(dict( + doctype='Process Deferred Accounting', + company=company.name, + posting_date=posting_date, + start_date=start_date, + end_date=end_date, + type=record_type + )) + + doc.insert() + doc.submit() def make_gl_entries(doc, credit_account, debit_account, against, amount, base_amount, posting_date, project, account_currency, cost_center, item, deferred_process=None): From 0bfd56e615f0e9f9053f572d945bbc15f3948dd6 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 1 Jul 2021 11:50:48 +0530 Subject: [PATCH 31/60] fix: update cost not working in the draft bom --- erpnext/manufacturing/doctype/bom/bom.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js index 27019dbbae2..15a7c316c91 100644 --- a/erpnext/manufacturing/doctype/bom/bom.js +++ b/erpnext/manufacturing/doctype/bom/bom.js @@ -325,8 +325,7 @@ frappe.ui.form.on("BOM", { freeze: true, args: { update_parent: true, - from_child_bom:false, - save: frm.doc.docstatus === 1 ? true : false + from_child_bom:false }, callback: function(r) { refresh_field("items"); From a856624ccb9c20b3cf4428204a0da6840df767c2 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 30 Jun 2021 23:27:24 +0530 Subject: [PATCH 32/60] fix: employee selection not working in payroll entry --- .../doctype/payroll_entry/payroll_entry.js | 51 ++++++++++++------- 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.js b/erpnext/payroll/doctype/payroll_entry/payroll_entry.js index f2892600d12..496c37b2fad 100644 --- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.js +++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.js @@ -135,10 +135,26 @@ frappe.ui.form.on('Payroll Entry', { }); frm.set_query('employee', 'employees', () => { - if (!frm.doc.company) { - frappe.msgprint(__("Please set a Company")); - return []; + let error_fields = []; + let mandatory_fields = ['company', 'payroll_frequency', 'start_date', 'end_date']; + + let message = __('Mandatory fields required in {0}', [__(frm.doc.doctype)]); + + mandatory_fields.forEach(field => { + if (!frm.doc[field]) { + error_fields.push(frappe.unscrub(field)); + } + }); + + if (error_fields && error_fields.length) { + message = message + '

  • ' + error_fields.join('
  • ') + "
"; + frappe.throw({ + message: message, + indicator: 'red', + title: __('Missing Fields') + }); } + return { query: "erpnext.payroll.doctype.payroll_entry.payroll_entry.employee_query", filters: frm.events.get_employee_filters(frm) @@ -148,25 +164,22 @@ frappe.ui.form.on('Payroll Entry', { get_employee_filters: function (frm) { let filters = {}; - filters['company'] = frm.doc.company; - filters['start_date'] = frm.doc.start_date; - filters['end_date'] = frm.doc.end_date; filters['salary_slip_based_on_timesheet'] = frm.doc.salary_slip_based_on_timesheet; - filters['payroll_frequency'] = frm.doc.payroll_frequency; - filters['payroll_payable_account'] = frm.doc.payroll_payable_account; - filters['currency'] = frm.doc.currency; - if (frm.doc.department) { - filters['department'] = frm.doc.department; - } - if (frm.doc.branch) { - filters['branch'] = frm.doc.branch; - } - if (frm.doc.designation) { - filters['designation'] = frm.doc.designation; - } + let fields = ['company', 'start_date', 'end_date', 'payroll_frequency', 'payroll_payable_account', + 'currency', 'department', 'branch', 'designation']; + + fields.forEach(field => { + if (frm.doc[field]) { + filters[field] = frm.doc[field]; + } + }); + if (frm.doc.employees) { - filters['employees'] = frm.doc.employees.filter(d => d.employee).map(d => d.employee); + let employees = frm.doc.employees.filter(d => d.employee).map(d => d.employee); + if (employees && employees.length) { + filters['employees'] = employees; + } } return filters; }, From d8bc51422656f6446584d7d7d75a7d0d209b7993 Mon Sep 17 00:00:00 2001 From: Anuja Pawar <60467153+Anuja-pawar@users.noreply.github.com> Date: Thu, 1 Jul 2021 14:06:01 +0530 Subject: [PATCH 33/60] fix: to fetch the correct field in Tax Rule (#25927) --- erpnext/accounts/doctype/tax_rule/tax_rule.js | 18 - .../accounts/doctype/tax_rule/tax_rule.json | 1273 +++-------------- .../doctype/tax_rule/test_tax_rule.py | 2 +- 3 files changed, 211 insertions(+), 1082 deletions(-) diff --git a/erpnext/accounts/doctype/tax_rule/tax_rule.js b/erpnext/accounts/doctype/tax_rule/tax_rule.js index 370890e4d8e..bc497163e8b 100644 --- a/erpnext/accounts/doctype/tax_rule/tax_rule.js +++ b/erpnext/accounts/doctype/tax_rule/tax_rule.js @@ -1,24 +1,6 @@ // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // License: GNU General Public License v3. See license.txt -cur_frm.add_fetch("customer", "customer_group", "customer_group" ); -cur_frm.add_fetch("supplier", "supplier_group_name", "supplier_group" ); - -frappe.ui.form.on("Tax Rule", "tax_type", function(frm) { - frm.toggle_reqd("sales_tax_template", frm.doc.tax_type=="Sales"); - frm.toggle_reqd("purchase_tax_template", frm.doc.tax_type=="Purchase"); -}) - -frappe.ui.form.on("Tax Rule", "onload", function(frm) { - if(frm.doc.__islocal) { - frm.set_value("use_for_shopping_cart", 1); - } -}) - -frappe.ui.form.on("Tax Rule", "refresh", function(frm) { - frappe.ui.form.trigger("Tax Rule", "tax_type"); -}) - frappe.ui.form.on("Tax Rule", "customer", function(frm) { if(frm.doc.customer) { frappe.call({ diff --git a/erpnext/accounts/doctype/tax_rule/tax_rule.json b/erpnext/accounts/doctype/tax_rule/tax_rule.json index ef155381c0b..27467484329 100644 --- a/erpnext/accounts/doctype/tax_rule/tax_rule.json +++ b/erpnext/accounts/doctype/tax_rule/tax_rule.json @@ -1,1103 +1,250 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 1, - "allow_rename": 0, - "autoname": "ACC-TAX-RULE-.YYYY.-.#####", - "beta": 0, - "creation": "2015-08-07 02:33:52.670866", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Setup", - "editable_grid": 0, + "actions": [], + "allow_import": 1, + "autoname": "ACC-TAX-RULE-.YYYY.-.#####", + "creation": "2015-08-07 02:33:52.670866", + "doctype": "DocType", + "document_type": "Setup", + "engine": "InnoDB", + "field_order": [ + "tax_type", + "use_for_shopping_cart", + "column_break_1", + "sales_tax_template", + "purchase_tax_template", + "filters", + "customer", + "supplier", + "item", + "billing_city", + "billing_county", + "billing_state", + "billing_zipcode", + "billing_country", + "tax_category", + "column_break_2", + "customer_group", + "supplier_group", + "item_group", + "shipping_city", + "shipping_county", + "shipping_state", + "shipping_zipcode", + "shipping_country", + "section_break_4", + "from_date", + "column_break_7", + "to_date", + "section_break_6", + "priority", + "column_break_20", + "company" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Sales", - "fieldname": "tax_type", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Tax Type", - "length": 0, - "no_copy": 0, - "options": "Sales\nPurchase", - "permlevel": 0, - "precision": "", - "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 - }, + "default": "Sales", + "fieldname": "tax_type", + "fieldtype": "Select", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Tax Type", + "options": "Sales\nPurchase" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "use_for_shopping_cart", - "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": "Use for Shopping Cart", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "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 - }, + "default": "1", + "fieldname": "use_for_shopping_cart", + "fieldtype": "Check", + "label": "Use for Shopping Cart" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_1", - "fieldtype": "Column Break", - "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, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "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 - }, + "fieldname": "column_break_1", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.tax_type==\"Sales\"", - "fieldname": "sales_tax_template", - "fieldtype": "Link", - "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": "Sales Tax Template", - "length": 0, - "no_copy": 0, - "options": "Sales Taxes and Charges Template", - "permlevel": 0, - "precision": "", - "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 - }, + "depends_on": "eval:doc.tax_type==\"Sales\"", + "fieldname": "sales_tax_template", + "fieldtype": "Link", + "label": "Sales Tax Template", + "options": "Sales Taxes and Charges Template" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.tax_type==\"Purchase\"", - "fieldname": "purchase_tax_template", - "fieldtype": "Link", - "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": "Purchase Tax Template", - "length": 0, - "no_copy": 0, - "options": "Purchase Taxes and Charges Template", - "permlevel": 0, - "precision": "", - "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 - }, + "depends_on": "eval:doc.tax_type==\"Purchase\"", + "fieldname": "purchase_tax_template", + "fieldtype": "Link", + "label": "Purchase Tax Template", + "options": "Purchase Taxes and Charges Template" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "filters", - "fieldtype": "Section Break", - "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": "Filters", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "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 - }, + "fieldname": "filters", + "fieldtype": "Section Break", + "label": "Filters" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.tax_type==\"Sales\"", - "fieldname": "customer", - "fieldtype": "Link", - "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": "Customer", - "length": 0, - "no_copy": 0, - "options": "Customer", - "permlevel": 0, - "precision": "", - "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 - }, + "depends_on": "eval:doc.tax_type==\"Sales\"", + "fieldname": "customer", + "fieldtype": "Link", + "label": "Customer", + "options": "Customer" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.tax_type==\"Purchase\"", - "fieldname": "supplier", - "fieldtype": "Link", - "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": "Supplier", - "length": 0, - "no_copy": 0, - "options": "Supplier", - "permlevel": 0, - "precision": "", - "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 - }, + "depends_on": "eval:doc.tax_type==\"Purchase\"", + "fieldname": "supplier", + "fieldtype": "Link", + "label": "Supplier", + "options": "Supplier" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "item", - "fieldtype": "Link", - "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": "Item", - "length": 0, - "no_copy": 0, - "options": "Item", - "permlevel": 0, - "precision": "", - "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 - }, + "fieldname": "item", + "fieldtype": "Link", + "label": "Item", + "options": "Item" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "billing_city", - "fieldtype": "Data", - "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": "Billing City", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "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 - }, + "fieldname": "billing_city", + "fieldtype": "Data", + "label": "Billing City" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "billing_county", - "fieldtype": "Data", - "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": "Billing County", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "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 - }, + "fieldname": "billing_county", + "fieldtype": "Data", + "label": "Billing County" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "billing_state", - "fieldtype": "Data", - "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": "Billing State", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "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 - }, + "fieldname": "billing_state", + "fieldtype": "Data", + "label": "Billing State" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "billing_zipcode", - "fieldtype": "Data", - "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": "Billing Zipcode", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "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 - }, + "fieldname": "billing_zipcode", + "fieldtype": "Data", + "label": "Billing Zipcode" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "billing_country", - "fieldtype": "Link", - "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": "Billing Country", - "length": 0, - "no_copy": 0, - "options": "Country", - "permlevel": 0, - "precision": "", - "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 - }, + "fieldname": "billing_country", + "fieldtype": "Link", + "label": "Billing Country", + "options": "Country" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "tax_category", - "fieldtype": "Link", - "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": "Tax Category", - "length": 0, - "no_copy": 0, - "options": "Tax Category", - "permlevel": 0, - "precision": "", - "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 - }, + "fieldname": "tax_category", + "fieldtype": "Link", + "label": "Tax Category", + "options": "Tax Category" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_2", - "fieldtype": "Column Break", - "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, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "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 - }, + "fieldname": "column_break_2", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.tax_type==\"Sales\"", - "fieldname": "customer_group", - "fieldtype": "Link", - "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": "Customer Group", - "length": 0, - "no_copy": 0, - "options": "Customer Group", - "permlevel": 0, - "precision": "", - "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 - }, + "depends_on": "eval:doc.tax_type==\"Sales\"", + "fetch_from": "customer.customer_group", + "fieldname": "customer_group", + "fieldtype": "Link", + "label": "Customer Group", + "options": "Customer Group" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.tax_type==\"Purchase\"", - "fieldname": "supplier_group", - "fieldtype": "Link", - "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": "Supplier Group", - "length": 0, - "no_copy": 0, - "options": "Supplier Group", - "permlevel": 0, - "precision": "", - "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 - }, + "depends_on": "eval:doc.tax_type==\"Purchase\"", + "fetch_from": "supplier.supplier_group", + "fieldname": "supplier_group", + "fieldtype": "Link", + "label": "Supplier Group", + "options": "Supplier Group" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "item_group", - "fieldtype": "Link", - "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": "Item Group", - "length": 0, - "no_copy": 0, - "options": "Item Group", - "permlevel": 0, - "precision": "", - "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 - }, + "fieldname": "item_group", + "fieldtype": "Link", + "label": "Item Group", + "options": "Item Group" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "shipping_city", - "fieldtype": "Data", - "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": "Shipping City", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "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 - }, + "fieldname": "shipping_city", + "fieldtype": "Data", + "label": "Shipping City" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "shipping_county", - "fieldtype": "Data", - "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": "Shipping County", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "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 - }, + "fieldname": "shipping_county", + "fieldtype": "Data", + "label": "Shipping County" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "shipping_state", - "fieldtype": "Data", - "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": "Shipping State", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "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 - }, + "fieldname": "shipping_state", + "fieldtype": "Data", + "label": "Shipping State" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "shipping_zipcode", - "fieldtype": "Data", - "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": "Shipping Zipcode", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "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 - }, + "fieldname": "shipping_zipcode", + "fieldtype": "Data", + "label": "Shipping Zipcode" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "shipping_country", - "fieldtype": "Link", - "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": "Shipping Country", - "length": 0, - "no_copy": 0, - "options": "Country", - "permlevel": 0, - "precision": "", - "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 - }, + "fieldname": "shipping_country", + "fieldtype": "Link", + "label": "Shipping Country", + "options": "Country" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_4", - "fieldtype": "Section Break", - "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": "Validity", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "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 - }, + "fieldname": "section_break_4", + "fieldtype": "Section Break", + "label": "Validity" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "from_date", - "fieldtype": "Date", - "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": "From Date", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "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 - }, + "fieldname": "from_date", + "fieldtype": "Date", + "label": "From Date" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_7", - "fieldtype": "Column Break", - "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, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "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 - }, + "fieldname": "column_break_7", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "to_date", - "fieldtype": "Date", - "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": "To Date", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "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 - }, + "fieldname": "to_date", + "fieldtype": "Date", + "label": "To Date" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_6", - "fieldtype": "Section Break", - "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, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "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 - }, + "fieldname": "section_break_6", + "fieldtype": "Section Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "1", - "fieldname": "priority", - "fieldtype": "Int", - "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": "Priority", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "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 - }, + "default": "1", + "fieldname": "priority", + "fieldtype": "Int", + "label": "Priority" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_20", - "fieldtype": "Column Break", - "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, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "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 - }, + "fieldname": "column_break_20", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "company", - "fieldtype": "Link", - "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": "Company", - "length": 0, - "no_copy": 0, - "options": "Company", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldname": "company", + "fieldtype": "Link", + "label": "Company", + "options": "Company", + "remember_last_selected_value": 1 } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2018-12-27 01:22:17.721636", - "modified_by": "Administrator", - "module": "Accounts", - "name": "Tax Rule", - "name_case": "", - "owner": "Administrator", + ], + "links": [], + "modified": "2021-06-04 23:14:27.186879", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Tax Rule", + "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Accounts Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts Manager", + "share": 1, "write": 1 } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 1, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 0, - "track_seen": 0, - "track_views": 0 + ], + "show_name_in_global_search": 1, + "sort_field": "modified", + "sort_order": "DESC" } \ No newline at end of file diff --git a/erpnext/accounts/doctype/tax_rule/test_tax_rule.py b/erpnext/accounts/doctype/tax_rule/test_tax_rule.py index ac1ffd9e750..cf7226822ed 100644 --- a/erpnext/accounts/doctype/tax_rule/test_tax_rule.py +++ b/erpnext/accounts/doctype/tax_rule/test_tax_rule.py @@ -50,7 +50,7 @@ class TestTaxRule(unittest.TestCase): tax_rule1 = make_tax_rule(customer_group= "All Customer Groups", sales_tax_template = "_Test Sales Taxes and Charges Template - _TC", priority = 1, from_date = "2015-01-01") tax_rule1.save() - self.assertEqual(get_tax_template("2015-01-01", {"customer_group" : "Commercial", "use_for_shopping_cart":0}), + self.assertEqual(get_tax_template("2015-01-01", {"customer_group" : "Commercial", "use_for_shopping_cart":1}), "_Test Sales Taxes and Charges Template - _TC") def test_conflict_with_overlapping_dates(self): From 752f099e9dd5187669d11b6420dece399d072aff Mon Sep 17 00:00:00 2001 From: marination Date: Thu, 1 Jul 2021 17:20:24 +0530 Subject: [PATCH 34/60] fix: Order Items by weightage in the web items query --- erpnext/shopping_cart/product_query.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/shopping_cart/product_query.py b/erpnext/shopping_cart/product_query.py index d96d803416c..6c92d967d0c 100644 --- a/erpnext/shopping_cart/product_query.py +++ b/erpnext/shopping_cart/product_query.py @@ -71,7 +71,8 @@ class ProductQuery: ], or_filters=self.or_filters, start=start, - limit=self.page_length + limit=self.page_length, + order_by="weightage desc" ) items_dict = {item.name: item for item in items} @@ -86,7 +87,8 @@ class ProductQuery: filters=self.filters, or_filters=self.or_filters, start=start, - limit=self.page_length + limit=self.page_length, + order_by="weightage desc" ) # Combine results having context of website item groups into item results From ba2c3c776f15394ed287454dd9ccce60acd6f228 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 1 Jul 2021 18:56:51 +0530 Subject: [PATCH 35/60] fix: Bank statement import --- .../doctype/bank_statement_import/bank_statement_import.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py index 5f110e2727c..ffc9d1c4658 100644 --- a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py +++ b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py @@ -51,7 +51,7 @@ class BankStatementImport(DataImport): self.import_file, self.google_sheets_url ) - if 'Bank Account' not in json.dumps(preview): + if 'Bank Account' not in json.dumps(preview['columns']): frappe.throw(_("Please add the Bank Account column")) from frappe.core.page.background_jobs.background_jobs import get_info From 5173e74a041d33d87d4ab3172a4bfc4b64d66381 Mon Sep 17 00:00:00 2001 From: Jannat Patel <31363128+pateljannat@users.noreply.github.com> Date: Fri, 2 Jul 2021 11:48:46 +0530 Subject: [PATCH 36/60] fix: Project Portal Enhancements (#26290) * fix: project portal enhancements * fix: condition for pills --- erpnext/hooks.py | 1 + .../includes/projects/project_row.html | 80 ++++--- .../includes/projects/project_tasks.html | 33 +-- .../includes/projects/project_timesheets.html | 54 +++-- erpnext/templates/pages/projects.html | 215 ++++++++++++------ erpnext/templates/pages/projects.py | 41 +--- 6 files changed, 250 insertions(+), 174 deletions(-) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 8ad77a1524d..52daec91805 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -157,6 +157,7 @@ website_route_rules = [ "parents": [{"label": _("Material Request"), "route": "material-requests"}] } }, + {"from_route": "/project", "to_route": "Project"} ] standard_portal_menu_items = [ diff --git a/erpnext/templates/includes/projects/project_row.html b/erpnext/templates/includes/projects/project_row.html index 4c8c40db003..bfd63494c32 100644 --- a/erpnext/templates/includes/projects/project_row.html +++ b/erpnext/templates/includes/projects/project_row.html @@ -1,28 +1,54 @@ -{% if doc.status=="Open" %} - +{% if doc.status == "Open" %} +
+
+
+ Link + {{ doc.name }} +
+
+ {{ doc.project_name }} +
+
+ {% if doc.percent_complete %} + {% set pill_class = "green" if doc.percent_complete | round == 100 else + "orange" %} +
+ + {{ frappe.utils.cint(doc.percent_complete) }} + % + +
+ {% else %} + + {{ doc.status }} + {% endif %} +
+ {% if doc["_assign"] %} + {% set assigned_users = json.loads(doc["_assign"])%} +
+ {% for user in assigned_users %} + {% set user_details = frappe + .db + .get_value("User", user, [ + "full_name", "user_image" + ], as_dict = True) %} + {% if user_details.user_image %} + + + + {% else %} + +
+ {{ frappe.utils.get_abbr(user_details.full_name) }} +
+
+ {% endif %} + {% endfor %} +
+ {% endif %} +
+ {{ frappe.utils.pretty_date(doc.modified) }} +
+
+
{% endif %} diff --git a/erpnext/templates/includes/projects/project_tasks.html b/erpnext/templates/includes/projects/project_tasks.html index 50b9f4b2597..2b07a5f0d07 100644 --- a/erpnext/templates/includes/projects/project_tasks.html +++ b/erpnext/templates/includes/projects/project_tasks.html @@ -1,32 +1,5 @@ {% for task in doc.tasks %} - +
+ {{ task_row(task, 0) }} +
{% endfor %} diff --git a/erpnext/templates/includes/projects/project_timesheets.html b/erpnext/templates/includes/projects/project_timesheets.html index 05a07c12e8b..850c5e98631 100644 --- a/erpnext/templates/includes/projects/project_timesheets.html +++ b/erpnext/templates/includes/projects/project_timesheets.html @@ -1,23 +1,33 @@ {% for timesheet in doc.timesheets %} - -{% endfor %} \ No newline at end of file +
+
+
{{ timesheet.name }}
+ Link +
{{ timesheet.status }}
+
{{ frappe.utils.format_date(timesheet.from_time, "medium") }}
+
{{ frappe.utils.format_date(timesheet.to_time, "medium") }}
+
+ {% set user_details = frappe + .db + .get_value("User", timesheet.modified_by, [ + "full_name", "user_image" + ], as_dict = True) + %} + {% if user_details.user_image %} + + + + {% else %} + +
+ {{ frappe.utils.get_abbr(user_details.full_name) }} +
+
+ {% endif %} +
+
+ {{ frappe.utils.pretty_date(timesheet.modified) }} +
+
+
+{% endfor %} diff --git a/erpnext/templates/pages/projects.html b/erpnext/templates/pages/projects.html index 7e294e076b3..76eaf75cf30 100644 --- a/erpnext/templates/pages/projects.html +++ b/erpnext/templates/pages/projects.html @@ -1,90 +1,173 @@ {% extends "templates/web.html" %} -{% block title %}{{ doc.project_name }}{% endblock %} +{% block title %} + {{ doc.project_name }} +{% endblock %} + +{% block head_include %} + +{% endblock %} {% block header %} -

{{ doc.project_name }}

+

{{ doc.project_name }}

{% endblock %} {% block style %} - + {% endblock %} - {% block page_content %} -{% if doc.percent_complete %} -
-
-
-
-{% endif %} -
-

{{ _("Tasks") }}

- {{ _("New task") }} -
+ {{ progress_bar(doc.percent_complete) }} -

- -

+
+

Status:

+

Progress: + {{ doc.percent_complete }} + % +

+

Hours Spent: + {{ doc.actual_time }} +

+
-{% if doc.tasks %} -
-
- {% include "erpnext/templates/includes/projects/project_tasks.html" %} -
-

-

-{% else %} -

{{ _("No tasks") }}

-{% endif %} + {{ progress_bar(doc.percent_complete) }} + {% if doc.tasks %} +
+
+
+
+

Tasks

+

Status

+

End Date

+

Assigned To

+ +
+
+ {% include "erpnext/templates/includes/projects/project_tasks.html" %} +
+
+ {% else %} +

{{ _("No Tasks") }}

+ {% endif %} -
+ {% if doc.timesheets %} +
+
+
+
+

Timesheets

+

Status

+

From

+

To

+

Modified By

+

Modified On

+
+
+ {% include "erpnext/templates/includes/projects/project_timesheets.html" %} +
+
+ {% else %} +

{{ _("No Timesheets") }}

+ {% endif %} -

{{ _("Timesheets") }}

+ {% if doc.attachments %} +
-{% if doc.timesheets %} -
- {% include "erpnext/templates/includes/projects/project_timesheets.html" %} -
- {% if doc.timesheets|length > 9 %} -

{{ _("More") }}

- {% endif %} -{% else %} -

{{ _("No time sheets") }}

-{% endif %} - -{% if doc.attachments %} -
- -

{{ _("Attachments") }}

-
- {% for attachment in doc.attachments %} - - {% endfor %} -
-{% endif %} +

{{ _("Attachments") }}

+
+ {% for attachment in doc.attachments %} + + {% endfor %} +
+ {% endif %}
{% endblock %} + +{% macro progress_bar(percent_complete) %} +{% if percent_complete %} +
+
+
+{% else %} +
+{% endif %} +{% endmacro %} + +{% macro task_row(task, indent) %} +
+ +
{{ task.status }}
+
+ {% if task.exp_end_date %} + {{ task.exp_end_date }} + {% else %} + -- + {% endif %} +
+
+ {% if task["_assign"] %} + {% set assigned_users = json.loads(task["_assign"])%} + {% for user in assigned_users %} + {% set user_details = frappe.db.get_value("User", user, + ["full_name", "user_image"], + as_dict = True)%} + {% if user_details.user_image %} + + + + {% else %} + +
+ {{ frappe.utils.get_abbr(user_details.full_name) }} +
+
+ {% endif %} + {% endfor %} + {% endif %} +
+
+ {{ frappe.utils.pretty_date(task.modified) }} +
+
+{% if task.children %} + {% for child in task.children %} + {{ task_row(child, indent + 30) }} + {% endfor %} +{% endif %} +{% endmacro %} diff --git a/erpnext/templates/pages/projects.py b/erpnext/templates/pages/projects.py index d23fed9e7d1..b369cb6a991 100644 --- a/erpnext/templates/pages/projects.py +++ b/erpnext/templates/pages/projects.py @@ -35,26 +35,16 @@ def get_tasks(project, start=0, search=None, item_status=None): # if item_status: # filters["status"] = item_status tasks = frappe.get_all("Task", filters=filters, - fields=["name", "subject", "status", "_seen", "_comments", "modified", "description"], + fields=["name", "subject", "status", "modified", "_assign", "exp_end_date", "is_group", "parent_task"], limit_start=start, limit_page_length=10) - + task_nest = [] for task in tasks: - task.todo = frappe.get_all('ToDo',filters={'reference_name':task.name, 'reference_type':'Task'}, - fields=["assigned_by", "owner", "modified", "modified_by"]) - - if task.todo: - task.todo=task.todo[0] - task.todo.user_image = frappe.db.get_value('User', task.todo.owner, 'user_image') - - - task.comment_count = len(json.loads(task._comments or "[]")) - - task.css_seen = '' - if task._seen: - if frappe.session.user in json.loads(task._seen): - task.css_seen = 'seen' - - return tasks + if task.is_group: + child_tasks = list(filter(lambda x: x.parent_task == task.name, tasks)) + if len(child_tasks): + task.children = child_tasks + task_nest.append(task) + return list(filter(lambda x: not x.parent_task, tasks)) @frappe.whitelist() def get_task_html(project, start=0, item_status=None): @@ -74,19 +64,12 @@ def get_timesheets(project, start=0, search=None): fields=['project','activity_type','from_time','to_time','parent'], limit_start=start, limit_page_length=10) for timesheet in timesheets: - timesheet.infos = frappe.get_all('Timesheet', filters={"name": timesheet.parent}, - fields=['name','_comments','_seen','status','modified','modified_by'], + info = frappe.get_all('Timesheet', filters={"name": timesheet.parent}, + fields=['name','status','modified','modified_by'], limit_start=start, limit_page_length=10) - for timesheet.info in timesheet.infos: - timesheet.info.user_image = frappe.db.get_value('User', timesheet.info.modified_by, 'user_image') - - timesheet.info.comment_count = len(json.loads(timesheet.info._comments or "[]")) - - timesheet.info.css_seen = '' - if timesheet.info._seen: - if frappe.session.user in json.loads(timesheet.info._seen): - timesheet.info.css_seen = 'seen' + if len(info): + timesheet.update(info[0]) return timesheets @frappe.whitelist() From ad6f20c5c778fc2377f4febf8389d575d6c87185 Mon Sep 17 00:00:00 2001 From: Mohammed Yusuf Shaikh <49878143+mohammedyusufshaikh@users.noreply.github.com> Date: Fri, 2 Jul 2021 12:32:22 +0530 Subject: [PATCH 37/60] fix: Added permission for employee to book appointment (#26255) --- erpnext/crm/doctype/appointment/appointment.json | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/erpnext/crm/doctype/appointment/appointment.json b/erpnext/crm/doctype/appointment/appointment.json index 8517ddec321..306be7faa74 100644 --- a/erpnext/crm/doctype/appointment/appointment.json +++ b/erpnext/crm/doctype/appointment/appointment.json @@ -102,7 +102,7 @@ } ], "links": [], - "modified": "2020-01-28 16:16:45.447213", + "modified": "2021-06-29 18:27:02.832979", "modified_by": "Administrator", "module": "CRM", "name": "Appointment", @@ -153,6 +153,18 @@ "role": "Sales User", "share": 1, "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Employee", + "share": 1, + "write": 1 } ], "quick_entry": 1, From 18533e381a832bfb78f08d34eb51731edaeacb05 Mon Sep 17 00:00:00 2001 From: Jannat Patel <31363128+pateljannat@users.noreply.github.com> Date: Fri, 2 Jul 2021 12:57:06 +0530 Subject: [PATCH 38/60] fix: lms progress issue (#26253) --- erpnext/education/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/education/utils.py b/erpnext/education/utils.py index 9db8a4a90df..3070e6a3e8a 100644 --- a/erpnext/education/utils.py +++ b/erpnext/education/utils.py @@ -355,11 +355,11 @@ def get_or_create_course_enrollment(course, program): student = get_current_student() course_enrollment = get_enrollment("course", course, student.name) if not course_enrollment: - program_enrollment = get_enrollment('program', program, student.name) + program_enrollment = get_enrollment('program', program.name, student.name) if not program_enrollment: frappe.throw(_("You are not enrolled in program {0}").format(program)) return - return student.enroll_in_course(course_name=course, program_enrollment=get_enrollment('program', program, student.name)) + return student.enroll_in_course(course_name=course, program_enrollment=get_enrollment('program', program.name, student.name)) else: return frappe.get_doc('Course Enrollment', course_enrollment) From 877597bc16c061a68e4577e434df6b1c041033c5 Mon Sep 17 00:00:00 2001 From: Anupam Kumar Date: Fri, 2 Jul 2021 13:10:18 +0530 Subject: [PATCH 39/60] fix: feating employee in payroll entry (#26271) --- erpnext/payroll/doctype/payroll_entry/payroll_entry.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py index 5c7c0a3b092..36e728fc992 100644 --- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py +++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py @@ -680,6 +680,10 @@ def employee_query(doctype, txt, searchfield, start, page_len, filters): conditions = [] include_employees = [] emp_cond = '' + + if not filters.payroll_frequency: + frappe.throw(_('Select Payroll Frequency.')) + if filters.start_date and filters.end_date: employee_list = get_employee_list(filters) emp = filters.get('employees') From c0817838d951a56d8ea38f268a6e20d43ef05c71 Mon Sep 17 00:00:00 2001 From: Ashish Shah Date: Fri, 2 Jul 2021 15:16:42 +0530 Subject: [PATCH 40/60] fix: when lead is created with mobile_no, mobile_no value gets lost (#26298) Summary: When a Lead is created with mobile_no, mobile_no value gets lost (mobile_no value is overwritten by phone value) It is backport of https://github.com/frappe/erpnext/pull/26116 Steps to reproduce [1]Create a Lead. [2]Enter Person Name(lead_name): before_fix Under Contact section, enter Phone(phone): 11 and Mobile No.(mobile_no):22 [3]Save it [4] F12, cur_frm.doc.phone : 11 (correct) cur_frm.doc.mobile_no : 11 (incorrect, it should be 22) [5]Under Address & Contact section ,check contact_html it shows before_fix Phone: 11 (Primary label is missing) Phone: 22 (incorrect, it should be Mobile No:22, also Primary label is missing) Actual: mobile_no value is lost. it is overwritten by phone value following is image with error (before fix) ![image](https://user-images.githubusercontent.com/29812965/122664017-54b2e880-d1bc-11eb-8e4c-767a23ed7eb7.png) Expected: mobile_no value should be retained following is image after fix ![image](https://user-images.githubusercontent.com/29812965/122664037-64323180-d1bc-11eb-8f6f-7628cdaa7adc.png) --- erpnext/crm/doctype/lead/lead.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/crm/doctype/lead/lead.py b/erpnext/crm/doctype/lead/lead.py index d1d096843bf..ce3de40fc3d 100644 --- a/erpnext/crm/doctype/lead/lead.py +++ b/erpnext/crm/doctype/lead/lead.py @@ -168,12 +168,13 @@ class Lead(SellingController): if self.phone: contact.append("phone_nos", { "phone": self.phone, - "is_primary": 1 + "is_primary_phone": 1 }) if self.mobile_no: contact.append("phone_nos", { - "phone": self.mobile_no + "phone": self.mobile_no, + "is_primary_mobile_no":1 }) contact.insert(ignore_permissions=True) From b6076f772d6fd9928ddadb8572e391a7beb33c92 Mon Sep 17 00:00:00 2001 From: Afshan Date: Fri, 2 Jul 2021 15:39:16 +0530 Subject: [PATCH 41/60] fix: only "Tax" type accounts should be shown for selection in GST Settings --- erpnext/regional/doctype/gst_settings/gst_settings.js | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/regional/doctype/gst_settings/gst_settings.js b/erpnext/regional/doctype/gst_settings/gst_settings.js index 808f9bc0789..cd682c5403e 100644 --- a/erpnext/regional/doctype/gst_settings/gst_settings.js +++ b/erpnext/regional/doctype/gst_settings/gst_settings.js @@ -35,6 +35,7 @@ frappe.ui.form.on('GST Settings', { return { filters: { company: row.company, + account_type: "Tax", is_group: 0 } }; From 4503a3836128784541ef14a575f9067db182438c Mon Sep 17 00:00:00 2001 From: marination Date: Fri, 2 Jul 2021 17:13:45 +0530 Subject: [PATCH 42/60] fix: Handle Stock Reco cancellation and limit reposting - Handled cancellation of reco with and without prior SLE - Repost / Recalculate balance qty only till next stock reco --- .../stock_reconciliation.py | 1 + erpnext/stock/stock_ledger.py | 84 ++++++++++++++++--- 2 files changed, 75 insertions(+), 10 deletions(-) diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index 2b51c1a5c38..7b84c4c2d93 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -357,6 +357,7 @@ class StockReconciliation(StockController): if row.current_qty: data.actual_qty = -1 * row.current_qty data.qty_after_transaction = flt(row.current_qty) + data.previous_qty_after_transaction = flt(row.qty) data.valuation_rate = flt(row.current_valuation_rate) data.stock_value = data.qty_after_transaction * data.valuation_rate data.stock_value_difference = -1 * flt(row.amount_difference) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 94bd3077a73..7425473f9d0 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -55,6 +55,11 @@ def make_sl_entries(sl_entries, allow_negative_stock=False, via_landed_cost_vouc sle_doc = make_entry(sle, allow_negative_stock, via_landed_cost_voucher) args = sle_doc.as_dict() + + if sle.get("voucher_type") == "Stock Reconciliation": + # preserve previous_qty_after_transaction for qty reposting + args.previous_qty_after_transaction = sle.get("previous_qty_after_transaction") + update_bin(args, allow_negative_stock, via_landed_cost_voucher) def get_args_for_future_sle(row): @@ -869,19 +874,21 @@ def get_valuation_rate(item_code, warehouse, voucher_type, voucher_no, def update_qty_in_future_sle(args, allow_negative_stock=None): """Recalculate Qty after Transaction in future SLEs based on current SLE.""" + datetime_limit_condition = "" + last_balance = None + qty_shift = args.actual_qty # find difference/shift in qty caused by stock reconciliation if args.voucher_type == "Stock Reconciliation": - last_balance = get_previous_sle_of_current_voucher( - args, - exclude_current_voucher=True - ).get("qty_after_transaction") - if last_balance is not None: - stock_reco_qty_shift = flt(args.qty_after_transaction) - flt(last_balance) - else: - stock_reco_qty_shift = args.qty_after_transaction - qty_shift = stock_reco_qty_shift + qty_shift = get_stock_reco_qty_shift(args) + + # find the next nearest stock reco so that we only recalculate SLEs till that point + next_stock_reco_detail = get_next_stock_reco(args) + if next_stock_reco_detail: + detail = next_stock_reco_detail[0] + # add condition to update SLEs before this date & time + datetime_limit_condition = get_datetime_limit_condition(detail) frappe.db.sql(""" update `tabStock Ledger Entry` @@ -897,10 +904,67 @@ def update_qty_in_future_sle(args, allow_negative_stock=None): and creation > %(creation)s ) ) - """.format(qty_shift=qty_shift), args) + {datetime_limit_condition} + """.format(qty_shift=qty_shift, datetime_limit_condition=datetime_limit_condition), args) validate_negative_qty_in_future_sle(args, allow_negative_stock) +def get_stock_reco_qty_shift(args): + stock_reco_qty_shift = 0 + if args.get("is_cancelled"): + if args.get("previous_qty_after_transaction"): + # get qty (balance) that was set at submission + last_balance = args.get("previous_qty_after_transaction") + stock_reco_qty_shift = flt(args.qty_after_transaction) - flt(last_balance) + else: + stock_reco_qty_shift = flt(args.actual_qty) + else: + # reco is being submitted + last_balance = get_previous_sle_of_current_voucher(args, + exclude_current_voucher=True).get("qty_after_transaction") + + if last_balance is not None: + stock_reco_qty_shift = flt(args.qty_after_transaction) - flt(last_balance) + else: + stock_reco_qty_shift = args.qty_after_transaction + + return stock_reco_qty_shift + +def get_next_stock_reco(args): + """Returns next nearest stock reconciliaton's details.""" + + return frappe.db.sql(""" + select + name, posting_date, posting_time, creation, voucher_no + from + `tabStock Ledger Entry` + where + item_code = %(item_code)s + and warehouse = %(warehouse)s + and voucher_type = 'Stock Reconciliation' + and voucher_no != %(voucher_no)s + and is_cancelled = 0 + and (timestamp(posting_date, posting_time) > timestamp(%(posting_date)s, %(posting_time)s) + or ( + timestamp(posting_date, posting_time) = timestamp(%(posting_date)s, %(posting_time)s) + and creation > %(creation)s + ) + ) + limit 1 + """, args, as_dict=1) + +def get_datetime_limit_condition(detail): + if not detail: return None + + return f""" + and + (timestamp(posting_date, posting_time) < timestamp('{detail.posting_date}', '{detail.posting_time}') + or ( + timestamp(posting_date, posting_time) = timestamp('{detail.posting_date}', '{detail.posting_time}') + and creation < '{detail.creation}' + ) + )""" + def validate_negative_qty_in_future_sle(args, allow_negative_stock=None): allow_negative_stock = allow_negative_stock \ or cint(frappe.db.get_single_value("Stock Settings", "allow_negative_stock")) From 73db919c99d376abbea9d98b2f6e53a8e46ff4ed Mon Sep 17 00:00:00 2001 From: Anurag Mishra <32095923+Anurag810@users.noreply.github.com> Date: Fri, 2 Jul 2021 17:55:42 +0530 Subject: [PATCH 43/60] fix: set query for training events (#26303) * fix: set query * fix: remove whitespace between function and params Co-authored-by: Rucha Mahabal --- .../hr/doctype/training_event/training_event.js | 14 ++++++++++---- .../training_event_employee.json | 3 ++- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/erpnext/hr/doctype/training_event/training_event.js b/erpnext/hr/doctype/training_event/training_event.js index b7d34b178a0..064dfb24557 100644 --- a/erpnext/hr/doctype/training_event/training_event.js +++ b/erpnext/hr/doctype/training_event/training_event.js @@ -20,11 +20,10 @@ frappe.ui.form.on('Training Event', { frappe.set_route("List", "Training Feedback"); }); } - } -}); + frm.events.set_employee_query(frm); + }, -frappe.ui.form.on("Training Event Employee", { - employee: function (frm) { + set_employee_query: function(frm) { let emp = []; for (let d in frm.doc.employees) { if (frm.doc.employees[d].employee) { @@ -40,3 +39,10 @@ frappe.ui.form.on("Training Event Employee", { }); } }); + +frappe.ui.form.on("Training Event Employee", { + employee: function(frm) { + frm.events.set_employee_query(frm); + } +}); + diff --git a/erpnext/hr/doctype/training_event_employee/training_event_employee.json b/erpnext/hr/doctype/training_event_employee/training_event_employee.json index 2d313e9faca..bcb7d5e5bc0 100644 --- a/erpnext/hr/doctype/training_event_employee/training_event_employee.json +++ b/erpnext/hr/doctype/training_event_employee/training_event_employee.json @@ -19,6 +19,7 @@ "fieldtype": "Link", "in_list_view": 1, "label": "Employee", + "no_copy": 1, "options": "Employee" }, { @@ -68,7 +69,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-05-21 12:41:59.336237", + "modified": "2021-07-02 17:20:27.630176", "modified_by": "Administrator", "module": "HR", "name": "Training Event Employee", From 311e277204a6aa28a6a132a888787385018e546f Mon Sep 17 00:00:00 2001 From: marination Date: Fri, 2 Jul 2021 17:46:05 +0530 Subject: [PATCH 44/60] fix: Sider --- erpnext/stock/stock_ledger.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 7425473f9d0..4e9c7689ae4 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -875,8 +875,6 @@ def get_valuation_rate(item_code, warehouse, voucher_type, voucher_no, def update_qty_in_future_sle(args, allow_negative_stock=None): """Recalculate Qty after Transaction in future SLEs based on current SLE.""" datetime_limit_condition = "" - last_balance = None - qty_shift = args.actual_qty # find difference/shift in qty caused by stock reconciliation @@ -937,7 +935,7 @@ def get_next_stock_reco(args): select name, posting_date, posting_time, creation, voucher_no from - `tabStock Ledger Entry` + `tabStock Ledger Entry` where item_code = %(item_code)s and warehouse = %(warehouse)s @@ -954,8 +952,6 @@ def get_next_stock_reco(args): """, args, as_dict=1) def get_datetime_limit_condition(detail): - if not detail: return None - return f""" and (timestamp(posting_date, posting_time) < timestamp('{detail.posting_date}', '{detail.posting_time}') From 3105332e3c707af1360e529ec77e438897cc9e2f Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Sat, 3 Jul 2021 17:22:09 +0530 Subject: [PATCH 45/60] fix: allow to make job card without employee --- .../doctype/job_card/job_card.py | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index 7f8f2ef68d0..420bb008039 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -192,15 +192,20 @@ class JobCard(Document): "completed_qty": args.get("completed_qty") or 0.0 }) elif args.get("start_time"): - for name in employees: - self.append("time_logs", { - "from_time": get_datetime(args.get("start_time")), - "employee": name.get('employee'), - "operation": args.get("sub_operation"), - "completed_qty": 0.0 - }) + new_args = { + "from_time": get_datetime(args.get("start_time")), + "operation": args.get("sub_operation"), + "completed_qty": 0.0 + } - if not self.employee: + if employees: + for name in employees: + new_args.employee = name.get('employee') + self.add_start_time_log(new_args) + else: + self.add_start_time_log(new_args) + + if not self.employee and employees: self.set_employees(employees) if self.status == "On Hold": @@ -208,6 +213,9 @@ class JobCard(Document): self.save() + def add_start_time_log(self, args): + self.append("time_logs", args) + def set_employees(self, employees): for name in employees: self.append('employee', { From a0599e5ac2d4da456256ab00bbe2105e03d3e821 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 5 Jul 2021 10:09:42 +0530 Subject: [PATCH 46/60] fix: Test cases for M-pesa --- .../doctype/mpesa_settings/test_mpesa_settings.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py b/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py index 3c2e59ab821..2dfd5aad5db 100644 --- a/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py +++ b/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py @@ -9,13 +9,17 @@ from erpnext.erpnext_integrations.doctype.mpesa_settings.mpesa_settings import p from erpnext.accounts.doctype.pos_invoice.test_pos_invoice import create_pos_invoice class TestMpesaSettings(unittest.TestCase): + def setUp(self): + # create payment gateway in setup + create_mpesa_settings(payment_gateway_name="_Test") + create_mpesa_settings(payment_gateway_name="_Account Balance") + create_mpesa_settings(payment_gateway_name="Payment") + def tearDown(self): frappe.db.sql('delete from `tabMpesa Settings`') frappe.db.sql('delete from `tabIntegration Request` where integration_request_service = "Mpesa"') def test_creation_of_payment_gateway(self): - create_mpesa_settings(payment_gateway_name="_Test") - mode_of_payment = frappe.get_doc("Mode of Payment", "Mpesa-_Test") self.assertTrue(frappe.db.exists("Payment Gateway Account", {'payment_gateway': "Mpesa-_Test"})) self.assertTrue(mode_of_payment.name) @@ -47,7 +51,6 @@ class TestMpesaSettings(unittest.TestCase): integration_request.delete() def test_processing_of_callback_payload(self): - create_mpesa_settings(payment_gateway_name="Payment") mpesa_account = frappe.db.get_value("Payment Gateway Account", {"payment_gateway": 'Mpesa-Payment'}, "payment_account") frappe.db.set_value("Account", mpesa_account, "account_currency", "KES") frappe.db.set_value("Customer", "_Test Customer", "default_currency", "KES") @@ -90,7 +93,6 @@ class TestMpesaSettings(unittest.TestCase): pos_invoice.delete() def test_processing_of_multiple_callback_payload(self): - create_mpesa_settings(payment_gateway_name="Payment") mpesa_account = frappe.db.get_value("Payment Gateway Account", {"payment_gateway": 'Mpesa-Payment'}, "payment_account") frappe.db.set_value("Account", mpesa_account, "account_currency", "KES") frappe.db.set_value("Mpesa Settings", "Payment", "transaction_limit", "500") @@ -141,7 +143,6 @@ class TestMpesaSettings(unittest.TestCase): pos_invoice.delete() def test_processing_of_only_one_succes_callback_payload(self): - create_mpesa_settings(payment_gateway_name="Payment") mpesa_account = frappe.db.get_value("Payment Gateway Account", {"payment_gateway": 'Mpesa-Payment'}, "payment_account") frappe.db.set_value("Account", mpesa_account, "account_currency", "KES") frappe.db.set_value("Mpesa Settings", "Payment", "transaction_limit", "500") From 75fdf79376ccec55e34cf0ff1911f297360a431d Mon Sep 17 00:00:00 2001 From: Richard Case <64409021+casesolved-co-uk@users.noreply.github.com> Date: Mon, 5 Jul 2021 06:26:34 +0100 Subject: [PATCH 47/60] fix: incorrect bom no. added for non-variant items on variant boms (#26320) --- erpnext/manufacturing/doctype/bom/bom.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index c31b1bd3e9c..c32a8a95a17 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -1115,6 +1115,8 @@ def make_variant_bom(source_name, bom_no, item, variant_items, target_doc=None): }, 'BOM Item': { 'doctype': 'BOM Item', + # stop get_mapped_doc copying parent bom_no to children + 'field_no_map': ['bom_no'], 'condition': lambda doc: doc.has_variants == 0 }, }, target_doc, postprocess) From db682d9e4cdea680f2ab0d4e589000e54baf6a20 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 5 Jul 2021 13:46:03 +0530 Subject: [PATCH 48/60] fix: Create mode of payment if doesn't exists --- .../doctype/mpesa_settings/test_mpesa_settings.py | 3 ++- erpnext/erpnext_integrations/utils.py | 7 ++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py b/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py index 2dfd5aad5db..f592c180a3b 100644 --- a/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py +++ b/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py @@ -7,6 +7,7 @@ import frappe import unittest from erpnext.erpnext_integrations.doctype.mpesa_settings.mpesa_settings import process_balance_info, verify_transaction from erpnext.accounts.doctype.pos_invoice.test_pos_invoice import create_pos_invoice +from erpnext.erpnext_integrations.utils import create_mode_of_payment class TestMpesaSettings(unittest.TestCase): def setUp(self): @@ -20,7 +21,7 @@ class TestMpesaSettings(unittest.TestCase): frappe.db.sql('delete from `tabIntegration Request` where integration_request_service = "Mpesa"') def test_creation_of_payment_gateway(self): - mode_of_payment = frappe.get_doc("Mode of Payment", "Mpesa-_Test") + mode_of_payment = create_mode_of_payment('Mpesa-_Test', payment_type="Phone") self.assertTrue(frappe.db.exists("Payment Gateway Account", {'payment_gateway': "Mpesa-_Test"})) self.assertTrue(mode_of_payment.name) self.assertEqual(mode_of_payment.type, "Phone") diff --git a/erpnext/erpnext_integrations/utils.py b/erpnext/erpnext_integrations/utils.py index 3840e781b4c..b764701103d 100644 --- a/erpnext/erpnext_integrations/utils.py +++ b/erpnext/erpnext_integrations/utils.py @@ -52,7 +52,8 @@ def create_mode_of_payment(gateway, payment_type="General"): "payment_gateway": gateway }, ['payment_account']) - if not frappe.db.exists("Mode of Payment", gateway) and payment_gateway_account: + mode_of_payment = frappe.db.exists("Mode of Payment", gateway) + if not mode_of_payment and payment_gateway_account: mode_of_payment = frappe.get_doc({ "doctype": "Mode of Payment", "mode_of_payment": gateway, @@ -66,6 +67,10 @@ def create_mode_of_payment(gateway, payment_type="General"): }) mode_of_payment.insert(ignore_permissions=True) + return mode_of_payment + else: + return frappe.get_doc("Mode of Payment", mode_of_payment) + def get_tracking_url(carrier, tracking_number): # Return the formatted Tracking URL. tracking_url = '' From 5638fbb1aac81bc1196c9a942e1abb9310983ce6 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 5 Jul 2021 13:54:05 +0530 Subject: [PATCH 49/60] fix: bom stock report not working --- .../manufacturing/report/bom_stock_report/bom_stock_report.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py b/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py index 1c6758e6f36..ed8b93929a1 100644 --- a/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py +++ b/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py @@ -70,12 +70,12 @@ def get_bom_stock(filters): ON bom_item.item_code = ledger.item_code {conditions} WHERE - bom_item.parent = '{bom}' and bom_item.parenttype='BOM' + bom_item.parent = {bom} and bom_item.parenttype='BOM' GROUP BY bom_item.item_code""".format( qty_field=qty_field, table=table, conditions=conditions, - bom=bom, + bom=frappe.db.escape(bom), qty_to_produce=qty_to_produce or 1) ) From 15b336df28c6a8746bea4edb362400bd6c1f8c3d Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 5 Jul 2021 14:45:33 +0530 Subject: [PATCH 50/60] fix: Test cases --- erpnext/erpnext_integrations/utils.py | 2 +- erpnext/hr/doctype/expense_claim/test_expense_claim.py | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/erpnext/erpnext_integrations/utils.py b/erpnext/erpnext_integrations/utils.py index b764701103d..a5e162f8b5d 100644 --- a/erpnext/erpnext_integrations/utils.py +++ b/erpnext/erpnext_integrations/utils.py @@ -68,7 +68,7 @@ def create_mode_of_payment(gateway, payment_type="General"): mode_of_payment.insert(ignore_permissions=True) return mode_of_payment - else: + elif mode_of_payment: return frappe.get_doc("Mode of Payment", mode_of_payment) def get_tracking_url(carrier, tracking_number): diff --git a/erpnext/hr/doctype/expense_claim/test_expense_claim.py b/erpnext/hr/doctype/expense_claim/test_expense_claim.py index 578eccf787d..141561fcdcc 100644 --- a/erpnext/hr/doctype/expense_claim/test_expense_claim.py +++ b/erpnext/hr/doctype/expense_claim/test_expense_claim.py @@ -72,7 +72,8 @@ class TestExpenseClaim(unittest.TestCase): def test_expense_claim_gl_entry(self): payable_account = get_payable_account(company_name) taxes = generate_taxes() - expense_claim = make_expense_claim(payable_account, 300, 200, company_name, "Travel Expenses - _TC4", do_not_submit=True, taxes=taxes) + expense_claim = make_expense_claim(payable_account, 300, 200, company_name, "Travel Expenses - _TC4", + do_not_submit=True, taxes=taxes) expense_claim.submit() gl_entries = frappe.db.sql("""select account, debit, credit @@ -145,7 +146,7 @@ def generate_taxes(): parent_account = frappe.db.get_value('Account', {'company': company_name, 'is_group':1, 'account_type': 'Tax'}, 'name') - account = create_account(company=company_name, account_name="CGST", account_type="Tax", parent_account=parent_account) + account = create_account(company=company_name, account_name="Output Tax CGST", account_type="Tax", parent_account=parent_account) return {'taxes':[{ "account_head": account, "rate": 0, From 9b6d9a41f4a66fefcb02e7ec0c4f3a4100c4d3ac Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 5 Jul 2021 17:08:27 +0530 Subject: [PATCH 51/60] fix: Test Cases --- .../doctype/mpesa_settings/test_mpesa_settings.py | 1 + erpnext/hr/doctype/expense_claim/test_expense_claim.py | 2 +- erpnext/setup/setup_wizard/operations/install_fixtures.py | 7 ++++++- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py b/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py index f592c180a3b..b0e662d3f32 100644 --- a/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py +++ b/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py @@ -204,6 +204,7 @@ def create_mpesa_settings(payment_gateway_name="Express"): doc = frappe.get_doc(dict( #nosec doctype="Mpesa Settings", + sandbox=1, payment_gateway_name=payment_gateway_name, consumer_key="5sMu9LVI1oS3oBGPJfh3JyvLHwZOdTKn", consumer_secret="VI1oS3oBGPJfh3JyvLHw", diff --git a/erpnext/hr/doctype/expense_claim/test_expense_claim.py b/erpnext/hr/doctype/expense_claim/test_expense_claim.py index 141561fcdcc..96ea686706c 100644 --- a/erpnext/hr/doctype/expense_claim/test_expense_claim.py +++ b/erpnext/hr/doctype/expense_claim/test_expense_claim.py @@ -83,7 +83,7 @@ class TestExpenseClaim(unittest.TestCase): self.assertTrue(gl_entries) expected_values = dict((d[0], d) for d in [ - ['CGST - _TC4',18.0, 0.0], + ['Output Tax CGST - _TC4',18.0, 0.0], [payable_account, 0.0, 218.0], ["Travel Expenses - _TC4", 200.0, 0.0] ]) diff --git a/erpnext/setup/setup_wizard/operations/install_fixtures.py b/erpnext/setup/setup_wizard/operations/install_fixtures.py index 7dfb9f4d3c2..3dcb63867c1 100644 --- a/erpnext/setup/setup_wizard/operations/install_fixtures.py +++ b/erpnext/setup/setup_wizard/operations/install_fixtures.py @@ -449,6 +449,8 @@ def install_defaults(args=None): set_active_domains(args) update_stock_settings() update_shopping_cart_settings(args) + + args.update({"set_default": 1}) create_bank_account(args) def set_global_defaults(args): @@ -499,7 +501,10 @@ def create_bank_account(args): try: doc = bank_account.insert() - frappe.db.set_value("Company", args.get('company_name'), "default_bank_account", bank_account.name, update_modified=False) + if args.get('set_default'): + frappe.db.set_value("Company", args.get('company_name'), "default_bank_account", bank_account.name, update_modified=False) + + return doc except RootNotEditable: frappe.throw(_("Bank account cannot be named as {0}").format(args.get('bank_account'))) From fa9e67502cf538e56b42e89e03878a9766b034f6 Mon Sep 17 00:00:00 2001 From: marination Date: Mon, 5 Jul 2021 20:23:00 +0530 Subject: [PATCH 52/60] chore: Test for backdated reco qty reposting --- .../test_stock_reconciliation.py | 71 ++++++++++++++++++- 1 file changed, 70 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py index 36380b838b1..f7b243221a9 100644 --- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py @@ -6,7 +6,7 @@ from __future__ import unicode_literals import frappe, unittest -from frappe.utils import flt, nowdate, nowtime +from frappe.utils import flt, nowdate, nowtime, add_days from erpnext.accounts.utils import get_stock_and_account_balance from erpnext.stock.stock_ledger import get_previous_sle, update_entries_after from erpnext.stock.doctype.stock_reconciliation.stock_reconciliation import EmptyStockReconciliationItemsError, get_items @@ -14,6 +14,7 @@ from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse from erpnext.stock.doctype.item.test_item import create_item from erpnext.stock.utils import get_incoming_rate, get_stock_value_on, get_valuation_method from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos +from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt class TestStockReconciliation(unittest.TestCase): @classmethod @@ -204,6 +205,74 @@ class TestStockReconciliation(unittest.TestCase): self.assertEqual(sr.get("items")[0].valuation_rate, 0) self.assertEqual(sr.get("items")[0].amount, 0) + def test_backdated_stock_reco_qty_reposting(self): + """ + Test if a backdated stock reco recalculates future qty until next reco. + ------------------------------------------- + Var | Doc | Qty | Balance + ------------------------------------------- + SR5 | Reco | 0 | 8 (posting date: today-4) [backdated] + PR1 | PR | 10 | 18 (posting date: today-3) + PR2 | PR | 1 | 19 (posting date: today-2) + SR4 | Reco | 0 | 6 (posting date: today-1) [backdated] + PR3 | PR | 1 | 7 (posting date: today) # can't post future PR + """ + item_code = "Backdated-Reco-Item" + warehouse = "_Test Warehouse - _TC" + create_item(item_code) + + pr1 = make_purchase_receipt(item_code=item_code, warehouse=warehouse, qty=10, rate=100, + posting_date=add_days(nowdate(), -3)) + pr2 = make_purchase_receipt(item_code=item_code, warehouse=warehouse, qty=1, rate=100, + posting_date=add_days(nowdate(), -2)) + pr3 = make_purchase_receipt(item_code=item_code, warehouse=warehouse, qty=1, rate=100, + posting_date=nowdate()) + + pr1_balance = frappe.db.get_value("Stock Ledger Entry", {"voucher_no": pr1.name, "is_cancelled": 0}, + "qty_after_transaction") + pr3_balance = frappe.db.get_value("Stock Ledger Entry", {"voucher_no": pr3.name, "is_cancelled": 0}, + "qty_after_transaction") + self.assertEqual(pr1_balance, 10) + self.assertEqual(pr3_balance, 12) + + # post backdated stock reco in between + sr4 = create_stock_reconciliation(item_code=item_code, warehouse=warehouse, qty=6, rate=100, + posting_date=add_days(nowdate(), -1)) + pr3_balance = frappe.db.get_value("Stock Ledger Entry", {"voucher_no": pr3.name, "is_cancelled": 0}, + "qty_after_transaction") + self.assertEqual(pr3_balance, 7) + + # post backdated stock reco at the start + sr5 = create_stock_reconciliation(item_code=item_code, warehouse=warehouse, qty=8, rate=100, + posting_date=add_days(nowdate(), -4)) + pr1_balance = frappe.db.get_value("Stock Ledger Entry", {"voucher_no": pr1.name, "is_cancelled": 0}, + "qty_after_transaction") + pr2_balance = frappe.db.get_value("Stock Ledger Entry", {"voucher_no": pr2.name, "is_cancelled": 0}, + "qty_after_transaction") + sr4_balance = frappe.db.get_value("Stock Ledger Entry", {"voucher_no": sr4.name, "is_cancelled": 0}, + "qty_after_transaction") + self.assertEqual(pr1_balance, 18) + self.assertEqual(pr2_balance, 19) + self.assertEqual(sr4_balance, 6) # check if future stock reco is unaffected + + # cancel backdated stock reco and check future impact + sr5.cancel() + pr1_balance = frappe.db.get_value("Stock Ledger Entry", {"voucher_no": pr1.name, "is_cancelled": 0}, + "qty_after_transaction") + pr2_balance = frappe.db.get_value("Stock Ledger Entry", {"voucher_no": pr2.name, "is_cancelled": 0}, + "qty_after_transaction") + sr4_balance = frappe.db.get_value("Stock Ledger Entry", {"voucher_no": sr4.name, "is_cancelled": 0}, + "qty_after_transaction") + self.assertEqual(pr1_balance, 10) + self.assertEqual(pr2_balance, 11) + self.assertEqual(sr4_balance, 6) # check if future stock reco is unaffected + + # teardown + sr4.cancel() + pr3.cancel() + pr2.cancel() + pr1.cancel() + def insert_existing_sle(warehouse): from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry From 57d06a86f8b2ab481cf5c2cb6d8a3f7c40c05ec5 Mon Sep 17 00:00:00 2001 From: marination Date: Mon, 5 Jul 2021 21:56:03 +0530 Subject: [PATCH 53/60] chore: Test to block backdated reco causing future scarcity --- .../test_stock_reconciliation.py | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py index cd891c0ed64..84cdc491282 100644 --- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py @@ -309,6 +309,49 @@ class TestStockReconciliation(unittest.TestCase): pr2.cancel() pr1.cancel() + def test_backdated_stock_reco_future_negative_stock(self): + """ + Test if a backdated stock reco causes future negative stock and is blocked. + ------------------------------------------- + Var | Doc | Qty | Balance + ------------------------------------------- + PR1 | PR | 10 | 10 (posting date: today-2) + SR3 | Reco | 0 | 1 (posting date: today-1) [backdated & blocked] + DN2 | DN | -2 | 8(-1) (posting date: today) + """ + from erpnext.stock.stock_ledger import NegativeStockError + from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note + + item_code = "Backdated-Reco-Item" + warehouse = "_Test Warehouse - _TC" + create_item(item_code) + + negative_stock_setting = frappe.db.get_single_value("Stock Settings", "allow_negative_stock") + frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 0) + + pr1 = make_purchase_receipt(item_code=item_code, warehouse=warehouse, qty=10, rate=100, + posting_date=add_days(nowdate(), -2)) + dn2 = create_delivery_note(item_code=item_code, warehouse=warehouse, qty=2, rate=120, + posting_date=nowdate()) + + pr1_balance = frappe.db.get_value("Stock Ledger Entry", {"voucher_no": pr1.name, "is_cancelled": 0}, + "qty_after_transaction") + dn2_balance = frappe.db.get_value("Stock Ledger Entry", {"voucher_no": dn2.name, "is_cancelled": 0}, + "qty_after_transaction") + self.assertEqual(pr1_balance, 10) + self.assertEqual(dn2_balance, 8) + + # check if stock reco is blocked + sr3 = create_stock_reconciliation(item_code=item_code, warehouse=warehouse, qty=1, rate=100, + posting_date=add_days(nowdate(), -1), do_not_submit=True) + self.assertRaises(NegativeStockError, sr3.submit) + + # teardown + frappe.db.set_value("Stock Settings", None, "allow_negative_stock", negative_stock_setting) + sr3.cancel() + dn2.cancel() + pr1.cancel() + def insert_existing_sle(warehouse): from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry From 842674ce79c8f2130d60b90b05afd9feb9c05bb0 Mon Sep 17 00:00:00 2001 From: Mohammed Yusuf Shaikh <49878143+mohammedyusufshaikh@users.noreply.github.com> Date: Tue, 6 Jul 2021 13:34:32 +0530 Subject: [PATCH 54/60] fix: Added a message to enable appontment booking if disabled (#26334) --- erpnext/www/book_appointment/index.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/www/book_appointment/index.py b/erpnext/www/book_appointment/index.py index 7bfac89f308..ccfa97bc62f 100644 --- a/erpnext/www/book_appointment/index.py +++ b/erpnext/www/book_appointment/index.py @@ -2,6 +2,7 @@ import frappe import datetime import json import pytz +from frappe import _ WEEKDAYS = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"] @@ -14,7 +15,8 @@ def get_context(context): if is_enabled: return context else: - frappe.local.flags.redirect_location = '/404' + frappe.redirect_to_message(_("Appointment Scheduling Disabled"), _("Appointment Scheduling has been disabled for this site"), + http_status_code=302, indicator_color="red") raise frappe.Redirect @frappe.whitelist(allow_guest=True) From c8eca8a448de4e4654c16141fc61f047d563cf55 Mon Sep 17 00:00:00 2001 From: Afshan <33727827+AfshanKhan@users.noreply.github.com> Date: Tue, 6 Jul 2021 14:37:57 +0530 Subject: [PATCH 55/60] fix: remove cancelled entries in consolidated financial statements (#26331) --- .../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 7793af737f9..56a67bb0989 100644 --- a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py +++ b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py @@ -380,7 +380,7 @@ def set_gl_entries_by_account(from_date, to_date, root_lft, root_rgt, filters, g gl_entries = frappe.db.sql("""select gl.posting_date, gl.account, gl.debit, gl.credit, gl.is_opening, gl.company, gl.fiscal_year, gl.debit_in_account_currency, gl.credit_in_account_currency, gl.account_currency, acc.account_name, acc.account_number - from `tabGL Entry` gl, `tabAccount` acc where acc.name = gl.account and gl.company = %(company)s + from `tabGL Entry` gl, `tabAccount` acc where acc.name = gl.account and gl.company = %(company)s and gl.is_cancelled = 0 {additional_conditions} and gl.posting_date <= %(to_date)s and acc.lft >= %(lft)s and acc.rgt <= %(rgt)s order by gl.account, gl.posting_date""".format(additional_conditions=additional_conditions), { From 7f794cc0ea062ed8c3a799400cd9d2b044a164af Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 6 Jul 2021 17:58:44 +0530 Subject: [PATCH 56/60] fix: Purchase Invoice advance test case --- .../purchase_invoice/test_purchase_invoice.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 2f5d36c8fab..311745d3cd8 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -1010,21 +1010,21 @@ class TestPurchaseInvoice(unittest.TestCase): # Check GLE for Purchase Invoice # Zero net effect on final TDS Payable on invoice expected_gle = [ - ['_Test Account Cost for Goods Sold - _TC', 30000, 0], - ['_Test Account Excise Duty - _TC', 0, 3000], - ['Creditors - _TC', 0, 27000], - ['TDS Payable - _TC', 3000, 3000] + ['_Test Account Cost for Goods Sold - _TC', 30000], + ['_Test Account Excise Duty - _TC', -3000], + ['Creditors - _TC', -27000], + ['TDS Payable - _TC', 0] ] - gl_entries = frappe.db.sql("""select account, debit, credit + gl_entries = frappe.db.sql("""select account, sum(debit - credit) as amount from `tabGL Entry` where voucher_type='Purchase Invoice' and voucher_no=%s + group by account order by account asc""", (purchase_invoice.name), as_dict=1) for i, gle in enumerate(gl_entries): self.assertEqual(expected_gle[i][0], gle.account) - self.assertEqual(expected_gle[i][1], gle.debit) - self.assertEqual(expected_gle[i][2], gle.credit) + self.assertEqual(expected_gle[i][1], gle.amount) def update_tax_witholding_category(company, account, date): from erpnext.accounts.utils import get_fiscal_year From 5e99aa7f65ea424159b6122b50064023427f89c8 Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Tue, 6 Jul 2021 18:00:35 +0530 Subject: [PATCH 57/60] fix: stock_rbnb not defined (#26354) --- erpnext/stock/doctype/purchase_receipt/purchase_receipt.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index e488b695b5f..82c87a83a50 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -386,6 +386,7 @@ class PurchaseReceipt(BuyingController): against_account = ", ".join([d.account for d in gl_entries if flt(d.debit) > 0]) total_valuation_amount = sum(valuation_tax.values()) amount_including_divisional_loss = negative_expense_to_be_booked + stock_rbnb = self.get_company_default("stock_received_but_not_billed") i = 1 for tax in self.get("taxes"): if valuation_tax.get(tax.name): From 422325bb74ff39f6e4ebe7367d57ecf3622d56b2 Mon Sep 17 00:00:00 2001 From: Saqib Date: Tue, 6 Jul 2021 18:09:21 +0530 Subject: [PATCH 58/60] test: fetching of previous sle (#26352) --- .../test_stock_ledger_entry.py | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py index ba31ad7b06b..af2ada8c9a4 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py +++ b/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py @@ -54,7 +54,7 @@ class TestStockLedgerEntry(unittest.TestCase): ) # _Test Item for Reposting transferred from Stores to FG warehouse on 30-04-2020 - make_stock_entry( + se = make_stock_entry( item_code="_Test Item for Reposting", source="Stores - _TC", target="Finished Goods - _TC", @@ -64,29 +64,29 @@ class TestStockLedgerEntry(unittest.TestCase): posting_date='2020-04-30', posting_time='14:00' ) - target_wh_sle = get_previous_sle({ + target_wh_sle = frappe.db.get_value('Stock Ledger Entry', { "item_code": "_Test Item for Reposting", "warehouse": "Finished Goods - _TC", - "posting_date": '2020-04-30', - "posting_time": '14:00' - }) + "voucher_type": "Stock Entry", + "voucher_no": se.name + }, ["valuation_rate"], as_dict=1) self.assertEqual(target_wh_sle.get("valuation_rate"), 150) # Repack entry on 5-5-2020 repack = create_repack_entry(company=company, posting_date='2020-05-05', posting_time='14:00') - finished_item_sle = get_previous_sle({ + finished_item_sle = frappe.db.get_value('Stock Ledger Entry', { "item_code": "_Test Finished Item for Reposting", "warehouse": "Finished Goods - _TC", - "posting_date": '2020-05-05', - "posting_time": '14:00' - }) + "voucher_type": "Stock Entry", + "voucher_no": repack.name + }, ["incoming_rate", "valuation_rate"], as_dict=1) self.assertEqual(finished_item_sle.get("incoming_rate"), 540) self.assertEqual(finished_item_sle.get("valuation_rate"), 540) # Reconciliation for _Test Item for Reposting at Stores on 12-04-2020: Qty = 50, Rate = 150 - create_stock_reconciliation( + sr = create_stock_reconciliation( item_code="_Test Item for Reposting", warehouse="Stores - _TC", qty=50, @@ -109,12 +109,12 @@ class TestStockLedgerEntry(unittest.TestCase): self.assertEqual(target_wh_sle.get("valuation_rate"), 175) # Check valuation rate of repacked item after back-dated entry at Stores - finished_item_sle = get_previous_sle({ + finished_item_sle = frappe.db.get_value('Stock Ledger Entry', { "item_code": "_Test Finished Item for Reposting", "warehouse": "Finished Goods - _TC", - "posting_date": '2020-05-05', - "posting_time": '14:00' - }) + "voucher_type": "Stock Entry", + "voucher_no": repack.name + }, ["incoming_rate", "valuation_rate"], as_dict=1) self.assertEqual(finished_item_sle.get("incoming_rate"), 790) self.assertEqual(finished_item_sle.get("valuation_rate"), 790) From 0bd190b88592faeae6783fa7cfd9f9b70bd93fb4 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 6 Jul 2021 14:24:42 +0530 Subject: [PATCH 59/60] fix: stock entry with putaway rule not working --- erpnext/stock/doctype/putaway_rule/putaway_rule.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/putaway_rule/putaway_rule.py b/erpnext/stock/doctype/putaway_rule/putaway_rule.py index ea26caced04..0f50bcd6ea8 100644 --- a/erpnext/stock/doctype/putaway_rule/putaway_rule.py +++ b/erpnext/stock/doctype/putaway_rule/putaway_rule.py @@ -97,7 +97,7 @@ def apply_putaway_rule(doctype, items, company, sync=None, purpose=None): at_capacity, rules = get_ordered_putaway_rules(item_code, company, source_warehouse=source_warehouse) if not rules: - warehouse = source_warehouse or item.warehouse + warehouse = source_warehouse or item.get('warehouse') if at_capacity: # rules available, but no free space items_not_accomodated.append([item_code, pending_qty]) From 00f90c50c0577d86c530197810dfca28c683345b Mon Sep 17 00:00:00 2001 From: Mohammad Hasnain Mohsin Rajan Date: Wed, 7 Jul 2021 12:10:02 +0530 Subject: [PATCH 60/60] chore: add product listing link in settings (#26026) * chore: add product listing link in settings * chore: add icon in workspace card Co-authored-by: Ankush --- .../workspace/erpnext_settings/erpnext_settings.json | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/erpnext/setup/workspace/erpnext_settings/erpnext_settings.json b/erpnext/setup/workspace/erpnext_settings/erpnext_settings.json index 014f4095c15..6ca3d637da4 100644 --- a/erpnext/setup/workspace/erpnext_settings/erpnext_settings.json +++ b/erpnext/setup/workspace/erpnext_settings/erpnext_settings.json @@ -11,10 +11,11 @@ "hide_custom": 0, "icon": "settings", "idx": 0, + "is_default": 0, "is_standard": 1, "label": "ERPNext Settings", "links": [], - "modified": "2020-12-01 13:38:37.759596", + "modified": "2021-06-12 01:58:11.399566", "modified_by": "Administrator", "module": "Setup", "name": "ERPNext Settings", @@ -109,6 +110,13 @@ "label": "Domain Settings", "link_to": "Domain Settings", "type": "DocType" + }, + { + "doc_view": "", + "icon": "retail", + "label": "Products Settings", + "link_to": "Products Settings", + "type": "DocType" } ] -} \ No newline at end of file +}