From dcfc3d7d1278d90e0a9154c9d7d7d2dcd2b57bc4 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 4 Mar 2021 19:29:55 +0100 Subject: [PATCH 001/326] fix: remove redundant calls to create_sales_tax --- erpnext/regional/saudi_arabia/setup.py | 5 +---- erpnext/regional/united_arab_emirates/setup.py | 4 +--- erpnext/setup/setup_wizard/utils.py | 1 - 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/erpnext/regional/saudi_arabia/setup.py b/erpnext/regional/saudi_arabia/setup.py index d9ac6cb0f6..9b3677d2c6 100644 --- a/erpnext/regional/saudi_arabia/setup.py +++ b/erpnext/regional/saudi_arabia/setup.py @@ -4,11 +4,8 @@ from __future__ import unicode_literals from erpnext.regional.united_arab_emirates.setup import make_custom_fields, add_print_formats -from erpnext.setup.setup_wizard.operations.taxes_setup import create_sales_tax + def setup(company=None, patch=True): make_custom_fields() add_print_formats() - - if company: - create_sales_tax(company) \ No newline at end of file diff --git a/erpnext/regional/united_arab_emirates/setup.py b/erpnext/regional/united_arab_emirates/setup.py index 776a82c730..d5a29fc200 100644 --- a/erpnext/regional/united_arab_emirates/setup.py +++ b/erpnext/regional/united_arab_emirates/setup.py @@ -6,15 +6,13 @@ from __future__ import unicode_literals import frappe, os, json from frappe.custom.doctype.custom_field.custom_field import create_custom_fields from frappe.permissions import add_permission, update_permission_property -from erpnext.setup.setup_wizard.operations.taxes_setup import create_sales_tax + def setup(company=None, patch=True): make_custom_fields() add_print_formats() add_custom_roles_for_reports() add_permissions() - if company: - create_sales_tax(company) def make_custom_fields(): is_zero_rated = dict(fieldname='is_zero_rated', label='Is Zero Rated', diff --git a/erpnext/setup/setup_wizard/utils.py b/erpnext/setup/setup_wizard/utils.py index e82bc96d93..4223f000a6 100644 --- a/erpnext/setup/setup_wizard/utils.py +++ b/erpnext/setup/setup_wizard/utils.py @@ -9,5 +9,4 @@ def complete(): 'data', 'test_mfg.json'), 'r') as f: data = json.loads(f.read()) - #setup_wizard.create_sales_tax(data) setup_complete(data) From 25afad3dc1d75031ba09b405ff8b12de5f0d8007 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 4 Mar 2021 21:11:31 +0100 Subject: [PATCH 002/326] refactor: extend taxes and charges setup Add option to specify taxes and charges template depending on the CoA used. Differentiate between purchase, sales and item taxes. Maintain flexibility by using wildcards. --- erpnext/setup/doctype/company/company.py | 7 +- .../setup_wizard/data/country_wise_tax.json | 307 ++++++++++++++-- .../setup_wizard/operations/taxes_setup.py | 330 ++++++++++++------ 3 files changed, 513 insertions(+), 131 deletions(-) diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py index 819ba78e66..d3021bafea 100644 --- a/erpnext/setup/doctype/company/company.py +++ b/erpnext/setup/doctype/company/company.py @@ -17,6 +17,7 @@ from frappe.utils.nestedset import NestedSet from past.builtins import cmp import functools from erpnext.accounts.doctype.account.account import get_account_currency +from erpnext.setup.setup_wizard.operations.taxes_setup import setup_taxes_and_charges class Company(NestedSet): nsm_parent_field = 'parent_company' @@ -67,11 +68,7 @@ class Company(NestedSet): frappe.throw(_("Abbreviation already used for another company")) def create_default_tax_template(self): - from erpnext.setup.setup_wizard.operations.taxes_setup import create_sales_tax - create_sales_tax({ - 'country': self.country, - 'company_name': self.name - }) + setup_taxes_and_charges(self.name, self.country) def validate_default_accounts(self): accounts = [ diff --git a/erpnext/setup/setup_wizard/data/country_wise_tax.json b/erpnext/setup/setup_wizard/data/country_wise_tax.json index beddaeed79..9ccbdb965b 100644 --- a/erpnext/setup/setup_wizard/data/country_wise_tax.json +++ b/erpnext/setup/setup_wizard/data/country_wise_tax.json @@ -481,14 +481,230 @@ }, "Germany": { - "Germany VAT 19%": { - "account_name": "VAT 19%", - "tax_rate": 19.00, - "default": 1 - }, - "Germany VAT 7%": { - "account_name": "VAT 7%", - "tax_rate": 7.00 + "chart_of_accounts": { + "SKR04 mit Kontonummern": { + "sales_tax_templates": [ + { + "title": "Umsatzsteuer 19%", + "is_default": 1, + "accounts": [ + { + "type": "On Net Total", + "account_name": "Umsatzsteuer 19%", + "account_number": "3806", + "rate": 19.00 + } + ] + }, + { + "title": "Umsatzsteuer 7%", + "accounts": [ + { + "type": "On Net Total", + "account_name": "Umsatzsteuer 7%", + "account_number": "3801", + "tax_rate": 7.00 + } + ] + } + ], + "purchase_tax_templates": [ + { + "title": "Abziehbare Vorsteuer 19%", + "is_default": 1, + "accounts": [ + { + "account_name": "Abziehbare Vorsteuer 19%", + "account_number": "1406", + "root_type": "Asset", + "tax_rate": 19.00 + } + ] + }, + { + "title": "Abziehbare Vorsteuer 7%", + "accounts": [ + { + "account_name": "Abziehbare Vorsteuer 7%", + "account_number": "1401", + "root_type": "Asset", + "tax_rate": 7.00 + } + ] + }, + { + "title": "Innergemeinschaftlicher Erwerb 19% Umsatzsteuer und 19% Vorsteuer", + "accounts": [ + { + "account_name": "Abziehbare Vorsteuer nach § 13b UStG 19%", + "account_number": "1407", + "root_type": "Asset", + "tax_rate": 19.00, + "add_deduct_tax": "Add" + }, + { + "account_name": "Umsatzsteuer nach § 13b UStG 19%", + "account_number": "3837", + "root_type": "Liability", + "tax_rate": 19.00, + "add_deduct_tax": "Deduct" + } + ] + } + ] + }, + "SKR03 mit Kontonummern": { + "sales_tax_templates": [ + { + "title": "Umsatzsteuer 19%", + "is_default": 1, + "accounts": [ + { + "type": "On Net Total", + "account_name": "Umsatzsteuer 19%", + "account_number": "1776", + "rate": 19.00 + } + ] + }, + { + "title": "Umsatzsteuer 7%", + "accounts": [ + { + "type": "On Net Total", + "account_name": "Umsatzsteuer 7%", + "account_number": "1771", + "tax_rate": 7.00 + } + ] + } + ], + "purchase_tax_templates": [ + { + "title": "Abziehbare Vorsteuer 19%", + "is_default": 1, + "accounts": [ + { + "account_name": "Abziehbare Vorsteuer 19%", + "account_number": "1576", + "root_type": "Asset", + "tax_rate": 19.00 + } + ] + }, + { + "title": "Abziehbare Vorsteuer 7%", + "accounts": [ + { + "account_name": "Abziehbare Vorsteuer 7%", + "account_number": "1571", + "root_type": "Asset", + "tax_rate": 7.00 + } + ] + } + ] + }, + "Standard with Numbers": { + "sales_tax_templates": [ + { + "title": "Umsatzsteuer 19%", + "is_default": 1, + "accounts": [ + { + "type": "On Net Total", + "account_name": "Umsatzsteuer 19%", + "account_number": "2301", + "rate": 19.00 + } + ] + }, + { + "title": "Umsatzsteuer 7%", + "accounts": [ + { + "type": "On Net Total", + "account_name": "Umsatzsteuer 7%", + "account_number": "2302", + "tax_rate": 7.00 + } + ] + } + ], + "purchase_tax_templates": [ + { + "title": "Abziehbare Vorsteuer 19%", + "is_default": 1, + "accounts": [ + { + "account_name": "Abziehbare Vorsteuer 19%", + "account_number": "1501", + "root_type": "Asset", + "tax_rate": 19.00 + } + ] + }, + { + "title": "Abziehbare Vorsteuer 7%", + "accounts": [ + { + "account_name": "Abziehbare Vorsteuer 7%", + "account_number": "1502", + "root_type": "Asset", + "tax_rate": 7.00 + } + ] + } + ] + }, + "*": { + "sales_tax_templates": [ + { + "title": "Umsatzsteuer 19%", + "is_default": 1, + "accounts": [ + { + "type": "On Net Total", + "account_name": "Umsatzsteuer 19%", + "rate": 19.00 + } + ] + }, + { + "title": "Umsatzsteuer 7%", + "accounts": [ + { + "type": "On Net Total", + "account_name": "Umsatzsteuer 7%", + "tax_rate": 7.00 + } + ] + } + ], + "purchase_tax_templates": [ + { + "title": "Abziehbare Vorsteuer 19%", + "is_default": 1, + "accounts": [ + { + "account_name": "Abziehbare Vorsteuer 19%", + "root_type": "Asset", + "tax_rate": 19.00 + } + ] + }, + { + "title": "Abziehbare Vorsteuer 7%", + "accounts": [ + { + "account_name": "Abziehbare Vorsteuer 7%", + "root_type": "Asset", + "tax_rate": 7.00 + } + ] + } + ] + } } }, @@ -580,26 +796,61 @@ }, "India": { - "In State GST": { - "account_name": ["SGST", "CGST"], - "tax_rate": [9.00, 9.00], - "default": 1 - }, - "Out of State GST": { - "account_name": "IGST", - "tax_rate": 18.00 - }, - "VAT 5%": { - "account_name": "VAT 5%", - "tax_rate": 5.00 - }, - "VAT 4%": { - "account_name": "VAT 4%", - "tax_rate": 4.00 - }, - "VAT 14%": { - "account_name": "VAT 14%", - "tax_rate": 14.00 + "chart_of_accounts": { + "*": { + "*": [ + { + "title": "In State GST", + "is_default": 1, + "accounts": [ + { + "account_name": "SGST", + "tax_rate": 9.00 + }, + { + "account_name": "CGST", + "tax_rate": 9.00 + } + ] + }, + { + "title": "Out of State GST", + "accounts": [ + { + "account_name": "IGST", + "tax_rate": 18.00 + } + ] + }, + { + "title": "VAT 5%", + "accounts": [ + { + "account_name": "VAT 5%", + "tax_rate": 5.00 + } + ] + }, + { + "title": "VAT 4%", + "accounts": [ + { + "account_name": "VAT 4%", + "tax_rate": 4.00 + } + ] + }, + { + "title": "VAT 14%", + "accounts": [ + { + "account_name": "VAT 14%", + "tax_rate": 14.00 + } + ] + } + ] + } } }, diff --git a/erpnext/setup/setup_wizard/operations/taxes_setup.py b/erpnext/setup/setup_wizard/operations/taxes_setup.py index c3c1593c04..81506c4352 100644 --- a/erpnext/setup/setup_wizard/operations/taxes_setup.py +++ b/erpnext/setup/setup_wizard/operations/taxes_setup.py @@ -1,123 +1,257 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt from __future__ import unicode_literals -import frappe, copy, os, json -from frappe.utils import flt -from erpnext.accounts.doctype.account.account import RootNotEditable -def create_sales_tax(args): - country_wise_tax = get_country_wise_tax(args.get("country")) - if country_wise_tax and len(country_wise_tax) > 0: - for sales_tax, tax_data in country_wise_tax.items(): - make_tax_account_and_template( - args.get("company_name"), - tax_data.get('account_name'), - tax_data.get('tax_rate'), sales_tax) +import os +import json -def make_tax_account_and_template(company, account_name, tax_rate, template_name=None): - if not isinstance(account_name, (list, tuple)): - account_name = [account_name] - tax_rate = [tax_rate] +import frappe +from frappe import _ - accounts = [] - for i, name in enumerate(account_name): - tax_account = make_tax_account(company, account_name[i], tax_rate[i]) - if tax_account: - accounts.append(tax_account) - try: - if accounts: - make_sales_and_purchase_tax_templates(accounts, template_name) - make_item_tax_templates(accounts, template_name) - except frappe.NameError: - if frappe.message_log: frappe.message_log.pop() - except RootNotEditable: - pass +def setup_taxes_and_charges(company_name: str, country: str): + file_path = os.path.join(os.path.dirname(__file__), '..', 'data', 'country_wise_tax.json') + with open(file_path, 'r') as json_file: + tax_data = json.load(json_file) -def make_tax_account(company, account_name, tax_rate): - tax_group = get_tax_account_group(company) - if tax_group: - try: - return frappe.get_doc({ - "doctype":"Account", - "company": company, - "parent_account": tax_group, - "account_name": account_name, - "is_group": 0, - "report_type": "Balance Sheet", - "root_type": "Liability", - "account_type": "Tax", - "tax_rate": flt(tax_rate) if tax_rate else None - }).insert(ignore_permissions=True, ignore_mandatory=True) - except frappe.NameError: - if frappe.message_log: frappe.message_log.pop() - abbr = frappe.get_cached_value('Company', company, 'abbr') - account = '{0} - {1}'.format(account_name, abbr) - return frappe.get_doc('Account', account) + country_wise_tax = tax_data.get(country) -def make_sales_and_purchase_tax_templates(accounts, template_name=None): - if not template_name: - template_name = accounts[0].name + if country_wise_tax: + if 'chart_of_accounts' in country_wise_tax: + from_detailed_data(company_name, country_wise_tax.get('chart_of_accounts')) + else: + from_simple_data(company_name, country_wise_tax) - sales_tax_template = { - "doctype": "Sales Taxes and Charges Template", - "title": template_name, - "company": accounts[0].company, - 'taxes': [] + +def from_detailed_data(company_name, data): + """ + Create Taxes and Charges Templates from detailed data like this: + + { + "chart_of_accounts": { + coa_name: { + "sales_tax_templates": [ + { + 'title': '', + 'is_default': 1, + 'accounts': [ + { + 'account_name': '', + 'account_number': '', + 'root_type': '', + } + ] + } + ], + "purchase_tax_templates": [ ... ], + "item_tax_templates": [ ... ], + "*": [ ... ] + } + } } + """ + coa_name = frappe.db.get_value('Company', company_name, 'chart_of_accounts') + tax_templates = data.get(coa_name) or data.get('*') + 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('*') + + if sales_tax_templates: + for template in sales_tax_templates: + make_tax_template(company_name, 'Sales Taxes and Charges Template', template) + + if purchase_tax_templates: + for template in purchase_tax_templates: + make_tax_template(company_name, 'Purchase Taxes and Charges Template', template) + + if item_tax_templates: + for template in item_tax_templates: + make_item_tax_template(company_name, template) + + +def from_simple_data(company_name, data): + """ + Create Taxes and Charges Templates from simple data like this: + + "Austria Tax": { + "account_name": "VAT", + "tax_rate": 20.00 + } + """ + for template_name, tax_data in data.items(): + template = { + 'title': template_name, + 'is_default': tax_data.get('default'), + 'accounts': [ + { + 'account_name': tax_data.get('account_name'), + 'tax_rate': tax_data.get('tax_rate') + } + ] + } + make_tax_template(company_name, 'Sales Taxes and Charges Template', template) + make_tax_template(company_name, 'Purchase Taxes and Charges Template', template) + make_item_tax_template(company_name, template) + + +def make_tax_template(company_name, doctype, template): + if frappe.db.exists(doctype, {'title': template.get('title'), 'company': company_name}): + return + + accounts = get_or_create_accounts(company_name, template.get('accounts')) + + # Get all fields of the Taxes and Charges Template + tax_template = {'doctype': doctype} + tax_template_fields = frappe.get_meta(doctype).fields + tax_template_fieldnames = [field.fieldname for field in tax_template_fields] + + # Get all fields of the taxes child table + table_doctype = [field.options for field in tax_template_fields if field.fieldname=='taxes'][0] + table_fields = frappe.get_meta(table_doctype).fields + table_field_names = [field.fieldname for field in table_fields] + + # Check if field exists as a key in the import data and, if yes, set the + # value accordingly + for field in tax_template_fieldnames: + if field in template: + tax_template[field] = template.get(field) + + # However, company always fixed and taxes table must be empty to start with + tax_template['company'] = company_name + tax_template['taxes'] = [] for account in accounts: - sales_tax_template['taxes'].append({ - "category": "Total", - "charge_type": "On Net Total", - "account_head": account.name, - "description": "{0} @ {1}".format(account.account_name, account.tax_rate), - "rate": account.tax_rate - }) - # Sales - frappe.get_doc(copy.deepcopy(sales_tax_template)).insert(ignore_permissions=True) + row = { + 'category': 'Total', + 'charge_type': 'On Net Total', + 'account_head': account.get('name'), + 'description': '{0} @ {1}'.format(account.get('account_name'), account.get('tax_rate')), + 'rate': account.get('tax_rate') + } + # Check if field exists as a key in the import data and, if yes, set the + # value accordingly + for field in table_field_names: + if field in account: + row[field] = account.get(field) - # Purchase - purchase_tax_template = copy.deepcopy(sales_tax_template) - purchase_tax_template["doctype"] = "Purchase Taxes and Charges Template" + tax_template['taxes'].append(row) - doc = frappe.get_doc(purchase_tax_template) - doc.insert(ignore_permissions=True) + return frappe.get_doc(tax_template).insert(ignore_permissions=True) -def make_item_tax_templates(accounts, template_name=None): - if not template_name: - template_name = accounts[0].name + +def make_item_tax_template(company_name, template): + """Create an Item Tax Template. + + This requires a separate method because Item Tax Template is structured + differently from Sales and Purchase Tax Templates. + """ + doctype = 'Item Tax Template' + if frappe.db.exists(doctype, {'title': template.get('title'), 'company': company_name}): + return + + accounts = get_or_create_accounts(company_name, template.get('accounts')) item_tax_template = { - "doctype": "Item Tax Template", - "title": template_name, - "company": accounts[0].company, - 'taxes': [] + 'doctype': doctype, + 'title': template.get('title'), + 'company': company_name, + 'taxes': [{ + 'tax_type': account.get('name'), + 'tax_rate': account.get('tax_rate') + } for account in accounts] } + return frappe.get_doc(item_tax_template).insert(ignore_permissions=True) - for account in accounts: - item_tax_template['taxes'].append({ - "tax_type": account.name, - "tax_rate": account.tax_rate - }) - # Items - frappe.get_doc(copy.deepcopy(item_tax_template)).insert(ignore_permissions=True) +def get_or_create_accounts(company: str, account_data: list): + for account in account_data: + if 'creation' in account: + # Hack to check if account already contains a real Account doc + # or just the attibutes from country_wise_tax.json + continue -def get_tax_account_group(company): - tax_group = frappe.db.get_value("Account", - {"account_name": "Duties and Taxes", "is_group": 1, "company": company}) - if not tax_group: - tax_group = frappe.db.get_value("Account", {"is_group": 1, "root_type": "Liability", - "account_type": "Tax", "company": company}) + # tax_rate should survive the following lines because it might not be + # specified in an existing account or different rates might get booked + # onto the same account. + tax_rate = account.get('tax_rate') + doc = get_or_create_account(company, account) + account.update(doc.as_dict()) + account['tax_rate'] = tax_rate + + return account_data + + +def get_or_create_account(company, account_data): + """ + Check if account already exists. If not, create it. + Return a tax account or None. + """ + root_type = account_data.get('root_type', 'Liability') + account_name = account_data.get('account_name') + account_number = account_data.get('account_number') + + existing_accounts = frappe.get_list('Account', + filters={ + 'company': company, + 'root_type': root_type + }, + or_filters={ + 'account_name': account_name, + 'account_number': account_number + } + ) + + if existing_accounts: + return frappe.get_doc('Account', existing_accounts[0].name) + + tax_group = get_or_create_tax_account_group(company, root_type) + full_account_data = { + 'doctype': 'Account', + 'account_name': account_name, + 'account_number': account_number, + 'tax_rate': account_data.get('tax_rate'), + 'company': company, + 'parent_account': tax_group, + 'is_group': 0, + 'report_type': 'Balance Sheet', + 'root_type': root_type, + 'account_type': 'Tax' + } + return frappe.get_doc(full_account_data).insert(ignore_permissions=True, ignore_mandatory=True) + + +def get_or_create_tax_account_group(company, root_type): + tax_group = frappe.db.get_value('Account', { + 'is_group': 1, + 'root_type': root_type, + 'account_type': 'Tax', + 'company': company + }) + + if tax_group: + return tax_group + + root = frappe.get_list('Account', { + 'is_group': 1, + 'root_type': root_type, + 'company': company, + 'report_type': 'Balance Sheet', + 'parent_account': ('is', 'not set') + }, limit=1)[0].name + + doc = frappe.get_doc({ + 'doctype': 'Account', + 'company': company, + 'is_group': 1, + 'report_type': 'Balance Sheet', + 'root_type': root_type, + 'account_type': 'Tax', + 'account_name': _('Duties and Taxes') if root_type == 'Liability' else _('Tax Assets'), + 'parent_account': root + }).insert(ignore_permissions=True) + + tax_group = doc.name return tax_group - -def get_country_wise_tax(country): - data = {} - with open (os.path.join(os.path.dirname(__file__), "..", "data", "country_wise_tax.json")) as countrywise_tax: - data = json.load(countrywise_tax).get(country) - - return data From ebd1d08e55b8146e652096d2c591754f8d676504 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Mon, 8 Mar 2021 19:53:50 +0100 Subject: [PATCH 003/326] refactor: taxes setup Better structure of input data. --- .../setup_wizard/data/country_wise_tax.json | 310 ++++++++++++------ .../setup_wizard/operations/taxes_setup.py | 279 +++++++--------- 2 files changed, 334 insertions(+), 255 deletions(-) diff --git a/erpnext/setup/setup_wizard/data/country_wise_tax.json b/erpnext/setup/setup_wizard/data/country_wise_tax.json index 9ccbdb965b..6305442ef2 100644 --- a/erpnext/setup/setup_wizard/data/country_wise_tax.json +++ b/erpnext/setup/setup_wizard/data/country_wise_tax.json @@ -487,23 +487,25 @@ { "title": "Umsatzsteuer 19%", "is_default": 1, - "accounts": [ + "taxes": [ { - "type": "On Net Total", - "account_name": "Umsatzsteuer 19%", - "account_number": "3806", - "rate": 19.00 + "account_head": { + "account_name": "Umsatzsteuer 19%", + "account_number": "3806", + "tax_rate": 19.00 + } } ] }, { "title": "Umsatzsteuer 7%", - "accounts": [ + "taxes": [ { - "type": "On Net Total", - "account_name": "Umsatzsteuer 7%", - "account_number": "3801", - "tax_rate": 7.00 + "account_head": { + "account_name": "Umsatzsteuer 7%", + "account_number": "3801", + "tax_rate": 7.00 + } } ] } @@ -512,41 +514,49 @@ { "title": "Abziehbare Vorsteuer 19%", "is_default": 1, - "accounts": [ + "taxes": [ { - "account_name": "Abziehbare Vorsteuer 19%", - "account_number": "1406", - "root_type": "Asset", - "tax_rate": 19.00 + "account_head": { + "account_name": "Abziehbare Vorsteuer 19%", + "account_number": "1406", + "root_type": "Asset", + "tax_rate": 19.00 + } } ] }, { "title": "Abziehbare Vorsteuer 7%", - "accounts": [ + "taxes": [ { - "account_name": "Abziehbare Vorsteuer 7%", - "account_number": "1401", - "root_type": "Asset", - "tax_rate": 7.00 + "account_head": { + "account_name": "Abziehbare Vorsteuer 7%", + "account_number": "1401", + "root_type": "Asset", + "tax_rate": 7.00 + } } ] }, { "title": "Innergemeinschaftlicher Erwerb 19% Umsatzsteuer und 19% Vorsteuer", - "accounts": [ + "taxes": [ { - "account_name": "Abziehbare Vorsteuer nach § 13b UStG 19%", - "account_number": "1407", - "root_type": "Asset", - "tax_rate": 19.00, + "account_head": { + "account_name": "Abziehbare Vorsteuer nach § 13b UStG 19%", + "account_number": "1407", + "root_type": "Asset", + "tax_rate": 19.00 + }, "add_deduct_tax": "Add" }, { - "account_name": "Umsatzsteuer nach § 13b UStG 19%", - "account_number": "3837", - "root_type": "Liability", - "tax_rate": 19.00, + "account_head": { + "account_name": "Umsatzsteuer nach § 13b UStG 19%", + "account_number": "3837", + "root_type": "Liability", + "tax_rate": 19.00 + }, "add_deduct_tax": "Deduct" } ] @@ -558,23 +568,25 @@ { "title": "Umsatzsteuer 19%", "is_default": 1, - "accounts": [ + "taxes": [ { - "type": "On Net Total", - "account_name": "Umsatzsteuer 19%", - "account_number": "1776", - "rate": 19.00 + "account_head": { + "account_name": "Umsatzsteuer 19%", + "account_number": "1776", + "tax_rate": 19.00 + } } ] }, { "title": "Umsatzsteuer 7%", - "accounts": [ + "taxes": [ { - "type": "On Net Total", - "account_name": "Umsatzsteuer 7%", - "account_number": "1771", - "tax_rate": 7.00 + "account_head": { + "account_name": "Umsatzsteuer 7%", + "account_number": "1771", + "tax_rate": 7.00 + } } ] } @@ -583,23 +595,27 @@ { "title": "Abziehbare Vorsteuer 19%", "is_default": 1, - "accounts": [ + "taxes": [ { - "account_name": "Abziehbare Vorsteuer 19%", - "account_number": "1576", - "root_type": "Asset", - "tax_rate": 19.00 + "account_head": { + "account_name": "Abziehbare Vorsteuer 19%", + "account_number": "1576", + "root_type": "Asset", + "tax_rate": 19.00 + } } ] }, { "title": "Abziehbare Vorsteuer 7%", - "accounts": [ + "taxes": [ { - "account_name": "Abziehbare Vorsteuer 7%", - "account_number": "1571", - "root_type": "Asset", - "tax_rate": 7.00 + "account_head": { + "account_name": "Abziehbare Vorsteuer 7%", + "account_number": "1571", + "root_type": "Asset", + "tax_rate": 7.00 + } } ] } @@ -610,23 +626,25 @@ { "title": "Umsatzsteuer 19%", "is_default": 1, - "accounts": [ + "taxes": [ { - "type": "On Net Total", - "account_name": "Umsatzsteuer 19%", - "account_number": "2301", - "rate": 19.00 + "account_head": { + "account_name": "Umsatzsteuer 19%", + "account_number": "2301", + "tax_rate": 19.00 + } } ] }, { "title": "Umsatzsteuer 7%", - "accounts": [ + "taxes": [ { - "type": "On Net Total", - "account_name": "Umsatzsteuer 7%", - "account_number": "2302", - "tax_rate": 7.00 + "account_head": { + "account_name": "Umsatzsteuer 7%", + "account_number": "2302", + "tax_rate": 7.00 + } } ] } @@ -635,23 +653,27 @@ { "title": "Abziehbare Vorsteuer 19%", "is_default": 1, - "accounts": [ + "taxes": [ { - "account_name": "Abziehbare Vorsteuer 19%", - "account_number": "1501", - "root_type": "Asset", - "tax_rate": 19.00 + "account_head": { + "account_name": "Abziehbare Vorsteuer 19%", + "account_number": "1501", + "root_type": "Asset", + "tax_rate": 19.00 + } } ] }, { "title": "Abziehbare Vorsteuer 7%", - "accounts": [ + "taxes": [ { - "account_name": "Abziehbare Vorsteuer 7%", - "account_number": "1502", - "root_type": "Asset", - "tax_rate": 7.00 + "account_head": { + "account_name": "Abziehbare Vorsteuer 7%", + "account_number": "1502", + "root_type": "Asset", + "tax_rate": 7.00 + } } ] } @@ -662,21 +684,23 @@ { "title": "Umsatzsteuer 19%", "is_default": 1, - "accounts": [ + "taxes": [ { - "type": "On Net Total", - "account_name": "Umsatzsteuer 19%", - "rate": 19.00 + "account_head": { + "account_name": "Umsatzsteuer 19%", + "tax_rate": 19.00 + } } ] }, { "title": "Umsatzsteuer 7%", - "accounts": [ + "taxes": [ { - "type": "On Net Total", - "account_name": "Umsatzsteuer 7%", - "tax_rate": 7.00 + "account_head": { + "account_name": "Umsatzsteuer 7%", + "tax_rate": 7.00 + } } ] } @@ -685,21 +709,25 @@ { "title": "Abziehbare Vorsteuer 19%", "is_default": 1, - "accounts": [ + "taxes": [ { - "account_name": "Abziehbare Vorsteuer 19%", - "root_type": "Asset", - "tax_rate": 19.00 + "account_head": { + "account_name": "Abziehbare Vorsteuer 19%", + "tax_rate": 19.00, + "root_type": "Asset" + } } ] }, { "title": "Abziehbare Vorsteuer 7%", - "accounts": [ + "taxes": [ { - "account_name": "Abziehbare Vorsteuer 7%", - "root_type": "Asset", - "tax_rate": 7.00 + "account_head": { + "account_name": "Abziehbare Vorsteuer 7%", + "root_type": "Asset", + "tax_rate": 7.00 + } } ] } @@ -798,54 +826,130 @@ "India": { "chart_of_accounts": { "*": { - "*": [ + "item_tax_templates": [ { "title": "In State GST", "is_default": 1, - "accounts": [ + "taxes": [ { - "account_name": "SGST", - "tax_rate": 9.00 + "tax_type": { + "account_name": "SGST", + "tax_rate": 9.00 + } }, { - "account_name": "CGST", - "tax_rate": 9.00 + "tax_type": { + "account_name": "CGST", + "tax_rate": 9.00 + } } ] }, { "title": "Out of State GST", - "accounts": [ + "taxes": [ { - "account_name": "IGST", - "tax_rate": 18.00 + "tax_type": { + "account_name": "IGST", + "tax_rate": 18.00 + } } ] }, { "title": "VAT 5%", - "accounts": [ + "taxes": [ { - "account_name": "VAT 5%", - "tax_rate": 5.00 + "tax_type": { + "account_name": "VAT 5%", + "tax_rate": 5.00 + } } ] }, { "title": "VAT 4%", - "accounts": [ + "taxes": [ { - "account_name": "VAT 4%", - "tax_rate": 4.00 + "tax_type": { + "account_name": "VAT 4%", + "tax_rate": 4.00 + } } ] }, { "title": "VAT 14%", - "accounts": [ + "taxes": [ { - "account_name": "VAT 14%", - "tax_rate": 14.00 + "tax_type": { + "account_name": "VAT 14%", + "tax_rate": 14.00 + } + } + ] + } + ], + "*": [ + { + "title": "In State GST", + "is_default": 1, + "taxes": [ + { + "account_head": { + "account_name": "SGST", + "tax_rate": 9.00 + } + }, + { + "account_head": { + "account_name": "CGST", + "tax_rate": 9.00 + } + } + ] + }, + { + "title": "Out of State GST", + "taxes": [ + { + "account_head": { + "account_name": "IGST", + "tax_rate": 18.00 + } + } + ] + }, + { + "title": "VAT 5%", + "taxes": [ + { + "account_head": { + "account_name": "VAT 5%", + "tax_rate": 5.00 + } + } + ] + }, + { + "title": "VAT 4%", + "taxes": [ + { + "account_head": { + "account_name": "VAT 4%", + "tax_rate": 4.00 + } + } + ] + }, + { + "title": "VAT 14%", + "taxes": [ + { + "account_head": { + "account_name": "VAT 14%", + "tax_rate": 14.00 + } } ] } diff --git a/erpnext/setup/setup_wizard/operations/taxes_setup.py b/erpnext/setup/setup_wizard/operations/taxes_setup.py index 81506c4352..429a558c58 100644 --- a/erpnext/setup/setup_wizard/operations/taxes_setup.py +++ b/erpnext/setup/setup_wizard/operations/taxes_setup.py @@ -17,40 +17,62 @@ def setup_taxes_and_charges(company_name: str, country: str): country_wise_tax = tax_data.get(country) - if country_wise_tax: - if 'chart_of_accounts' in country_wise_tax: - from_detailed_data(company_name, country_wise_tax.get('chart_of_accounts')) - else: - from_simple_data(company_name, country_wise_tax) + if not country_wise_tax: + return + + if 'chart_of_accounts' not in country_wise_tax: + country_wise_tax = simple_to_detailed(country_wise_tax) + + from_detailed_data(company_name, country_wise_tax.get('chart_of_accounts')) -def from_detailed_data(company_name, data): +def simple_to_detailed(templates): """ - Create Taxes and Charges Templates from detailed data like this: + Convert a simple taxes object into a more detailed data structure. + + Example input: { - "chart_of_accounts": { - coa_name: { - "sales_tax_templates": [ - { - 'title': '', - 'is_default': 1, - 'accounts': [ - { - 'account_name': '', - 'account_number': '', - 'root_type': '', - } - ] - } - ], - "purchase_tax_templates": [ ... ], - "item_tax_templates": [ ... ], - "*": [ ... ] - } + "France VAT 20%": { + "account_name": "VAT 20%", + "tax_rate": 20, + "default": 1 + }, + "France VAT 10%": { + "account_name": "VAT 10%", + "tax_rate": 10 } } """ + return { + 'chart_of_accounts': { + '*': { + 'item_tax_templates': [{ + 'title': title, + 'taxes': [{ + 'tax_type': { + 'account_name': data.get('account_name'), + 'tax_rate': data.get('tax_rate') + } + }] + } for title, data in templates.items()], + '*': [{ + 'title': title, + 'is_default': data.get('default', 0), + 'taxes': [{ + 'account_head': { + 'account_name': data.get('account_name'), + 'tax_rate': data.get('tax_rate') + } + }] + } for title, data in templates.items()] + } + } + } + + +def from_detailed_data(company_name, data): + """Create Taxes and Charges Templates from detailed data.""" coa_name = frappe.db.get_value('Company', company_name, 'chart_of_accounts') tax_templates = data.get(coa_name) or data.get('*') sales_tax_templates = tax_templates.get('sales_tax_templates') or tax_templates.get('*') @@ -59,85 +81,44 @@ def from_detailed_data(company_name, data): if sales_tax_templates: for template in sales_tax_templates: - make_tax_template(company_name, 'Sales Taxes and Charges Template', template) + make_taxes_and_charges_template(company_name, 'Sales Taxes and Charges Template', template) if purchase_tax_templates: for template in purchase_tax_templates: - make_tax_template(company_name, 'Purchase Taxes and Charges Template', template) + make_taxes_and_charges_template(company_name, 'Purchase Taxes and Charges Template', template) if item_tax_templates: for template in item_tax_templates: make_item_tax_template(company_name, template) -def from_simple_data(company_name, data): - """ - Create Taxes and Charges Templates from simple data like this: +def make_taxes_and_charges_template(company_name, doctype, template): + template['company'] = company_name + template['doctype'] = doctype - "Austria Tax": { - "account_name": "VAT", - "tax_rate": 20.00 - } - """ - for template_name, tax_data in data.items(): - template = { - 'title': template_name, - 'is_default': tax_data.get('default'), - 'accounts': [ - { - 'account_name': tax_data.get('account_name'), - 'tax_rate': tax_data.get('tax_rate') - } - ] - } - make_tax_template(company_name, 'Sales Taxes and Charges Template', template) - make_tax_template(company_name, 'Purchase Taxes and Charges Template', template) - make_item_tax_template(company_name, template) - - -def make_tax_template(company_name, doctype, template): if frappe.db.exists(doctype, {'title': template.get('title'), 'company': company_name}): return - accounts = get_or_create_accounts(company_name, template.get('accounts')) - - # Get all fields of the Taxes and Charges Template - tax_template = {'doctype': doctype} - tax_template_fields = frappe.get_meta(doctype).fields - tax_template_fieldnames = [field.fieldname for field in tax_template_fields] - - # Get all fields of the taxes child table - table_doctype = [field.options for field in tax_template_fields if field.fieldname=='taxes'][0] - table_fields = frappe.get_meta(table_doctype).fields - table_field_names = [field.fieldname for field in table_fields] - - # Check if field exists as a key in the import data and, if yes, set the - # value accordingly - for field in tax_template_fieldnames: - if field in template: - tax_template[field] = template.get(field) - - # However, company always fixed and taxes table must be empty to start with - tax_template['company'] = company_name - tax_template['taxes'] = [] - - for account in accounts: - row = { + for tax_row in template.get('taxes'): + account_data = tax_row.get('account_head') + tax_row_defaults = { 'category': 'Total', - 'charge_type': 'On Net Total', - 'account_head': account.get('name'), - 'description': '{0} @ {1}'.format(account.get('account_name'), account.get('tax_rate')), - 'rate': account.get('tax_rate') + 'charge_type': 'On Net Total' } - # Check if field exists as a key in the import data and, if yes, set the - # value accordingly - for field in table_field_names: - if field in account: - row[field] = account.get(field) - tax_template['taxes'].append(row) + # if account_head is a dict, search or create the account and get it's name + if isinstance(account_data, dict): + tax_row_defaults['description'] = '{0} @ {1}'.format(account_data.get('account_name'), account_data.get('tax_rate')) + tax_row_defaults['rate'] = account_data.get('tax_rate') + account = get_or_create_account(company_name, account_data) + tax_row['account_head'] = account.name - return frappe.get_doc(tax_template).insert(ignore_permissions=True) + # use the default value if nothing other is specified + for fieldname, default_value in tax_row_defaults.items(): + if fieldname not in tax_row: + tax_row[fieldname] = default_value + + return frappe.get_doc(template).insert(ignore_permissions=True) def make_item_tax_template(company_name, template): @@ -147,111 +128,105 @@ def make_item_tax_template(company_name, template): differently from Sales and Purchase Tax Templates. """ doctype = 'Item Tax Template' + template['company'] = company_name + template['doctype'] = doctype + if frappe.db.exists(doctype, {'title': template.get('title'), 'company': company_name}): return - accounts = get_or_create_accounts(company_name, template.get('accounts')) + for tax_row in template.get('taxes'): + account_data = tax_row.get('tax_type') - item_tax_template = { - 'doctype': doctype, - 'title': template.get('title'), - 'company': company_name, - 'taxes': [{ - 'tax_type': account.get('name'), - 'tax_rate': account.get('tax_rate') - } for account in accounts] - } + # if tax_type is a dict, search or create the account and get it's name + if isinstance(account_data, dict): + account = get_or_create_account(company_name, account_data) + tax_row['tax_type'] = account.name + if 'tax_rate' not in tax_row: + tax_row['tax_rate'] = account_data.get('tax_rate') - return frappe.get_doc(item_tax_template).insert(ignore_permissions=True) + return frappe.get_doc(template).insert(ignore_permissions=True) -def get_or_create_accounts(company: str, account_data: list): - for account in account_data: - if 'creation' in account: - # Hack to check if account already contains a real Account doc - # or just the attibutes from country_wise_tax.json - continue - - # tax_rate should survive the following lines because it might not be - # specified in an existing account or different rates might get booked - # onto the same account. - tax_rate = account.get('tax_rate') - doc = get_or_create_account(company, account) - account.update(doc.as_dict()) - account['tax_rate'] = tax_rate - - return account_data - - -def get_or_create_account(company, account_data): +def get_or_create_account(company_name, account): """ Check if account already exists. If not, create it. Return a tax account or None. """ - root_type = account_data.get('root_type', 'Liability') - account_name = account_data.get('account_name') - account_number = account_data.get('account_number') + default_root_type = 'Liability' + root_type = account.get('root_type', default_root_type) existing_accounts = frappe.get_list('Account', filters={ - 'company': company, + 'company': company_name, 'root_type': root_type }, or_filters={ - 'account_name': account_name, - 'account_number': account_number + 'account_name': account.get('account_name'), + 'account_number': account.get('account_number') } ) if existing_accounts: return frappe.get_doc('Account', existing_accounts[0].name) - tax_group = get_or_create_tax_account_group(company, root_type) - full_account_data = { - 'doctype': 'Account', - 'account_name': account_name, - 'account_number': account_number, - 'tax_rate': account_data.get('tax_rate'), - 'company': company, - 'parent_account': tax_group, - 'is_group': 0, - 'report_type': 'Balance Sheet', - 'root_type': root_type, - 'account_type': 'Tax' - } - return frappe.get_doc(full_account_data).insert(ignore_permissions=True, ignore_mandatory=True) + tax_group = get_or_create_tax_group(company_name, root_type) + + account['doctype'] = 'Account' + account['company'] = company_name + account['parent_account'] = tax_group + account['report_type'] = 'Balance Sheet' + account['account_type'] = 'Tax' + account['root_type'] = root_type + account['is_group'] = 0 + + return frappe.get_doc(account).insert(ignore_permissions=True, ignore_mandatory=True) -def get_or_create_tax_account_group(company, root_type): - tax_group = frappe.db.get_value('Account', { +def get_or_create_tax_group(company_name, root_type): + # Look for a group account of type 'Tax' + tax_group_name = frappe.db.get_value('Account', { 'is_group': 1, 'root_type': root_type, 'account_type': 'Tax', - 'company': company + 'company': company_name }) - if tax_group: - return tax_group + if tax_group_name: + return tax_group_name - root = frappe.get_list('Account', { + # Look for a group account named 'Duties and Taxes' or 'Tax Assets' + account_name = _('Duties and Taxes') if root_type == 'Liability' else _('Tax Assets') + tax_group_name = frappe.db.get_value('Account', { 'is_group': 1, 'root_type': root_type, - 'company': company, + 'account_name': account_name, + 'company': company_name + }) + + if tax_group_name: + return tax_group_name + + # Create a new group account named 'Duties and Taxes' or 'Tax Assets' just + # below the root account + root_account = frappe.get_list('Account', { + 'is_group': 1, + 'root_type': root_type, + 'company': company_name, 'report_type': 'Balance Sheet', 'parent_account': ('is', 'not set') - }, limit=1)[0].name + }, limit=1)[0] - doc = frappe.get_doc({ + tax_group_account = frappe.get_doc({ 'doctype': 'Account', - 'company': company, + 'company': company_name, 'is_group': 1, 'report_type': 'Balance Sheet', 'root_type': root_type, 'account_type': 'Tax', - 'account_name': _('Duties and Taxes') if root_type == 'Liability' else _('Tax Assets'), - 'parent_account': root + 'account_name': account_name, + 'parent_account': root_account.name }).insert(ignore_permissions=True) - tax_group = doc.name + tax_group_name = tax_group_account.name - return tax_group + return tax_group_name From d8f7a75eae189f4f59124500d76eb6509bf66537 Mon Sep 17 00:00:00 2001 From: Anupam Date: Mon, 15 Mar 2021 10:31:53 +0530 Subject: [PATCH 004/326] feat: add total available stock field --- .../purchase_order_item/purchase_order_item.json | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) 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 75b2954ddd..eba52f2cd2 100644 --- a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json +++ b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json @@ -50,6 +50,7 @@ "base_net_amount", "warehouse_and_reference", "warehouse", + "actual_qty", "material_request", "material_request_item", "sales_order", @@ -735,13 +736,21 @@ "label": "Rate of Stock UOM", "options": "currency", "read_only": 1 + }, + { + "allow_on_submit": 1, + "fieldname": "actual_qty", + "fieldtype": "Float", + "label": "Available Qty at Warehouse", + "print_hide": 1, + "read_only": 1 } ], "idx": 1, "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-01-30 21:44:41.816974", + "modified": "2021-03-15 10:10:16.342693", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order Item", From a2068d9b980f25e5e08c7b6d405a6c94c4b32367 Mon Sep 17 00:00:00 2001 From: pateljannat Date: Thu, 18 Mar 2021 15:25:22 +0530 Subject: [PATCH 005/326] feat: profitability report --- .../projects/report/profitability/__init__.py | 0 .../report/profitability/profitability.js | 32 +++++ .../report/profitability/profitability.json | 39 ++++++ .../report/profitability/profitability.py | 119 ++++++++++++++++++ 4 files changed, 190 insertions(+) create mode 100644 erpnext/projects/report/profitability/__init__.py create mode 100644 erpnext/projects/report/profitability/profitability.js create mode 100644 erpnext/projects/report/profitability/profitability.json create mode 100644 erpnext/projects/report/profitability/profitability.py diff --git a/erpnext/projects/report/profitability/__init__.py b/erpnext/projects/report/profitability/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/projects/report/profitability/profitability.js b/erpnext/projects/report/profitability/profitability.js new file mode 100644 index 0000000000..dbf918760f --- /dev/null +++ b/erpnext/projects/report/profitability/profitability.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["Profitability"] = { + "filters": [ + { + "fieldname": "start_date", + "label": __("Start Date"), + "fieldtype": "Date", + "reqd": 1 + }, + { + "fieldname": "end_date", + "label": __("End Date"), + "fieldtype": "Date", + "reqd": 1 + }, + { + "fieldname": "customer_name", + "label": __("Customer"), + "fieldtype": "Link", + "options": "Customer" + }, + { + "fieldname": "employee", + "label": __("Employee"), + "fieldtype": "Link", + "options": "Employee" + } + ] +}; diff --git a/erpnext/projects/report/profitability/profitability.json b/erpnext/projects/report/profitability/profitability.json new file mode 100644 index 0000000000..4f91accf58 --- /dev/null +++ b/erpnext/projects/report/profitability/profitability.json @@ -0,0 +1,39 @@ +{ + "add_total_row": 0, + "columns": [], + "creation": "2021-03-18 10:19:40.124932", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 0, + "is_standard": "Yes", + "json": "{}", + "modified": "2021-03-18 10:20:15.559305", + "modified_by": "Administrator", + "module": "Projects", + "name": "Profitability", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Timesheet", + "report_name": "Profitability", + "report_type": "Script Report", + "roles": [ + { + "role": "Projects User" + }, + { + "role": "HR User" + }, + { + "role": "Manufacturing User" + }, + { + "role": "Employee" + }, + { + "role": "Accounts User" + } + ] +} \ No newline at end of file diff --git a/erpnext/projects/report/profitability/profitability.py b/erpnext/projects/report/profitability/profitability.py new file mode 100644 index 0000000000..48adf97264 --- /dev/null +++ b/erpnext/projects/report/profitability/profitability.py @@ -0,0 +1,119 @@ +# 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 import _ + +def execute(filters=None): + columns, data = [], [] + data = get_data(filters) + columns = get_columns() + return columns, data + +def get_columns(): + return [ + { + "fieldname": "customer_name", + "label": _("Customer"), + "fieldtype": "Link", + "options": "Customer", + "width": 150 + }, + { + "fieldname": "title", + "label": _("Name"), + "fieldtype": "Data", + "width": 120 + }, + { + "fieldname": "employee", + "label": _("Employee"), + "fieldtype": "Link", + "options": "employee", + "width": 150 + }, + { + "fieldname": "grand_total", + "label": _("Bill Amount"), + "fieldtype": "Currency", + "options": "currency", + "width": 120 + }, + { + "fieldname": "gross_pay", + "label": _("Cost"), + "fieldtype": "Currency", + "options": "currency", + "width": 120 + }, + { + "fieldname": "profit", + "label": _("Profit"), + "fieldtype": "Currency", + "options": "currency", + "width": 120 + }, + { + "fieldname": "end_date", + "label": _("End Date"), + "fieldtype": "Date", + "width": 120 + }, + { + "fieldname": "total_billed_hours", + "label": _("Total Billed Hours"), + "fieldtype": "Int", + "width": 120 + }, + { + "fieldname": "utilization", + "label": _("Utilization"), + "fieldtype": "Percentage", + "width": 120 + }, + { + "fieldname": "fractional_cost", + "label": _("Fractional Cost"), + "fieldtype": "Int", + "width": 100 + } + ] + +def get_data(filters): + conditions = get_conditions(filters) + sql = """ + select + *, + t.gross_pay * t.utilization as fractional_cost, + t.grand_total - t.gross_pay * t.utilization as profit + from + (select + si.customer_name,tabTimesheet.title,tabTimesheet.employee,si.grand_total,si.name as voucher_no, + ss.gross_pay,ss.total_working_days,tabTimesheet.end_date,tabTimesheet.total_billed_hours, + tabTimesheet.total_billed_hours/(ss.total_working_days * 8) as utilization + from + `tabSalary Slip Timesheet` as sst join `tabTimesheet` on tabTimesheet.name = sst.time_sheet + join `tabSales Invoice Timesheet` as sit on sit.time_sheet = tabTimesheet. name + join `tabSales Invoice` as si on si. name = sit.parent and si.status != "Cancelled" + join `tabSalary Slip` as ss on ss.name = sst.parent """ + if conditions: + sql += """ + where + %s) as t"""%(conditions) + data = frappe.db.sql(sql,filters, as_dict=True) + + return data + +def get_conditions(filters): + conditions = [] + if filters.get("customer_name"): + conditions.append("si.customer_name='%s'"%filters.get("customer_name")) + if filters.get("start_date"): + conditions.append("tabTimesheet.start_date>='%s'"%filters.get("start_date")) + if filters.get("end_date"): + conditions.append("tabTimesheet.end_date<='%s'"%filters.get("end_date")) + if filters.get("employee"): + conditions.append("tabTimesheet.employee='%s'"%filters.get("employee")) + conditions = " and ".join(conditions) + return conditions \ No newline at end of file From 453e07e32a856039d8306ad6a432757e814c8574 Mon Sep 17 00:00:00 2001 From: pateljannat Date: Tue, 23 Mar 2021 11:18:57 +0530 Subject: [PATCH 006/326] fix: exclude cancelled salary slips --- erpnext/projects/report/profitability/profitability.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/projects/report/profitability/profitability.py b/erpnext/projects/report/profitability/profitability.py index 48adf97264..0edecd8e1b 100644 --- a/erpnext/projects/report/profitability/profitability.py +++ b/erpnext/projects/report/profitability/profitability.py @@ -96,7 +96,7 @@ def get_data(filters): `tabSalary Slip Timesheet` as sst join `tabTimesheet` on tabTimesheet.name = sst.time_sheet join `tabSales Invoice Timesheet` as sit on sit.time_sheet = tabTimesheet. name join `tabSales Invoice` as si on si. name = sit.parent and si.status != "Cancelled" - join `tabSalary Slip` as ss on ss.name = sst.parent """ + join `tabSalary Slip` as ss on ss.name = sst.parent and ss.status != "Cancelled" """ if conditions: sql += """ where From 6597bf7dd76c9e24ff77ec35ee7a0eca994d2349 Mon Sep 17 00:00:00 2001 From: pateljannat Date: Thu, 25 Mar 2021 13:31:43 +0530 Subject: [PATCH 007/326] feat: profitability report default working hours and tests --- .../hr/doctype/hr_settings/hr_settings.json | 9 +- .../doctype/timesheet/test_timesheet.py | 4 +- .../report/profitability/profitability.js | 14 +- .../report/profitability/profitability.py | 128 ++++++++++++------ .../profitability/test_profitability.py | 52 +++++++ .../projects/workspace/projects/projects.json | 13 +- 6 files changed, 172 insertions(+), 48 deletions(-) create mode 100644 erpnext/projects/report/profitability/test_profitability.py diff --git a/erpnext/hr/doctype/hr_settings/hr_settings.json b/erpnext/hr/doctype/hr_settings/hr_settings.json index 09666c5db5..4fa50c4852 100644 --- a/erpnext/hr/doctype/hr_settings/hr_settings.json +++ b/erpnext/hr/doctype/hr_settings/hr_settings.json @@ -10,6 +10,7 @@ "retirement_age", "emp_created_by", "column_break_4", + "default_working_hours", "stop_birthday_reminders", "expense_approver_mandatory_in_expense_claim", "leave_settings", @@ -143,13 +144,19 @@ "fieldname": "send_leave_notification", "fieldtype": "Check", "label": "Send Leave Notification" + }, + { + "default": "8", + "fieldname": "default_working_hours", + "fieldtype": "Int", + "label": "Default Working Hours" } ], "icon": "fa fa-cog", "idx": 1, "issingle": 1, "links": [], - "modified": "2021-03-14 02:04:22.907159", + "modified": "2021-03-25 13:18:21.648077", "modified_by": "Administrator", "module": "HR", "name": "HR Settings", diff --git a/erpnext/projects/doctype/timesheet/test_timesheet.py b/erpnext/projects/doctype/timesheet/test_timesheet.py index f7c764e1bd..d21ac0f2f0 100644 --- a/erpnext/projects/doctype/timesheet/test_timesheet.py +++ b/erpnext/projects/doctype/timesheet/test_timesheet.py @@ -151,11 +151,11 @@ class TestTimesheet(unittest.TestCase): settings.save() -def make_salary_structure_for_timesheet(employee): +def make_salary_structure_for_timesheet(employee, company=None): salary_structure_name = "Timesheet Salary Structure Test" frequency = "Monthly" - salary_structure = make_salary_structure(salary_structure_name, frequency, dont_submit=True) + salary_structure = make_salary_structure(salary_structure_name, frequency, company=company, dont_submit=True) salary_structure.salary_component = "Timesheet Component" salary_structure.salary_slip_based_on_timesheet = 1 salary_structure.hour_rate = 50.0 diff --git a/erpnext/projects/report/profitability/profitability.js b/erpnext/projects/report/profitability/profitability.js index dbf918760f..6cb6e39d34 100644 --- a/erpnext/projects/report/profitability/profitability.js +++ b/erpnext/projects/report/profitability/profitability.js @@ -4,17 +4,27 @@ frappe.query_reports["Profitability"] = { "filters": [ + { + "fieldname": "company", + "label": __("Company"), + "fieldtype": "Link", + "options": "Company", + "default": frappe.defaults.get_user_default("Company"), + "reqd": 1 + }, { "fieldname": "start_date", "label": __("Start Date"), "fieldtype": "Date", - "reqd": 1 + "reqd": 1, + "default": frappe.datetime.add_months(frappe.datetime.get_today(), -1) }, { "fieldname": "end_date", "label": __("End Date"), "fieldtype": "Date", - "reqd": 1 + "reqd": 1, + "default": frappe.datetime.now_date() }, { "fieldname": "customer_name", diff --git a/erpnext/projects/report/profitability/profitability.py b/erpnext/projects/report/profitability/profitability.py index 0edecd8e1b..8c052b5e17 100644 --- a/erpnext/projects/report/profitability/profitability.py +++ b/erpnext/projects/report/profitability/profitability.py @@ -4,12 +4,80 @@ from __future__ import unicode_literals import frappe from frappe import _ +from frappe.utils import nowdate, time_diff_in_hours def execute(filters=None): columns, data = [], [] data = get_data(filters) columns = get_columns() - return columns, data + charts = get_chart_data(data) + return columns, data, None, charts + +def get_data(filters): + conditions = get_conditions(filters) + default_working_hours = frappe.db.get_single_value("HR Settings", "default_working_hours") + sql = """ + select + *, + t.gross_pay * t.utilization as fractional_cost, + t.grand_total - t.gross_pay * t.utilization as profit + from + (select + si.customer_name,tabTimesheet.title,tabTimesheet.employee,si.grand_total,si.name as voucher_no, + ss.gross_pay,ss.total_working_days,tabTimesheet.end_date,tabTimesheet.total_billed_hours,tabTimesheet.name as timesheet, + tabTimesheet.total_billed_hours/(ss.total_working_days * %s) as utilization + from + `tabSalary Slip Timesheet` as sst join `tabTimesheet` on tabTimesheet.name = sst.time_sheet + join `tabSales Invoice Timesheet` as sit on sit.time_sheet = tabTimesheet.name + join `tabSales Invoice` as si on si.name = sit.parent and si.status != "Cancelled" + join `tabSalary Slip` as ss on ss.name = sst.parent and ss.status != "Cancelled" """%(default_working_hours) + if conditions: + sql += """ + where + %s) as t"""%(conditions) + data = frappe.db.sql(sql,filters, as_dict=True) + return data + +def get_conditions(filters): + conditions = [] + if filters.get("company"): + conditions.append("tabTimesheet.company='%s'"%filters.get("company")) + if filters.get("customer_name"): + conditions.append("si.customer_name='%s'"%filters.get("customer_name")) + if filters.get("start_date"): + conditions.append("tabTimesheet.start_date>='%s'"%filters.get("start_date")) + if filters.get("end_date"): + conditions.append("tabTimesheet.end_date<='%s'"%filters.get("end_date")) + if filters.get("employee"): + conditions.append("tabTimesheet.employee='%s'"%filters.get("employee")) + + conditions = " and ".join(conditions) + return conditions + +def get_chart_data(data): + if not data: + return None + + labels = [] + utilization = [] + + for entry in data: + labels.append(entry.get("title") + " - " + str(entry.get("end_date"))) + utilization.append(entry.get("utilization")) + charts = { + 'data': { + 'labels': labels, + 'datasets': [ + { + 'name': 'Utilization', + 'values': utilization + } + ] + }, + 'type': 'bar', + 'colors': ['#84BDD5'] + } + return charts def get_columns(): return [ @@ -30,7 +98,21 @@ def get_columns(): "fieldname": "employee", "label": _("Employee"), "fieldtype": "Link", - "options": "employee", + "options": "Employee", + "width": 150 + }, + { + "fieldname": "voucher_no", + "label": _("Sales Invoice"), + "fieldtype": "Link", + "options": "Sales Invoice", + "width": 200 + }, + { + "fieldname": "timesheet", + "label": _("Timesheet"), + "fieldtype": "Link", + "options": "Timesheet", "width": 150 }, { @@ -64,7 +146,7 @@ def get_columns(): "fieldname": "total_billed_hours", "label": _("Total Billed Hours"), "fieldtype": "Int", - "width": 120 + "width": 100 }, { "fieldname": "utilization", @@ -78,42 +160,4 @@ def get_columns(): "fieldtype": "Int", "width": 100 } - ] - -def get_data(filters): - conditions = get_conditions(filters) - sql = """ - select - *, - t.gross_pay * t.utilization as fractional_cost, - t.grand_total - t.gross_pay * t.utilization as profit - from - (select - si.customer_name,tabTimesheet.title,tabTimesheet.employee,si.grand_total,si.name as voucher_no, - ss.gross_pay,ss.total_working_days,tabTimesheet.end_date,tabTimesheet.total_billed_hours, - tabTimesheet.total_billed_hours/(ss.total_working_days * 8) as utilization - from - `tabSalary Slip Timesheet` as sst join `tabTimesheet` on tabTimesheet.name = sst.time_sheet - join `tabSales Invoice Timesheet` as sit on sit.time_sheet = tabTimesheet. name - join `tabSales Invoice` as si on si. name = sit.parent and si.status != "Cancelled" - join `tabSalary Slip` as ss on ss.name = sst.parent and ss.status != "Cancelled" """ - if conditions: - sql += """ - where - %s) as t"""%(conditions) - data = frappe.db.sql(sql,filters, as_dict=True) - - return data - -def get_conditions(filters): - conditions = [] - if filters.get("customer_name"): - conditions.append("si.customer_name='%s'"%filters.get("customer_name")) - if filters.get("start_date"): - conditions.append("tabTimesheet.start_date>='%s'"%filters.get("start_date")) - if filters.get("end_date"): - conditions.append("tabTimesheet.end_date<='%s'"%filters.get("end_date")) - if filters.get("employee"): - conditions.append("tabTimesheet.employee='%s'"%filters.get("employee")) - conditions = " and ".join(conditions) - return conditions \ No newline at end of file + ] \ No newline at end of file diff --git a/erpnext/projects/report/profitability/test_profitability.py b/erpnext/projects/report/profitability/test_profitability.py new file mode 100644 index 0000000000..dfdef0dcec --- /dev/null +++ b/erpnext/projects/report/profitability/test_profitability.py @@ -0,0 +1,52 @@ +from __future__ import unicode_literals +import unittest +import frappe +import datetime +from frappe.utils import getdate, nowdate, add_days, add_months +from erpnext.hr.doctype.employee.test_employee import make_employee +from erpnext.projects.doctype.timesheet.test_timesheet import make_salary_structure_for_timesheet, make_timesheet +from erpnext.projects.doctype.timesheet.timesheet import make_salary_slip, make_sales_invoice +from erpnext.projects.report.profitability.profitability import execute + +class TestProfitability(unittest.TestCase): + @classmethod + def setUp(self): + emp = make_employee("test_employee_9@salary.com", company="_Test Company") + if not frappe.db.exists("Salary Component", "Timesheet Component"): + frappe.get_doc({"doctype": "Salary Component", "salary_component": "Timesheet Component"}).insert() + make_salary_structure_for_timesheet(emp, company="_Test Company") + self.timesheet = make_timesheet(emp, simulate = True, billable=1) + self.salary_slip = make_salary_slip(self.timesheet.name) + self.salary_slip.submit() + self.sales_invoice = make_sales_invoice(self.timesheet.name, '_Test Item', '_Test Customer') + self.sales_invoice.due_date = nowdate() + self.sales_invoice.submit() + + def test_profitability(self): + filters = { + 'company': '_Test Company', + 'start_date': getdate(), + 'end_date': getdate() + } + + report = execute(filters) + expected_data = [ + { + "customer_name": "_Test Customer", + "title": "test_employee_9@salary.com", + "grand_total": 100.0, + "gross_pay": 78100.0, + "profit": -19425.0, + "total_billed_hours": 2.0, + "utilization": 0.25, + "fractional_cost": 19525.0, + "total_working_days": 1.0 + } + ] + for key in ["customer_name","title","grand_total","gross_pay","profit","total_billed_hours","utilization","fractional_cost","total_working_days"]: + self.assertEqual(expected_data[0].get(key), report[1][0].get(key)) + + def tearDown(self): + frappe.get_doc("Sales Invoice", self.sales_invoice.name).cancel() + frappe.get_doc("Salary Slip", self.salary_slip.name).cancel() + frappe.get_doc("Timesheet", self.timesheet.name).cancel() \ No newline at end of file diff --git a/erpnext/projects/workspace/projects/projects.json b/erpnext/projects/workspace/projects/projects.json index dbbd7e1458..8703ffb756 100644 --- a/erpnext/projects/workspace/projects/projects.json +++ b/erpnext/projects/workspace/projects/projects.json @@ -15,6 +15,7 @@ "hide_custom": 0, "icon": "project", "idx": 0, + "is_default": 0, "is_standard": 1, "label": "Projects", "links": [ @@ -129,6 +130,16 @@ "onboard": 1, "type": "Link" }, + { + "dependencies": "Timesheet, Sales Invoice, Salary Slip", + "hidden": 0, + "is_query_report": 1, + "label": "Profitability", + "link_to": "Profitability", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, { "dependencies": "Project", "hidden": 0, @@ -150,7 +161,7 @@ "type": "Link" } ], - "modified": "2020-12-01 13:38:37.856224", + "modified": "2021-03-25 13:25:17.609608", "modified_by": "Administrator", "module": "Projects", "name": "Projects", From c0b4eea415ca3cb3a242f78f882e7ea0f811a836 Mon Sep 17 00:00:00 2001 From: pateljannat Date: Thu, 25 Mar 2021 14:39:05 +0530 Subject: [PATCH 008/326] fix: sider --- .../report/profitability/profitability.py | 17 ++++++++--------- .../report/profitability/test_profitability.py | 3 +-- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/erpnext/projects/report/profitability/profitability.py b/erpnext/projects/report/profitability/profitability.py index 8c052b5e17..8ce2eb09ee 100644 --- a/erpnext/projects/report/profitability/profitability.py +++ b/erpnext/projects/report/profitability/profitability.py @@ -4,7 +4,6 @@ from __future__ import unicode_literals import frappe from frappe import _ -from frappe.utils import nowdate, time_diff_in_hours def execute(filters=None): columns, data = [], [] @@ -25,31 +24,31 @@ def get_data(filters): (select si.customer_name,tabTimesheet.title,tabTimesheet.employee,si.grand_total,si.name as voucher_no, ss.gross_pay,ss.total_working_days,tabTimesheet.end_date,tabTimesheet.total_billed_hours,tabTimesheet.name as timesheet, - tabTimesheet.total_billed_hours/(ss.total_working_days * %s) as utilization + tabTimesheet.total_billed_hours/(ss.total_working_days * {0}) as utilization from `tabSalary Slip Timesheet` as sst join `tabTimesheet` on tabTimesheet.name = sst.time_sheet join `tabSales Invoice Timesheet` as sit on sit.time_sheet = tabTimesheet.name join `tabSales Invoice` as si on si.name = sit.parent and si.status != "Cancelled" - join `tabSalary Slip` as ss on ss.name = sst.parent and ss.status != "Cancelled" """%(default_working_hours) + join `tabSalary Slip` as ss on ss.name = sst.parent and ss.status != "Cancelled" """.format(default_working_hours) if conditions: sql += """ where - %s) as t"""%(conditions) + {0}) as t""".format(conditions) data = frappe.db.sql(sql,filters, as_dict=True) return data def get_conditions(filters): conditions = [] if filters.get("company"): - conditions.append("tabTimesheet.company='%s'"%filters.get("company")) + conditions.append('tabTimesheet.company="{0}"'.format(filters.get("company"))) if filters.get("customer_name"): - conditions.append("si.customer_name='%s'"%filters.get("customer_name")) + conditions.append("si.customer_name='{0}'".format(filters.get("customer_name"))) if filters.get("start_date"): - conditions.append("tabTimesheet.start_date>='%s'"%filters.get("start_date")) + conditions.append("tabTimesheet.start_date>='{0}'".format(filters.get("start_date"))) if filters.get("end_date"): - conditions.append("tabTimesheet.end_date<='%s'"%filters.get("end_date")) + conditions.append("tabTimesheet.end_date<='{0}'".format(filters.get("end_date"))) if filters.get("employee"): - conditions.append("tabTimesheet.employee='%s'"%filters.get("employee")) + conditions.append("tabTimesheet.employee='{0}'".format(filters.get("employee"))) conditions = " and ".join(conditions) return conditions diff --git a/erpnext/projects/report/profitability/test_profitability.py b/erpnext/projects/report/profitability/test_profitability.py index dfdef0dcec..64ab6787eb 100644 --- a/erpnext/projects/report/profitability/test_profitability.py +++ b/erpnext/projects/report/profitability/test_profitability.py @@ -1,8 +1,7 @@ from __future__ import unicode_literals import unittest import frappe -import datetime -from frappe.utils import getdate, nowdate, add_days, add_months +from frappe.utils import getdate, nowdate from erpnext.hr.doctype.employee.test_employee import make_employee from erpnext.projects.doctype.timesheet.test_timesheet import make_salary_structure_for_timesheet, make_timesheet from erpnext.projects.doctype.timesheet.timesheet import make_salary_slip, make_sales_invoice From b7f2ba1556fc800526cc90e9b5a0f277441d39d9 Mon Sep 17 00:00:00 2001 From: Hussain Nagaria Date: Tue, 30 Mar 2021 12:19:16 +0530 Subject: [PATCH 009/326] feat: Add Report boilerplate --- .../__init__.py | 0 ...ee_hours_utilisation_based_on_timesheet.js | 9 ++++++++ ..._hours_utilisation_based_on_timesheet.json | 22 +++++++++++++++++++ ...ee_hours_utilisation_based_on_timesheet.py | 9 ++++++++ 4 files changed, 40 insertions(+) create mode 100644 erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/__init__.py create mode 100644 erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.js create mode 100644 erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.json create mode 100644 erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.py diff --git a/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/__init__.py b/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.js b/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.js new file mode 100644 index 0000000000..f800877e38 --- /dev/null +++ b/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.js @@ -0,0 +1,9 @@ +// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports["Employee Hours Utilisation Based On Timesheet"] = { + "filters": [ + + ] +}; diff --git a/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.json b/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.json new file mode 100644 index 0000000000..1b504e011d --- /dev/null +++ b/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.json @@ -0,0 +1,22 @@ +{ + "add_total_row": 0, + "columns": [], + "creation": "2021-03-30 12:18:02.090630", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 0, + "is_standard": "Yes", + "modified": "2021-03-30 12:18:02.090630", + "modified_by": "Administrator", + "module": "Projects", + "name": "Employee Hours Utilisation Based On Timesheet", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Timesheet", + "report_name": "Employee Hours Utilisation Based On Timesheet", + "report_type": "Script Report", + "roles": [] +} \ No newline at end of file diff --git a/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.py b/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.py new file mode 100644 index 0000000000..de2aa1ff41 --- /dev/null +++ b/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.py @@ -0,0 +1,9 @@ +# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe + +def execute(filters=None): + columns, data = [], [] + return columns, data From 2b85a81a1ca8d42b3ef9fd5e85aeab4d7d566f50 Mon Sep 17 00:00:00 2001 From: Hussain Nagaria Date: Tue, 30 Mar 2021 13:57:44 +0530 Subject: [PATCH 010/326] feat: Add filters to front end --- ...ee_hours_utilisation_based_on_timesheet.js | 35 ++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.js b/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.js index f800877e38..e9b817ab67 100644 --- a/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.js +++ b/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.js @@ -4,6 +4,39 @@ frappe.query_reports["Employee Hours Utilisation Based On Timesheet"] = { "filters": [ - + { + fieldname: "company", + label: __("Company"), + fieldtype: "Link", + options: "Company", + default: frappe.defaults.get_user_default("Company"), + reqd: 1 + }, + { + fieldname: "from_date", + label: __("From Date"), + fieldtype: "Date", + default: frappe.defaults.get_global_default("year_start_date"), + reqd: 1 + }, + { + fieldname:"to_date", + label: __("To Date"), + fieldtype: "Date", + default: frappe.defaults.get_global_default("year_end_date"), + reqd: 1 + }, + { + fieldname: "employee", + label: __("Employee"), + fieldtype: "Link", + options: "Employee" + }, + { + fieldname: "project", + label: __("Project"), + fieldtype: "Link", + options: "Project" + } ] }; From 1a0229eb36ad0af47a7471718aeaf1493c3af701 Mon Sep 17 00:00:00 2001 From: Hussain Nagaria Date: Tue, 30 Mar 2021 18:20:22 +0530 Subject: [PATCH 011/326] feat: Add report table data generation --- ...ee_hours_utilisation_based_on_timesheet.py | 126 +++++++++++++++++- 1 file changed, 123 insertions(+), 3 deletions(-) diff --git a/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.py b/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.py index de2aa1ff41..71ed2e79eb 100644 --- a/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.py +++ b/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.py @@ -2,8 +2,128 @@ # For license information, please see license.txt from __future__ import unicode_literals -# import frappe +import frappe +from frappe import _ +from frappe.utils import flt, getdate +from six import iteritems def execute(filters=None): - columns, data = [], [] - return columns, data + return EmployeeHoursReport(filters).run() + +class EmployeeHoursReport: + '''Employee Hours Utilisation Report Based On Timesheet''' + def __init__(self, filters=None): + self.filters = frappe._dict(filters or {}) + + self.from_date = getdate(self.filters.from_date) + self.to_date = getdate(self.filters.to_date) + + self.validate_dates() + + def validate_dates(self): + self.day_span = (self.to_date - self.from_date).days + + if self.day_span < 0: + frappe.throw(_('From Date must come before To Date')) + + def run(self): + self.generate_columns() + self.generate_data() + + return self.columns, self.data + + def generate_columns(self): + self.columns = [ + { + 'label': _('Employee'), + 'options': 'Employee', + 'fieldname': 'employee', + 'fieldtype': 'Link', + 'width': 200 + }, + { + 'label': _('Total Hours'), + 'fieldname': 'total_hours', + 'fieldtype': 'Float', + 'width': 150 + }, + { + 'label': _('Billed Hours'), + 'fieldname': 'billed_hours', + 'fieldtype': 'Float', + 'width': 150 + }, + { + 'label': _('Non-Billed Hours'), + 'fieldname': 'non_billed_hours', + 'fieldtype': 'Float', + 'width': 150 + }, + { + 'label': _('Untracked Hours'), + 'fieldname': 'untracked_hours', + 'fieldtype': 'Float', + 'width': 150 + }, + { + 'label': _('% Utilization'), + 'fieldname': 'per_util', + 'fieldtype': 'Percentage', + 'width': 200 + } + ] + + def generate_data(self): + self.generate_filtered_time_logs() + self.generate_stats_by_employee() + self.calculate_utilisations() + + self.data = [] + + for emp, data in iteritems(self.stats_by_employee): + row = frappe._dict() + row['employee'] = emp + row.update(data) + self.data.append(row) + + def generate_filtered_time_logs(self): + additional_filters = '' + + if self.filters.employee: + additional_filters += f'AND tt.employee = {self.filters.employee}' + + if self.filters.project: + additional_filters += f'AND ttd.project = {self.filters.project}' + + self.filtered_time_logs = frappe.db.sql(''' + SELECT tt.employee AS employee, ttd.hours AS hours, ttd.billable AS billable, ttd.project AS project + FROM `tabTimesheet Detail` AS ttd + JOIN `tabTimesheet` AS tt + ON ttd.parent = tt.name + WHERE tt.start_date BETWEEN '{0}' AND '{1}' + AND tt.end_date BETWEEN '{0}' AND '{1}' + {2}; + '''.format(self.filters.from_date, self.filters.to_date, additional_filters)) + + def generate_stats_by_employee(self): + self.stats_by_employee = frappe._dict() + + for emp, hours, billable, project in self.filtered_time_logs: + self.stats_by_employee.setdefault( + emp, frappe._dict() + ).setdefault('billed_hours', 0.0) + + self.stats_by_employee[emp].setdefault('non_billed_hours', 0.0) + + if billable: + self.stats_by_employee[emp]['billed_hours'] += flt(hours, 2) + else: + self.stats_by_employee[emp]['non_billed_hours'] += flt(hours, 2) + + def calculate_utilisations(self): + # (9.0) Will be fetched from HR settings + TOTAL_HOURS = flt(9.0 * self.day_span, 2) + for emp, data in iteritems(self.stats_by_employee): + data['total_hours'] = TOTAL_HOURS + data['untracked_hours'] = flt(TOTAL_HOURS - data['billed_hours'] - data['non_billed_hours'], 2) + data['per_util'] = flt(((data['billed_hours'] + data['non_billed_hours']) / TOTAL_HOURS) * 100, 2) \ No newline at end of file From f5576d67dd4723d5f8be4aa83aa6a70174ccd2f5 Mon Sep 17 00:00:00 2001 From: Hussain Nagaria Date: Wed, 31 Mar 2021 12:25:36 +0530 Subject: [PATCH 012/326] feat: Add Report Summary generation --- ...ee_hours_utilisation_based_on_timesheet.py | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.py b/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.py index 71ed2e79eb..702c85a824 100644 --- a/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.py +++ b/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.py @@ -29,8 +29,9 @@ class EmployeeHoursReport: def run(self): self.generate_columns() self.generate_data() + self.generate_report_summary() - return self.columns, self.data + return self.columns, self.data, None, None, self.report_summary def generate_columns(self): self.columns = [ @@ -126,4 +127,25 @@ class EmployeeHoursReport: for emp, data in iteritems(self.stats_by_employee): data['total_hours'] = TOTAL_HOURS data['untracked_hours'] = flt(TOTAL_HOURS - data['billed_hours'] - data['non_billed_hours'], 2) - data['per_util'] = flt(((data['billed_hours'] + data['non_billed_hours']) / TOTAL_HOURS) * 100, 2) \ No newline at end of file + data['per_util'] = flt(((data['billed_hours'] + data['non_billed_hours']) / TOTAL_HOURS) * 100, 2) + + def generate_report_summary(self): + if not self.data: + return + + avg_utilisation = 0.0 + for row in self.data: + avg_utilisation += row['per_util'] + + avg_utilisation /= len(self.data) + avg_utilisation = flt(avg_utilisation, 2) + + THRESHOLD_PERCENTAGE = 70.0 + self.report_summary = [ + { + 'value': f'{avg_utilisation}%', + 'indicator': 'Red' if avg_utilisation < THRESHOLD_PERCENTAGE else 'Green', + 'label': _('Average Utilisation'), + 'datatype': 'Percentage' + } + ] From 92c768972ff4cdce051328b1c66a8ff10bff5168 Mon Sep 17 00:00:00 2001 From: Hussain Nagaria Date: Wed, 31 Mar 2021 12:26:37 +0530 Subject: [PATCH 013/326] fix: Missing quotation marks in SQL query --- .../employee_hours_utilisation_based_on_timesheet.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.py b/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.py index 702c85a824..e3d6b9b7f2 100644 --- a/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.py +++ b/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.py @@ -91,10 +91,10 @@ class EmployeeHoursReport: additional_filters = '' if self.filters.employee: - additional_filters += f'AND tt.employee = {self.filters.employee}' + additional_filters += f"AND tt.employee = '{self.filters.employee}'" if self.filters.project: - additional_filters += f'AND ttd.project = {self.filters.project}' + additional_filters += f"AND ttd.project = '{self.filters.project}'" self.filtered_time_logs = frappe.db.sql(''' SELECT tt.employee AS employee, ttd.hours AS hours, ttd.billable AS billable, ttd.project AS project From 39b75c63ad053f9fc6b7315f75e86cef9d39edc9 Mon Sep 17 00:00:00 2001 From: Hussain Nagaria Date: Wed, 31 Mar 2021 13:13:25 +0530 Subject: [PATCH 014/326] feat: Add chart generation --- ...ee_hours_utilisation_based_on_timesheet.py | 48 ++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.py b/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.py index e3d6b9b7f2..8124c7a1be 100644 --- a/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.py +++ b/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.py @@ -30,8 +30,9 @@ class EmployeeHoursReport: self.generate_columns() self.generate_data() self.generate_report_summary() + self.generate_chart_data() - return self.columns, self.data, None, None, self.report_summary + return self.columns, self.data, None, self.chart, self.report_summary def generate_columns(self): self.columns = [ @@ -87,6 +88,9 @@ class EmployeeHoursReport: row.update(data) self.data.append(row) + # Sort by descending order of percentage utilisation + self.data.sort(key=lambda x: x['per_util'], reverse=True) + def generate_filtered_time_logs(self): additional_filters = '' @@ -149,3 +153,45 @@ class EmployeeHoursReport: 'datatype': 'Percentage' } ] + + def generate_chart_data(self): + self.chart = {} + + labels = [] + billed_hours = [] + non_billed_hours = [] + untracked_hours = [] + + + for row in self.data: + emp_name = frappe.db.get_value( + 'Employee', row['employee'], 'employee_name' + ) + labels.append(emp_name) + billed_hours.append(row.get('billed_hours')) + non_billed_hours.append(row.get('non_billed_hours')) + untracked_hours.append(row.get('untracked_hours')) + + self.chart = { + 'data': { + 'labels': labels[:30], + 'datasets': [ + { + 'name': _('Billed Hours'), + 'values': billed_hours[:30] + }, + { + 'name': _('Non-Billed Hours'), + 'values': non_billed_hours[:30] + }, + { + 'name': _('Untracked Hours'), + 'values': untracked_hours[:30] + } + ] + }, + 'type': 'bar', + 'barOptions': { + 'stacked': True + } + } From 1e772bb9a4718e8c5f707f378f77e12ed38626e8 Mon Sep 17 00:00:00 2001 From: Hussain Nagaria Date: Thu, 1 Apr 2021 00:44:35 +0530 Subject: [PATCH 015/326] test: Basic report data testing --- ...ee_hours_utilisation_based_on_timesheet.py | 7 +- .../test_employee_util.py | 147 ++++++++++++++++++ 2 files changed, 153 insertions(+), 1 deletion(-) create mode 100644 erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/test_employee_util.py diff --git a/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.py b/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.py index 8124c7a1be..19793917ce 100644 --- a/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.py +++ b/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.py @@ -99,7 +99,10 @@ class EmployeeHoursReport: if self.filters.project: additional_filters += f"AND ttd.project = '{self.filters.project}'" - + + if self.filters.company: + additional_filters += f"AND tt.company = '{self.filters.company}'" + self.filtered_time_logs = frappe.db.sql(''' SELECT tt.employee AS employee, ttd.hours AS hours, ttd.billable AS billable, ttd.project AS project FROM `tabTimesheet Detail` AS ttd @@ -134,6 +137,8 @@ class EmployeeHoursReport: data['per_util'] = flt(((data['billed_hours'] + data['non_billed_hours']) / TOTAL_HOURS) * 100, 2) def generate_report_summary(self): + self.report_summary = [] + if not self.data: return diff --git a/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/test_employee_util.py b/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/test_employee_util.py new file mode 100644 index 0000000000..7b310fa1fe --- /dev/null +++ b/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/test_employee_util.py @@ -0,0 +1,147 @@ +from __future__ import unicode_literals +import unittest +import frappe + +from frappe.utils.make_random import get_random +from frappe.utils import get_timestamp +from erpnext.projects.report.employee_hours_utilisation_based_on_timesheet.employee_hours_utilisation_based_on_timesheet import execute +from erpnext.hr.doctype.employee.test_employee import make_employee +from erpnext.projects.doctype.project.test_project import make_project + +class TestEmployeeUtilisation(unittest.TestCase): + @classmethod + def setUpClass(cls): + # Create test employee + cls.test_emp1 = make_employee("test@example.com", "_Test Company") + cls.test_emp2 = make_employee("test1@example.com", "_Test Company") + + # Create test project + cls.test_project = make_project({"project_name": "_Test Project"}) + + # Create test timesheets + cls.create_test_timesheets() + + @classmethod + def create_test_timesheets(cls): + timesheet1 = frappe.new_doc("Timesheet") + timesheet1.employee = cls.test_emp1 + timesheet1.company = '_Test Company' + + timesheet1.append("time_logs", { + "activity_type": get_random("Activity Type"), + "hours": 5, + "billable": 1, + "from_time": '2021-04-01 13:30:00.000000', + "to_time": '2021-04-01 18:30:00.000000' + }) + + timesheet1.save() + timesheet1.submit() + + timesheet2 = frappe.new_doc("Timesheet") + timesheet2.employee = cls.test_emp2 + timesheet2.company = '_Test Company' + + timesheet2.append("time_logs", { + "activity_type": get_random("Activity Type"), + "hours": 10, + "billable": 0, + "from_time": '2021-04-01 13:30:00.000000', + "to_time": '2021-04-01 23:30:00.000000', + "project": cls.test_project.name + }) + + timesheet2.save() + timesheet2.submit() + + @classmethod + def tearDownClass(cls): + # Delete time logs + frappe.db.sql(""" + DELETE FROM `tabTimesheet Detail` + WHERE parent IN ( + SELECT name + FROM `tabTimesheet` + WHERE company = '_Test Company' + ) + """) + + frappe.db.sql("DELETE FROM `tabTimesheet` WHERE company='_Test Company'") + frappe.db.sql(f"DELETE FROM `tabProject` WHERE name='{cls.test_project.name}'") + print(f"DELETE FROM `tabProject` WHERE name='{cls.test_project.name}'") + + def test_utilisation_report_with_required_filters_only(self): + filters = { + "company": "_Test Company", + "from_date": "2021-04-01", + "to_date": "2021-04-03" + } + + report = execute(filters) + + expected_data = [ + { + 'employee': 'EMP-00002', + 'billed_hours': 0.0, + 'non_billed_hours': 10.0, + 'total_hours': 18.0, + 'untracked_hours': 8.0, + 'per_util': 55.56 + }, + { + 'employee': 'EMP-00001', + 'billed_hours': 5.0, + 'non_billed_hours': 0.0, + 'total_hours': 18.0, + 'untracked_hours': 13.0, + 'per_util': 27.78 + } + ] + + self.assertEqual(report[1], expected_data) + + def test_utilisation_report_for_single_employee(self): + filters = { + "company": "_Test Company", + "from_date": "2021-04-01", + "to_date": "2021-04-03", + "employee": self.test_emp1 + } + + report = execute(filters) + + expected_data = [ + { + 'employee': 'EMP-00001', + 'billed_hours': 5.0, + 'non_billed_hours': 0.0, + 'total_hours': 18.0, + 'untracked_hours': 13.0, + 'per_util': 27.78 + } + ] + + self.assertEqual(report[1], expected_data) + + def test_utilisation_report_for_project(self): + filters = { + "company": "_Test Company", + "from_date": "2021-04-01", + "to_date": "2021-04-03", + "project": self.test_project.name + } + + report = execute(filters) + + expected_data = [ + { + 'employee': 'EMP-00002', + 'billed_hours': 0.0, + 'non_billed_hours': 10.0, + 'total_hours': 18.0, + 'untracked_hours': 8.0, + 'per_util': 55.56 + } + ] + + self.assertEqual(report[1], expected_data) \ No newline at end of file From e41d48c7deeb228cb7d1bfd2604f1d0e48db8a80 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 2 Apr 2021 13:21:08 +0530 Subject: [PATCH 016/326] fix: disable auto naming of customer during import --- erpnext/selling/doctype/customer/customer.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index 96b3fa4ccd..49ca9423e8 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -38,11 +38,19 @@ class Customer(TransactionBase): set_name_by_naming_series(self) def get_customer_name(self): - if frappe.db.get_value("Customer", self.customer_name): + + if frappe.db.get_value("Customer", self.customer_name) and not frappe.flags.in_import: count = frappe.db.sql("""select ifnull(MAX(CAST(SUBSTRING_INDEX(name, ' ', -1) AS UNSIGNED)), 0) from tabCustomer where name like %s""", "%{0} - %".format(self.customer_name), as_list=1)[0][0] count = cint(count) + 1 - return "{0} - {1}".format(self.customer_name, cstr(count)) + + new_customer_name = "{0} - {1}".format(self.customer_name, cstr(count)) + + msgprint(_("Changed customer name to '{}' as '{}' already exists.") + .format(new_customer_name, self.customer_name), + title=_("Note"), indicator="yellow") + + return new_customer_name return self.customer_name From d27fc93eddf3d04fec977464d698c5ecec9c1e6b Mon Sep 17 00:00:00 2001 From: Hussain Nagaria Date: Fri, 2 Apr 2021 19:54:58 +0530 Subject: [PATCH 017/326] feat: Add additional stats to report summary --- ...ee_hours_utilisation_based_on_timesheet.py | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.py b/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.py index 19793917ce..c27efc96fb 100644 --- a/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.py +++ b/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.py @@ -143,8 +143,14 @@ class EmployeeHoursReport: return avg_utilisation = 0.0 + total_billed, total_non_billed = 0.0, 0.0 + total_untracked = 0.0 + for row in self.data: avg_utilisation += row['per_util'] + total_billed += row['billed_hours'] + total_non_billed += row['non_billed_hours'] + total_untracked += row['untracked_hours'] avg_utilisation /= len(self.data) avg_utilisation = flt(avg_utilisation, 2) @@ -156,6 +162,21 @@ class EmployeeHoursReport: 'indicator': 'Red' if avg_utilisation < THRESHOLD_PERCENTAGE else 'Green', 'label': _('Average Utilisation'), 'datatype': 'Percentage' + }, + { + 'value': total_billed, + 'label': _('Total Billed Hours'), + 'datatype': 'Float' + }, + { + 'value': total_non_billed, + 'label': _('Total Non-Billed Hours'), + 'datatype': 'Float' + }, + { + 'value': total_untracked, + 'label': _('Total Untracked Hours'), + 'datatype': 'Float' } ] From 40abd2a2e12b52a814dd414115e7b29e8729a0dd Mon Sep 17 00:00:00 2001 From: Hussain Nagaria Date: Fri, 2 Apr 2021 19:55:54 +0530 Subject: [PATCH 018/326] test: Add test case for summary data --- .../test_employee_util.py | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/test_employee_util.py b/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/test_employee_util.py index 7b310fa1fe..adf55afc14 100644 --- a/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/test_employee_util.py +++ b/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/test_employee_util.py @@ -68,7 +68,6 @@ class TestEmployeeUtilisation(unittest.TestCase): frappe.db.sql("DELETE FROM `tabTimesheet` WHERE company='_Test Company'") frappe.db.sql(f"DELETE FROM `tabProject` WHERE name='{cls.test_project.name}'") - print(f"DELETE FROM `tabProject` WHERE name='{cls.test_project.name}'") def test_utilisation_report_with_required_filters_only(self): filters = { @@ -144,4 +143,22 @@ class TestEmployeeUtilisation(unittest.TestCase): } ] - self.assertEqual(report[1], expected_data) \ No newline at end of file + self.assertEqual(report[1], expected_data) + + def test_report_summary_data(self): + filters = { + "company": "_Test Company", + "from_date": "2021-04-01", + "to_date": "2021-04-03" + } + + report = execute(filters) + summary = report[4] + expected_summary_values = ['41.67%', 5.0, 10.0, 21.0] + + self.assertEqual(len(summary), 4) + + for i in range(4): + self.assertEqual( + summary[i]['value'], expected_summary_values[i] + ) From ccb15de1c9e4c23487787b3f8be3bf3e8d7dfb86 Mon Sep 17 00:00:00 2001 From: Hussain Nagaria Date: Mon, 5 Apr 2021 11:23:06 +0530 Subject: [PATCH 019/326] fix: Handle overtime edge case --- .../employee_hours_utilisation_based_on_timesheet.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.py b/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.py index c27efc96fb..19cb1c911d 100644 --- a/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.py +++ b/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.py @@ -134,6 +134,11 @@ class EmployeeHoursReport: for emp, data in iteritems(self.stats_by_employee): data['total_hours'] = TOTAL_HOURS data['untracked_hours'] = flt(TOTAL_HOURS - data['billed_hours'] - data['non_billed_hours'], 2) + + # To handle overtime edge-case + if data['untracked_hours'] < 0: + data['untracked_hours'] = 0.0 + data['per_util'] = flt(((data['billed_hours'] + data['non_billed_hours']) / TOTAL_HOURS) * 100, 2) def generate_report_summary(self): From bb32f5a92a0f20dbd0f673c3b3beab69f47dd892 Mon Sep 17 00:00:00 2001 From: Hussain Nagaria Date: Mon, 5 Apr 2021 19:32:10 +0530 Subject: [PATCH 020/326] fix: Rename utilisation to utilization --- .../__init__.py | 0 ...e_hours_utilization_based_on_timesheet.js} | 2 +- ...hours_utilization_based_on_timesheet.json} | 8 +++---- ...e_hours_utilization_based_on_timesheet.py} | 22 +++++++++---------- .../test_employee_util.py | 10 ++++----- 5 files changed, 21 insertions(+), 21 deletions(-) rename erpnext/projects/report/{employee_hours_utilisation_based_on_timesheet => employee_hours_utilization_based_on_timesheet}/__init__.py (100%) rename erpnext/projects/report/{employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.js => employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.js} (93%) rename erpnext/projects/report/{employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.json => employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.json} (63%) rename erpnext/projects/report/{employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.py => employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py} (92%) rename erpnext/projects/report/{employee_hours_utilisation_based_on_timesheet => employee_hours_utilization_based_on_timesheet}/test_employee_util.py (92%) diff --git a/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/__init__.py b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/__init__.py similarity index 100% rename from erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/__init__.py rename to erpnext/projects/report/employee_hours_utilization_based_on_timesheet/__init__.py diff --git a/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.js b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.js similarity index 93% rename from erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.js rename to erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.js index e9b817ab67..2a5ea3655d 100644 --- a/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.js +++ b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.js @@ -2,7 +2,7 @@ // For license information, please see license.txt /* eslint-disable */ -frappe.query_reports["Employee Hours Utilisation Based On Timesheet"] = { +frappe.query_reports["Employee Hours Utilization Based On Timesheet"] = { "filters": [ { fieldname: "company", diff --git a/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.json b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.json similarity index 63% rename from erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.json rename to erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.json index 1b504e011d..5ff8186572 100644 --- a/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.json +++ b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.json @@ -1,7 +1,7 @@ { "add_total_row": 0, "columns": [], - "creation": "2021-03-30 12:18:02.090630", + "creation": "2021-04-05 19:23:43.838623", "disable_prepared_report": 0, "disabled": 0, "docstatus": 0, @@ -9,14 +9,14 @@ "filters": [], "idx": 0, "is_standard": "Yes", - "modified": "2021-03-30 12:18:02.090630", + "modified": "2021-04-05 19:23:43.838623", "modified_by": "Administrator", "module": "Projects", - "name": "Employee Hours Utilisation Based On Timesheet", + "name": "Employee Hours Utilization Based On Timesheet", "owner": "Administrator", "prepared_report": 0, "ref_doctype": "Timesheet", - "report_name": "Employee Hours Utilisation Based On Timesheet", + "report_name": "Employee Hours Utilization Based On Timesheet", "report_type": "Script Report", "roles": [] } \ No newline at end of file diff --git a/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.py b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py similarity index 92% rename from erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.py rename to erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py index 19cb1c911d..bc9dc7d095 100644 --- a/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/employee_hours_utilisation_based_on_timesheet.py +++ b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py @@ -11,7 +11,7 @@ def execute(filters=None): return EmployeeHoursReport(filters).run() class EmployeeHoursReport: - '''Employee Hours Utilisation Report Based On Timesheet''' + '''Employee Hours Utilization Report Based On Timesheet''' def __init__(self, filters=None): self.filters = frappe._dict(filters or {}) @@ -78,7 +78,7 @@ class EmployeeHoursReport: def generate_data(self): self.generate_filtered_time_logs() self.generate_stats_by_employee() - self.calculate_utilisations() + self.calculate_utilizations() self.data = [] @@ -88,7 +88,7 @@ class EmployeeHoursReport: row.update(data) self.data.append(row) - # Sort by descending order of percentage utilisation + # Sort by descending order of percentage utilization self.data.sort(key=lambda x: x['per_util'], reverse=True) def generate_filtered_time_logs(self): @@ -128,7 +128,7 @@ class EmployeeHoursReport: else: self.stats_by_employee[emp]['non_billed_hours'] += flt(hours, 2) - def calculate_utilisations(self): + def calculate_utilizations(self): # (9.0) Will be fetched from HR settings TOTAL_HOURS = flt(9.0 * self.day_span, 2) for emp, data in iteritems(self.stats_by_employee): @@ -147,25 +147,25 @@ class EmployeeHoursReport: if not self.data: return - avg_utilisation = 0.0 + avg_utilization = 0.0 total_billed, total_non_billed = 0.0, 0.0 total_untracked = 0.0 for row in self.data: - avg_utilisation += row['per_util'] + avg_utilization += row['per_util'] total_billed += row['billed_hours'] total_non_billed += row['non_billed_hours'] total_untracked += row['untracked_hours'] - avg_utilisation /= len(self.data) - avg_utilisation = flt(avg_utilisation, 2) + avg_utilization /= len(self.data) + avg_utilization = flt(avg_utilization, 2) THRESHOLD_PERCENTAGE = 70.0 self.report_summary = [ { - 'value': f'{avg_utilisation}%', - 'indicator': 'Red' if avg_utilisation < THRESHOLD_PERCENTAGE else 'Green', - 'label': _('Average Utilisation'), + 'value': f'{avg_utilization}%', + 'indicator': 'Red' if avg_utilization < THRESHOLD_PERCENTAGE else 'Green', + 'label': _('Average Utilization'), 'datatype': 'Percentage' }, { diff --git a/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/test_employee_util.py b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/test_employee_util.py similarity index 92% rename from erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/test_employee_util.py rename to erpnext/projects/report/employee_hours_utilization_based_on_timesheet/test_employee_util.py index adf55afc14..5306b922eb 100644 --- a/erpnext/projects/report/employee_hours_utilisation_based_on_timesheet/test_employee_util.py +++ b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/test_employee_util.py @@ -4,11 +4,11 @@ import frappe from frappe.utils.make_random import get_random from frappe.utils import get_timestamp -from erpnext.projects.report.employee_hours_utilisation_based_on_timesheet.employee_hours_utilisation_based_on_timesheet import execute +from erpnext.projects.report.employee_hours_utilization_based_on_timesheet.employee_hours_utilization_based_on_timesheet import execute from erpnext.hr.doctype.employee.test_employee import make_employee from erpnext.projects.doctype.project.test_project import make_project -class TestEmployeeUtilisation(unittest.TestCase): +class TestEmployeeUtilization(unittest.TestCase): @classmethod def setUpClass(cls): # Create test employee @@ -69,7 +69,7 @@ class TestEmployeeUtilisation(unittest.TestCase): frappe.db.sql("DELETE FROM `tabTimesheet` WHERE company='_Test Company'") frappe.db.sql(f"DELETE FROM `tabProject` WHERE name='{cls.test_project.name}'") - def test_utilisation_report_with_required_filters_only(self): + def test_utilization_report_with_required_filters_only(self): filters = { "company": "_Test Company", "from_date": "2021-04-01", @@ -99,7 +99,7 @@ class TestEmployeeUtilisation(unittest.TestCase): self.assertEqual(report[1], expected_data) - def test_utilisation_report_for_single_employee(self): + def test_utilization_report_for_single_employee(self): filters = { "company": "_Test Company", "from_date": "2021-04-01", @@ -122,7 +122,7 @@ class TestEmployeeUtilisation(unittest.TestCase): self.assertEqual(report[1], expected_data) - def test_utilisation_report_for_project(self): + def test_utilization_report_for_project(self): filters = { "company": "_Test Company", "from_date": "2021-04-01", From 33e0c8ed191eb08eea2a2bc7f9544f2defed84fa Mon Sep 17 00:00:00 2001 From: Hussain Nagaria Date: Tue, 6 Apr 2021 11:40:35 +0530 Subject: [PATCH 021/326] test: Unique Employee IDs for tests --- .../employee_hours_utilization_based_on_timesheet.py | 2 +- .../test_employee_util.py | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py index bc9dc7d095..2c78b3125d 100644 --- a/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py +++ b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py @@ -23,7 +23,7 @@ class EmployeeHoursReport: def validate_dates(self): self.day_span = (self.to_date - self.from_date).days - if self.day_span < 0: + if self.day_span <= 0: frappe.throw(_('From Date must come before To Date')) def run(self): diff --git a/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/test_employee_util.py b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/test_employee_util.py index 5306b922eb..e01e9b1539 100644 --- a/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/test_employee_util.py +++ b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/test_employee_util.py @@ -12,8 +12,8 @@ class TestEmployeeUtilization(unittest.TestCase): @classmethod def setUpClass(cls): # Create test employee - cls.test_emp1 = make_employee("test@example.com", "_Test Company") - cls.test_emp2 = make_employee("test1@example.com", "_Test Company") + cls.test_emp1 = make_employee("test1@employeeutil.com", "_Test Company") + cls.test_emp2 = make_employee("test2@employeeutil.com", "_Test Company") # Create test project cls.test_project = make_project({"project_name": "_Test Project"}) @@ -80,7 +80,7 @@ class TestEmployeeUtilization(unittest.TestCase): expected_data = [ { - 'employee': 'EMP-00002', + 'employee': self.test_emp2, 'billed_hours': 0.0, 'non_billed_hours': 10.0, 'total_hours': 18.0, @@ -88,7 +88,7 @@ class TestEmployeeUtilization(unittest.TestCase): 'per_util': 55.56 }, { - 'employee': 'EMP-00001', + 'employee': self.test_emp1, 'billed_hours': 5.0, 'non_billed_hours': 0.0, 'total_hours': 18.0, @@ -111,7 +111,7 @@ class TestEmployeeUtilization(unittest.TestCase): expected_data = [ { - 'employee': 'EMP-00001', + 'employee': self.test_emp1, 'billed_hours': 5.0, 'non_billed_hours': 0.0, 'total_hours': 18.0, @@ -134,7 +134,7 @@ class TestEmployeeUtilization(unittest.TestCase): expected_data = [ { - 'employee': 'EMP-00002', + 'employee': self.test_emp2, 'billed_hours': 0.0, 'non_billed_hours': 10.0, 'total_hours': 18.0, From d2da7b673bf7a4525819451d429eb889c227b619 Mon Sep 17 00:00:00 2001 From: Hussain Nagaria Date: Tue, 6 Apr 2021 11:44:54 +0530 Subject: [PATCH 022/326] fix: Sider Issues --- .../test_employee_util.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/test_employee_util.py b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/test_employee_util.py index e01e9b1539..8b5d5be0e1 100644 --- a/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/test_employee_util.py +++ b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/test_employee_util.py @@ -3,7 +3,6 @@ import unittest import frappe from frappe.utils.make_random import get_random -from frappe.utils import get_timestamp from erpnext.projects.report.employee_hours_utilization_based_on_timesheet.employee_hours_utilization_based_on_timesheet import execute from erpnext.hr.doctype.employee.test_employee import make_employee from erpnext.projects.doctype.project.test_project import make_project @@ -161,4 +160,4 @@ class TestEmployeeUtilization(unittest.TestCase): for i in range(4): self.assertEqual( summary[i]['value'], expected_summary_values[i] - ) + ) From 09c7598a6782e53d90978b41f8e22e27dfc866d9 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Wed, 7 Apr 2021 20:03:33 +0200 Subject: [PATCH 023/326] fix: make account field non-mandatory --- .../doctype/party_account/party_account.json | 114 +++++------------- 1 file changed, 33 insertions(+), 81 deletions(-) diff --git a/erpnext/accounts/doctype/party_account/party_account.json b/erpnext/accounts/doctype/party_account/party_account.json index aa32d95373..c9f15a6a47 100644 --- a/erpnext/accounts/doctype/party_account/party_account.json +++ b/erpnext/accounts/doctype/party_account/party_account.json @@ -1,87 +1,39 @@ { - "allow_copy": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2014-08-29 16:02:39.740505", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, + "actions": [], + "creation": "2014-08-29 16:02:39.740505", + "doctype": "DocType", + "editable_grid": 1, + "field_order": [ + "company", + "account" + ], "fields": [ { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "company", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 1, - "label": "Company", - "length": 0, - "no_copy": 0, - "options": "Company", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "company", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Company", + "options": "Company", + "reqd": 1 + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "account", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 1, - "label": "Account", - "length": 0, - "no_copy": 0, - "options": "Account", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 + "fieldname": "account", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Account", + "options": "Account" } - ], - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2016-07-11 03:28:03.348246", - "modified_by": "Administrator", - "module": "Accounts", - "name": "Party Account", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_seen": 0 + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-04-07 18:13:08.833822", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Party Account", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC" } \ No newline at end of file From 6b2e4f2b5d7555e14091f0908588ac5ebc360e64 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Wed, 7 Apr 2021 20:03:59 +0200 Subject: [PATCH 024/326] feat: add custom field debtor_creditor_number to Party Account --- erpnext/regional/germany/setup.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/erpnext/regional/germany/setup.py b/erpnext/regional/germany/setup.py index d6047e863c..6258c05459 100644 --- a/erpnext/regional/germany/setup.py +++ b/erpnext/regional/germany/setup.py @@ -1,6 +1,18 @@ import os import frappe +from frappe.custom.doctype.custom_field.custom_field import create_custom_fields def setup(company=None, patch=True): - pass + make_custom_fields() + + +def make_custom_fields(): + custom_fields = { + 'Party Account': [ + dict(fieldname='debtor_creditor_number', label='Debtor/Creditor Number', + fieldtype='Data', insert_after='account') + ] + } + + create_custom_fields(custom_fields) From b39608a02e389426d7da405a9d5467ec05514834 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Wed, 7 Apr 2021 20:04:38 +0200 Subject: [PATCH 025/326] fix: handle encoding errors replace unknown characters by '?' --- erpnext/regional/germany/utils/datev/datev_csv.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/regional/germany/utils/datev/datev_csv.py b/erpnext/regional/germany/utils/datev/datev_csv.py index f138a807bc..826d51f712 100644 --- a/erpnext/regional/germany/utils/datev/datev_csv.py +++ b/erpnext/regional/germany/utils/datev/datev_csv.py @@ -56,10 +56,10 @@ def get_datev_csv(data, filters, csv_class): ) if not six.PY2: - data = data.encode('latin_1') + data = data.encode('latin_1', errors='replace') header = get_header(filters, csv_class) - header = ';'.join(header).encode('latin_1') + header = ';'.join(header).encode('latin_1', errors='replace') # 1st Row: Header with meta data # 2nd Row: Data heading (Überschrift der Nutzdaten), included in `data` here. From 4fe2d35b2e4ee548728f2592fb1cb322a2df2506 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Wed, 7 Apr 2021 20:05:41 +0200 Subject: [PATCH 026/326] feat: more infos for transactions --- erpnext/regional/report/datev/datev.py | 45 +++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/erpnext/regional/report/datev/datev.py b/erpnext/regional/report/datev/datev.py index cbc9478987..e1b0c54b2b 100644 --- a/erpnext/regional/report/datev/datev.py +++ b/erpnext/regional/report/datev/datev.py @@ -88,6 +88,32 @@ COLUMNS = [ "fieldtype": "Dynamic Link", "options": "Beleginfo - Art 2", "width": 150 + }, + { + "label": "Beleginfo - Art 3", + "fieldname": "Beleginfo - Art 3", + "fieldtype": "Link", + "options": "DocType", + "width": 100 + }, + { + "label": "Beleginfo - Inhalt 3", + "fieldname": "Beleginfo - Inhalt 3", + "fieldtype": "Dynamic Link", + "options": "Beleginfo - Art 3", + "width": 150 + } + ,{ + "label": "Beleginfo - Art 4", + "fieldname": "Beleginfo - Art 4", + "fieldtype": "Data", + "width": 100 + }, + { + "label": "Beleginfo - Inhalt 4", + "fieldname": "Beleginfo - Inhalt 4", + "fieldtype": "Data", + "width": 150 } ] @@ -169,7 +195,11 @@ def get_transactions(filters, as_dict=1): gl.voucher_type as 'Beleginfo - Art 1', gl.voucher_no as 'Beleginfo - Inhalt 1', gl.against_voucher_type as 'Beleginfo - Art 2', - gl.against_voucher as 'Beleginfo - Inhalt 2' + gl.against_voucher as 'Beleginfo - Inhalt 2', + gl.party_type as 'Beleginfo - Art 3', + gl.party as 'Beleginfo - Inhalt 3', + case gl.party_type when 'Customer' then 'Debitorennummer' when 'Supplier' then 'Kreditorennummer' else NULL end as 'Beleginfo - Art 4', + par.debtor_creditor_number as 'Beleginfo - Inhalt 4' FROM `tabGL Entry` gl @@ -177,6 +207,19 @@ def get_transactions(filters, as_dict=1): left join `tabAccount` acc on gl.account = acc.name + left join `tabCustomer` cus + on gl.party_type = 'Customer' + and gl.party = cus.name + + left join `tabSupplier` sup + on gl.party_type = 'Supplier' + and gl.party = sup.name + + left join `tabParty Account` par + on par.parent = gl.party + and par.parenttype = gl.party_type + and par.company = %(company)s + WHERE gl.company = %(company)s AND DATE(gl.posting_date) >= %(from_date)s AND DATE(gl.posting_date) <= %(to_date)s From 94f293940c947873c5c78ca20c16c1fb2bad0e3e Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Wed, 7 Apr 2021 20:06:16 +0200 Subject: [PATCH 027/326] fix: better party export --- erpnext/regional/report/datev/datev.py | 106 +++++++++++++++---------- 1 file changed, 63 insertions(+), 43 deletions(-) diff --git a/erpnext/regional/report/datev/datev.py b/erpnext/regional/report/datev/datev.py index e1b0c54b2b..42e4f96d1f 100644 --- a/erpnext/regional/report/datev/datev.py +++ b/erpnext/regional/report/datev/datev.py @@ -239,40 +239,56 @@ def get_customers(filters): return frappe.db.sql(""" SELECT - acc.account_number as 'Konto', - CASE cus.customer_type WHEN 'Company' THEN cus.customer_name ELSE null END as 'Name (Adressatentyp Unternehmen)', - CASE cus.customer_type WHEN 'Individual' THEN con.last_name ELSE null END as 'Name (Adressatentyp natürl. Person)', - CASE cus.customer_type WHEN 'Individual' THEN con.first_name ELSE null END as 'Vorname (Adressatentyp natürl. Person)', - CASE cus.customer_type WHEN 'Individual' THEN '1' WHEN 'Company' THEN '2' ELSE '0' end as 'Adressatentyp', + par.debtor_creditor_number as 'Konto', + CASE cus.customer_type + WHEN 'Company' THEN cus.customer_name + ELSE null + END as 'Name (Adressatentyp Unternehmen)', + CASE cus.customer_type + WHEN 'Individual' THEN TRIM(SUBSTR(cus.customer_name, LOCATE(' ', cus.customer_name))) + ELSE null + END as 'Name (Adressatentyp natürl. Person)', + CASE cus.customer_type + WHEN 'Individual' THEN SUBSTRING_INDEX(SUBSTRING_INDEX(cus.customer_name, ' ', 1), ' ', -1) + ELSE null + END as 'Vorname (Adressatentyp natürl. Person)', + CASE cus.customer_type + WHEN 'Individual' THEN '1' + WHEN 'Company' THEN '2' + ELSE '0' + END as 'Adressatentyp', adr.address_line1 as 'Straße', adr.pincode as 'Postleitzahl', adr.city as 'Ort', UPPER(country.code) as 'Land', adr.address_line2 as 'Adresszusatz', - con.email_id as 'E-Mail', - coalesce(con.mobile_no, con.phone) as 'Telefon', + adr.email_id as 'E-Mail', + adr.phone as 'Telefon', + adr.fax as 'Fax', cus.website as 'Internet', cus.tax_id as 'Steuernummer' - FROM `tabParty Account` par + FROM `tabCustomer` cus - left join `tabAccount` acc - on acc.name = par.account + left join `tabParty Account` par + on par.parent = cus.name + and par.parenttype = 'Customer' + and par.company = %(company)s - left join `tabCustomer` cus - on cus.name = par.parent + left join `tabDynamic Link` dyn_adr + on dyn_adr.link_name = cus.name + and dyn_adr.link_doctype = 'Customer' + and dyn_adr.parenttype = 'Address' left join `tabAddress` adr - on adr.name = cus.customer_primary_address + on adr.name = dyn_adr.parent + and adr.is_primary_address = '1' left join `tabCountry` country on country.name = adr.country - left join `tabContact` con - on con.name = cus.customer_primary_contact - - WHERE par.company = %(company)s - AND par.parenttype = 'Customer'""", filters, as_dict=1) + WHERE adr.is_primary_address = '1' + """, filters, as_dict=1) def get_suppliers(filters): @@ -285,35 +301,48 @@ def get_suppliers(filters): return frappe.db.sql(""" SELECT - acc.account_number as 'Konto', - CASE sup.supplier_type WHEN 'Company' THEN sup.supplier_name ELSE null END as 'Name (Adressatentyp Unternehmen)', - CASE sup.supplier_type WHEN 'Individual' THEN con.last_name ELSE null END as 'Name (Adressatentyp natürl. Person)', - CASE sup.supplier_type WHEN 'Individual' THEN con.first_name ELSE null END as 'Vorname (Adressatentyp natürl. Person)', - CASE sup.supplier_type WHEN 'Individual' THEN '1' WHEN 'Company' THEN '2' ELSE '0' end as 'Adressatentyp', + par.debtor_creditor_number as 'Konto', + CASE sup.supplier_type + WHEN 'Company' THEN sup.supplier_name + ELSE null + END as 'Name (Adressatentyp Unternehmen)', + CASE sup.supplier_type + WHEN 'Individual' THEN TRIM(SUBSTR(sup.supplier_name, LOCATE(' ', sup.supplier_name))) + ELSE null + END as 'Name (Adressatentyp natürl. Person)', + CASE sup.supplier_type + WHEN 'Individual' THEN SUBSTRING_INDEX(SUBSTRING_INDEX(sup.supplier_name, ' ', 1), ' ', -1) + ELSE null + END as 'Vorname (Adressatentyp natürl. Person)', + CASE sup.supplier_type + WHEN 'Individual' THEN '1' + WHEN 'Company' THEN '2' + ELSE '0' + END as 'Adressatentyp', adr.address_line1 as 'Straße', adr.pincode as 'Postleitzahl', adr.city as 'Ort', UPPER(country.code) as 'Land', adr.address_line2 as 'Adresszusatz', - con.email_id as 'E-Mail', - coalesce(con.mobile_no, con.phone) as 'Telefon', + adr.email_id as 'E-Mail', + adr.phone as 'Telefon', + adr.fax as 'Fax', sup.website as 'Internet', sup.tax_id as 'Steuernummer', case sup.on_hold when 1 then sup.release_date else null end as 'Zahlungssperre bis' - FROM `tabParty Account` par + FROM `tabSupplier` sup - left join `tabAccount` acc - on acc.name = par.account - - left join `tabSupplier` sup - on sup.name = par.parent + left join `tabParty Account` par + on par.parent = sup.name + and par.parenttype = 'Supplier' + and par.company = %(company)s left join `tabDynamic Link` dyn_adr on dyn_adr.link_name = sup.name and dyn_adr.link_doctype = 'Supplier' and dyn_adr.parenttype = 'Address' - + left join `tabAddress` adr on adr.name = dyn_adr.parent and adr.is_primary_address = '1' @@ -321,17 +350,8 @@ def get_suppliers(filters): left join `tabCountry` country on country.name = adr.country - left join `tabDynamic Link` dyn_con - on dyn_con.link_name = sup.name - and dyn_con.link_doctype = 'Supplier' - and dyn_con.parenttype = 'Contact' - - left join `tabContact` con - on con.name = dyn_con.parent - and con.is_primary_contact = '1' - - WHERE par.company = %(company)s - AND par.parenttype = 'Supplier'""", filters, as_dict=1) + WHERE adr.is_primary_address = '1' + """, filters, as_dict=1) def get_account_names(filters): From 368a6541e9471f53b88e00d585820f7a1a7ba1dd Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 8 Apr 2021 16:26:56 +0200 Subject: [PATCH 028/326] fix: Debtor/Creditor Number is not translatable --- erpnext/regional/germany/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/germany/setup.py b/erpnext/regional/germany/setup.py index 6258c05459..83e4f97519 100644 --- a/erpnext/regional/germany/setup.py +++ b/erpnext/regional/germany/setup.py @@ -11,7 +11,7 @@ def make_custom_fields(): custom_fields = { 'Party Account': [ dict(fieldname='debtor_creditor_number', label='Debtor/Creditor Number', - fieldtype='Data', insert_after='account') + fieldtype='Data', insert_after='account', translatable=0) ] } From 03425071e7392673a9dfd22a4fb83294a2b19c0c Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 8 Apr 2021 16:31:00 +0200 Subject: [PATCH 029/326] fix: patch to add custom fields --- erpnext/patches.txt | 1 + .../patches/v13_0/germany_make_custom_fields.py | 14 ++++++++++++++ 2 files changed, 15 insertions(+) create mode 100644 erpnext/patches/v13_0/germany_make_custom_fields.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 16863142bc..df004abc48 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -764,3 +764,4 @@ erpnext.patches.v13_0.setup_uae_vat_fields execute:frappe.db.set_value('System Settings', None, 'app_name', 'ERPNext') erpnext.patches.v13_0.rename_discharge_date_in_ip_record erpnext.patches.v12_0.purchase_receipt_status +erpnext.patches.v13_0.germany_make_custom_fields diff --git a/erpnext/patches/v13_0/germany_make_custom_fields.py b/erpnext/patches/v13_0/germany_make_custom_fields.py new file mode 100644 index 0000000000..cf1fe219dc --- /dev/null +++ b/erpnext/patches/v13_0/germany_make_custom_fields.py @@ -0,0 +1,14 @@ +# Copyright (c) 2019, Frappe and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals + +import frappe +from erpnext.regional.germany.setup import make_custom_fields + +def execute(): + company_list = frappe.get_all('Company', filters = {'country': 'Germany'}) + if not company_list: + return + + make_custom_fields() From ed36fb2073de13459e09993ace93116322858a92 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 8 Apr 2021 16:57:20 +0200 Subject: [PATCH 030/326] docs: doctring for patch --- erpnext/patches/v13_0/germany_make_custom_fields.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/erpnext/patches/v13_0/germany_make_custom_fields.py b/erpnext/patches/v13_0/germany_make_custom_fields.py index cf1fe219dc..41ab945eb1 100644 --- a/erpnext/patches/v13_0/germany_make_custom_fields.py +++ b/erpnext/patches/v13_0/germany_make_custom_fields.py @@ -6,7 +6,13 @@ from __future__ import unicode_literals import frappe from erpnext.regional.germany.setup import make_custom_fields + def execute(): + """Execute the make_custom_fields method for german companies. + + It is usually run once at setup of a new company. Since it's new, run it + once for existing companies as well. + """ company_list = frappe.get_all('Company', filters = {'country': 'Germany'}) if not company_list: return From c6e13ac218e3a95ccb3df25dfe7ebcd40c2ffdb2 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 8 Apr 2021 16:57:55 +0200 Subject: [PATCH 031/326] fix: patch to fill Debtor/Creditor Number --- erpnext/patches.txt | 1 + .../germany_fill_debtor_creditor_number.py | 31 +++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 erpnext/patches/v13_0/germany_fill_debtor_creditor_number.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index df004abc48..a03bd00735 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -765,3 +765,4 @@ execute:frappe.db.set_value('System Settings', None, 'app_name', 'ERPNext') erpnext.patches.v13_0.rename_discharge_date_in_ip_record erpnext.patches.v12_0.purchase_receipt_status erpnext.patches.v13_0.germany_make_custom_fields +erpnext.patches.v13_0.germany_fill_debtor_creditor_number diff --git a/erpnext/patches/v13_0/germany_fill_debtor_creditor_number.py b/erpnext/patches/v13_0/germany_fill_debtor_creditor_number.py new file mode 100644 index 0000000000..8482730ec8 --- /dev/null +++ b/erpnext/patches/v13_0/germany_fill_debtor_creditor_number.py @@ -0,0 +1,31 @@ +# Copyright (c) 2019, Frappe and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals + +import frappe +from erpnext.regional.germany.setup import make_custom_fields + +def execute(): + """Move account number into the new custom field debtor_creditor_number. + + German companies used to use a dedicated payable/receivable account for + every party to mimick party accounts in the external accounting software + "DATEV". This is no longer necessary. The reference ID for DATEV will be + stored in a new custom field "debtor_creditor_number". + """ + company_list = frappe.get_all('Company', filters={'country': 'Germany'}) + + for company in company_list: + party_account_list = frappe.get_all('Party Account', filters={'company': company.name}, fields=['name', 'account', 'debtor_creditor_number']) + for party_account in party_account_list: + if (not party_account.account) or party_account.debtor_creditor_number: + # account empty or debtor_creditor_number already filled + continue + + account_number = frappe.db.get_value('Account', party_account.account, 'account_number') + if not account_number: + continue + + frappe.db.set_value('Party Account', party_account.name, 'debtor_creditor_number', account_number) + frappe.db.set_value('Party Account', party_account.name, 'account', '') From 3a12f1f1ae0a76321eda86c32c16ba3a224d177c Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 8 Apr 2021 17:53:48 +0200 Subject: [PATCH 032/326] fix: prettier error log --- erpnext/regional/report/datev/datev.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/erpnext/regional/report/datev/datev.py b/erpnext/regional/report/datev/datev.py index 42e4f96d1f..c81568bfa6 100644 --- a/erpnext/regional/report/datev/datev.py +++ b/erpnext/regional/report/datev/datev.py @@ -146,10 +146,8 @@ def validate(filters): validate_fiscal_year(from_date, to_date, company) if not frappe.db.exists('DATEV Settings', filters.get('company')): - frappe.log_error(_('Please create {} for Company {}.').format( - '{}'.format(_('DATEV Settings')), - frappe.bold(filters.get('company')) - )) + msg = 'Please create DATEV Settings for Company {}'.format(filters.get('company')) + frappe.log_error(msg, title='DATEV Settings missing') return False return True From 0a45fc8c58927c3f0810d0124b4e3bdf580234a9 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 8 Apr 2021 18:25:17 +0200 Subject: [PATCH 033/326] fix: remove unused import --- erpnext/patches/v13_0/germany_fill_debtor_creditor_number.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/patches/v13_0/germany_fill_debtor_creditor_number.py b/erpnext/patches/v13_0/germany_fill_debtor_creditor_number.py index 8482730ec8..11e1e9b3b9 100644 --- a/erpnext/patches/v13_0/germany_fill_debtor_creditor_number.py +++ b/erpnext/patches/v13_0/germany_fill_debtor_creditor_number.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals import frappe -from erpnext.regional.germany.setup import make_custom_fields + def execute(): """Move account number into the new custom field debtor_creditor_number. From e66cf0aa444d33eaebf72314e4a77590793100f7 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 8 Apr 2021 18:26:45 +0200 Subject: [PATCH 034/326] fix: hanging indent --- erpnext/regional/report/datev/datev.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/regional/report/datev/datev.py b/erpnext/regional/report/datev/datev.py index c81568bfa6..1a215031c1 100644 --- a/erpnext/regional/report/datev/datev.py +++ b/erpnext/regional/report/datev/datev.py @@ -102,8 +102,8 @@ COLUMNS = [ "fieldtype": "Dynamic Link", "options": "Beleginfo - Art 3", "width": 150 - } - ,{ + }, + { "label": "Beleginfo - Art 4", "fieldname": "Beleginfo - Art 4", "fieldtype": "Data", From 31b5dfe9eed3de15effd1998f0c004896652af39 Mon Sep 17 00:00:00 2001 From: Afshan Date: Fri, 9 Apr 2021 16:52:14 +0530 Subject: [PATCH 035/326] feat: Dimension-wise Accounts Balance Report --- .../__init__.py | 0 .../dimension_wise_accounts_balance_report.js | 81 +++++++ ...imension_wise_accounts_balance_report.json | 22 ++ .../dimension_wise_accounts_balance_report.py | 205 ++++++++++++++++++ 4 files changed, 308 insertions(+) create mode 100644 erpnext/accounts/report/dimension_wise_accounts_balance_report/__init__.py create mode 100644 erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.js create mode 100644 erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.json create mode 100644 erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.py diff --git a/erpnext/accounts/report/dimension_wise_accounts_balance_report/__init__.py b/erpnext/accounts/report/dimension_wise_accounts_balance_report/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.js b/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.js new file mode 100644 index 0000000000..6a0394861b --- /dev/null +++ b/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.js @@ -0,0 +1,81 @@ +// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt +/* eslint-disable */ + +frappe.require("assets/erpnext/js/financial_statements.js", function() { + frappe.query_reports["Dimension-wise Accounts Balance Report"] = { + "filters": [ + { + "fieldname": "company", + "label": __("Company"), + "fieldtype": "Link", + "options": "Company", + "default": frappe.defaults.get_user_default("Company"), + "reqd": 1 + }, + { + "fieldname": "fiscal_year", + "label": __("Fiscal Year"), + "fieldtype": "Link", + "options": "Fiscal Year", + "default": frappe.defaults.get_user_default("fiscal_year"), + "reqd": 1, + "on_change": function(query_report) { + var fiscal_year = query_report.get_values().fiscal_year; + if (!fiscal_year) { + return; + } + frappe.model.with_doc("Fiscal Year", fiscal_year, function(r) { + var fy = frappe.model.get_doc("Fiscal Year", fiscal_year); + frappe.query_report.set_filter_value({ + from_date: fy.year_start_date, + to_date: fy.year_end_date + }); + }); + } + }, + { + "fieldname": "from_date", + "label": __("From Date"), + "fieldtype": "Date", + "default": frappe.defaults.get_user_default("year_start_date"), + }, + { + "fieldname": "to_date", + "label": __("To Date"), + "fieldtype": "Date", + "default": frappe.defaults.get_user_default("year_end_date"), + }, + { + "fieldname": "finance_book", + "label": __("Finance Book"), + "fieldtype": "Link", + "options": "Finance Book", + }, + { + "fieldname": "dimension", + "label": __("Select Dimension"), + "fieldtype": "Select", + "options": get_accounting_dimension_options(), + "reqd": 1, + }, + ], + "formatter": erpnext.financial_statements.formatter, + "tree": true, + "name_field": "account", + "parent_field": "parent_account", + "initial_depth": 3 + } + +}); + +function get_accounting_dimension_options() { + let options =["", "Cost Center", "Project"]; + frappe.db.get_list('Accounting Dimension', + {fields:['document_type']}).then((res) => { + res.forEach((dimension) => { + options.push(dimension.document_type); + }); + }); + return options +} diff --git a/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.json b/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.json new file mode 100644 index 0000000000..6141944f9d --- /dev/null +++ b/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.json @@ -0,0 +1,22 @@ +{ + "add_total_row": 0, + "columns": [], + "creation": "2021-04-09 16:48:59.548018", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 0, + "is_standard": "Yes", + "modified": "2021-04-09 16:48:59.548018", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Dimension-wise Accounts Balance Report", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "GL Entry", + "report_name": "Dimension-wise Accounts Balance Report", + "report_type": "Script Report", + "roles": [] +} \ No newline at end of file diff --git a/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.py b/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.py new file mode 100644 index 0000000000..9769a458dd --- /dev/null +++ b/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.py @@ -0,0 +1,205 @@ +# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe, erpnext +from frappe import _ +from frappe.utils import (flt, getdate, get_first_day, add_months, add_days, formatdate, cstr, cint) + +from erpnext.accounts.report.financial_statements import get_accounts, filter_accounts, get_appropriate_currency, get_fiscal_year_data, validate_fiscal_year, filter_out_zero_value_rows +from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions, get_dimension_with_children +from erpnext.accounts.report.trial_balance.trial_balance import validate_filters, get_opening_balances, calculate_values + +from six import itervalues + +def execute(filters=None): + validate_filters(filters) + dimension_items_list = get_dimension_items_list(filters.dimension, filters.company) + + if not dimension_items_list: + return [], [] + + dimension_items_list = [''.join(d) for d in dimension_items_list] + columns = get_columns(dimension_items_list) + data = get_data(filters, dimension_items_list) + + return columns, data + +def get_data(filters, dimension_items_list): + company_currency = erpnext.get_company_currency(filters.company) + acc = frappe.db.sql(""" + select + name, account_number, parent_account, lft, rgt, root_type, + report_type, account_name, include_in_gross, account_type, is_group + from + `tabAccount` + where + company=%s + order by lft""", (filters.company), as_dict=True) + + if not acc: + return None + + accounts, accounts_by_name, parent_children_map = filter_accounts(acc) + + min_lft, max_rgt = frappe.db.sql("""select min(lft), max(rgt) from `tabAccount` + where company=%s""", (filters.company))[0] + + account = frappe.db.sql_list("""select name from `tabAccount` + where lft >= %s and rgt <= %s and company = %s""", (min_lft, max_rgt, filters.company)) + + gl_entries_by_account = {} + set_gl_entries_by_account(dimension_items_list, filters, account, gl_entries_by_account) + format_gl_entries(gl_entries_by_account, accounts_by_name, dimension_items_list) + accumulate_values_into_parents(accounts, accounts_by_name, dimension_items_list) + out = prepare_data(accounts, filters, parent_children_map, company_currency, dimension_items_list) + out = filter_out_zero_value_rows(out, parent_children_map) + + return out + +def set_gl_entries_by_account(dimension_items_list, filters, account, gl_entries_by_account): + for item in dimension_items_list: + condition = get_condition(filters.from_date, item, filters.dimension) + if account: + condition += " and account in ({})"\ + .format(", ".join([frappe.db.escape(d) for d in account])) + + gl_filters = { + "company": filters.get("company"), + "from_date": filters.get("from_date"), + "to_date": filters.get("to_date"), + "finance_book": cstr(filters.get("finance_book")) + } + + gl_filters['item'] = ''.join(item) + + if filters.get("include_default_book_entries"): + gl_filters["company_fb"] = frappe.db.get_value("Company", + filters.company, 'default_finance_book') + + for key, value in filters.items(): + if value: + gl_filters.update({ + key: value + }) + + gl_entries = frappe.db.sql(""" + select + posting_date, account, debit, credit, is_opening, fiscal_year, + debit_in_account_currency, credit_in_account_currency, account_currency + from + `tabGL Entry` + where + company=%(company)s + {condition} + and posting_date <= %(to_date)s + and is_cancelled = 0 + order by account, posting_date""".format( + condition=condition), + gl_filters, as_dict=True) #nosec + + for entry in gl_entries: + entry['dimension_item'] = ''.join(item) + gl_entries_by_account.setdefault(entry.account, []).append(entry) + +def format_gl_entries(gl_entries_by_account, accounts_by_name, dimension_items_list): + + for entries in itervalues(gl_entries_by_account): + for entry in entries: + d = accounts_by_name.get(entry.account) + if not d: + frappe.msgprint( + _("Could not retrieve information for {0}.").format(entry.account), title="Error", + raise_exception=1 + ) + for item in dimension_items_list: + if item == entry.dimension_item: + x = flt(entry.debit) - flt(entry.credit) + d[frappe.scrub(item)] = d.get(frappe.scrub(item), 0.0) + flt(entry.debit) - flt(entry.credit) + +def prepare_data(accounts, filters, parent_children_map, company_currency, dimension_items_list): + data = [] + + for d in accounts: + has_value = False + row = { + "account": d.name, + "parent_account": d.parent_account, + "indent": d.indent, + "from_date": filters.from_date, + "to_date": filters.to_date, + "currency": company_currency, + "account_name": ('{} - {}'.format(d.account_number, d.account_name) + if d.account_number else d.account_name) + } + + for item in dimension_items_list: + row[frappe.scrub(item)] = flt(d.get(frappe.scrub(item), 0.0), 3) + + if abs(row[frappe.scrub(item)]) >= 0.005: + # ignore zero values + has_value = True + + row["has_value"] = has_value + data.append(row) + + return data + +def accumulate_values_into_parents(accounts, accounts_by_name, dimension_items_list): + """accumulate children's values in parent accounts""" + for d in reversed(accounts): + if d.parent_account: + for item in dimension_items_list: + accounts_by_name[d.parent_account][frappe.scrub(item)] = \ + accounts_by_name[d.parent_account].get(frappe.scrub(item), 0.0) + d.get(frappe.scrub(item), 0.0) + +def get_condition(from_date, item, dimension): + conditions = [] + + if from_date: + conditions.append("posting_date >= %(from_date)s") + if dimension: + if dimension not in ['Cost Center', 'Project']: + if dimension in ['Customer', 'Supplier']: + dimension = 'Party' + else: + dimension = 'Voucher No' + txt = "{0} = %(item)s".format(frappe.scrub(dimension)) + conditions.append(txt) + + return " and {}".format(" and ".join(conditions)) if conditions else "" + +def get_dimension_items_list(dimension, company): + meta = frappe.get_meta(dimension, cached=False) + fieldnames = [d.fieldname for d in meta.get("fields")] + filters = {} + if 'company' in fieldnames: + filters['company'] = company + return frappe.get_all(dimension, filters, as_list=True) + +def get_columns(dimension_items_list, accumulated_values=1, company=None): + columns = [{ + "fieldname": "account", + "label": _("Account"), + "fieldtype": "Link", + "options": "Account", + "width": 300 + }] + if company: + columns.append({ + "fieldname": "currency", + "label": _("Currency"), + "fieldtype": "Link", + "options": "Currency", + "hidden": 1 + }) + for item in dimension_items_list: + columns.append({ + "fieldname": frappe.scrub(item), + "label": item, + "fieldtype": "Currency", + "options": "currency", + "width": 150 + }) + + return columns From c42318ec24fdecda9d5cdc0a8ca8f63e9be83a30 Mon Sep 17 00:00:00 2001 From: Afshan Date: Fri, 9 Apr 2021 17:44:30 +0530 Subject: [PATCH 036/326] chores: clean up --- .../dimension_wise_accounts_balance_report.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.py b/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.py index 9769a458dd..7f38770a32 100644 --- a/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.py +++ b/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.py @@ -4,11 +4,10 @@ from __future__ import unicode_literals import frappe, erpnext from frappe import _ -from frappe.utils import (flt, getdate, get_first_day, add_months, add_days, formatdate, cstr, cint) +from frappe.utils import (flt, cstr) -from erpnext.accounts.report.financial_statements import get_accounts, filter_accounts, get_appropriate_currency, get_fiscal_year_data, validate_fiscal_year, filter_out_zero_value_rows -from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions, get_dimension_with_children -from erpnext.accounts.report.trial_balance.trial_balance import validate_filters, get_opening_balances, calculate_values +from erpnext.accounts.report.financial_statements import filter_accounts, filter_out_zero_value_rows +from erpnext.accounts.report.trial_balance.trial_balance import validate_filters from six import itervalues @@ -114,7 +113,6 @@ def format_gl_entries(gl_entries_by_account, accounts_by_name, dimension_items_l ) for item in dimension_items_list: if item == entry.dimension_item: - x = flt(entry.debit) - flt(entry.credit) d[frappe.scrub(item)] = d.get(frappe.scrub(item), 0.0) + flt(entry.debit) - flt(entry.credit) def prepare_data(accounts, filters, parent_children_map, company_currency, dimension_items_list): From cb6494876fe780c1900174a9f11957bbe8c88a8d Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Sat, 27 Mar 2021 22:21:41 +0530 Subject: [PATCH 037/326] feat: purchase receipt creation from purchase invoice --- .../purchase_invoice/purchase_invoice.js | 30 +++++ .../purchase_invoice/purchase_invoice.json | 12 +- .../purchase_invoice/purchase_invoice.py | 38 +++++++ .../purchase_invoice_item.json | 3 +- .../billed_items_to_be_received/__init__.py | 0 .../billed_items_to_be_received.js | 29 +++++ .../billed_items_to_be_received.json | 39 +++++++ .../billed_items_to_be_received.py | 107 ++++++++++++++++++ .../purchase_receipt/purchase_receipt.js | 28 +++++ .../purchase_receipt/purchase_receipt.py | 17 ++- .../purchase_receipt_item.json | 24 +++- 11 files changed, 320 insertions(+), 7 deletions(-) create mode 100644 erpnext/accounts/report/billed_items_to_be_received/__init__.py create mode 100644 erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.js create mode 100644 erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.json create mode 100644 erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.py diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index 66a8e206a8..73475a1131 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -523,6 +523,28 @@ frappe.ui.form.on("Purchase Invoice", { } }, + refresh: function(frm) { + frm.events.add_custom_buttons(frm); + }, + + add_custom_buttons: function(frm) { + if (frm.doc.per_received < 100) { + frm.add_custom_button(__('Purchase Receipt'), () => { + frm.events.make_purchase_receipt(frm); + }, __('Create')); + } + + if (frm.doc.docstatus == 1 && frm.doc.per_received > 0) { + frm.add_custom_button(__('Purchase Receipt'), () => { + frappe.route_options = { + 'purchase_invoice': frm.doc.name + } + + frappe.set_route("List", "Purchase Receipt", "List") + }, __('View')); + } + }, + onload: function(frm) { if(frm.doc.__onload && frm.is_new()) { if(frm.doc.supplier) { @@ -548,5 +570,13 @@ frappe.ui.form.on("Purchase Invoice", { update_stock: function(frm) { hide_fields(frm.doc); frm.fields_dict.items.grid.toggle_reqd("item_code", frm.doc.update_stock? true: false); + }, + + make_purchase_receipt: function(frm) { + frappe.model.open_mapped_doc({ + method: "erpnext.accounts.doctype.purchase_invoice.purchase_invoice.make_purchase_receipt", + frm: frm, + freeze_message: __("Creating Purchase Receipt ...") + }) } }) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index 739bd671f7..5c37eda8d4 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -163,7 +163,8 @@ "to_date", "column_break_114", "auto_repeat", - "update_auto_repeat_reference" + "update_auto_repeat_reference", + "per_received" ], "fields": [ { @@ -1364,6 +1365,15 @@ "print_hide": 1, "print_width": "50px", "width": "50px" + }, + { + "fieldname": "per_received", + "fieldtype": "Percent", + "hidden": 1, + "label": "Per Received", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 } ], "icon": "fa fa-file-text", diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 5c4e32e493..7d008507a2 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -1207,3 +1207,41 @@ def make_inter_company_sales_invoice(source_name, target_doc=None): def on_doctype_update(): frappe.db.add_index("Purchase Invoice", ["supplier", "is_return", "return_against"]) + +@frappe.whitelist() +def make_purchase_receipt(source_name, target_doc=None): + def update_item(obj, target, source_parent): + target.qty = flt(obj.qty) - flt(obj.received_qty) + target.received_qty = flt(obj.qty) - flt(obj.received_qty) + target.stock_qty = (flt(obj.qty) - flt(obj.received_qty)) * flt(obj.conversion_factor) + target.amount = (flt(obj.qty) - flt(obj.received_qty)) * flt(obj.rate) + target.base_amount = (flt(obj.qty) - flt(obj.received_qty)) * \ + flt(obj.rate) * flt(source_parent.conversion_rate) + + doc = get_mapped_doc("Purchase Invoice", source_name, { + "Purchase Invoice": { + "doctype": "Purchase Receipt", + "validation": { + "docstatus": ["=", 1], + } + }, + "Purchase Invoice Item": { + "doctype": "Purchase Receipt Item", + "field_map": { + "name": "purchase_invoice_item", + "parent": "purchase_invoice", + "bom": "bom", + "purchase_order": "purchase_order", + "po_detail": "purchase_order_item", + "material_request": "material_request", + "material_request_item": "material_request_item" + }, + "postprocess": update_item, + "condition": lambda doc: abs(doc.received_qty) < abs(doc.qty) + }, + "Purchase Taxes and Charges": { + "doctype": "Purchase Taxes and Charges" + } + }, target_doc) + + return doc diff --git a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json index 96ad0fd785..10e1c73ea9 100644 --- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json +++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json @@ -607,6 +607,7 @@ "oldfieldname": "purchase_order", "oldfieldtype": "Link", "options": "Purchase Order", + "print_hide": 1, "read_only": 1, "search_index": 1 }, @@ -853,7 +854,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2021-02-23 00:59:52.614805", + "modified": "2021-03-30 09:02:39.256602", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice Item", diff --git a/erpnext/accounts/report/billed_items_to_be_received/__init__.py b/erpnext/accounts/report/billed_items_to_be_received/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.js b/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.js new file mode 100644 index 0000000000..e1fccb6e72 --- /dev/null +++ b/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.js @@ -0,0 +1,29 @@ +// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports['Billed Items To Be Received'] = { + 'filters': [ + { + 'label': __('Company'), + 'fieldname': 'company', + 'fieldtype': 'Link', + 'options': 'Company', + 'reqd': 1, + 'default': frappe.defaults.get_default('Company') + }, + { + 'label': __('As on Date'), + 'fieldname': 'posting_date', + 'fieldtype': 'Date', + 'reqd': 1, + 'default': get_today() + }, + { + 'label': __('Purchase Invoice'), + 'fieldname': 'purchase_invoice', + 'fieldtype': 'Link', + 'options': 'Purchase Invoice' + } + ] +}; diff --git a/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.json b/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.json new file mode 100644 index 0000000000..de09b33c96 --- /dev/null +++ b/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.json @@ -0,0 +1,39 @@ +{ + "add_total_row": 0, + "columns": [], + "creation": "2021-03-30 09:35:38.683028", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 0, + "is_standard": "Yes", + "modified": "2021-03-31 08:48:30.944429", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Billed Items To Be Received", + "owner": "Administrator", + "prepared_report": 0, + "query": "", + "ref_doctype": "Purchase Invoice", + "report_name": "Billed Items To Be Received", + "report_type": "Script Report", + "roles": [ + { + "role": "Accounts User" + }, + { + "role": "Purchase User" + }, + { + "role": "Accounts Manager" + }, + { + "role": "Auditor" + }, + { + "role": "Stock User" + } + ] +} \ No newline at end of file diff --git a/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.py b/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.py new file mode 100644 index 0000000000..2ce5d50edf --- /dev/null +++ b/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.py @@ -0,0 +1,107 @@ +# 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 import _ + +def execute(filters=None): + data = get_data(filters) or [] + columns = get_columns() + + return columns, data + +def get_data(report_filters): + filters = get_report_filters(report_filters) + fields = get_report_fields() + + return frappe.get_all('Purchase Invoice', + fields= fields, filters=filters) + +def get_report_filters(report_filters): + filters = [['Purchase Invoice','company','=',report_filters.get('company')], + ['Purchase Invoice','posting_date','<=',report_filters.get('posting_date')], ['Purchase Invoice','docstatus','=',1], + ['Purchase Invoice','per_received','<',100], ['Purchase Invoice','update_stock','=',0]] + + if report_filters.get('purchase_invoice'): + filters.append(['Purchase Invoice','per_received','in',[report_filters.get('purchase_invoice')]]) + + return filters + +def get_report_fields(): + fields = [] + for p_field in ['name', 'supplier', 'company', 'posting_date', 'currency']: + fields.append('`tabPurchase Invoice`.`{}`'.format(p_field)) + + for c_field in ['item_code', 'item_name', 'uom', 'qty', 'received_qty', 'rate', 'amount']: + fields.append('`tabPurchase Invoice Item`.`{}`'.format(c_field)) + + return fields + +def get_columns(): + return [ + { + 'label': _('Purchase Invoice'), + 'fieldname': 'name', + 'fieldtype': 'Link', + 'options': 'Purchase Invoice', + 'width': 170 + }, + { + 'label': _('Supplier'), + 'fieldname': 'supplier', + 'fieldtype': 'Link', + 'options': 'Supplier', + 'width': 120 + }, + { + 'label': _('Posting Date'), + 'fieldname': 'posting_date', + 'fieldtype': 'Date', + 'width': 100 + }, + { + 'label': _('Item Code'), + 'fieldname': 'item_code', + 'fieldtype': 'Link', + 'options': 'Item', + 'width': 100 + }, + { + 'label': _('Item Name'), + 'fieldname': 'item_name', + 'fieldtype': 'Data', + 'width': 100 + }, + { + 'label': _('UOM'), + 'fieldname': 'uom', + 'fieldtype': 'Link', + 'options': 'UOM', + 'width': 100 + }, + { + 'label': _('Invoiced Qty'), + 'fieldname': 'qty', + 'fieldtype': 'Float', + 'width': 100 + }, + { + 'label': _('Received Qty'), + 'fieldname': 'received_qty', + 'fieldtype': 'Float', + 'width': 100 + }, + { + 'label': _('Rate'), + 'fieldname': 'rate', + 'fieldtype': 'Currency', + 'width': 100 + }, + { + 'label': _('Amount'), + 'fieldname': 'amount', + 'fieldtype': 'Currency', + 'width': 100 + } + ] \ No newline at end of file diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js index 57cc3504a9..f9abea6453 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js @@ -73,6 +73,34 @@ frappe.ui.form.on("Purchase Receipt", { }) }, __('Create')); } + + frm.events.add_custom_buttons(frm); + }, + + add_custom_buttons: function(frm) { + if (frm.doc.docstatus == 0) { + frm.add_custom_button(__('Purchase Invoice'), function () { + if (!frm.doc.supplier) { + frappe.throw({ + title: __("Mandatory"), + message: __("Please Select a Supplier") + }); + } + erpnext.utils.map_current_doc({ + method: "erpnext.accounts.doctype.purchase_invoice.purchase_invoice.make_purchase_receipt", + source_doctype: "Purchase Invoice", + target: frm, + setters: { + supplier: frm.doc.supplier, + }, + get_query_filters: { + docstatus: 1, + per_received: ["<", 100], + company: frm.doc.company + } + }) + }, __("Get Items From")); + } }, company: function(frm) { diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 5d7597b2db..b4cf659bf3 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -53,7 +53,20 @@ class PurchaseReceipt(BuyingController): 'target_ref_field': 'stock_qty', 'source_field': 'stock_qty', 'percent_join_field': 'material_request' + }, + { + 'source_dt': 'Purchase Receipt Item', + 'target_dt': 'Purchase Invoice Item', + 'join_field': 'purchase_invoice_item', + 'target_field': 'received_qty', + 'target_parent_dt': 'Purchase Invoice', + 'target_parent_field': 'per_received', + 'target_ref_field': 'qty', + 'source_field': 'received_qty', + 'percent_join_field': 'purchase_invoice', + 'overflow_type': 'receipt' }] + if cint(self.is_return): self.status_updater.extend([ { @@ -513,7 +526,9 @@ class PurchaseReceipt(BuyingController): def update_billing_status(self, update_modified=True): updated_pr = [self.name] for d in self.get("items"): - if d.purchase_order_item: + if d.purchase_invoice and d.purchase_invoice_item: + d.db_set('billed_amt', d.amount, update_modified=update_modified) + elif d.purchase_order_item: updated_pr += update_billed_amount_based_on_po(d.purchase_order_item, update_modified) for pr in set(updated_pr): diff --git a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json index efe3642d23..82cc98e7f7 100644 --- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json +++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json @@ -72,16 +72,18 @@ "warehouse", "rejected_warehouse", "from_warehouse", - "purchase_order", "material_request", + "purchase_order", + "purchase_invoice", "column_break_40", "is_fixed_asset", "asset_location", "asset_category", "schedule_date", "quality_inspection", - "purchase_order_item", "material_request_item", + "purchase_order_item", + "purchase_invoice_item", "purchase_receipt_item", "delivery_note_item", "putaway_rule", @@ -937,7 +939,21 @@ "fieldname": "base_rate_with_margin", "fieldtype": "Currency", "label": "Rate With Margin (Company Currency)", - "options": "Company:company:default_currency", + "options": "Company:company:default_currency" + }, + { + "fieldname": "purchase_invoice", + "fieldtype": "Link", + "label": "Purchase Invoice", + "options": "Purchase Invoice", + "read_only": 1 + }, + { + "fieldname": "purchase_invoice_item", + "fieldtype": "Data", + "hidden": 1, + "label": "Purchase Invoice Item", + "no_copy": 1, "print_hide": 1, "read_only": 1 } @@ -945,7 +961,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2021-02-23 00:59:14.360847", + "modified": "2021-03-29 04:17:00.336298", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt Item", From a5d062453ecf6266d4e420e343bea88f1e54d96c Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 8 Apr 2021 22:20:10 +0530 Subject: [PATCH 038/326] feat: added test case --- .../purchase_invoice/purchase_invoice.py | 2 +- .../purchase_order/test_purchase_order.py | 29 +++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 7d008507a2..83e9f7583e 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -1218,7 +1218,7 @@ def make_purchase_receipt(source_name, target_doc=None): target.base_amount = (flt(obj.qty) - flt(obj.received_qty)) * \ flt(obj.rate) * flt(source_parent.conversion_rate) - doc = get_mapped_doc("Purchase Invoice", source_name, { + doc = get_mapped_doc("Purchase Invoice", source_name, { "Purchase Invoice": { "doctype": "Purchase Receipt", "validation": { diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py index 3c4f908ee4..42f4472f29 100644 --- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py @@ -435,6 +435,35 @@ class TestPurchaseOrder(unittest.TestCase): po.load_from_db() self.assertEqual(po.get("items")[0].received_qty, 5) + def test_purchase_order_invoice_receipt_workflow(self): + from erpnext.accounts.doctype.purchase_invoice.purchase_invoice import make_purchase_receipt + + po = create_purchase_order() + pi = make_pi_from_po(po.name) + + pi.submit() + + pr = make_purchase_receipt(pi.name) + pr.submit() + + pi.load_from_db() + + self.assertEquals(pi.per_received, 100.00) + self.assertEquals(pi.items[0].qty, pi.items[0].received_qty) + + po.load_from_db() + + self.assertEquals(po.per_received, 100.00) + self.assertEquals(po.per_billed, 100.00) + + pr.cancel() + + pi.load_from_db() + pi.cancel() + + po.load_from_db() + po.cancel() + def test_make_purchase_invoice(self): po = create_purchase_order(do_not_submit=True) From faca47831780c5cbcee303a2b2a1032c78ab653a Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Mon, 12 Apr 2021 15:40:27 +0530 Subject: [PATCH 039/326] fix: payment amount showing in foreign currency --- .../accounts/doctype/payment_schedule/payment_schedule.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/payment_schedule/payment_schedule.json b/erpnext/accounts/doctype/payment_schedule/payment_schedule.json index e362566af0..39eda331db 100644 --- a/erpnext/accounts/doctype/payment_schedule/payment_schedule.json +++ b/erpnext/accounts/doctype/payment_schedule/payment_schedule.json @@ -65,7 +65,6 @@ "fieldtype": "Currency", "in_list_view": 1, "label": "Payment Amount", - "options": "currency", "reqd": 1 }, { @@ -150,7 +149,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-02-15 21:03:12.540546", + "modified": "2021-04-12 15:39:47.363609", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Schedule", From 6a014d12c1594cfccb4c2713cba7f5936010e2cb Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 12 Apr 2021 20:21:27 +0530 Subject: [PATCH 040/326] fix(stock_ledger): round off values near to zero --- erpnext/stock/stock_ledger.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 121c51cf6a..a9e2f77490 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -603,7 +603,7 @@ class update_entries_after(object): batch = self.wh_data.stock_queue[index] if qty_to_pop >= batch[0]: # consume current batch - qty_to_pop = qty_to_pop - batch[0] + qty_to_pop = _round_off_if_near_zero(qty_to_pop - batch[0]) self.wh_data.stock_queue.pop(index) if not self.wh_data.stock_queue and qty_to_pop: # stock finished, qty still remains to be withdrawn @@ -617,8 +617,8 @@ class update_entries_after(object): batch[0] = batch[0] - qty_to_pop qty_to_pop = 0 - stock_value = sum((flt(batch[0]) * flt(batch[1]) for batch in self.wh_data.stock_queue)) - stock_qty = sum((flt(batch[0]) for batch in self.wh_data.stock_queue)) + stock_value = _round_off_if_near_zero(sum((flt(batch[0]) * flt(batch[1]) for batch in self.wh_data.stock_queue))) + stock_qty = _round_off_if_near_zero(sum((flt(batch[0]) for batch in self.wh_data.stock_queue))) if stock_qty: self.wh_data.valuation_rate = stock_value / flt(stock_qty) @@ -857,3 +857,12 @@ def get_future_sle_with_negative_qty(args): order by timestamp(posting_date, posting_time) asc limit 1 """, args, as_dict=1) + +def _round_off_if_near_zero(number: float, precision: int = 6) -> float: + """ Rounds off the number to zero only if number is close to zero for decimal + specified in precision. Precision defaults to 6. + """ + if flt(number) < (1.0 / (10**precision)): + return 0 + + return flt(number) From b54cc7c7f569171bf2e8cd77e92bad5a39274eef Mon Sep 17 00:00:00 2001 From: Saqib Date: Wed, 14 Apr 2021 15:09:11 +0530 Subject: [PATCH 041/326] fix: pos print receipt (#25330) --- .../selling/page/point_of_sale/pos_past_order_summary.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/selling/page/point_of_sale/pos_past_order_summary.js b/erpnext/selling/page/point_of_sale/pos_past_order_summary.js index a5a739cff9..acf4eb371f 100644 --- a/erpnext/selling/page/point_of_sale/pos_past_order_summary.js +++ b/erpnext/selling/page/point_of_sale/pos_past_order_summary.js @@ -204,11 +204,11 @@ erpnext.PointOfSale.PastOrderSummary = class { print_receipt() { const frm = this.events.get_frm(); frappe.utils.print( - frm.doctype, - frm.docname, + this.doc.doctype, + this.doc.name, frm.pos_print_format, - frm.doc.letter_head, - frm.doc.language || frappe.boot.lang + this.doc.letter_head, + this.doc.language || frappe.boot.lang ); } From 593071bd53c65ebbe3ebde477cd6283b73d9f32c Mon Sep 17 00:00:00 2001 From: Saqib Date: Thu, 15 Apr 2021 14:12:11 +0530 Subject: [PATCH 042/326] fix: currency symbol in bank transaction list view (#25336) --- .../doctype/bank_transaction/bank_transaction.json | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction.json b/erpnext/accounts/doctype/bank_transaction/bank_transaction.json index 69ee4971cd..88aa7ef8b5 100644 --- a/erpnext/accounts/doctype/bank_transaction/bank_transaction.json +++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction.json @@ -175,22 +175,24 @@ }, { "fieldname": "deposit", - "oldfieldname": "debit", "fieldtype": "Currency", "in_list_view": 1, - "label": "Deposit" + "label": "Deposit", + "oldfieldname": "debit", + "options": "currency" }, { "fieldname": "withdrawal", - "oldfieldname": "credit", "fieldtype": "Currency", "in_list_view": 1, - "label": "Withdrawal" + "label": "Withdrawal", + "oldfieldname": "credit", + "options": "currency" } ], "is_submittable": 1, "links": [], - "modified": "2020-12-30 19:40:54.221070", + "modified": "2021-04-14 17:31:58.963529", "modified_by": "Administrator", "module": "Accounts", "name": "Bank Transaction", From c50fbc689737800f2cb1e78786f31f2dbedd3ba9 Mon Sep 17 00:00:00 2001 From: Sun Howwrongbum Date: Thu, 15 Apr 2021 14:52:13 +0530 Subject: [PATCH 043/326] fix: list lookup with undefined variable (#25210) Co-authored-by: Ankush Menat --- erpnext/public/js/utils/serial_no_batch_selector.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js index d49a8138fb..3333d569a7 100644 --- a/erpnext/public/js/utils/serial_no_batch_selector.js +++ b/erpnext/public/js/utils/serial_no_batch_selector.js @@ -353,9 +353,9 @@ erpnext.SerialNoBatchSelector = Class.extend({ return row.on_grid_fields_dict.batch_no.get_value(); } }); - if (selected_batches.includes(val)) { + if (selected_batches.includes(batch_no)) { this.set_value(""); - frappe.throw(__('Batch {0} already selected.', [val])); + frappe.throw(__('Batch {0} already selected.', [batch_no])); } if (me.warehouse_details.name) { From 39b1cd827a5e3ba04c29189426e5f85f7fe77daf Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Thu, 15 Apr 2021 18:54:29 +0530 Subject: [PATCH 044/326] fix: Additional Salary component amount not getting set (#25355) --- erpnext/payroll/doctype/salary_slip/salary_slip.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py index 539f2c56d3..afdf081ac8 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py @@ -633,6 +633,8 @@ class SalarySlip(TransactionBase): if additional_salary: component_row.default_amount = 0 + component_row.additional_amount = amount + component_row.additional_salary = additional_salary.name component_row.deduct_full_tax_on_selected_payroll_date = \ additional_salary.deduct_full_tax_on_selected_payroll_date else: From 56c428663ffcda075cff985a719a86b0dc87bf2c Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Thu, 15 Apr 2021 18:54:38 +0530 Subject: [PATCH 045/326] fix: Additional Salary component amount not getting set (#25356) --- erpnext/payroll/doctype/salary_slip/salary_slip.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py index 539f2c56d3..afdf081ac8 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py @@ -633,6 +633,8 @@ class SalarySlip(TransactionBase): if additional_salary: component_row.default_amount = 0 + component_row.additional_amount = amount + component_row.additional_salary = additional_salary.name component_row.deduct_full_tax_on_selected_payroll_date = \ additional_salary.deduct_full_tax_on_selected_payroll_date else: From 597bb8be184778748980ef453c7c48119317ff2f Mon Sep 17 00:00:00 2001 From: 18alantom <2.alan.tom@gmail.com> Date: Thu, 15 Apr 2021 20:32:45 +0530 Subject: [PATCH 046/326] fix: remove pickup_to, pickup_from and get_pickup_time relies on server-side validation instead js controller --- erpnext/stock/doctype/shipment/shipment.js | 37 ---------------------- 1 file changed, 37 deletions(-) diff --git a/erpnext/stock/doctype/shipment/shipment.js b/erpnext/stock/doctype/shipment/shipment.js index 7af16af898..ce2906ecbe 100644 --- a/erpnext/stock/doctype/shipment/shipment.js +++ b/erpnext/stock/doctype/shipment/shipment.js @@ -363,43 +363,6 @@ frappe.ui.form.on('Shipment', { if (frm.doc.pickup_date < frappe.datetime.get_today()) { frappe.throw(__("Pickup Date cannot be before this day")); } - if (frm.doc.pickup_date == frappe.datetime.get_today()) { - var pickup_time = frm.events.get_pickup_time(frm); - frm.set_value("pickup_from", pickup_time); - frm.trigger('set_pickup_to_time'); - } - }, - pickup_from: function(frm) { - var pickup_time = frm.events.get_pickup_time(frm); - if (frm.doc.pickup_from && frm.doc.pickup_date == frappe.datetime.get_today()) { - let current_hour = pickup_time.split(':')[0]; - let current_min = pickup_time.split(':')[1]; - let pickup_hour = frm.doc.pickup_from.split(':')[0]; - let pickup_min = frm.doc.pickup_from.split(':')[1]; - if (pickup_hour < current_hour || (pickup_hour == current_hour && pickup_min < current_min)) { - frm.set_value("pickup_from", pickup_time); - frappe.throw(__("Pickup Time cannot be in the past")); - } - } - frm.trigger('set_pickup_to_time'); - }, - get_pickup_time: function() { - let current_hour = new Date().getHours(); - let current_min = new Date().toLocaleString('en-US', {minute: 'numeric'}); - if (current_min < 30) { - current_min = '30'; - } else { - current_min = '00'; - current_hour = Number(current_hour)+1; - } - let pickup_time = current_hour +':'+ current_min; - return pickup_time; - }, - set_pickup_to_time: function(frm) { - let pickup_to_hour = Number(frm.doc.pickup_from.split(':')[0])+5; - let pickup_to_min = frm.doc.pickup_from.split(':')[1]; - let pickup_to = pickup_to_hour +':'+ pickup_to_min; - frm.set_value("pickup_to", pickup_to); }, clear_pickup_fields: function(frm) { let fields = ["pickup_address_name", "pickup_contact_name", "pickup_address", "pickup_contact", "pickup_contact_email", "pickup_contact_person"]; From 6179cc1311df15ea0459ec7c9a8e65e2b2d3086d Mon Sep 17 00:00:00 2001 From: 18alantom <2.alan.tom@gmail.com> Date: Thu, 15 Apr 2021 20:36:28 +0530 Subject: [PATCH 047/326] fix: make pickup_to and pickup_from mandatory fields --- erpnext/stock/doctype/shipment/shipment.json | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/erpnext/stock/doctype/shipment/shipment.json b/erpnext/stock/doctype/shipment/shipment.json index 76c331c5c2..a33cbc288c 100644 --- a/erpnext/stock/doctype/shipment/shipment.json +++ b/erpnext/stock/doctype/shipment/shipment.json @@ -275,14 +275,16 @@ "default": "09:00", "fieldname": "pickup_from", "fieldtype": "Time", - "label": "Pickup from" + "label": "Pickup from", + "reqd": 1 }, { "allow_on_submit": 1, "default": "17:00", "fieldname": "pickup_to", "fieldtype": "Time", - "label": "Pickup to" + "label": "Pickup to", + "reqd": 1 }, { "fieldname": "column_break_36", @@ -431,7 +433,7 @@ ], "is_submittable": 1, "links": [], - "modified": "2020-12-25 15:02:34.891976", + "modified": "2021-04-13 17:14:18.181818", "modified_by": "Administrator", "module": "Stock", "name": "Shipment", @@ -469,4 +471,4 @@ "sort_field": "modified", "sort_order": "DESC", "track_changes": 1 -} \ No newline at end of file +} From c0db286dc1572d591a9a6c7c70620ffebfbd28d2 Mon Sep 17 00:00:00 2001 From: Afshan <33727827+AfshanKhan@users.noreply.github.com> Date: Thu, 15 Apr 2021 23:48:25 +0530 Subject: [PATCH 048/326] fix: filter for employees in salary slip (#25360) --- erpnext/payroll/doctype/salary_slip/salary_slip.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.js b/erpnext/payroll/doctype/salary_slip/salary_slip.js index a0ddd39ca2..5258f3aff9 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.js +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.js @@ -40,7 +40,9 @@ frappe.ui.form.on("Salary Slip", { frm.set_query("employee", function() { return { query: "erpnext.controllers.queries.employee_query", - filters: frm.doc.company + filters: { + company: frm.doc.company + } }; }); }, From 4b496b65b7f249a3448592b1f3fc5f1c1a69f906 Mon Sep 17 00:00:00 2001 From: Afshan <33727827+AfshanKhan@users.noreply.github.com> Date: Thu, 15 Apr 2021 23:53:12 +0530 Subject: [PATCH 049/326] fix: filter for employees in salary slip (#25361) --- erpnext/payroll/doctype/salary_slip/salary_slip.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.js b/erpnext/payroll/doctype/salary_slip/salary_slip.js index a0ddd39ca2..5258f3aff9 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.js +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.js @@ -40,7 +40,9 @@ frappe.ui.form.on("Salary Slip", { frm.set_query("employee", function() { return { query: "erpnext.controllers.queries.employee_query", - filters: frm.doc.company + filters: { + company: frm.doc.company + } }; }); }, From 9d9c256e70ebf00c493f5131179700c3a17e4404 Mon Sep 17 00:00:00 2001 From: Anupam Date: Fri, 16 Apr 2021 00:11:40 +0530 Subject: [PATCH 050/326] feat: added Disable Rounded Total in sales transactions --- .../doctype/sales_invoice/sales_invoice.json | 14 +++++++++++++- .../selling/doctype/sales_order/sales_order.json | 14 +++++++++++++- .../stock/doctype/delivery_note/delivery_note.json | 14 +++++++++++++- 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index d382386a32..c6c67b4ddc 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -118,6 +118,7 @@ "in_words", "total_advance", "outstanding_amount", + "disable_rounded_total", "advances_section", "allocate_advances_automatically", "get_advances", @@ -1109,6 +1110,7 @@ "reqd": 1 }, { + "depends_on": "eval:!doc.disable_rounded_total", "fieldname": "base_rounding_adjustment", "fieldtype": "Currency", "hide_days": 1, @@ -1120,6 +1122,7 @@ "read_only": 1 }, { + "depends_on": "eval:!doc.disable_rounded_total", "fieldname": "base_rounded_total", "fieldtype": "Currency", "hide_days": 1, @@ -1168,6 +1171,7 @@ "reqd": 1 }, { + "depends_on": "eval:!doc.disable_rounded_total", "fieldname": "rounding_adjustment", "fieldtype": "Currency", "hide_days": 1, @@ -1180,6 +1184,7 @@ }, { "bold": 1, + "depends_on": "eval:!doc.disable_rounded_total", "fieldname": "rounded_total", "fieldtype": "Currency", "hide_days": 1, @@ -1945,6 +1950,13 @@ "fieldtype": "Link", "label": "Set Target Warehouse", "options": "Warehouse" + }, + { + "default": "0", + "depends_on": "grand_total", + "fieldname": "disable_rounded_total", + "fieldtype": "Check", + "label": "Disable Rounded Total" } ], "icon": "fa fa-file-text", @@ -1957,7 +1969,7 @@ "link_fieldname": "consolidated_invoice" } ], - "modified": "2021-03-31 15:42:26.261540", + "modified": "2021-04-15 23:57:58.766651", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", diff --git a/erpnext/selling/doctype/sales_order/sales_order.json b/erpnext/selling/doctype/sales_order/sales_order.json index 0a5c6651ba..762b6f1d6c 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.json +++ b/erpnext/selling/doctype/sales_order/sales_order.json @@ -98,6 +98,7 @@ "rounded_total", "in_words", "advance_paid", + "disable_rounded_total", "packing_list", "packed_items", "payment_schedule_section", @@ -901,6 +902,7 @@ "width": "150px" }, { + "depends_on": "eval:!doc.disable_rounded_total", "fieldname": "base_rounding_adjustment", "fieldtype": "Currency", "hide_days": 1, @@ -912,6 +914,7 @@ "read_only": 1 }, { + "depends_on": "eval:!doc.disable_rounded_total", "fieldname": "base_rounded_total", "fieldtype": "Currency", "hide_days": 1, @@ -961,6 +964,7 @@ "width": "150px" }, { + "depends_on": "eval:!doc.disable_rounded_total", "fieldname": "rounding_adjustment", "fieldtype": "Currency", "hide_days": 1, @@ -973,6 +977,7 @@ }, { "bold": 1, + "depends_on": "eval:!doc.disable_rounded_total", "fieldname": "rounded_total", "fieldtype": "Currency", "hide_days": 1, @@ -1474,13 +1479,20 @@ "label": "Represents Company", "options": "Company", "read_only": 1 + }, + { + "default": "0", + "depends_on": "grand_total", + "fieldname": "disable_rounded_total", + "fieldtype": "Check", + "label": "Disable Rounded Total" } ], "icon": "fa fa-file-text", "idx": 105, "is_submittable": 1, "links": [], - "modified": "2021-01-20 23:40:39.929296", + "modified": "2021-04-15 23:55:13.439068", "modified_by": "Administrator", "module": "Selling", "name": "Sales Order", diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.json b/erpnext/stock/doctype/delivery_note/delivery_note.json index f595aade91..280fde158f 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.json +++ b/erpnext/stock/doctype/delivery_note/delivery_note.json @@ -99,6 +99,7 @@ "rounding_adjustment", "rounded_total", "in_words", + "disable_rounded_total", "terms_section_break", "tc_name", "terms", @@ -768,6 +769,7 @@ "width": "150px" }, { + "depends_on": "eval:!doc.disable_rounded_total", "fieldname": "base_rounding_adjustment", "fieldtype": "Currency", "label": "Rounding Adjustment (Company Currency)", @@ -777,6 +779,7 @@ "read_only": 1 }, { + "depends_on": "eval:!doc.disable_rounded_total", "fieldname": "base_rounded_total", "fieldtype": "Currency", "label": "Rounded Total (Company Currency)", @@ -819,6 +822,7 @@ "width": "150px" }, { + "depends_on": "eval:!doc.disable_rounded_total", "fieldname": "rounding_adjustment", "fieldtype": "Currency", "label": "Rounding Adjustment", @@ -829,6 +833,7 @@ }, { "bold": 1, + "depends_on": "eval:!doc.disable_rounded_total", "fieldname": "rounded_total", "fieldtype": "Currency", "label": "Rounded Total", @@ -1271,13 +1276,20 @@ "label": "Represents Company", "options": "Company", "read_only": 1 + }, + { + "default": "0", + "depends_on": "grand_total", + "fieldname": "disable_rounded_total", + "fieldtype": "Check", + "label": "Disable Rounded Total" } ], "icon": "fa fa-truck", "idx": 146, "is_submittable": 1, "links": [], - "modified": "2020-12-26 17:07:59.194403", + "modified": "2021-04-15 23:55:49.620641", "modified_by": "Administrator", "module": "Stock", "name": "Delivery Note", From f1b4ce7430e5320ad67e4430628f83c7cdc0b873 Mon Sep 17 00:00:00 2001 From: Hussain Nagaria Date: Fri, 16 Apr 2021 15:36:43 +0530 Subject: [PATCH 051/326] feat: Add filtering by department Also: - Add department filter to js - Add department column to report - Fetch only those timesheets which have an Employee Linked - Update unit tests --- ...ee_hours_utilization_based_on_timesheet.js | 6 ++ ...ee_hours_utilization_based_on_timesheet.py | 61 ++++++++++++---- .../test_employee_util.py | 71 +++++++++++++------ 3 files changed, 102 insertions(+), 36 deletions(-) diff --git a/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.js b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.js index 2a5ea3655d..b11a1fc97f 100644 --- a/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.js +++ b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.js @@ -32,6 +32,12 @@ frappe.query_reports["Employee Hours Utilization Based On Timesheet"] = { fieldtype: "Link", options: "Employee" }, + { + fieldname: "department", + label: __("Department"), + fieldtype: "Link", + options: "Department" + }, { fieldname: "project", label: __("Project"), diff --git a/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py index 2c78b3125d..48eb7b4bb0 100644 --- a/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py +++ b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py @@ -41,7 +41,14 @@ class EmployeeHoursReport: 'options': 'Employee', 'fieldname': 'employee', 'fieldtype': 'Link', - 'width': 200 + 'width': 230 + }, + { + 'label': _('Department'), + 'options': 'Department', + 'fieldname': 'department', + 'fieldtype': 'Link', + 'width': 170 }, { 'label': _('Total Hours'), @@ -68,7 +75,7 @@ class EmployeeHoursReport: 'width': 150 }, { - 'label': _('% Utilization'), + 'label': _('% Utilization (Billed Hours + Non-Billed Hours / Total Hours)'), 'fieldname': 'per_util', 'fieldtype': 'Percentage', 'width': 200 @@ -78,6 +85,11 @@ class EmployeeHoursReport: def generate_data(self): self.generate_filtered_time_logs() self.generate_stats_by_employee() + self.set_employee_department_and_name() + + if self.filters.department: + self.filter_stats_by_department() + self.calculate_utilizations() self.data = [] @@ -91,26 +103,36 @@ class EmployeeHoursReport: # Sort by descending order of percentage utilization self.data.sort(key=lambda x: x['per_util'], reverse=True) + def filter_stats_by_department(self): + filtered_data = frappe._dict() + for emp, data in self.stats_by_employee.items(): + if data['department'] == self.filters.department: + filtered_data[emp] = data + + # Update stats + self.stats_by_employee = filtered_data + def generate_filtered_time_logs(self): additional_filters = '' - if self.filters.employee: - additional_filters += f"AND tt.employee = '{self.filters.employee}'" - - if self.filters.project: - additional_filters += f"AND ttd.project = '{self.filters.project}'" + filter_fields = ['employee', 'project', 'company'] - if self.filters.company: - additional_filters += f"AND tt.company = '{self.filters.company}'" + for field in filter_fields: + if self.filters.get(field): + if field == 'project': + additional_filters += f"AND ttd.{field} = '{self.filters.get(field)}'" + else: + additional_filters += f"AND tt.{field} = '{self.filters.get(field)}'" self.filtered_time_logs = frappe.db.sql(''' SELECT tt.employee AS employee, ttd.hours AS hours, ttd.billable AS billable, ttd.project AS project FROM `tabTimesheet Detail` AS ttd JOIN `tabTimesheet` AS tt ON ttd.parent = tt.name - WHERE tt.start_date BETWEEN '{0}' AND '{1}' + WHERE tt.employee IS NOT NULL + AND tt.start_date BETWEEN '{0}' AND '{1}' AND tt.end_date BETWEEN '{0}' AND '{1}' - {2}; + {2} '''.format(self.filters.from_date, self.filters.to_date, additional_filters)) def generate_stats_by_employee(self): @@ -128,6 +150,18 @@ class EmployeeHoursReport: else: self.stats_by_employee[emp]['non_billed_hours'] += flt(hours, 2) + def set_employee_department_and_name(self): + for emp in self.stats_by_employee: + emp_name = frappe.db.get_value( + 'Employee', emp, 'employee_name' + ) + emp_dept = frappe.db.get_value( + 'Employee', emp, 'department' + ) + + self.stats_by_employee[emp]['department'] = emp_dept + self.stats_by_employee[emp]['employee_name'] = emp_name + def calculate_utilizations(self): # (9.0) Will be fetched from HR settings TOTAL_HOURS = flt(9.0 * self.day_span, 2) @@ -195,10 +229,7 @@ class EmployeeHoursReport: for row in self.data: - emp_name = frappe.db.get_value( - 'Employee', row['employee'], 'employee_name' - ) - labels.append(emp_name) + labels.append(row.get('employee_name')) billed_hours.append(row.get('billed_hours')) non_billed_hours.append(row.get('non_billed_hours')) untracked_hours.append(row.get('untracked_hours')) diff --git a/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/test_employee_util.py b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/test_employee_util.py index 8b5d5be0e1..977a10d1d9 100644 --- a/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/test_employee_util.py +++ b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/test_employee_util.py @@ -77,25 +77,7 @@ class TestEmployeeUtilization(unittest.TestCase): report = execute(filters) - expected_data = [ - { - 'employee': self.test_emp2, - 'billed_hours': 0.0, - 'non_billed_hours': 10.0, - 'total_hours': 18.0, - 'untracked_hours': 8.0, - 'per_util': 55.56 - }, - { - 'employee': self.test_emp1, - 'billed_hours': 5.0, - 'non_billed_hours': 0.0, - 'total_hours': 18.0, - 'untracked_hours': 13.0, - 'per_util': 27.78 - } - ] - + expected_data = self.get_expected_data_for_test_employees() self.assertEqual(report[1], expected_data) def test_utilization_report_for_single_employee(self): @@ -108,9 +90,12 @@ class TestEmployeeUtilization(unittest.TestCase): report = execute(filters) + emp1_data = frappe.get_doc('Employee', self.test_emp1) expected_data = [ { 'employee': self.test_emp1, + 'employee_name': emp1_data.employee_name, + 'department': emp1_data.department, 'billed_hours': 5.0, 'non_billed_hours': 0.0, 'total_hours': 18.0, @@ -130,10 +115,13 @@ class TestEmployeeUtilization(unittest.TestCase): } report = execute(filters) - + + emp2_data = frappe.get_doc('Employee', self.test_emp2) expected_data = [ { - 'employee': self.test_emp2, + 'employee': self.test_emp2, + 'employee_name': emp2_data.employee_name, + 'department': emp2_data.department, 'billed_hours': 0.0, 'non_billed_hours': 10.0, 'total_hours': 18.0, @@ -144,6 +132,20 @@ class TestEmployeeUtilization(unittest.TestCase): self.assertEqual(report[1], expected_data) + def test_utilization_report_for_department(self): + emp1_data = frappe.get_doc('Employee', self.test_emp1) + filters = { + "company": "_Test Company", + "from_date": "2021-04-01", + "to_date": "2021-04-03", + "department": emp1_data.department + } + + report = execute(filters) + + expected_data = self.get_expected_data_for_test_employees() + self.assertEqual(report[1], expected_data) + def test_report_summary_data(self): filters = { "company": "_Test Company", @@ -161,3 +163,30 @@ class TestEmployeeUtilization(unittest.TestCase): self.assertEqual( summary[i]['value'], expected_summary_values[i] ) + + def get_expected_data_for_test_employees(self): + emp1_data = frappe.get_doc('Employee', self.test_emp1) + emp2_data = frappe.get_doc('Employee', self.test_emp2) + + return [ + { + 'employee': self.test_emp2, + 'employee_name': emp2_data.employee_name, + 'department': emp2_data.department, + 'billed_hours': 0.0, + 'non_billed_hours': 10.0, + 'total_hours': 18.0, + 'untracked_hours': 8.0, + 'per_util': 55.56 + }, + { + 'employee': self.test_emp1, + 'employee_name': emp1_data.employee_name, + 'department': emp1_data.department, + 'billed_hours': 5.0, + 'non_billed_hours': 0.0, + 'total_hours': 18.0, + 'untracked_hours': 13.0, + 'per_util': 27.78 + } + ] \ No newline at end of file From b1aad63a9910367fecab17efb1193453db717a88 Mon Sep 17 00:00:00 2001 From: Jannat Patel <31363128+pateljannat@users.noreply.github.com> Date: Fri, 16 Apr 2021 16:08:22 +0530 Subject: [PATCH 052/326] fix: leave policy in leave allocation (#25334) Co-authored-by: Rucha Mahabal --- erpnext/hr/doctype/leave_allocation/leave_allocation.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.json b/erpnext/hr/doctype/leave_allocation/leave_allocation.json index 3a300c0d63..ae02c512c2 100644 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation.json +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.json @@ -218,8 +218,7 @@ "fieldname": "leave_policy_assignment", "fieldtype": "Link", "label": "Leave Policy Assignment", - "options": "Leave Policy Assignment", - "read_only": 1 + "options": "Leave Policy Assignment" }, { "fetch_from": "employee.company", @@ -236,7 +235,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2021-01-04 18:46:13.184104", + "modified": "2021-04-14 15:28:26.335104", "modified_by": "Administrator", "module": "HR", "name": "Leave Allocation", From ede339f80bf514c8f7e5372be3e91567d063b26a Mon Sep 17 00:00:00 2001 From: Marica Date: Fri, 16 Apr 2021 18:42:54 +0530 Subject: [PATCH 053/326] fix: Serial No not updated correctly via Inter Company Stock Transfer (#25006) * fix: Serial No not updated correctly via Inter Company Stock Transfer * chore: Added More Test Cases for inter company Serial Transfer * fix: Test for serial no duplication - fixed serial no test - made errors more meaningful on serial no validation * fix: Stock Reco Test Co-authored-by: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> --- .../purchase_receipt/test_purchase_receipt.py | 1 + erpnext/stock/doctype/serial_no/serial_no.py | 33 ++++- .../stock/doctype/serial_no/test_serial_no.py | 129 +++++++++++++++++- .../stock_reconciliation.py | 2 +- .../test_stock_reconciliation.py | 4 +- 5 files changed, 159 insertions(+), 10 deletions(-) diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 7f0c3fa801..16eea24f84 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -582,6 +582,7 @@ class TestPurchaseReceipt(unittest.TestCase): serial_no=serial_no, basic_rate=100, do_not_submit=True) se.submit() + se.cancel() dn.cancel() pr1.cancel() diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py index c8d8ca9e17..c02dd2e518 100644 --- a/erpnext/stock/doctype/serial_no/serial_no.py +++ b/erpnext/stock/doctype/serial_no/serial_no.py @@ -14,6 +14,7 @@ from frappe import _, ValidationError from erpnext.controllers.stock_controller import StockController from six import string_types from six.moves import map + class SerialNoCannotCreateDirectError(ValidationError): pass class SerialNoCannotCannotChangeError(ValidationError): pass class SerialNoNotRequiredError(ValidationError): pass @@ -322,11 +323,35 @@ def validate_serial_no(sle, item_det): frappe.throw(_("Serial Nos Required for Serialized Item {0}").format(sle.item_code), SerialNoRequiredError) elif serial_nos: + # SLE is being cancelled and has serial nos for serial_no in serial_nos: - sr = frappe.db.get_value("Serial No", serial_no, ["name", "warehouse"], as_dict=1) - if sr and cint(sle.actual_qty) < 0 and sr.warehouse != sle.warehouse: - frappe.throw(_("Cannot cancel {0} {1} because Serial No {2} does not belong to the warehouse {3}") - .format(sle.voucher_type, sle.voucher_no, serial_no, sle.warehouse)) + check_serial_no_validity_on_cancel(serial_no, sle) + +def check_serial_no_validity_on_cancel(serial_no, sle): + sr = frappe.db.get_value("Serial No", serial_no, ["name", "warehouse", "company", "status"], as_dict=1) + sr_link = frappe.utils.get_link_to_form("Serial No", serial_no) + doc_link = frappe.utils.get_link_to_form(sle.voucher_type, sle.voucher_no) + actual_qty = cint(sle.actual_qty) + is_stock_reco = sle.voucher_type == "Stock Reconciliation" + msg = None + + if sr and (actual_qty < 0 or is_stock_reco) and sr.warehouse != sle.warehouse: + # receipt(inward) is being cancelled + msg = _("Cannot cancel {0} {1} as Serial No {2} does not belong to the warehouse {3}").format( + sle.voucher_type, doc_link, sr_link, frappe.bold(sle.warehouse)) + elif sr and actual_qty > 0 and not is_stock_reco: + # delivery is being cancelled, check for warehouse. + if sr.warehouse: + # serial no is active in another warehouse/company. + msg = _("Cannot cancel {0} {1} as Serial No {2} is active in warehouse {3}").format( + sle.voucher_type, doc_link, sr_link, frappe.bold(sr.warehouse)) + elif sr.company != sle.company and sr.status == "Delivered": + # serial no is inactive (allowed) or delivered from another company (block). + msg = _("Cannot cancel {0} {1} as Serial No {2} does not belong to the company {3}").format( + sle.voucher_type, doc_link, sr_link, frappe.bold(sle.company)) + + if msg: + frappe.throw(msg, title=_("Cannot cancel")) def validate_material_transfer_entry(sle_doc): sle_doc.update({ diff --git a/erpnext/stock/doctype/serial_no/test_serial_no.py b/erpnext/stock/doctype/serial_no/test_serial_no.py index ed70790b2c..cde7fe07c6 100644 --- a/erpnext/stock/doctype/serial_no/test_serial_no.py +++ b/erpnext/stock/doctype/serial_no/test_serial_no.py @@ -40,16 +40,139 @@ class TestSerialNo(unittest.TestCase): se = make_serialized_item(target_warehouse="_Test Warehouse - _TC") serial_nos = get_serial_nos(se.get("items")[0].serial_no) - create_delivery_note(item_code="_Test Serialized Item With Series", qty=1, serial_no=serial_nos[0]) + dn = create_delivery_note(item_code="_Test Serialized Item With Series", qty=1, serial_no=serial_nos[0]) + + serial_no = frappe.get_doc("Serial No", serial_nos[0]) + + # check Serial No details after delivery + self.assertEqual(serial_no.status, "Delivered") + self.assertEqual(serial_no.warehouse, None) + self.assertEqual(serial_no.company, "_Test Company") + self.assertEqual(serial_no.delivery_document_type, "Delivery Note") + self.assertEqual(serial_no.delivery_document_no, dn.name) wh = create_warehouse("_Test Warehouse", company="_Test Company 1") - make_purchase_receipt(item_code="_Test Serialized Item With Series", qty=1, serial_no=serial_nos[0], + pr = make_purchase_receipt(item_code="_Test Serialized Item With Series", qty=1, serial_no=serial_nos[0], company="_Test Company 1", warehouse=wh) - serial_no = frappe.db.get_value("Serial No", serial_nos[0], ["warehouse", "company"], as_dict=1) + serial_no.reload() + # check Serial No details after purchase in second company + self.assertEqual(serial_no.status, "Active") self.assertEqual(serial_no.warehouse, wh) self.assertEqual(serial_no.company, "_Test Company 1") + self.assertEqual(serial_no.purchase_document_type, "Purchase Receipt") + self.assertEqual(serial_no.purchase_document_no, pr.name) + + def test_inter_company_transfer_intermediate_cancellation(self): + """ + Receive into and Deliver Serial No from one company. + Then Receive into and Deliver from second company. + Try to cancel intermediate receipts/deliveries to test if it is blocked. + """ + se = make_serialized_item(target_warehouse="_Test Warehouse - _TC") + serial_nos = get_serial_nos(se.get("items")[0].serial_no) + + sn_doc = frappe.get_doc("Serial No", serial_nos[0]) + + # check Serial No details after purchase in first company + self.assertEqual(sn_doc.status, "Active") + self.assertEqual(sn_doc.company, "_Test Company") + self.assertEqual(sn_doc.warehouse, "_Test Warehouse - _TC") + self.assertEqual(sn_doc.purchase_document_no, se.name) + + dn = create_delivery_note(item_code="_Test Serialized Item With Series", + qty=1, serial_no=serial_nos[0]) + sn_doc.reload() + # check Serial No details after delivery from **first** company + self.assertEqual(sn_doc.status, "Delivered") + self.assertEqual(sn_doc.company, "_Test Company") + self.assertEqual(sn_doc.warehouse, None) + self.assertEqual(sn_doc.delivery_document_no, dn.name) + + # try cancelling the first Serial No Receipt, even though it is delivered + # block cancellation is Serial No is out of the warehouse + self.assertRaises(frappe.ValidationError, se.cancel) + + # receive serial no in second company + wh = create_warehouse("_Test Warehouse", company="_Test Company 1") + pr = make_purchase_receipt(item_code="_Test Serialized Item With Series", + qty=1, serial_no=serial_nos[0], company="_Test Company 1", warehouse=wh) + sn_doc.reload() + + self.assertEqual(sn_doc.warehouse, wh) + # try cancelling the delivery from the first company + # block cancellation as Serial No belongs to different company + self.assertRaises(frappe.ValidationError, dn.cancel) + + # deliver from second company + dn_2 = create_delivery_note(item_code="_Test Serialized Item With Series", + qty=1, serial_no=serial_nos[0], company="_Test Company 1", warehouse=wh) + sn_doc.reload() + + # check Serial No details after delivery from **second** company + self.assertEqual(sn_doc.status, "Delivered") + self.assertEqual(sn_doc.company, "_Test Company 1") + self.assertEqual(sn_doc.warehouse, None) + self.assertEqual(sn_doc.delivery_document_no, dn_2.name) + + # cannot cancel any intermediate document before last Delivery Note + self.assertRaises(frappe.ValidationError, se.cancel) + self.assertRaises(frappe.ValidationError, dn.cancel) + self.assertRaises(frappe.ValidationError, pr.cancel) + + def test_inter_company_transfer_fallback_on_cancel(self): + """ + Test Serial No state changes on cancellation. + If Delivery cancelled, it should fall back on last Receipt in the same company. + If Receipt is cancelled, it should be Inactive in the same company. + """ + # Receipt in **first** company + se = make_serialized_item(target_warehouse="_Test Warehouse - _TC") + serial_nos = get_serial_nos(se.get("items")[0].serial_no) + sn_doc = frappe.get_doc("Serial No", serial_nos[0]) + + # Delivery from first company + dn = create_delivery_note(item_code="_Test Serialized Item With Series", + qty=1, serial_no=serial_nos[0]) + + # Receipt in **second** company + wh = create_warehouse("_Test Warehouse", company="_Test Company 1") + pr = make_purchase_receipt(item_code="_Test Serialized Item With Series", + qty=1, serial_no=serial_nos[0], company="_Test Company 1", warehouse=wh) + + # Delivery from second company + dn_2 = create_delivery_note(item_code="_Test Serialized Item With Series", + qty=1, serial_no=serial_nos[0], company="_Test Company 1", warehouse=wh) + sn_doc.reload() + + self.assertEqual(sn_doc.status, "Delivered") + self.assertEqual(sn_doc.company, "_Test Company 1") + self.assertEqual(sn_doc.delivery_document_no, dn_2.name) + + dn_2.cancel() + sn_doc.reload() + # Fallback on Purchase Receipt if Delivery is cancelled + self.assertEqual(sn_doc.status, "Active") + self.assertEqual(sn_doc.company, "_Test Company 1") + self.assertEqual(sn_doc.warehouse, wh) + self.assertEqual(sn_doc.purchase_document_no, pr.name) + + pr.cancel() + sn_doc.reload() + # Inactive in same company if Receipt cancelled + self.assertEqual(sn_doc.status, "Inactive") + self.assertEqual(sn_doc.company, "_Test Company 1") + self.assertEqual(sn_doc.warehouse, None) + + dn.cancel() + sn_doc.reload() + # Fallback on Purchase Receipt in FIRST company if + # Delivery from FIRST company is cancelled + self.assertEqual(sn_doc.status, "Active") + self.assertEqual(sn_doc.company, "_Test Company") + self.assertEqual(sn_doc.warehouse, "_Test Warehouse - _TC") + self.assertEqual(sn_doc.purchase_document_no, se.name) def tearDown(self): frappe.db.rollback() \ No newline at end of file diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index b452e96c5e..1396f19d3f 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -398,7 +398,7 @@ class StockReconciliation(StockController): merge_similar_entries = {} for d in sl_entries: - if not d.serial_no or d.actual_qty < 0: + if not d.serial_no or flt(d.get("actual_qty")) < 0: new_sl_entries.append(d) continue diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py index 6690c6a606..36380b838b 100644 --- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py @@ -32,7 +32,7 @@ class TestStockReconciliation(unittest.TestCase): company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company') # [[qty, valuation_rate, posting_date, # posting_time, expected_stock_value, bin_qty, bin_valuation]] - + input_data = [ [50, 1000, "2012-12-26", "12:00"], [25, 900, "2012-12-26", "12:00"], @@ -86,7 +86,7 @@ class TestStockReconciliation(unittest.TestCase): se1.cancel() def test_get_items(self): - create_warehouse("_Test Warehouse Group 1", + create_warehouse("_Test Warehouse Group 1", {"is_group": 1, "company": "_Test Company", "parent_warehouse": "All Warehouses - _TC"}) create_warehouse("_Test Warehouse Ledger 1", {"is_group": 0, "parent_warehouse": "_Test Warehouse Group 1 - _TC", "company": "_Test Company"}) From 36247faf1a77623bc0a4d739e0fa1bc75086e835 Mon Sep 17 00:00:00 2001 From: Saqib Date: Fri, 16 Apr 2021 18:45:19 +0530 Subject: [PATCH 054/326] fix(e-invoicing): add company validation for e-invoicing (#25349) * fix(e-invoicing): add company validation for e-invoicing * fix: test --- .../doctype/sales_invoice/test_sales_invoice.py | 15 +++++++++++++-- erpnext/regional/india/e_invoice/utils.py | 7 ++++--- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 4a6f9d1d6a..9059d0b040 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -1879,7 +1879,17 @@ class TestSalesInvoice(unittest.TestCase): def test_einvoice_submission_without_irn(self): # init - frappe.db.set_value('E Invoice Settings', 'E Invoice Settings', 'enable', 1) + einvoice_settings = frappe.get_doc('E Invoice Settings') + einvoice_settings.enable = 1 + einvoice_settings.applicable_from = nowdate() + einvoice_settings.append('credentials', { + 'company': '_Test Company', + 'gstin': '27AAECE4835E1ZR', + 'username': 'test', + 'password': 'test' + }) + einvoice_settings.save() + country = frappe.flags.country frappe.flags.country = 'India' @@ -1890,7 +1900,8 @@ class TestSalesInvoice(unittest.TestCase): si.submit() # reset - frappe.db.set_value('E Invoice Settings', 'E Invoice Settings', 'enable', 0) + einvoice_settings = frappe.get_doc('E Invoice Settings') + einvoice_settings.enable = 0 frappe.flags.country = country def test_einvoice_json(self): diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 59c098c1ca..1d3cb661dd 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -38,12 +38,13 @@ def validate_eligibility(doc): einvoicing_eligible_from = frappe.db.get_single_value('E Invoice Settings', 'applicable_from') or '2021-04-01' if getdate(doc.get('posting_date')) < getdate(einvoicing_eligible_from): return False - + + invalid_company = not frappe.db.get_value('E Invoice User', { 'company': doc.get('company') }) invalid_supply_type = doc.get('gst_category') not in ['Registered Regular', 'SEZ', 'Overseas', 'Deemed Export'] company_transaction = doc.get('billing_address_gstin') == doc.get('company_gstin') no_taxes_applied = not doc.get('taxes') - if invalid_supply_type or company_transaction or no_taxes_applied: + if invalid_company or invalid_supply_type or company_transaction or no_taxes_applied: return False return True @@ -400,7 +401,7 @@ def validate_totals(einvoice): if abs(flt(value_details['AssVal']) - total_item_ass_value) > 1: frappe.throw(_('Total Taxable Value of the items is not equal to the Invoice Net Total. Please check item taxes / discounts for any correction.')) - if abs(flt(value_details['TotInvVal']) + flt(value_details['Discount']) - total_item_value) > 1: + if abs(flt(value_details['TotInvVal']) + flt(value_details['Discount']) - flt(value_details['OthChrg']) - total_item_value) > 1: frappe.throw(_('Total Value of the items is not equal to the Invoice Grand Total. Please check item taxes / discounts for any correction.')) calculated_invoice_value = \ From adf974810d03b40ad282197bd77c1513b77ef91e Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 16 Apr 2021 21:15:50 +0530 Subject: [PATCH 055/326] fix: equality check instead of assignment in cart --- erpnext/shopping_cart/cart.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/shopping_cart/cart.py b/erpnext/shopping_cart/cart.py index 8515db3300..56afe95efd 100644 --- a/erpnext/shopping_cart/cart.py +++ b/erpnext/shopping_cart/cart.py @@ -230,12 +230,12 @@ def update_cart_address(address_type, address_name): if address_type.lower() == "billing": quotation.customer_address = address_name quotation.address_display = address_display - quotation.shipping_address_name == quotation.shipping_address_name or address_name + quotation.shipping_address_name = quotation.shipping_address_name or address_name address_doc = next((doc for doc in get_billing_addresses() if doc["name"] == address_name), None) elif address_type.lower() == "shipping": quotation.shipping_address_name = address_name quotation.shipping_address = address_display - quotation.customer_address == quotation.customer_address or address_name + quotation.customer_address = quotation.customer_address or address_name address_doc = next((doc for doc in get_shipping_addresses() if doc["name"] == address_name), None) apply_cart_settings(quotation=quotation) From 67e647232cf289858d1aa3dbea8fe94d5ba746d2 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 16 Apr 2021 21:44:49 +0530 Subject: [PATCH 056/326] ci(semgrep): Add semgrep testing (#24871) Adds semgrep testing in CI. Refer to: - https://github.com/frappe/frappe/pull/12524 - https://github.com/frappe/frappe/pull/12577 --- .github/helper/semgrep_rules/README.md | 38 +++++++++++ .../semgrep_rules/frappe_correctness.py | 28 +++++++++ .../semgrep_rules/frappe_correctness.yml | 56 +++++++++++++++++ .github/helper/semgrep_rules/security.py | 6 ++ .github/helper/semgrep_rules/security.yml | 25 ++++++++ .github/helper/semgrep_rules/translate.js | 37 +++++++++++ .github/helper/semgrep_rules/translate.py | 53 ++++++++++++++++ .github/helper/semgrep_rules/translate.yml | 63 +++++++++++++++++++ .github/helper/semgrep_rules/ux.py | 31 +++++++++ .github/helper/semgrep_rules/ux.yml | 15 +++++ .github/workflows/semgrep.yml | 24 +++++++ 11 files changed, 376 insertions(+) create mode 100644 .github/helper/semgrep_rules/README.md create mode 100644 .github/helper/semgrep_rules/frappe_correctness.py create mode 100644 .github/helper/semgrep_rules/frappe_correctness.yml create mode 100644 .github/helper/semgrep_rules/security.py create mode 100644 .github/helper/semgrep_rules/security.yml create mode 100644 .github/helper/semgrep_rules/translate.js create mode 100644 .github/helper/semgrep_rules/translate.py create mode 100644 .github/helper/semgrep_rules/translate.yml create mode 100644 .github/helper/semgrep_rules/ux.py create mode 100644 .github/helper/semgrep_rules/ux.yml create mode 100644 .github/workflows/semgrep.yml diff --git a/.github/helper/semgrep_rules/README.md b/.github/helper/semgrep_rules/README.md new file mode 100644 index 0000000000..670d8d280f --- /dev/null +++ b/.github/helper/semgrep_rules/README.md @@ -0,0 +1,38 @@ +# Semgrep linting + +## What is semgrep? +Semgrep or "semantic grep" is language agnostic static analysis tool. In simple terms semgrep is syntax-aware `grep`, so unlike regex it doesn't get confused by different ways of writing same thing or whitespaces or code split in multiple lines etc. + +Example: + +To check if a translate function is using f-string or not the regex would be `r"_\(\s*f[\"']"` while equivalent rule in semgrep would be `_(f"...")`. As semgrep knows grammer of language it takes care of unnecessary whitespace, type of quotation marks etc. + +You can read more such examples in `.github/helper/semgrep_rules` directory. + +# Why/when to use this? +We want to maintain quality of contributions, at the same time remembering all the good practices can be pain to deal with while evaluating contributions. Using semgrep if you can translate "best practice" into a rule then it can automate the task for us. + +## Running locally + +Install semgrep using homebrew `brew install semgrep` or pip `pip install semgrep`. + +To run locally use following command: + +`semgrep --config=.github/helper/semgrep_rules [file/folder names]` + +## Testing +semgrep allows testing the tests. Refer to this page: https://semgrep.dev/docs/writing-rules/testing-rules/ + +When writing new rules you should write few positive and few negative cases as shown in the guide and current tests. + +To run current tests: `semgrep --test --test-ignore-todo .github/helper/semgrep_rules` + + +## Reference + +If you are new to Semgrep read following pages to get started on writing/modifying rules: + +- https://semgrep.dev/docs/getting-started/ +- https://semgrep.dev/docs/writing-rules/rule-syntax +- https://semgrep.dev/docs/writing-rules/pattern-examples/ +- https://semgrep.dev/docs/writing-rules/rule-ideas/#common-use-cases diff --git a/.github/helper/semgrep_rules/frappe_correctness.py b/.github/helper/semgrep_rules/frappe_correctness.py new file mode 100644 index 0000000000..4798b927f8 --- /dev/null +++ b/.github/helper/semgrep_rules/frappe_correctness.py @@ -0,0 +1,28 @@ +import frappe +from frappe import _, flt + +from frappe.model.document import Document + + +def on_submit(self): + if self.value_of_goods == 0: + frappe.throw(_('Value of goods cannot be 0')) + # ruleid: frappe-modifying-after-submit + self.status = 'Submitted' + +def on_submit(self): + if flt(self.per_billed) < 100: + self.update_billing_status() + else: + # todook: frappe-modifying-after-submit + self.status = "Completed" + self.db_set("status", "Completed") + +class TestDoc(Document): + pass + + def validate(self): + #ruleid: frappe-modifying-child-tables-while-iterating + for item in self.child_table: + if item.value < 0: + self.remove(item) diff --git a/.github/helper/semgrep_rules/frappe_correctness.yml b/.github/helper/semgrep_rules/frappe_correctness.yml new file mode 100644 index 0000000000..394abbf74d --- /dev/null +++ b/.github/helper/semgrep_rules/frappe_correctness.yml @@ -0,0 +1,56 @@ +# This file specifies rules for correctness according to how frappe doctype data model works. + +rules: +- id: frappe-modifying-after-submit + patterns: + - pattern: self.$ATTR = ... + - pattern-inside: | + def on_submit(self, ...): + ... + message: | + Doctype modified after submission. Please check if modification of self.$ATTR is commited to database. + languages: [python] + severity: ERROR + +- id: frappe-print-function-in-doctypes + pattern: print(...) + message: | + Did you mean to leave this print statement in? Consider using msgprint or logger instead of print statement. + languages: [python] + severity: WARNING + paths: + exclude: + - test_*.py + include: + - "*/**/doctype/*" + +- id: frappe-modifying-child-tables-while-iterating + pattern-either: + - pattern: | + for $ROW in self.$TABLE: + ... + self.remove(...) + - pattern: | + for $ROW in self.$TABLE: + ... + self.append(...) + message: | + Child table being modified while iterating on it. + languages: [python] + severity: ERROR + paths: + include: + - "*/**/doctype/*" + +- id: frappe-same-key-assigned-twice + pattern-either: + - pattern: | + {..., $X: $A, ..., $X: $B, ...} + - pattern: | + dict(..., ($X, $A), ..., ($X, $B), ...) + - pattern: | + _dict(..., ($X, $A), ..., ($X, $B), ...) + message: | + key `$X` is uselessly assigned twice. This could be a potential bug. + languages: [python] + severity: ERROR diff --git a/.github/helper/semgrep_rules/security.py b/.github/helper/semgrep_rules/security.py new file mode 100644 index 0000000000..f477d7c176 --- /dev/null +++ b/.github/helper/semgrep_rules/security.py @@ -0,0 +1,6 @@ +def function_name(input): + # ruleid: frappe-codeinjection-eval + eval(input) + +# ok: frappe-codeinjection-eval +eval("1 + 1") diff --git a/.github/helper/semgrep_rules/security.yml b/.github/helper/semgrep_rules/security.yml new file mode 100644 index 0000000000..5a5098bf50 --- /dev/null +++ b/.github/helper/semgrep_rules/security.yml @@ -0,0 +1,25 @@ +rules: +- id: frappe-codeinjection-eval + patterns: + - pattern-not: eval("...") + - pattern: eval(...) + message: | + Detected the use of eval(). eval() can be dangerous if used to evaluate + dynamic content. Avoid it or use safe_eval(). + languages: [python] + severity: ERROR + +- id: frappe-sqli-format-strings + patterns: + - pattern-inside: | + @frappe.whitelist() + def $FUNC(...): + ... + - pattern-either: + - pattern: frappe.db.sql("..." % ...) + - pattern: frappe.db.sql(f"...", ...) + - pattern: frappe.db.sql("...".format(...), ...) + message: | + Detected use of raw string formatting for SQL queries. This can lead to sql injection vulnerabilities. Refer security guidelines - https://github.com/frappe/erpnext/wiki/Code-Security-Guidelines + languages: [python] + severity: WARNING diff --git a/.github/helper/semgrep_rules/translate.js b/.github/helper/semgrep_rules/translate.js new file mode 100644 index 0000000000..7b92fe2dff --- /dev/null +++ b/.github/helper/semgrep_rules/translate.js @@ -0,0 +1,37 @@ +// ruleid: frappe-translation-empty-string +__("") +// ruleid: frappe-translation-empty-string +__('') + +// ok: frappe-translation-js-formatting +__('Welcome {0}, get started with ERPNext in just a few clicks.', [full_name]); + +// ruleid: frappe-translation-js-formatting +__(`Welcome ${full_name}, get started with ERPNext in just a few clicks.`); + +// ok: frappe-translation-js-formatting +__('This is fine'); + + +// ok: frappe-translation-trailing-spaces +__('This is fine'); + +// ruleid: frappe-translation-trailing-spaces +__(' this is not ok '); +// ruleid: frappe-translation-trailing-spaces +__('this is not ok '); +// ruleid: frappe-translation-trailing-spaces +__(' this is not ok'); + +// ok: frappe-translation-js-splitting +__('You have {0} subscribers in your mailing list.', [subscribers.length]) + +// todoruleid: frappe-translation-js-splitting +__('You have') + subscribers.length + __('subscribers in your mailing list.') + +// ruleid: frappe-translation-js-splitting +__('You have' + 'subscribers in your mailing list.') + +// ruleid: frappe-translation-js-splitting +__('You have {0} subscribers' + + 'in your mailing list', [subscribers.length]) diff --git a/.github/helper/semgrep_rules/translate.py b/.github/helper/semgrep_rules/translate.py new file mode 100644 index 0000000000..bd6cd9126c --- /dev/null +++ b/.github/helper/semgrep_rules/translate.py @@ -0,0 +1,53 @@ +# Examples taken from https://frappeframework.com/docs/user/en/translations +# This file is used for testing the tests. + +from frappe import _ + +full_name = "Jon Doe" +# ok: frappe-translation-python-formatting +_('Welcome {0}, get started with ERPNext in just a few clicks.').format(full_name) + +# ruleid: frappe-translation-python-formatting +_('Welcome %s, get started with ERPNext in just a few clicks.' % full_name) +# ruleid: frappe-translation-python-formatting +_('Welcome %(name)s, get started with ERPNext in just a few clicks.' % {'name': full_name}) + +# ruleid: frappe-translation-python-formatting +_('Welcome {0}, get started with ERPNext in just a few clicks.'.format(full_name)) + + +subscribers = ["Jon", "Doe"] +# ok: frappe-translation-python-formatting +_('You have {0} subscribers in your mailing list.').format(len(subscribers)) + +# ruleid: frappe-translation-python-splitting +_('You have') + len(subscribers) + _('subscribers in your mailing list.') + +# ruleid: frappe-translation-python-splitting +_('You have {0} subscribers \ + in your mailing list').format(len(subscribers)) + +# ok: frappe-translation-python-splitting +_('You have {0} subscribers') \ + + 'in your mailing list' + +# ruleid: frappe-translation-trailing-spaces +msg = _(" You have {0} pending invoice ") +# ruleid: frappe-translation-trailing-spaces +msg = _("You have {0} pending invoice ") +# ruleid: frappe-translation-trailing-spaces +msg = _(" You have {0} pending invoice") + +# ok: frappe-translation-trailing-spaces +msg = ' ' + _("You have {0} pending invoices") + ' ' + +# ruleid: frappe-translation-python-formatting +_(f"can not format like this - {subscribers}") +# ruleid: frappe-translation-python-splitting +_(f"what" + f"this is also not cool") + + +# ruleid: frappe-translation-empty-string +_("") +# ruleid: frappe-translation-empty-string +_('') diff --git a/.github/helper/semgrep_rules/translate.yml b/.github/helper/semgrep_rules/translate.yml new file mode 100644 index 0000000000..3737da5a7e --- /dev/null +++ b/.github/helper/semgrep_rules/translate.yml @@ -0,0 +1,63 @@ +rules: +- id: frappe-translation-empty-string + pattern-either: + - pattern: _("") + - pattern: __("") + message: | + Empty string is useless for translation. + Please refer: https://frappeframework.com/docs/user/en/translations + languages: [python, javascript, json] + severity: ERROR + +- id: frappe-translation-trailing-spaces + pattern-either: + - pattern: _("=~/(^[ \t]+|[ \t]+$)/") + - pattern: __("=~/(^[ \t]+|[ \t]+$)/") + message: | + Trailing or leading whitespace not allowed in translate strings. + Please refer: https://frappeframework.com/docs/user/en/translations + languages: [python, javascript, json] + severity: ERROR + +- id: frappe-translation-python-formatting + pattern-either: + - pattern: _("..." % ...) + - pattern: _("...".format(...)) + - pattern: _(f"...") + message: | + Only positional formatters are allowed and formatting should not be done before translating. + Please refer: https://frappeframework.com/docs/user/en/translations + languages: [python] + severity: ERROR + +- id: frappe-translation-js-formatting + patterns: + - pattern: __(`...`) + - pattern-not: __("...") + message: | + Template strings are not allowed for text formatting. + Please refer: https://frappeframework.com/docs/user/en/translations + languages: [javascript, json] + severity: ERROR + +- id: frappe-translation-python-splitting + pattern-either: + - pattern: _(...) + ... + _(...) + - pattern: _("..." + "...") + - pattern-regex: '_\([^\)]*\\\s*' + message: | + Do not split strings inside translate function. Do not concatenate using translate functions. + Please refer: https://frappeframework.com/docs/user/en/translations + languages: [python] + severity: ERROR + +- id: frappe-translation-js-splitting + pattern-either: + - pattern-regex: '__\([^\)]*[\+\\]\s*' + - pattern: __('...' + '...') + - pattern: __('...') + __('...') + message: | + Do not split strings inside translate function. Do not concatenate using translate functions. + Please refer: https://frappeframework.com/docs/user/en/translations + languages: [javascript, json] + severity: ERROR diff --git a/.github/helper/semgrep_rules/ux.py b/.github/helper/semgrep_rules/ux.py new file mode 100644 index 0000000000..4a74457435 --- /dev/null +++ b/.github/helper/semgrep_rules/ux.py @@ -0,0 +1,31 @@ +import frappe +from frappe import msgprint, throw, _ + + +# ruleid: frappe-missing-translate-function +throw("Error Occured") + +# ruleid: frappe-missing-translate-function +frappe.throw("Error Occured") + +# ruleid: frappe-missing-translate-function +frappe.msgprint("Useful message") + +# ruleid: frappe-missing-translate-function +msgprint("Useful message") + + +# ok: frappe-missing-translate-function +translatedmessage = _("Hello") + +# ok: frappe-missing-translate-function +throw(translatedmessage) + +# ok: frappe-missing-translate-function +msgprint(translatedmessage) + +# ok: frappe-missing-translate-function +msgprint(_("Helpful message")) + +# ok: frappe-missing-translate-function +frappe.throw(_("Error occured")) diff --git a/.github/helper/semgrep_rules/ux.yml b/.github/helper/semgrep_rules/ux.yml new file mode 100644 index 0000000000..ed06a6a80c --- /dev/null +++ b/.github/helper/semgrep_rules/ux.yml @@ -0,0 +1,15 @@ +rules: +- id: frappe-missing-translate-function + pattern-either: + - patterns: + - pattern: frappe.msgprint("...", ...) + - pattern-not: frappe.msgprint(_("..."), ...) + - pattern-not: frappe.msgprint(__("..."), ...) + - patterns: + - pattern: frappe.throw("...", ...) + - pattern-not: frappe.throw(_("..."), ...) + - pattern-not: frappe.throw(__("..."), ...) + message: | + All user facing text must be wrapped in translate function. Please refer to translation documentation. https://frappeframework.com/docs/user/en/guides/basics/translations + languages: [python, javascript, json] + severity: ERROR diff --git a/.github/workflows/semgrep.yml b/.github/workflows/semgrep.yml new file mode 100644 index 0000000000..df08263236 --- /dev/null +++ b/.github/workflows/semgrep.yml @@ -0,0 +1,24 @@ +name: Semgrep + +on: + pull_request: + branches: + - develop +jobs: + semgrep: + name: Frappe Linter + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Setup python3 + uses: actions/setup-python@v2 + with: + python-version: 3.8 + - name: Run semgrep + run: | + python -m pip install -q semgrep + git fetch origin $GITHUB_BASE_REF:$GITHUB_BASE_REF -q + files=$(git diff --name-only --diff-filter=d $GITHUB_BASE_REF) + [[ -d .github/helper/semgrep_rules ]] && semgrep --severity ERROR --config=.github/helper/semgrep_rules --quiet --error $files + semgrep --config="r/python.lang.correctness" --quiet --error $files + [[ -d .github/helper/semgrep_rules ]] && semgrep --severity WARNING --severity INFO --config=.github/helper/semgrep_rules --quiet $files From d6be154ac27dcc69655213a0770610d81e438327 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 16 Apr 2021 22:08:44 +0530 Subject: [PATCH 057/326] fix: implicit string concatenation (#25371) * fix: implicit string concatenations * chore: rerun healthcare patch for company fields --- .../accounts/doctype/promotional_scheme/promotional_scheme.py | 4 ++-- erpnext/patches.txt | 2 +- .../patches/v13_0/set_company_field_in_healthcare_doctypes.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py b/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py index 523e9ee08a..7d9302382f 100644 --- a/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py +++ b/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py @@ -9,7 +9,7 @@ from frappe.utils import cstr from frappe.model.naming import make_autoname from frappe.model.document import Document -pricing_rule_fields = ['apply_on', 'mixed_conditions', 'is_cumulative', 'other_item_code', 'other_item_group' +pricing_rule_fields = ['apply_on', 'mixed_conditions', 'is_cumulative', 'other_item_code', 'other_item_group', 'apply_rule_on_other', 'other_brand', 'selling', 'buying', 'applicable_for', 'valid_from', 'valid_upto', 'customer', 'customer_group', 'territory', 'sales_partner', 'campaign', 'supplier', 'supplier_group', 'company', 'currency', 'apply_multiple_pricing_rules'] @@ -111,4 +111,4 @@ def get_args_for_pricing_rule(doc): for d in pricing_rule_fields: args[d] = doc.get(d) - return args \ No newline at end of file + return args diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 1f800889c7..112f6d8a83 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -693,7 +693,7 @@ execute:frappe.reload_doctype('Dashboard') execute:frappe.reload_doc('desk', 'doctype', 'number_card_link') execute:frappe.delete_doc_if_exists('Dashboard', 'Accounts') erpnext.patches.v13_0.update_actual_start_and_end_date_in_wo -erpnext.patches.v13_0.set_company_field_in_healthcare_doctypes #2020-05-25 +erpnext.patches.v13_0.set_company_field_in_healthcare_doctypes #2021-04-16 erpnext.patches.v12_0.update_bom_in_so_mr execute:frappe.delete_doc("Report", "Department Analytics") execute:frappe.rename_doc("Desk Page", "Loan Management", "Loan", force=True) diff --git a/erpnext/patches/v13_0/set_company_field_in_healthcare_doctypes.py b/erpnext/patches/v13_0/set_company_field_in_healthcare_doctypes.py index be5e30f307..a5b93f6307 100644 --- a/erpnext/patches/v13_0/set_company_field_in_healthcare_doctypes.py +++ b/erpnext/patches/v13_0/set_company_field_in_healthcare_doctypes.py @@ -3,7 +3,7 @@ import frappe def execute(): company = frappe.db.get_single_value('Global Defaults', 'default_company') - doctypes = ['Clinical Procedure', 'Inpatient Record', 'Lab Test', 'Sample Collection' 'Patient Appointment', 'Patient Encounter', 'Vital Signs', 'Therapy Session', 'Therapy Plan', 'Patient Assessment'] + doctypes = ['Clinical Procedure', 'Inpatient Record', 'Lab Test', 'Sample Collection', 'Patient Appointment', 'Patient Encounter', 'Vital Signs', 'Therapy Session', 'Therapy Plan', 'Patient Assessment'] for entry in doctypes: if frappe.db.exists('DocType', entry): frappe.reload_doc('Healthcare', 'doctype', entry) From 18c7815a1b313c10cedf4135b51ecaeaa5c76bb7 Mon Sep 17 00:00:00 2001 From: Saqib Date: Sat, 17 Apr 2021 15:37:40 +0530 Subject: [PATCH 058/326] fix: presentation currency in statement of accounts (#25367) --- .../process_statement_of_accounts.html | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html index e1ddeff61f..94ae79a0c6 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html @@ -38,22 +38,22 @@ {% endif %} - {{ frappe.utils.fmt_money(row.debit, filters.presentation_currency) }} + {{ frappe.utils.fmt_money(row.debit, currency=filters.presentation_currency) }} - {{ frappe.utils.fmt_money(row.credit, filters.presentation_currency) }} + {{ frappe.utils.fmt_money(row.credit, currency=filters.presentation_currency) }} {% else %} {{ frappe.format(row.account, {fieldtype: "Link"}) or " " }} - {{ row.account and frappe.utils.fmt_money(row.debit, filters.presentation_currency) }} + {{ row.account and frappe.utils.fmt_money(row.debit, currency=filters.presentation_currency) }} - {{ row.account and frappe.utils.fmt_money(row.credit, filters.presentation_currency) }} + {{ row.account and frappe.utils.fmt_money(row.credit, currency=filters.presentation_currency) }} {% endif %} - {{ frappe.utils.fmt_money(row.balance, filters.presentation_currency) }} + {{ frappe.utils.fmt_money(row.balance, currency=filters.presentation_currency) }} {% endfor %} From 75e13f7bb662d903dca2a4038ed489333604e34c Mon Sep 17 00:00:00 2001 From: Saqib Date: Sat, 17 Apr 2021 15:38:47 +0530 Subject: [PATCH 059/326] fix(e-invoicing): add company validation for e-invoicing (#25348) Co-authored-by: Nabin Hait --- .../doctype/sales_invoice/test_sales_invoice.py | 15 +++++++++++++-- erpnext/regional/india/e_invoice/utils.py | 7 ++++--- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 4a6f9d1d6a..9059d0b040 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -1879,7 +1879,17 @@ class TestSalesInvoice(unittest.TestCase): def test_einvoice_submission_without_irn(self): # init - frappe.db.set_value('E Invoice Settings', 'E Invoice Settings', 'enable', 1) + einvoice_settings = frappe.get_doc('E Invoice Settings') + einvoice_settings.enable = 1 + einvoice_settings.applicable_from = nowdate() + einvoice_settings.append('credentials', { + 'company': '_Test Company', + 'gstin': '27AAECE4835E1ZR', + 'username': 'test', + 'password': 'test' + }) + einvoice_settings.save() + country = frappe.flags.country frappe.flags.country = 'India' @@ -1890,7 +1900,8 @@ class TestSalesInvoice(unittest.TestCase): si.submit() # reset - frappe.db.set_value('E Invoice Settings', 'E Invoice Settings', 'enable', 0) + einvoice_settings = frappe.get_doc('E Invoice Settings') + einvoice_settings.enable = 0 frappe.flags.country = country def test_einvoice_json(self): diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 59c098c1ca..1d3cb661dd 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -38,12 +38,13 @@ def validate_eligibility(doc): einvoicing_eligible_from = frappe.db.get_single_value('E Invoice Settings', 'applicable_from') or '2021-04-01' if getdate(doc.get('posting_date')) < getdate(einvoicing_eligible_from): return False - + + invalid_company = not frappe.db.get_value('E Invoice User', { 'company': doc.get('company') }) invalid_supply_type = doc.get('gst_category') not in ['Registered Regular', 'SEZ', 'Overseas', 'Deemed Export'] company_transaction = doc.get('billing_address_gstin') == doc.get('company_gstin') no_taxes_applied = not doc.get('taxes') - if invalid_supply_type or company_transaction or no_taxes_applied: + if invalid_company or invalid_supply_type or company_transaction or no_taxes_applied: return False return True @@ -400,7 +401,7 @@ def validate_totals(einvoice): if abs(flt(value_details['AssVal']) - total_item_ass_value) > 1: frappe.throw(_('Total Taxable Value of the items is not equal to the Invoice Net Total. Please check item taxes / discounts for any correction.')) - if abs(flt(value_details['TotInvVal']) + flt(value_details['Discount']) - total_item_value) > 1: + if abs(flt(value_details['TotInvVal']) + flt(value_details['Discount']) - flt(value_details['OthChrg']) - total_item_value) > 1: frappe.throw(_('Total Value of the items is not equal to the Invoice Grand Total. Please check item taxes / discounts for any correction.')) calculated_invoice_value = \ From dedf2c1b61a8f31fb8da831e2c2a96611bacaa35 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sat, 17 Apr 2021 14:57:57 +0530 Subject: [PATCH 060/326] fix: remove duplicate keys from dictionaries --- erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 1 - erpnext/controllers/stock_controller.py | 1 - erpnext/manufacturing/dashboard_fixtures.py | 4 +--- .../manufacturing/doctype/production_plan/production_plan.py | 1 - erpnext/regional/report/gstr_1/gstr_1.py | 2 +- erpnext/stock/get_item_details.py | 2 -- erpnext/utilities/activation.py | 1 - 7 files changed, 2 insertions(+), 10 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 3c91dccaa7..a731e79574 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -46,7 +46,6 @@ class SalesInvoice(SellingController): 'target_parent_dt': 'Sales Order', 'target_parent_field': 'per_billed', 'source_field': 'amount', - 'join_field': 'so_detail', 'percent_join_field': 'sales_order', 'status_field': 'billing_status', 'keyword': 'Billed', diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 20499579ca..34f7b27e00 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -117,7 +117,6 @@ class StockController(AccountsController): "account": expense_account, "against": warehouse_account[sle.warehouse]["account"], "cost_center": item_row.cost_center, - "project": item_row.project or self.get('project'), "remarks": self.get("remarks") or "Accounting Entry for Stock", "credit": flt(sle.stock_value_difference, precision), "project": item_row.get("project") or self.get("project"), diff --git a/erpnext/manufacturing/dashboard_fixtures.py b/erpnext/manufacturing/dashboard_fixtures.py index 0e9a21c026..7ba43d6471 100644 --- a/erpnext/manufacturing/dashboard_fixtures.py +++ b/erpnext/manufacturing/dashboard_fixtures.py @@ -43,7 +43,6 @@ def get_charts(): return [{ "doctype": "Dashboard Chart", "based_on": "modified", - "time_interval": "Yearly", "chart_type": "Sum", "chart_name": _("Produced Quantity"), "name": "Produced Quantity", @@ -60,7 +59,6 @@ def get_charts(): }, { "doctype": "Dashboard Chart", "based_on": "creation", - "time_interval": "Yearly", "chart_type": "Sum", "chart_name": _("Completed Operation"), "name": "Completed Operation", @@ -238,4 +236,4 @@ def get_number_cards(): "label": _("Monthly Quality Inspections"), "show_percentage_stats": 1, "stats_time_interval": "Weekly" - }] \ No newline at end of file + }] diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index cef2d8be7a..e7c83ac050 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -561,7 +561,6 @@ def get_material_request_items(row, sales_order, company, 'item_name': row.item_name, 'quantity': required_qty, 'required_bom_qty': total_qty, - 'description': row.description, 'stock_uom': row.get("stock_uom"), 'warehouse': warehouse or row.get('source_warehouse') \ or row.get('default_warehouse') or item_group_defaults.get("default_warehouse"), diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py index 75076231c0..b637fb47b3 100644 --- a/erpnext/regional/report/gstr_1/gstr_1.py +++ b/erpnext/regional/report/gstr_1/gstr_1.py @@ -561,7 +561,7 @@ def get_json(filters, report_name, data): fp = "%02d%s" % (getdate(filters["to_date"]).month, getdate(filters["to_date"]).year) - gst_json = {"gstin": "", "version": "GST2.2.9", + gst_json = {"version": "GST2.2.9", "hash": "hash", "gstin": gstin, "fp": fp} res = {} diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index aaf14a535e..dedfe1d79b 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -309,8 +309,6 @@ def get_basic_details(args, item, overwrite_warehouse=True): "update_stock": args.get("update_stock") if args.get('doctype') in ['Sales Invoice', 'Purchase Invoice'] else 0, "delivered_by_supplier": item.delivered_by_supplier if args.get("doctype") in ["Sales Order", "Sales Invoice"] else 0, "is_fixed_asset": item.is_fixed_asset, - "weight_per_unit":item.weight_per_unit, - "weight_uom":item.weight_uom, "last_purchase_rate": item.last_purchase_rate if args.get("doctype") in ["Purchase Order"] else 0, "transaction_date": args.get("transaction_date"), "against_blanket_order": args.get("against_blanket_order"), diff --git a/erpnext/utilities/activation.py b/erpnext/utilities/activation.py index 7b17c8c464..50c4b255ce 100644 --- a/erpnext/utilities/activation.py +++ b/erpnext/utilities/activation.py @@ -18,7 +18,6 @@ def get_level(): "Delivery Note": 5, "Employee": 3, "Instructor": 5, - "Instructor": 5, "Issue": 5, "Item": 5, "Journal Entry": 3, From ad6a2657aee99b2d56afba99eca55bb09ef4f3d9 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sat, 17 Apr 2021 16:50:02 +0530 Subject: [PATCH 061/326] chore: minor translation fixes --- erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 6 +++--- erpnext/controllers/stock_controller.py | 2 +- .../doctype/production_plan/production_plan.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index a731e79574..4461f29fe3 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -275,7 +275,7 @@ class SalesInvoice(SellingController): pluck="pos_closing_entry" ) if pos_closing_entry: - msg = _("To cancel a {} you need to cancel the POS Closing Entry {}. ").format( + msg = _("To cancel a {} you need to cancel the POS Closing Entry {}.").format( frappe.bold("Consolidated Sales Invoice"), get_link_to_form("POS Closing Entry", pos_closing_entry[0]) ) @@ -548,12 +548,12 @@ class SalesInvoice(SellingController): frappe.throw(_("Debit To is required"), title=_("Account Missing")) if account.report_type != "Balance Sheet": - msg = _("Please ensure {} account is a Balance Sheet account. ").format(frappe.bold("Debit To")) + msg = _("Please ensure {} account is a Balance Sheet account.").format(frappe.bold("Debit To")) + " " msg += _("You can change the parent account to a Balance Sheet account or select a different account.") frappe.throw(msg, title=_("Invalid Account")) if self.customer and account.account_type != "Receivable": - msg = _("Please ensure {} account is a Receivable account. ").format(frappe.bold("Debit To")) + msg = _("Please ensure {} account is a Receivable account.").format(frappe.bold("Debit To")) + " " msg += _("Change the account type to Receivable or select a different account.") frappe.throw(msg, title=_("Invalid Account")) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 34f7b27e00..b14c274515 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -482,7 +482,7 @@ class StockController(AccountsController): ) message += "

" rule_link = frappe.utils.get_link_to_form("Putaway Rule", rule) - message += _(" Please adjust the qty or edit {0} to proceed.").format(rule_link) + message += _("Please adjust the qty or edit {0} to proceed.").format(rule_link) return message def repost_future_sle_and_gle(self): diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index e7c83ac050..a3e23a6897 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -765,7 +765,7 @@ def get_items_for_material_requests(doc, warehouses=None): to_enable = frappe.bold(_("Ignore Existing Projected Quantity")) warehouse = frappe.bold(doc.get('for_warehouse')) message = _("As there are sufficient raw materials, Material Request is not required for Warehouse {0}.").format(warehouse) + "

" - message += _(" If you still want to proceed, please enable {0}.").format(to_enable) + message += _("If you still want to proceed, please enable {0}.").format(to_enable) frappe.msgprint(message, title=_("Note")) From 9229ee1745d864656ddee0b216a33078b7a932c6 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sat, 17 Apr 2021 15:41:10 +0530 Subject: [PATCH 062/326] fix: update shipment status in database Caught by semgrep rule: https://github.com/frappe/erpnext/blob/develop/.github/helper/semgrep_rules/frappe_correctness.yml#L4 --- erpnext/stock/doctype/shipment/shipment.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/shipment/shipment.py b/erpnext/stock/doctype/shipment/shipment.py index 4697a7b323..01fcee4cac 100644 --- a/erpnext/stock/doctype/shipment/shipment.py +++ b/erpnext/stock/doctype/shipment/shipment.py @@ -23,10 +23,10 @@ class Shipment(Document): frappe.throw(_('Please enter Shipment Parcel information')) if self.value_of_goods == 0: frappe.throw(_('Value of goods cannot be 0')) - self.status = 'Submitted' + self.db_set('status', 'Submitted') def on_cancel(self): - self.status = 'Cancelled' + self.db_set('status', 'Cancelled') def validate_weight(self): for parcel in self.shipment_parcel: From e972ceb79840783f3947a1856551d477154ec4be Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sat, 17 Apr 2021 15:42:03 +0530 Subject: [PATCH 063/326] fix: patch for updating shipment status --- erpnext/patches.txt | 1 + erpnext/patches/v13_0/update_shipment_status.py | 14 ++++++++++++++ 2 files changed, 15 insertions(+) create mode 100644 erpnext/patches/v13_0/update_shipment_status.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 112f6d8a83..620cc5be62 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -772,3 +772,4 @@ erpnext.patches.v12_0.purchase_receipt_status erpnext.patches.v13_0.fix_non_unique_represents_company erpnext.patches.v12_0.add_document_type_field_for_italy_einvoicing erpnext.patches.v13_0.make_non_standard_user_type #13-04-2021 +erpnext.patches.v13_0.update_shipment_status diff --git a/erpnext/patches/v13_0/update_shipment_status.py b/erpnext/patches/v13_0/update_shipment_status.py new file mode 100644 index 0000000000..c425599e26 --- /dev/null +++ b/erpnext/patches/v13_0/update_shipment_status.py @@ -0,0 +1,14 @@ +import frappe + +def execute(): + frappe.reload_doc("stock", "doctype", "shipment") + + # update submitted status + frappe.db.sql("""UPDATE `tabShipment` + SET status = "Submitted" + WHERE status = "Draft" AND docstatus = 1""") + + # update cancelled status + frappe.db.sql("""UPDATE `tabShipment` + SET status = "Cancelled" + WHERE status = "Draft" AND docstatus = 2""") From c28fcba77964edb57609fee17b0470638596e5a3 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sat, 17 Apr 2021 15:47:34 +0530 Subject: [PATCH 064/326] ci(semgrep): add correctness rule for on_cancel Changes done to doctype object in `on_submit` are not commited to database. Add rule to catch similar bugs. --- .../semgrep_rules/frappe_correctness.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/.github/helper/semgrep_rules/frappe_correctness.yml b/.github/helper/semgrep_rules/frappe_correctness.yml index 394abbf74d..54df062480 100644 --- a/.github/helper/semgrep_rules/frappe_correctness.yml +++ b/.github/helper/semgrep_rules/frappe_correctness.yml @@ -7,11 +7,29 @@ rules: - pattern-inside: | def on_submit(self, ...): ... + - metavariable-regex: + metavariable: '$ATTR' + # this is negative look-ahead, add more attrs to ignore like (ignore|ignore_this_too|ignore_me) + regex: '^(?!status_updater)(.*)$' message: | Doctype modified after submission. Please check if modification of self.$ATTR is commited to database. languages: [python] severity: ERROR +- id: frappe-modifying-after-cancel + patterns: + - pattern: self.$ATTR = ... + - pattern-inside: | + def on_cancel(self, ...): + ... + - metavariable-regex: + metavariable: '$ATTR' + regex: '^(?!ignore_linked_doctypes|status_updater)(.*)$' + message: | + Doctype modified after cancellation. Please check if modification of self.$ATTR is commited to database. + languages: [python] + severity: ERROR + - id: frappe-print-function-in-doctypes pattern: print(...) message: | From 80d44cada4248946ed6e0cc8e61b09d2612b1547 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Sun, 18 Apr 2021 18:33:34 +0200 Subject: [PATCH 065/326] fix: remove is_default from country wise tax --- erpnext/setup/setup_wizard/data/country_wise_tax.json | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/erpnext/setup/setup_wizard/data/country_wise_tax.json b/erpnext/setup/setup_wizard/data/country_wise_tax.json index 6305442ef2..5876488033 100644 --- a/erpnext/setup/setup_wizard/data/country_wise_tax.json +++ b/erpnext/setup/setup_wizard/data/country_wise_tax.json @@ -486,7 +486,6 @@ "sales_tax_templates": [ { "title": "Umsatzsteuer 19%", - "is_default": 1, "taxes": [ { "account_head": { @@ -513,7 +512,6 @@ "purchase_tax_templates": [ { "title": "Abziehbare Vorsteuer 19%", - "is_default": 1, "taxes": [ { "account_head": { @@ -567,7 +565,6 @@ "sales_tax_templates": [ { "title": "Umsatzsteuer 19%", - "is_default": 1, "taxes": [ { "account_head": { @@ -594,7 +591,6 @@ "purchase_tax_templates": [ { "title": "Abziehbare Vorsteuer 19%", - "is_default": 1, "taxes": [ { "account_head": { @@ -625,7 +621,6 @@ "sales_tax_templates": [ { "title": "Umsatzsteuer 19%", - "is_default": 1, "taxes": [ { "account_head": { @@ -652,7 +647,6 @@ "purchase_tax_templates": [ { "title": "Abziehbare Vorsteuer 19%", - "is_default": 1, "taxes": [ { "account_head": { @@ -683,7 +677,6 @@ "sales_tax_templates": [ { "title": "Umsatzsteuer 19%", - "is_default": 1, "taxes": [ { "account_head": { @@ -708,7 +701,6 @@ "purchase_tax_templates": [ { "title": "Abziehbare Vorsteuer 19%", - "is_default": 1, "taxes": [ { "account_head": { @@ -829,7 +821,6 @@ "item_tax_templates": [ { "title": "In State GST", - "is_default": 1, "taxes": [ { "tax_type": { @@ -893,7 +884,6 @@ "*": [ { "title": "In State GST", - "is_default": 1, "taxes": [ { "account_head": { From e78253152916ef8a16a467445d670dcd03bd6c83 Mon Sep 17 00:00:00 2001 From: Saqib Date: Mon, 19 Apr 2021 10:15:51 +0530 Subject: [PATCH 066/326] fix: Apply single transaction threshold on net_total instead of supplier credit amount (#25243) * fix: Apply single transaction threshold on net_total instead of supplier credit amount * fix: Apply single transaction threshold on net_total instead of supplier credit amount * fix: test Co-authored-by: Nabin Hait --- .../tax_withholding_category.py | 2 +- .../test_tax_withholding_category.py | 44 ------------------- 2 files changed, 1 insertion(+), 45 deletions(-) diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py index 961bdb147f..09db7fee2b 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -251,7 +251,7 @@ def get_tds_amount(ldc, parties, inv, tax_details, fiscal_year_details, tax_dedu threshold = tax_details.get('threshold', 0) cumulative_threshold = tax_details.get('cumulative_threshold', 0) - if ((threshold and supp_credit_amt >= threshold) or (cumulative_threshold and supp_credit_amt >= cumulative_threshold)): + if ((threshold and inv.net_total >= threshold) or (cumulative_threshold and supp_credit_amt >= cumulative_threshold)): if ldc and is_valid_certificate( ldc.valid_from, ldc.valid_upto, inv.posting_date, tax_deducted, diff --git a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py index dd3b49aa04..0cea7612dd 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py @@ -87,50 +87,6 @@ class TestTaxWithholdingCategory(unittest.TestCase): for d in invoices: d.cancel() - def test_single_threshold_tds_with_previous_vouchers(self): - invoices = [] - frappe.db.set_value("Supplier", "Test TDS Supplier2", "tax_withholding_category", "Single Threshold TDS") - pi = create_purchase_invoice(supplier="Test TDS Supplier2") - pi.submit() - invoices.append(pi) - - pi = create_purchase_invoice(supplier="Test TDS Supplier2") - pi.submit() - invoices.append(pi) - - self.assertEqual(pi.taxes_and_charges_deducted, 2000) - self.assertEqual(pi.grand_total, 8000) - - # delete invoices to avoid clashing - for d in invoices: - d.cancel() - - def test_single_threshold_tds_with_previous_vouchers_and_no_tds(self): - invoices = [] - doc = create_supplier(supplier_name = "Test TDS Supplier ABC", - tax_withholding_category="Single Threshold TDS") - supplier = doc.name - - pi = create_purchase_invoice(supplier=supplier) - pi.submit() - invoices.append(pi) - - # TDS not applied - pi = create_purchase_invoice(supplier=supplier, do_not_apply_tds=True) - pi.submit() - invoices.append(pi) - - pi = create_purchase_invoice(supplier=supplier) - pi.submit() - invoices.append(pi) - - self.assertEqual(pi.taxes_and_charges_deducted, 2000) - self.assertEqual(pi.grand_total, 8000) - - # delete invoices to avoid clashing - for d in invoices: - d.cancel() - def test_cumulative_threshold_tcs(self): frappe.db.set_value("Customer", "Test TCS Customer", "tax_withholding_category", "Cumulative Threshold TCS") invoices = [] From 7eac4a250d1d156859f9c0d18a0f45c8bcc5215d Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 19 Apr 2021 10:33:39 +0530 Subject: [PATCH 067/326] fix: functions using mutable defaults (#25370) --- erpnext/controllers/queries.py | 4 +++- erpnext/controllers/status_updater.py | 4 +++- .../doctype/bom_update_tool/bom_update_tool.py | 6 ++++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index c0c13153de..bc1ac5ea06 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -713,7 +713,9 @@ def get_tax_template(doctype, txt, searchfield, start, page_len, filters): return [(d,) for d in set(taxes)] -def get_fields(doctype, fields=[]): +def get_fields(doctype, fields=None): + if fields is None: + fields = [] meta = frappe.get_meta(doctype) fields.extend(meta.get_search_fields()) diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py index 0987d0985e..cdb6d244a6 100644 --- a/erpnext/controllers/status_updater.py +++ b/erpnext/controllers/status_updater.py @@ -371,10 +371,12 @@ class StatusUpdater(Document): ref_doc.db_set("per_billed", per_billed) ref_doc.set_status(update=True) -def get_allowance_for(item_code, item_allowance={}, global_qty_allowance=None, global_amount_allowance=None, qty_or_amount="qty"): +def get_allowance_for(item_code, item_allowance=None, global_qty_allowance=None, global_amount_allowance=None, qty_or_amount="qty"): """ Returns the allowance for the item, if not set, returns global allowance """ + if item_allowance is None: + item_allowance = {} if qty_or_amount == "qty": if item_allowance.get(item_code, frappe._dict()).get("qty"): return item_allowance[item_code].qty, item_allowance, global_qty_allowance, global_amount_allowance diff --git a/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py b/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py index 742d18c4cd..8fbcd4ea1d 100644 --- a/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py +++ b/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py @@ -53,7 +53,9 @@ class BOMUpdateTool(Document): rate=%s, amount=stock_qty*%s where bom_no = %s and docstatus < 2 and parenttype='BOM'""", (self.new_bom, unit_cost, unit_cost, self.current_bom)) - def get_parent_boms(self, bom, bom_list=[]): + def get_parent_boms(self, bom, bom_list=None): + if bom_list is None: + bom_list = [] data = frappe.db.sql("""SELECT DISTINCT parent FROM `tabBOM Item` WHERE bom_no = %s AND docstatus < 2 AND parenttype='BOM'""", bom) @@ -106,4 +108,4 @@ def update_cost(): for bom in bom_list: frappe.get_doc("BOM", bom).update_cost(update_parent=False, from_child_bom=True) - frappe.db.auto_commit_on_many_writes = 0 \ No newline at end of file + frappe.db.auto_commit_on_many_writes = 0 From dcdd3bebbe3db01ee2987843d4bd4ca72cd913e5 Mon Sep 17 00:00:00 2001 From: Jannat Patel <31363128+pateljannat@users.noreply.github.com> Date: Mon, 19 Apr 2021 10:36:40 +0530 Subject: [PATCH 068/326] feat: Timer in LMS Quiz (#24246) * feat: new fields in quiz doctypes * feat: timer in lms quiz * fix: variable initialisation * fix: context, exception fix * fix:sider * fix:sider * fix: indentation * fix: timer * fix: sider * fix: return value and format * fix: show time taken only after all attempts are over * fix: sider Co-authored-by: pateljannat Co-authored-by: Marica --- .../course_enrollment/course_enrollment.py | 5 +- erpnext/education/doctype/quiz/quiz.json | 25 +- .../doctype/quiz_activity/quiz_activity.json | 423 ++---------------- erpnext/education/doctype/student/student.py | 2 +- erpnext/education/utils.py | 33 +- erpnext/public/js/education/lms/quiz.js | 72 ++- erpnext/www/lms/content.html | 55 ++- erpnext/www/lms/index.html | 12 +- erpnext/www/lms/topic.py | 2 +- 9 files changed, 218 insertions(+), 411 deletions(-) diff --git a/erpnext/education/doctype/course_enrollment/course_enrollment.py b/erpnext/education/doctype/course_enrollment/course_enrollment.py index f7aa6e9fc1..2b3acf1b93 100644 --- a/erpnext/education/doctype/course_enrollment/course_enrollment.py +++ b/erpnext/education/doctype/course_enrollment/course_enrollment.py @@ -41,7 +41,7 @@ class CourseEnrollment(Document): frappe.throw(_("Student is already enrolled via Course Enrollment {0}").format( get_link_to_form("Course Enrollment", enrollment)), title=_('Duplicate Entry')) - def add_quiz_activity(self, quiz_name, quiz_response, answers, score, status): + def add_quiz_activity(self, quiz_name, quiz_response, answers, score, status, time_taken): result = {k: ('Correct' if v else 'Wrong') for k,v in answers.items()} result_data = [] for key in answers: @@ -66,7 +66,8 @@ class CourseEnrollment(Document): "activity_date": frappe.utils.datetime.datetime.now(), "result": result_data, "score": score, - "status": status + "status": status, + "time_taken": time_taken }).insert(ignore_permissions = True) def add_activity(self, content_type, content): diff --git a/erpnext/education/doctype/quiz/quiz.json b/erpnext/education/doctype/quiz/quiz.json index 569c281f4c..16d7d7e4bf 100644 --- a/erpnext/education/doctype/quiz/quiz.json +++ b/erpnext/education/doctype/quiz/quiz.json @@ -1,4 +1,5 @@ { + "actions": [], "allow_import": 1, "allow_rename": 1, "autoname": "field:title", @@ -12,7 +13,10 @@ "quiz_configuration_section", "passing_score", "max_attempts", - "grading_basis" + "grading_basis", + "column_break_7", + "is_time_bound", + "duration" ], "fields": [ { @@ -58,9 +62,26 @@ "fieldtype": "Select", "label": "Grading Basis", "options": "Latest Highest Score\nLatest Attempt" + }, + { + "default": "0", + "fieldname": "is_time_bound", + "fieldtype": "Check", + "label": "Is Time-Bound" + }, + { + "depends_on": "is_time_bound", + "fieldname": "duration", + "fieldtype": "Duration", + "label": "Duration" + }, + { + "fieldname": "column_break_7", + "fieldtype": "Column Break" } ], - "modified": "2019-06-12 12:23:57.020508", + "links": [], + "modified": "2020-12-24 15:41:35.043262", "modified_by": "Administrator", "module": "Education", "name": "Quiz", diff --git a/erpnext/education/doctype/quiz_activity/quiz_activity.json b/erpnext/education/doctype/quiz_activity/quiz_activity.json index e78db42f7d..742c88754a 100644 --- a/erpnext/education/doctype/quiz_activity/quiz_activity.json +++ b/erpnext/education/doctype/quiz_activity/quiz_activity.json @@ -1,490 +1,163 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, + "actions": [], "autoname": "format:EDU-QA-{YYYY}-{#####}", "beta": 1, "creation": "2018-10-15 15:48:40.482821", - "custom": 0, - "docstatus": 0, "doctype": "DocType", - "document_type": "", "editable_grid": 1, "engine": "InnoDB", + "field_order": [ + "enrollment", + "student", + "column_break_3", + "course", + "section_break_5", + "quiz", + "column_break_7", + "status", + "section_break_9", + "result", + "section_break_11", + "activity_date", + "score", + "column_break_14", + "time_taken" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "enrollment", "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": "Enrollment", - "length": 0, - "no_copy": 0, "options": "Course Enrollment", - "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": 1, - "translatable": 0, - "unique": 0 + "set_only_once": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fetch_from": "enrollment.student", "fieldname": "student", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, "in_list_view": 1, - "in_standard_filter": 0, "label": "Student", - "length": 0, - "no_copy": 0, "options": "Student", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "column_break_3", - "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 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fetch_from": "enrollment.course", "fieldname": "course", "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": "Course", - "length": 0, - "no_copy": 0, "options": "Course", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 1, - "translatable": 0, - "unique": 0 + "set_only_once": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "section_break_5", - "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 + "fieldtype": "Section Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "quiz", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, "in_list_view": 1, - "in_standard_filter": 0, "label": "Quiz", - "length": 0, - "no_copy": 0, "options": "Quiz", - "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": 1, - "translatable": 0, - "unique": 0 + "set_only_once": 1 }, { - "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 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "status", "fieldtype": "Select", - "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": "Status", - "length": 0, - "no_copy": 0, "options": "\nPass\nFail", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "section_break_9", - "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 + "fieldtype": "Section Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "result", "fieldtype": "Table", - "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": "Result", - "length": 0, - "no_copy": 0, "options": "Quiz Result", - "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": 1, - "translatable": 0, - "unique": 0 + "set_only_once": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "activity_date", "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": "Activity 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": 1, - "translatable": 0, - "unique": 0 + "set_only_once": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "score", "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, "in_list_view": 1, - "in_standard_filter": 0, "label": "Score", - "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": 1, - "translatable": 0, - "unique": 0 + "set_only_once": 1 + }, + { + "fieldname": "time_taken", + "fieldtype": "Duration", + "label": "Time Taken", + "set_only_once": 1 + }, + { + "fieldname": "section_break_11", + "fieldtype": "Section Break" + }, + { + "fieldname": "column_break_14", + "fieldtype": "Column Break" } ], - "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-11-25 19:05:52.434437", + "links": [], + "modified": "2020-12-24 15:41:20.085380", "modified_by": "Administrator", "module": "Education", "name": "Quiz Activity", - "name_case": "", "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": "Academics User", - "set_user_permissions": 0, "share": 1, - "submit": 0, "write": 1 }, { - "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": "LMS User", - "set_user_permissions": 0, "share": 1, - "submit": 0, "write": 1 }, { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, "email": 1, "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, "print": 1, "read": 1, "report": 1, "role": "Instructor", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 0 + "share": 1 } ], "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, "sort_field": "modified", "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/education/doctype/student/student.py b/erpnext/education/doctype/student/student.py index 81626f1918..2dc0f634f0 100644 --- a/erpnext/education/doctype/student/student.py +++ b/erpnext/education/doctype/student/student.py @@ -114,7 +114,7 @@ class Student(Document): status = check_content_completion(content.name, content.doctype, course_enrollment_name) progress.append({'content': content.name, 'content_type': content.doctype, 'is_complete': status}) elif content.doctype == 'Quiz': - status, score, result = check_quiz_completion(content, course_enrollment_name) + status, score, result, time_taken = check_quiz_completion(content, course_enrollment_name) progress.append({'content': content.name, 'content_type': content.doctype, 'is_complete': status, 'score': score, 'result': result}) return progress diff --git a/erpnext/education/utils.py b/erpnext/education/utils.py index cffc3960a0..8f51fef847 100644 --- a/erpnext/education/utils.py +++ b/erpnext/education/utils.py @@ -194,7 +194,7 @@ def add_activity(course, content_type, content, program): return enrollment.add_activity(content_type, content) @frappe.whitelist() -def evaluate_quiz(quiz_response, quiz_name, course, program): +def evaluate_quiz(quiz_response, quiz_name, course, program, time_taken): import json student = get_current_student() @@ -209,7 +209,7 @@ def evaluate_quiz(quiz_response, quiz_name, course, program): if student: enrollment = get_or_create_course_enrollment(course, program) if quiz.allowed_attempt(enrollment, quiz_name): - enrollment.add_quiz_activity(quiz_name, quiz_response, result, score, status) + enrollment.add_quiz_activity(quiz_name, quiz_response, result, score, status, time_taken) return {'result': result, 'score': score, 'status': status} else: return None @@ -219,8 +219,9 @@ def get_quiz(quiz_name, course): try: quiz = frappe.get_doc("Quiz", quiz_name) questions = quiz.get_questions() + duration = quiz.duration except: - frappe.throw(_("Quiz {0} does not exist").format(quiz_name)) + frappe.throw(_("Quiz {0} does not exist").format(quiz_name), frappe.DoesNotExistError) return None questions = [{ @@ -232,12 +233,20 @@ def get_quiz(quiz_name, course): } for question in questions] if has_super_access(): - return {'questions': questions, 'activity': None} + return { + 'questions': questions, + 'activity': None, + 'duration':duration + } student = get_current_student() course_enrollment = get_enrollment("course", course, student.name) - status, score, result = check_quiz_completion(quiz, course_enrollment) - return {'questions': questions, 'activity': {'is_complete': status, 'score': score, 'result': result}} + status, score, result, time_taken = check_quiz_completion(quiz, course_enrollment) + return { + 'questions': questions, + 'activity': {'is_complete': status, 'score': score, 'result': result, 'time_taken': time_taken}, + 'duration': quiz.duration + } def get_topic_progress(topic, course_name, program): """ @@ -361,15 +370,23 @@ def check_content_completion(content_name, content_type, enrollment_name): return False def check_quiz_completion(quiz, enrollment_name): - attempts = frappe.get_all("Quiz Activity", filters={'enrollment': enrollment_name, 'quiz': quiz.name}, fields=["name", "activity_date", "score", "status"]) + attempts = frappe.get_all("Quiz Activity", + filters={ + 'enrollment': enrollment_name, + 'quiz': quiz.name + }, + fields=["name", "activity_date", "score", "status", "time_taken"] + ) status = False if quiz.max_attempts == 0 else bool(len(attempts) >= quiz.max_attempts) score = None result = None + time_taken = None if attempts: if quiz.grading_basis == 'Last Highest Score': attempts = sorted(attempts, key = lambda i: int(i.score), reverse=True) score = attempts[0]['score'] result = attempts[0]['status'] + time_taken = attempts[0]['time_taken'] if result == 'Pass': status = True - return status, score, result \ No newline at end of file + return status, score, result, time_taken \ No newline at end of file diff --git a/erpnext/public/js/education/lms/quiz.js b/erpnext/public/js/education/lms/quiz.js index 4a9d1e34e6..32fa4ab1ec 100644 --- a/erpnext/public/js/education/lms/quiz.js +++ b/erpnext/public/js/education/lms/quiz.js @@ -20,6 +20,16 @@ class Quiz { } make(data) { + if (data.duration) { + const timer_display = document.createElement("div"); + timer_display.classList.add("lms-timer", "float-right", "font-weight-bold"); + document.getElementsByClassName("lms-title")[0].appendChild(timer_display); + if (!data.activity || (data.activity && !data.activity.is_complete)) { + this.initialiseTimer(data.duration); + this.is_time_bound = true; + this.time_taken = 0; + } + } data.questions.forEach(question_data => { let question_wrapper = document.createElement('div'); let question = new Question({ @@ -37,12 +47,51 @@ class Quiz { indicator = 'green' message = 'You have already cleared the quiz.' } - + if (data.activity.time_taken) { + this.calculate_and_display_time(data.activity.time_taken, "Time Taken - "); + } this.set_quiz_footer(message, indicator, data.activity.score) } else { this.make_actions(); } + window.addEventListener('beforeunload', (event) => { + event.preventDefault(); + event.returnValue = ''; + }); + } + + initialiseTimer(duration) { + this.time_left = duration; + var self = this; + var old_diff; + this.calculate_and_display_time(this.time_left, "Time Left - "); + this.start_time = new Date().getTime(); + this.timer = setInterval(function () { + var diff = (new Date().getTime() - self.start_time)/1000; + var variation = old_diff ? diff - old_diff : diff; + old_diff = diff; + self.time_left -= variation; + self.time_taken += variation; + self.calculate_and_display_time(self.time_left, "Time Left - "); + if (self.time_left <= 0) { + clearInterval(self.timer); + self.time_taken -= 1; + self.submit(); + } + }, 1000); + } + + calculate_and_display_time(second, text) { + var timer_display = document.getElementsByClassName("lms-timer")[0]; + var hours = this.append_zero(Math.floor(second / 3600)); + var minutes = this.append_zero(Math.floor(second % 3600 / 60)); + var seconds = this.append_zero(Math.ceil(second % 3600 % 60)); + timer_display.innerText = text + hours + ":" + minutes + ":" + seconds; + } + + append_zero(time) { + return time > 9 ? time : "0" + time; } make_actions() { @@ -57,6 +106,10 @@ class Quiz { } submit() { + if (this.is_time_bound) { + clearInterval(this.timer); + $(".lms-timer").text(""); + } this.submit_btn.innerText = 'Evaluating..' this.submit_btn.disabled = true this.disable() @@ -64,7 +117,8 @@ class Quiz { quiz_name: this.name, quiz_response: this.get_selected(), course: this.course, - program: this.program + program: this.program, + time_taken: this.is_time_bound ? this.time_taken : "" }).then(res => { this.submit_btn.remove() if (!res.message) { @@ -157,7 +211,7 @@ class Question { return input; } - let make_label = function(name, value) { + let make_label = function (name, value) { let label = document.createElement('label'); label.classList.add('form-check-label'); label.htmlFor = name; @@ -166,14 +220,14 @@ class Question { } let make_option = function (wrapper, option) { - let option_div = document.createElement('div') - option_div.classList.add('form-check', 'pb-1') + let option_div = document.createElement('div'); + option_div.classList.add('form-check', 'pb-1'); let input = make_input(option.name, option.option); let label = make_label(option.name, option.option); - option_div.appendChild(input) - option_div.appendChild(label) - wrapper.appendChild(option_div) - return {input: input, ...option} + option_div.appendChild(input); + option_div.appendChild(label); + wrapper.appendChild(option_div); + return { input: input, ...option }; } let options_wrapper = document.createElement('div') diff --git a/erpnext/www/lms/content.html b/erpnext/www/lms/content.html index dc9b6d80fb..15afb097b9 100644 --- a/erpnext/www/lms/content.html +++ b/erpnext/www/lms/content.html @@ -62,7 +62,7 @@ {{_('Back to Course')}} -
+

{{ content.name }} ({{ position + 1 }}/{{length}})

{% endmacro %} @@ -169,14 +169,51 @@ const next_url = '/lms/course?name={{ course }}&program={{ program }}' {% endif %} frappe.ready(() => { - const quiz = new Quiz(document.getElementById('quiz-wrapper'), { - name: '{{ content.name }}', - course: '{{ course }}', - program: '{{ program }}', - quiz_exit_button: quiz_exit_button, - next_url: next_url - }) - window.quiz = quiz; + {% if content.is_time_bound %} + var duration = get_duration("{{content.duration}}") + var d = frappe.msgprint({ + title: __('Important Notice'), + indicator: "red", + message: __(`This is a Time-Bound Quiz.

+ A timer for ${duration} will start, once you click on Proceed.

+ If you fail to submit before the time is up, the Quiz will be submitted automatically.`), + primary_action: { + label: __("Proceed"), + action: () => { + create_quiz(); + d.hide(); + } + }, + secondary_action: { + action: () => { + d.hide(); + window.location.href = "/lms/course?name={{ course }}&program={{ program }}"; + }, + label: __("Go Back"), + } + }); + {% else %} + create_quiz(); + {% endif %} + function create_quiz() { + const quiz = new Quiz(document.getElementById('quiz-wrapper'), { + name: '{{ content.name }}', + course: '{{ course }}', + program: '{{ program }}', + quiz_exit_button: quiz_exit_button, + next_url: next_url + }) + window.quiz = quiz; + } + function get_duration(seconds){ + var hours = append_zero(Math.floor(seconds / 3600)); + var minutes = append_zero(Math.floor(seconds % 3600 / 60)); + var seconds = append_zero(Math.floor(seconds % 3600 % 60)); + return `${hours}:${minutes}:${seconds}`; + } + function append_zero(time) { + return time > 9 ? time : "0" + time; + } }) {% endif %} diff --git a/erpnext/www/lms/index.html b/erpnext/www/lms/index.html index 7b239acd56..c1e96205eb 100644 --- a/erpnext/www/lms/index.html +++ b/erpnext/www/lms/index.html @@ -42,7 +42,9 @@

{{ education_settings.portal_title }}

-

{{ education_settings.description }}

+ {% if education_settings.description %} +

{{ education_settings.description }}

+ {% endif %}

{% if frappe.session.user == 'Guest' %} {{_('Sign Up')}} @@ -51,13 +53,15 @@

- {% for program in featured_programs %} - {{ program_card(program.program, program.has_access) }} - {% endfor %} {% if featured_programs %} + {% for program in featured_programs %} + {{ program_card(program.program, program.has_access) }} + {% endfor %} {% for n in range( (3 - (featured_programs|length)) %3) %} {{ null_card() }} {% endfor %} + {% else %} +

You have not enrolled in any program. Contact your Instructor.

{% endif %}
diff --git a/erpnext/www/lms/topic.py b/erpnext/www/lms/topic.py index f75ae8e9b6..8abbc72e91 100644 --- a/erpnext/www/lms/topic.py +++ b/erpnext/www/lms/topic.py @@ -35,7 +35,7 @@ def get_contents(topic, course, program): progress.append({'content': content, 'content_type': content.doctype, 'completed': status}) elif content.doctype == 'Quiz': if student: - status, score, result = utils.check_quiz_completion(content, course_enrollment.name) + status, score, result, time_taken = utils.check_quiz_completion(content, course_enrollment.name) else: status = False score = None From e8bc912ffcafeccfd41c8944f6ac053a68d9ac56 Mon Sep 17 00:00:00 2001 From: Marica Date: Mon, 19 Apr 2021 11:05:21 +0530 Subject: [PATCH 069/326] perf: Fetching exchange rate on every line item slows down PO (#25345) * fix: Dont fetch exchange rates for each line item once fetched at parent ` * perf: Use price list conversion rate from parent - If price list conversion rate exists in args already from earlier call, use that - `get_price_list_currency_and_exchange_rate` wont be called for each child row Co-authored-by: Nabin Hait --- erpnext/public/js/controllers/transaction.js | 2 ++ erpnext/stock/get_item_details.py | 12 +++++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 6c2144d6cb..a0398e718f 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1103,6 +1103,8 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ to_currency: to_currency, args: args }, + freeze: true, + freeze_message: __("Fetching exchange rates ..."), callback: function(r) { callback(flt(r.message)); } diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index dedfe1d79b..1a61f30b9a 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -609,8 +609,12 @@ def get_price_list_rate(args, item_doc, out): meta = frappe.get_meta(args.parenttype or args.doctype) if meta.get_field("currency") or args.get('currency'): - pl_details = get_price_list_currency_and_exchange_rate(args) - args.update(pl_details) + if not args.get("price_list_currency") or not args.get("plc_conversion_rate"): + # if currency and plc_conversion_rate exist then + # `get_price_list_currency_and_exchange_rate` has already been called + pl_details = get_price_list_currency_and_exchange_rate(args) + args.update(pl_details) + if meta.get_field("currency"): validate_conversion_rate(args, meta) @@ -1000,6 +1004,8 @@ def apply_price_list(args, as_doc=False): args = process_args(args) parent = get_price_list_currency_and_exchange_rate(args) + args.update(parent) + children = [] if "items" in args: @@ -1064,7 +1070,7 @@ def get_price_list_currency_and_exchange_rate(args): return frappe._dict({ "price_list_currency": price_list_currency, "price_list_uom_dependant": price_list_uom_dependant, - "plc_conversion_rate": plc_conversion_rate + "plc_conversion_rate": plc_conversion_rate or 1 }) @frappe.whitelist() From 9c9907cf8e26ea65d330f1fec07d01afa2038017 Mon Sep 17 00:00:00 2001 From: Afshan <33727827+AfshanKhan@users.noreply.github.com> Date: Mon, 19 Apr 2021 11:48:28 +0530 Subject: [PATCH 070/326] fix: available employee for selection (#25377) * fix: available employee for selection * fix: available employee for selection fix: available employee for selection --- .../doctype/payroll_entry/payroll_entry.js | 4 + .../doctype/payroll_entry/payroll_entry.py | 153 ++++++++++-------- 2 files changed, 88 insertions(+), 69 deletions(-) diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.js b/erpnext/payroll/doctype/payroll_entry/payroll_entry.js index 85bb651af7..f2892600d1 100644 --- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.js +++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.js @@ -151,6 +151,10 @@ frappe.ui.form.on('Payroll Entry', { 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; diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py index 4c9469e277..3953b463f1 100644 --- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py +++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py @@ -52,49 +52,32 @@ class PayrollEntry(Document): Returns list of active employees based on selected criteria and for which salary structure exists """ - cond = self.get_filter_condition() - cond += self.get_joining_relieving_condition() + self.check_mandatory() + filters = self.make_filters() + cond = get_filter_condition(filters) + cond += get_joining_relieving_condition(self.start_date, self.end_date) condition = '' if self.payroll_frequency: condition = """and payroll_frequency = '%(payroll_frequency)s'"""% {"payroll_frequency": self.payroll_frequency} - sal_struct = frappe.db.sql_list(""" - select - name from `tabSalary Structure` - where - docstatus = 1 and - is_active = 'Yes' - and company = %(company)s - and currency = %(currency)s and - ifnull(salary_slip_based_on_timesheet,0) = %(salary_slip_based_on_timesheet)s - {condition}""".format(condition=condition), - {"company": self.company, "currency": self.currency, "salary_slip_based_on_timesheet":self.salary_slip_based_on_timesheet}) - + sal_struct = get_sal_struct(self.company, self.currency, self.salary_slip_based_on_timesheet, condition) if sal_struct: cond += "and t2.salary_structure IN %(sal_struct)s " cond += "and t2.payroll_payable_account = %(payroll_payable_account)s " cond += "and %(from_date)s >= t2.from_date" - emp_list = frappe.db.sql(""" - select - distinct t1.name as employee, t1.employee_name, t1.department, t1.designation - from - `tabEmployee` t1, `tabSalary Structure Assignment` t2 - where - t1.name = t2.employee - and t2.docstatus = 1 - %s order by t2.from_date desc - """ % cond, {"sal_struct": tuple(sal_struct), "from_date": self.end_date, "payroll_payable_account": self.payroll_payable_account}, as_dict=True) - - emp_list = self.remove_payrolled_employees(emp_list) + emp_list = get_emp_list(sal_struct, cond, self.end_date, self.payroll_payable_account) + emp_list = remove_payrolled_employees(emp_list, self.start_date, self.end_date) return emp_list - def remove_payrolled_employees(self, emp_list): - for employee_details in emp_list: - if frappe.db.exists("Salary Slip", {"employee": employee_details.employee, "start_date": self.start_date, "end_date": self.end_date, "docstatus": 1}): - emp_list.remove(employee_details) + def make_filters(self): + filters = frappe._dict() + filters['company'] = self.company + filters['branch'] = self.branch + filters['department'] = self.department + filters['designation'] = self.designation - return emp_list + return filters @frappe.whitelist() def fill_employee_details(self): @@ -122,23 +105,6 @@ class PayrollEntry(Document): if self.validate_attendance: return self.validate_employee_attendance() - def get_filter_condition(self): - self.check_mandatory() - - cond = '' - for f in ['company', 'branch', 'department', 'designation']: - if self.get(f): - cond += " and t1." + f + " = " + frappe.db.escape(self.get(f)) - - return cond - - def get_joining_relieving_condition(self): - cond = """ - and ifnull(t1.date_of_joining, '0000-00-00') <= '%(end_date)s' - and ifnull(t1.relieving_date, '2199-12-31') >= '%(start_date)s' - """ % {"start_date": self.start_date, "end_date": self.end_date} - return cond - def check_mandatory(self): for fieldname in ['company', 'start_date', 'end_date']: if not self.get(fieldname): @@ -451,6 +417,53 @@ class PayrollEntry(Document): marked_days = attendances[0][0] return marked_days +def get_sal_struct(company, currency, salary_slip_based_on_timesheet, condition): + return frappe.db.sql_list(""" + select + name from `tabSalary Structure` + where + docstatus = 1 and + is_active = 'Yes' + and company = %(company)s + and currency = %(currency)s and + ifnull(salary_slip_based_on_timesheet,0) = %(salary_slip_based_on_timesheet)s + {condition}""".format(condition=condition), + {"company": company, "currency": currency, "salary_slip_based_on_timesheet": salary_slip_based_on_timesheet}) + +def get_filter_condition(filters): + cond = '' + for f in ['company', 'branch', 'department', 'designation']: + if filters.get(f): + cond += " and t1." + f + " = " + frappe.db.escape(filters.get(f)) + + return cond + +def get_joining_relieving_condition(start_date, end_date): + cond = """ + and ifnull(t1.date_of_joining, '0000-00-00') <= '%(end_date)s' + and ifnull(t1.relieving_date, '2199-12-31') >= '%(start_date)s' + """ % {"start_date": start_date, "end_date": end_date} + return cond + +def get_emp_list(sal_struct, cond, end_date, payroll_payable_account): + return frappe.db.sql(""" + select + distinct t1.name as employee, t1.employee_name, t1.department, t1.designation + from + `tabEmployee` t1, `tabSalary Structure Assignment` t2 + where + t1.name = t2.employee + and t2.docstatus = 1 + %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) + +def remove_payrolled_employees(emp_list, start_date, end_date): + for employee_details in emp_list: + if frappe.db.exists("Salary Slip", {"employee": employee_details.employee, "start_date": start_date, "end_date": end_date, "docstatus": 1}): + emp_list.remove(employee_details) + + return emp_list + @frappe.whitelist() def get_start_end_dates(payroll_frequency, start_date=None, company=None): '''Returns dict of start and end dates for given payroll frequency based on start_date''' @@ -639,39 +652,41 @@ def get_payroll_entries_for_jv(doctype, txt, searchfield, start, page_len, filte 'start': start, 'page_len': page_len }) -def get_employee_with_existing_salary_slip(start_date, end_date, company): - return frappe.db.sql_list(""" - select employee from `tabSalary Slip` - where - (start_date between %(start_date)s and %(end_date)s - or - end_date between %(start_date)s and %(end_date)s - or - %(start_date)s between start_date and end_date) - and company = %(company)s - and docstatus = 1 - """, {'start_date': start_date, 'end_date': end_date, 'company': company}) +def get_employee_list(filters): + cond = get_filter_condition(filters) + cond += get_joining_relieving_condition(filters.start_date, filters.end_date) + condition = """and payroll_frequency = '%(payroll_frequency)s'"""% {"payroll_frequency": filters.payroll_frequency} + sal_struct = get_sal_struct(filters.company, filters.currency, filters.salary_slip_based_on_timesheet, condition) + if sal_struct: + cond += "and t2.salary_structure IN %(sal_struct)s " + cond += "and t2.payroll_payable_account = %(payroll_payable_account)s " + cond += "and %(from_date)s >= t2.from_date" + emp_list = get_emp_list(sal_struct, cond, filters.end_date, filters.payroll_payable_account) + emp_list = remove_payrolled_employees(emp_list, filters.start_date, filters.end_date) + return emp_list @frappe.whitelist() @frappe.validate_and_sanitize_search_inputs def employee_query(doctype, txt, searchfield, start, page_len, filters): filters = frappe._dict(filters) conditions = [] - exclude_employees = [] + include_employees = [] emp_cond = '' if filters.start_date and filters.end_date: - employee_list = get_employee_with_existing_salary_slip(filters.start_date, filters.end_date, filters.company) + employee_list = get_employee_list(filters) emp = filters.get('employees') + include_employees = [employee.employee for employee in employee_list if employee.employee not in emp] filters.pop('start_date') filters.pop('end_date') + filters.pop('salary_slip_based_on_timesheet') + filters.pop('payroll_frequency') + filters.pop('payroll_payable_account') + filters.pop('currency') if filters.employees is not None: filters.pop('employees') - if employee_list: - exclude_employees.extend(employee_list) - if emp: - exclude_employees.extend(emp) - if exclude_employees: - emp_cond += 'and employee not in %(exclude_employees)s' + + if include_employees: + emp_cond += 'and employee in %(include_employees)s' return frappe.db.sql("""select name, employee_name from `tabEmployee` where status = 'Active' @@ -695,4 +710,4 @@ def employee_query(doctype, txt, searchfield, start, page_len, filters): '_txt': txt.replace("%", ""), 'start': start, 'page_len': page_len, - 'exclude_employees': exclude_employees}) + 'include_employees': include_employees}) From b6d2106184ea3420425609c2402388c7a2cacb9b Mon Sep 17 00:00:00 2001 From: Afshan <33727827+AfshanKhan@users.noreply.github.com> Date: Mon, 19 Apr 2021 11:48:43 +0530 Subject: [PATCH 071/326] fix: available employee for selection (#25378) * fix: available employee for selection * fix: available employee for selection fix: available employee for selection --- .../doctype/payroll_entry/payroll_entry.js | 4 + .../doctype/payroll_entry/payroll_entry.py | 148 +++++++++--------- 2 files changed, 81 insertions(+), 71 deletions(-) diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.js b/erpnext/payroll/doctype/payroll_entry/payroll_entry.js index 85bb651af7..f2892600d1 100644 --- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.js +++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.js @@ -151,6 +151,10 @@ frappe.ui.form.on('Payroll Entry', { 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; diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py index 4c9469e277..b031129614 100644 --- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py +++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py @@ -52,50 +52,24 @@ class PayrollEntry(Document): Returns list of active employees based on selected criteria and for which salary structure exists """ - cond = self.get_filter_condition() - cond += self.get_joining_relieving_condition() + self.check_mandatory() + filters = self.make_filters() + cond = get_filter_condition(filters) + cond += get_joining_relieving_condition(self.start_date, self.end_date) condition = '' if self.payroll_frequency: condition = """and payroll_frequency = '%(payroll_frequency)s'"""% {"payroll_frequency": self.payroll_frequency} - sal_struct = frappe.db.sql_list(""" - select - name from `tabSalary Structure` - where - docstatus = 1 and - is_active = 'Yes' - and company = %(company)s - and currency = %(currency)s and - ifnull(salary_slip_based_on_timesheet,0) = %(salary_slip_based_on_timesheet)s - {condition}""".format(condition=condition), - {"company": self.company, "currency": self.currency, "salary_slip_based_on_timesheet":self.salary_slip_based_on_timesheet}) - + sal_struct = get_sal_struct(self.company, self.currency, self.salary_slip_based_on_timesheet, condition) if sal_struct: cond += "and t2.salary_structure IN %(sal_struct)s " cond += "and t2.payroll_payable_account = %(payroll_payable_account)s " cond += "and %(from_date)s >= t2.from_date" - emp_list = frappe.db.sql(""" - select - distinct t1.name as employee, t1.employee_name, t1.department, t1.designation - from - `tabEmployee` t1, `tabSalary Structure Assignment` t2 - where - t1.name = t2.employee - and t2.docstatus = 1 - %s order by t2.from_date desc - """ % cond, {"sal_struct": tuple(sal_struct), "from_date": self.end_date, "payroll_payable_account": self.payroll_payable_account}, as_dict=True) - - emp_list = self.remove_payrolled_employees(emp_list) + emp_list = get_emp_list(sal_struct, cond, self.end_date, self.payroll_payable_account) + emp_list = remove_payrolled_employees(emp_list, self.start_date, self.end_date) return emp_list - def remove_payrolled_employees(self, emp_list): - for employee_details in emp_list: - if frappe.db.exists("Salary Slip", {"employee": employee_details.employee, "start_date": self.start_date, "end_date": self.end_date, "docstatus": 1}): - emp_list.remove(employee_details) - - return emp_list - @frappe.whitelist() def fill_employee_details(self): self.set('employees', []) @@ -122,23 +96,6 @@ class PayrollEntry(Document): if self.validate_attendance: return self.validate_employee_attendance() - def get_filter_condition(self): - self.check_mandatory() - - cond = '' - for f in ['company', 'branch', 'department', 'designation']: - if self.get(f): - cond += " and t1." + f + " = " + frappe.db.escape(self.get(f)) - - return cond - - def get_joining_relieving_condition(self): - cond = """ - and ifnull(t1.date_of_joining, '0000-00-00') <= '%(end_date)s' - and ifnull(t1.relieving_date, '2199-12-31') >= '%(start_date)s' - """ % {"start_date": self.start_date, "end_date": self.end_date} - return cond - def check_mandatory(self): for fieldname in ['company', 'start_date', 'end_date']: if not self.get(fieldname): @@ -451,6 +408,53 @@ class PayrollEntry(Document): marked_days = attendances[0][0] return marked_days +def get_sal_struct(company, currency, salary_slip_based_on_timesheet, condition): + return frappe.db.sql_list(""" + select + name from `tabSalary Structure` + where + docstatus = 1 and + is_active = 'Yes' + and company = %(company)s + and currency = %(currency)s and + ifnull(salary_slip_based_on_timesheet,0) = %(salary_slip_based_on_timesheet)s + {condition}""".format(condition=condition), + {"company": company, "currency": currency, "salary_slip_based_on_timesheet": salary_slip_based_on_timesheet}) + +def get_filter_condition(filters): + cond = '' + for f in ['company', 'branch', 'department', 'designation']: + if filters.get(f): + cond += " and t1." + f + " = " + frappe.db.escape(filters.get(f)) + + return cond + +def get_joining_relieving_condition(start_date, end_date): + cond = """ + and ifnull(t1.date_of_joining, '0000-00-00') <= '%(end_date)s' + and ifnull(t1.relieving_date, '2199-12-31') >= '%(start_date)s' + """ % {"start_date": start_date, "end_date": end_date} + return cond + +def get_emp_list(sal_struct, cond, end_date, payroll_payable_account): + return frappe.db.sql(""" + select + distinct t1.name as employee, t1.employee_name, t1.department, t1.designation + from + `tabEmployee` t1, `tabSalary Structure Assignment` t2 + where + t1.name = t2.employee + and t2.docstatus = 1 + %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) + +def remove_payrolled_employees(emp_list, start_date, end_date): + for employee_details in emp_list: + if frappe.db.exists("Salary Slip", {"employee": employee_details.employee, "start_date": start_date, "end_date": end_date, "docstatus": 1}): + emp_list.remove(employee_details) + + return emp_list + @frappe.whitelist() def get_start_end_dates(payroll_frequency, start_date=None, company=None): '''Returns dict of start and end dates for given payroll frequency based on start_date''' @@ -639,39 +643,41 @@ def get_payroll_entries_for_jv(doctype, txt, searchfield, start, page_len, filte 'start': start, 'page_len': page_len }) -def get_employee_with_existing_salary_slip(start_date, end_date, company): - return frappe.db.sql_list(""" - select employee from `tabSalary Slip` - where - (start_date between %(start_date)s and %(end_date)s - or - end_date between %(start_date)s and %(end_date)s - or - %(start_date)s between start_date and end_date) - and company = %(company)s - and docstatus = 1 - """, {'start_date': start_date, 'end_date': end_date, 'company': company}) +def get_employee_list(filters): + cond = get_filter_condition(filters) + cond += get_joining_relieving_condition(filters.start_date, filters.end_date) + condition = """and payroll_frequency = '%(payroll_frequency)s'"""% {"payroll_frequency": filters.payroll_frequency} + sal_struct = get_sal_struct(filters.company, filters.currency, filters.salary_slip_based_on_timesheet, condition) + if sal_struct: + cond += "and t2.salary_structure IN %(sal_struct)s " + cond += "and t2.payroll_payable_account = %(payroll_payable_account)s " + cond += "and %(from_date)s >= t2.from_date" + emp_list = get_emp_list(sal_struct, cond, filters.end_date, filters.payroll_payable_account) + emp_list = remove_payrolled_employees(emp_list, filters.start_date, filters.end_date) + return emp_list @frappe.whitelist() @frappe.validate_and_sanitize_search_inputs def employee_query(doctype, txt, searchfield, start, page_len, filters): filters = frappe._dict(filters) conditions = [] - exclude_employees = [] + include_employees = [] emp_cond = '' if filters.start_date and filters.end_date: - employee_list = get_employee_with_existing_salary_slip(filters.start_date, filters.end_date, filters.company) + employee_list = get_employee_list(filters) emp = filters.get('employees') + include_employees = [employee.employee for employee in employee_list if employee.employee not in emp] filters.pop('start_date') filters.pop('end_date') + filters.pop('salary_slip_based_on_timesheet') + filters.pop('payroll_frequency') + filters.pop('payroll_payable_account') + filters.pop('currency') if filters.employees is not None: filters.pop('employees') - if employee_list: - exclude_employees.extend(employee_list) - if emp: - exclude_employees.extend(emp) - if exclude_employees: - emp_cond += 'and employee not in %(exclude_employees)s' + + if include_employees: + emp_cond += 'and employee in %(include_employees)s' return frappe.db.sql("""select name, employee_name from `tabEmployee` where status = 'Active' @@ -695,4 +701,4 @@ def employee_query(doctype, txt, searchfield, start, page_len, filters): '_txt': txt.replace("%", ""), 'start': start, 'page_len': page_len, - 'exclude_employees': exclude_employees}) + 'include_employees': include_employees}) From 6c88ab07c779de864a88d9c073a1092b696f51c8 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 19 Apr 2021 11:51:46 +0530 Subject: [PATCH 072/326] fix: commit leave_allocation change to db (#25382) --- .../compensatory_leave_request/compensatory_leave_request.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py b/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py index aa5a67f40c..a6fe429be1 100644 --- a/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py +++ b/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py @@ -66,7 +66,7 @@ class CompensatoryLeaveRequest(Document): else: leave_allocation = self.create_leave_allocation(leave_period, date_difference) - self.leave_allocation=leave_allocation.name + self.db_set("leave_allocation", leave_allocation.name) else: frappe.throw(_("There is no leave period in between {0} and {1}").format(format_date(self.work_from_date), format_date(self.work_end_date))) @@ -124,4 +124,4 @@ class CompensatoryLeaveRequest(Document): )) allocation.insert(ignore_permissions=True) allocation.submit() - return allocation \ No newline at end of file + return allocation From bb746fcbe4b8caaf7178e0443e2ed67e556e2839 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 19 Apr 2021 12:19:31 +0530 Subject: [PATCH 073/326] fix: Updated filters for process statement of accounts --- .../process_statement_of_accounts.html | 10 +++++----- .../process_statement_of_accounts.py | 7 ++++--- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html index 94ae79a0c6..b623898829 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html @@ -19,7 +19,7 @@ - {% for row in data %} + {% for row in data %} {% if(row.posting_date) %} {{ frappe.format(row.posting_date, 'Date') }} @@ -78,10 +78,10 @@ - {{ aging.range1 }} - {{ aging.range2 }} - {{ aging.range3 }} - {{ aging.range4 }} + {{ frappe.utils.fmt_money(aging.range1, currency=filters.presentation_currency) }} + {{ frappe.utils.fmt_money(aging.range2, currency=filters.presentation_currency) }} + {{ frappe.utils.fmt_money(aging.range3, currency=filters.presentation_currency) }} + {{ frappe.utils.fmt_money(aging.range4, currency=filters.presentation_currency) }} diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py index 43fbb0600a..3cbf59448a 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py @@ -7,8 +7,9 @@ import frappe from frappe.model.document import Document from erpnext.accounts.report.general_ledger.general_ledger import execute as get_soa from erpnext.accounts.report.accounts_receivable_summary.accounts_receivable_summary import execute as get_ageing -from frappe.core.doctype.communication.email import make +from erpnext import get_company_currency +from frappe.core.doctype.communication.email import make from frappe.utils.print_format import report_to_pdf from frappe.utils.pdf import get_pdf from frappe.utils import today, add_days, add_months, getdate, format_date @@ -64,16 +65,16 @@ def get_report_pdf(doc, consolidated=True): 'to_date': doc.to_date, 'company': doc.company, 'finance_book': doc.finance_book if doc.finance_book else None, - "account": doc.account if doc.account else None, + 'account': doc.account if doc.account else None, 'party_type': 'Customer', 'party': [entry.customer], + 'presentation_currency': doc.currency or get_company_currency(doc.company), 'group_by': doc.group_by, 'currency': doc.currency, 'cost_center': [cc.cost_center_name for cc in doc.cost_center], 'project': [p.project_name for p in doc.project], 'show_opening_entries': 0, 'include_default_book_entries': 0, - 'show_cancelled_entries': 1, 'tax_id': tax_id if tax_id else None }) col, res = get_soa(filters) From ac8a467b0a5694a86a992d3c8e1b14b362e19a57 Mon Sep 17 00:00:00 2001 From: Alan <2.alan.tom@gmail.com> Date: Mon, 19 Apr 2021 12:38:25 +0530 Subject: [PATCH 074/326] fix: exclude spurious Stock Entry Types from 'consumed' calculation (#25352) * fix: exclude spurious 'Stock Entry Type's from 'consumed' calculation * fix: filter using purpose, make requested changes Co-authored-by: Ankush Menat --- .../itemwise_recommended_reorder_level.py | 28 +++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py b/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py index 5df3fa8067..2f70523264 100644 --- a/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py +++ b/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py @@ -55,19 +55,31 @@ def get_item_info(filters): def get_consumed_items(condition): + purpose_to_exclude = [ + "Material Transfer for Manufacture", + "Material Transfer", + "Send to Subcontractor" + ] + + condition += """ + and ( + purpose is NULL + or purpose not in ({}) + ) + """.format(', '.join([f"'{p}'" for p in purpose_to_exclude])) + condition = condition.replace("posting_date", "sle.posting_date") + consumed_items = frappe.db.sql(""" select item_code, abs(sum(actual_qty)) as consumed_qty - from `tabStock Ledger Entry` - where actual_qty < 0 + from `tabStock Ledger Entry` as sle left join `tabStock Entry` as se + on sle.voucher_no = se.name + where + actual_qty < 0 and voucher_type not in ('Delivery Note', 'Sales Invoice') %s - group by item_code - """ % condition, as_dict=1) - - consumed_items_map = {} - for item in consumed_items: - consumed_items_map.setdefault(item.item_code, item.consumed_qty) + group by item_code""" % condition, as_dict=1) + consumed_items_map = {item.item_code : item.consumed_qty for item in consumed_items} return consumed_items_map def get_delivered_items(condition): From 119b27b97f8cd2092c8e915e5283dbd26876d1ce Mon Sep 17 00:00:00 2001 From: Jannat Patel <31363128+pateljannat@users.noreply.github.com> Date: Mon, 19 Apr 2021 12:46:14 +0530 Subject: [PATCH 075/326] feat: Delayed Tasks Summary (#25024) * feat: delayed deliverables summary * fix: sider * fix: renamed to delayed tasks * fix: renamed test * fix: test * fix: sider * fix: dates, validations and chart * fix: space and column width * feat: Sort tasks by descending order of delay Co-authored-by: Rucha Mahabal --- erpnext/projects/doctype/task/task.json | 15 +- erpnext/projects/doctype/task/task.py | 5 + .../report/delayed_tasks_summary/__init__.py | 0 .../delayed_tasks_summary.js | 41 ++++++ .../delayed_tasks_summary.json | 29 ++++ .../delayed_tasks_summary.py | 133 ++++++++++++++++++ .../test_delayed_tasks_summary.py | 54 +++++++ .../projects/workspace/projects/projects.json | 13 +- 8 files changed, 286 insertions(+), 4 deletions(-) create mode 100644 erpnext/projects/report/delayed_tasks_summary/__init__.py create mode 100644 erpnext/projects/report/delayed_tasks_summary/delayed_tasks_summary.js create mode 100644 erpnext/projects/report/delayed_tasks_summary/delayed_tasks_summary.json create mode 100644 erpnext/projects/report/delayed_tasks_summary/delayed_tasks_summary.py create mode 100644 erpnext/projects/report/delayed_tasks_summary/test_delayed_tasks_summary.py diff --git a/erpnext/projects/doctype/task/task.json b/erpnext/projects/doctype/task/task.json index 160cc5812f..ef4740d9ee 100644 --- a/erpnext/projects/doctype/task/task.json +++ b/erpnext/projects/doctype/task/task.json @@ -11,15 +11,16 @@ "project", "issue", "type", + "color", "is_group", "is_template", "column_break0", "status", "priority", "task_weight", - "completed_by", - "color", "parent_task", + "completed_by", + "completed_on", "sb_timeline", "exp_start_date", "expected_time", @@ -358,6 +359,7 @@ "read_only": 1 }, { + "depends_on": "eval: doc.status == \"Completed\"", "fieldname": "completed_by", "fieldtype": "Link", "label": "Completed By", @@ -381,6 +383,13 @@ "fieldname": "duration", "fieldtype": "Int", "label": "Duration (Days)" + }, + { + "depends_on": "eval: doc.status == \"Completed\"", + "fieldname": "completed_on", + "fieldtype": "Date", + "label": "Completed On", + "mandatory_depends_on": "eval: doc.status == \"Completed\"" } ], "icon": "fa fa-check", @@ -388,7 +397,7 @@ "is_tree": 1, "links": [], "max_attachments": 5, - "modified": "2020-12-28 11:32:58.714991", + "modified": "2021-04-16 12:46:51.556741", "modified_by": "Administrator", "module": "Projects", "name": "Task", diff --git a/erpnext/projects/doctype/task/task.py b/erpnext/projects/doctype/task/task.py index 855ff5f83e..d1583f1473 100755 --- a/erpnext/projects/doctype/task/task.py +++ b/erpnext/projects/doctype/task/task.py @@ -36,6 +36,7 @@ class Task(NestedSet): self.validate_status() self.update_depends_on() self.validate_dependencies_for_template_task() + self.validate_completed_on() def validate_dates(self): if self.exp_start_date and self.exp_end_date and getdate(self.exp_start_date) > getdate(self.exp_end_date): @@ -100,6 +101,10 @@ class Task(NestedSet): dependent_task_format = """{0}""".format(task.task) frappe.throw(_("Dependent Task {0} is not a Template Task").format(dependent_task_format)) + def validate_completed_on(self): + if self.completed_on and getdate(self.completed_on) > getdate(): + frappe.throw(_("Completed On cannot be greater than Today")) + def update_depends_on(self): depends_on_tasks = self.depends_on_tasks or "" for d in self.depends_on: diff --git a/erpnext/projects/report/delayed_tasks_summary/__init__.py b/erpnext/projects/report/delayed_tasks_summary/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/projects/report/delayed_tasks_summary/delayed_tasks_summary.js b/erpnext/projects/report/delayed_tasks_summary/delayed_tasks_summary.js new file mode 100644 index 0000000000..5aa44c0a8c --- /dev/null +++ b/erpnext/projects/report/delayed_tasks_summary/delayed_tasks_summary.js @@ -0,0 +1,41 @@ +// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports["Delayed Tasks Summary"] = { + "filters": [ + { + "fieldname": "from_date", + "label": __("From Date"), + "fieldtype": "Date" + }, + { + "fieldname": "to_date", + "label": __("To Date"), + "fieldtype": "Date" + }, + { + "fieldname": "priority", + "label": __("Priority"), + "fieldtype": "Select", + "options": ["", "Low", "Medium", "High", "Urgent"] + }, + { + "fieldname": "status", + "label": __("Status"), + "fieldtype": "Select", + "options": ["", "Open", "Working","Pending Review","Overdue","Completed"] + }, + ], + "formatter": function(value, row, column, data, default_formatter) { + value = default_formatter(value, row, column, data); + if (column.id == "delay") { + if (data["delay"] > 0) { + value = `

${value}

`; + } else { + value = `

${value}

`; + } + } + return value + } +}; diff --git a/erpnext/projects/report/delayed_tasks_summary/delayed_tasks_summary.json b/erpnext/projects/report/delayed_tasks_summary/delayed_tasks_summary.json new file mode 100644 index 0000000000..100c422433 --- /dev/null +++ b/erpnext/projects/report/delayed_tasks_summary/delayed_tasks_summary.json @@ -0,0 +1,29 @@ +{ + "add_total_row": 0, + "columns": [], + "creation": "2021-03-25 15:03:19.857418", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 0, + "is_standard": "Yes", + "modified": "2021-04-15 15:49:35.432486", + "modified_by": "Administrator", + "module": "Projects", + "name": "Delayed Tasks Summary", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Task", + "report_name": "Delayed Tasks Summary", + "report_type": "Script Report", + "roles": [ + { + "role": "Projects User" + }, + { + "role": "Projects Manager" + } + ] +} \ No newline at end of file diff --git a/erpnext/projects/report/delayed_tasks_summary/delayed_tasks_summary.py b/erpnext/projects/report/delayed_tasks_summary/delayed_tasks_summary.py new file mode 100644 index 0000000000..cdabe6487e --- /dev/null +++ b/erpnext/projects/report/delayed_tasks_summary/delayed_tasks_summary.py @@ -0,0 +1,133 @@ +# 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 date_diff, nowdate + +def execute(filters=None): + columns, data = [], [] + data = get_data(filters) + columns = get_columns() + charts = get_chart_data(data) + return columns, data, None, charts + +def get_data(filters): + conditions = get_conditions(filters) + tasks = frappe.get_all("Task", + filters = conditions, + fields = ["name", "subject", "exp_start_date", "exp_end_date", + "status", "priority", "completed_on", "progress"], + order_by="creation" + ) + for task in tasks: + if task.exp_end_date: + if task.completed_on: + task.delay = date_diff(task.completed_on, task.exp_end_date) + elif task.status == "Completed": + # task is completed but completed on is not set (for older tasks) + task.delay = 0 + else: + # task not completed + task.delay = date_diff(nowdate(), task.exp_end_date) + else: + # task has no end date, hence no delay + task.delay = 0 + + # Sort by descending order of delay + tasks.sort(key=lambda x: x["delay"], reverse=True) + return tasks + +def get_conditions(filters): + conditions = frappe._dict() + keys = ["priority", "status"] + for key in keys: + if filters.get(key): + conditions[key] = filters.get(key) + if filters.get("from_date"): + conditions.exp_end_date = [">=", filters.get("from_date")] + if filters.get("to_date"): + conditions.exp_start_date = ["<=", filters.get("to_date")] + return conditions + +def get_chart_data(data): + delay, on_track = 0, 0 + for entry in data: + if entry.get("delay") > 0: + delay = delay + 1 + else: + on_track = on_track + 1 + charts = { + "data": { + "labels": ["On Track", "Delayed"], + "datasets": [ + { + "name": "Delayed", + "values": [on_track, delay] + } + ] + }, + "type": "percentage", + "colors": ["#84D5BA", "#CB4B5F"] + } + return charts + +def get_columns(): + columns = [ + { + "fieldname": "name", + "fieldtype": "Link", + "label": "Task", + "options": "Task", + "width": 150 + }, + { + "fieldname": "subject", + "fieldtype": "Data", + "label": "Subject", + "width": 200 + }, + { + "fieldname": "status", + "fieldtype": "Data", + "label": "Status", + "width": 100 + }, + { + "fieldname": "priority", + "fieldtype": "Data", + "label": "Priority", + "width": 80 + }, + { + "fieldname": "progress", + "fieldtype": "Data", + "label": "Progress (%)", + "width": 120 + }, + { + "fieldname": "exp_start_date", + "fieldtype": "Date", + "label": "Expected Start Date", + "width": 150 + }, + { + "fieldname": "exp_end_date", + "fieldtype": "Date", + "label": "Expected End Date", + "width": 150 + }, + { + "fieldname": "completed_on", + "fieldtype": "Date", + "label": "Actual End Date", + "width": 130 + }, + { + "fieldname": "delay", + "fieldtype": "Data", + "label": "Delay (In Days)", + "width": 120 + } + ] + return columns diff --git a/erpnext/projects/report/delayed_tasks_summary/test_delayed_tasks_summary.py b/erpnext/projects/report/delayed_tasks_summary/test_delayed_tasks_summary.py new file mode 100644 index 0000000000..dbeedb4be9 --- /dev/null +++ b/erpnext/projects/report/delayed_tasks_summary/test_delayed_tasks_summary.py @@ -0,0 +1,54 @@ +from __future__ import unicode_literals +import unittest +import frappe +from frappe.utils import nowdate, add_days, add_months +from erpnext.projects.doctype.task.test_task import create_task +from erpnext.projects.report.delayed_tasks_summary.delayed_tasks_summary import execute + +class TestDelayedTasksSummary(unittest.TestCase): + @classmethod + def setUp(self): + task1 = create_task("_Test Task 98", add_days(nowdate(), -10), nowdate()) + create_task("_Test Task 99", add_days(nowdate(), -10), add_days(nowdate(), -1)) + + task1.status = "Completed" + task1.completed_on = add_days(nowdate(), -1) + task1.save() + + def test_delayed_tasks_summary(self): + filters = frappe._dict({ + "from_date": add_months(nowdate(), -1), + "to_date": nowdate(), + "priority": "Low", + "status": "Open" + }) + expected_data = [ + { + "subject": "_Test Task 99", + "status": "Open", + "priority": "Low", + "delay": 1 + }, + { + "subject": "_Test Task 98", + "status": "Completed", + "priority": "Low", + "delay": -1 + } + ] + report = execute(filters) + data = list(filter(lambda x: x.subject == "_Test Task 99", report[1]))[0] + + for key in ["subject", "status", "priority", "delay"]: + self.assertEqual(expected_data[0].get(key), data.get(key)) + + filters.status = "Completed" + report = execute(filters) + data = list(filter(lambda x: x.subject == "_Test Task 98", report[1]))[0] + + for key in ["subject", "status", "priority", "delay"]: + self.assertEqual(expected_data[1].get(key), data.get(key)) + + def tearDown(self): + for task in ["_Test Task 98", "_Test Task 99"]: + frappe.get_doc("Task", {"subject": task}).delete() \ No newline at end of file diff --git a/erpnext/projects/workspace/projects/projects.json b/erpnext/projects/workspace/projects/projects.json index dbbd7e1458..0ec17029a2 100644 --- a/erpnext/projects/workspace/projects/projects.json +++ b/erpnext/projects/workspace/projects/projects.json @@ -15,6 +15,7 @@ "hide_custom": 0, "icon": "project", "idx": 0, + "is_default": 0, "is_standard": 1, "label": "Projects", "links": [ @@ -148,9 +149,19 @@ "link_type": "Report", "onboard": 0, "type": "Link" + }, + { + "dependencies": "Task", + "hidden": 0, + "is_query_report": 1, + "label": "Delayed Tasks Summary", + "link_to": "Delayed Tasks Summary", + "link_type": "Report", + "onboard": 0, + "type": "Link" } ], - "modified": "2020-12-01 13:38:37.856224", + "modified": "2021-03-26 16:32:00.628561", "modified_by": "Administrator", "module": "Projects", "name": "Projects", From edf3dfa5a4a147e16c6cde83e911be201d3e8ba8 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 19 Apr 2021 12:47:41 +0530 Subject: [PATCH 076/326] fix: commit leave_allocation change to db (#25382) (#25383) --- .../compensatory_leave_request/compensatory_leave_request.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py b/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py index aa5a67f40c..a6fe429be1 100644 --- a/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py +++ b/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py @@ -66,7 +66,7 @@ class CompensatoryLeaveRequest(Document): else: leave_allocation = self.create_leave_allocation(leave_period, date_difference) - self.leave_allocation=leave_allocation.name + self.db_set("leave_allocation", leave_allocation.name) else: frappe.throw(_("There is no leave period in between {0} and {1}").format(format_date(self.work_from_date), format_date(self.work_end_date))) @@ -124,4 +124,4 @@ class CompensatoryLeaveRequest(Document): )) allocation.insert(ignore_permissions=True) allocation.submit() - return allocation \ No newline at end of file + return allocation From 632166a933d77be944e09453f9f4dd522804a6c6 Mon Sep 17 00:00:00 2001 From: Afshan <33727827+AfshanKhan@users.noreply.github.com> Date: Mon, 19 Apr 2021 12:58:27 +0530 Subject: [PATCH 077/326] fix: make filters for payroll entry (#25386) --- erpnext/payroll/doctype/payroll_entry/payroll_entry.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py index b031129614..3953b463f1 100644 --- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py +++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py @@ -70,6 +70,15 @@ class PayrollEntry(Document): emp_list = remove_payrolled_employees(emp_list, self.start_date, self.end_date) return emp_list + def make_filters(self): + filters = frappe._dict() + filters['company'] = self.company + filters['branch'] = self.branch + filters['department'] = self.department + filters['designation'] = self.designation + + return filters + @frappe.whitelist() def fill_employee_details(self): self.set('employees', []) From ceba5774be6910d6c48f0f69e3f4adc1345b1b4b Mon Sep 17 00:00:00 2001 From: Rakshith N <36509967+rakshithrddy@users.noreply.github.com> Date: Mon, 19 Apr 2021 13:21:49 +0530 Subject: [PATCH 078/326] fix(pos): special character scanning in point of sale (#25353) Co-authored-by: rakshith.n Co-authored-by: Saqib --- .../page/point_of_sale/pos_item_selector.js | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/erpnext/selling/page/point_of_sale/pos_item_selector.js b/erpnext/selling/page/point_of_sale/pos_item_selector.js index e0d5b73166..9fb3943b53 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_selector.js +++ b/erpnext/selling/page/point_of_sale/pos_item_selector.js @@ -159,6 +159,31 @@ erpnext.PointOfSale.ItemSelector = class { bind_events() { const me = this; window.onScan = onScan; + + onScan.decodeKeyEvent = function (oEvent) { + var iCode = this._getNormalizedKeyNum(oEvent); + switch (true) { + case iCode >= 48 && iCode <= 90: // numbers and letters + case iCode >= 106 && iCode <= 111: // operations on numeric keypad (+, -, etc.) + case (iCode >= 160 && iCode <= 164) || iCode == 170: // ^ ! # $ * + case iCode >= 186 && iCode <= 194: // (; = , - . / `) + case iCode >= 219 && iCode <= 222: // ([ \ ] ') + if (oEvent.key !== undefined && oEvent.key !== '') { + return oEvent.key; + } + + var sDecoded = String.fromCharCode(iCode); + switch (oEvent.shiftKey) { + case false: sDecoded = sDecoded.toLowerCase(); break; + case true: sDecoded = sDecoded.toUpperCase(); break; + } + return sDecoded; + case iCode >= 96 && iCode <= 105: // numbers on numeric keypad + return 0 + (iCode - 96); + } + return ''; + }; + onScan.attachTo(document, { onScan: (sScancode) => { if (this.search_field && this.$component.is(':visible')) { From cb718fce88e3dd7866980333237cec25b9a72acf Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Mon, 19 Apr 2021 13:25:15 +0530 Subject: [PATCH 079/326] feat: Role to allow over billing, delivery, receipt (#24854) * feat: Role to allow over billing, delivery, receipt * fix: Typo --- .../doctype/accounts_settings/accounts_settings.json | 10 +++++++++- erpnext/controllers/accounts_controller.py | 4 +++- erpnext/controllers/status_updater.py | 10 +++++++--- .../stock/doctype/stock_settings/stock_settings.json | 10 +++++++++- 4 files changed, 28 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json index a3c29b6d64..e1276e7da3 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json @@ -12,6 +12,7 @@ "frozen_accounts_modifier", "determine_address_tax_category_from", "over_billing_allowance", + "role_allowed_to_over_bill", "column_break_4", "credit_controller", "check_supplier_invoice_uniqueness", @@ -226,6 +227,13 @@ "fieldname": "delete_linked_ledger_entries", "fieldtype": "Check", "label": "Delete Accounting and Stock Ledger Entries on deletion of Transaction" + }, + { + "description": "Users with this role are allowed to over bill above the allowance percentage", + "fieldname": "role_allowed_to_over_bill", + "fieldtype": "Link", + "label": "Role Allowed to Over Bill ", + "options": "Role" } ], "icon": "icon-cog", @@ -233,7 +241,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2021-01-05 13:04:00.118892", + "modified": "2021-03-11 18:52:05.601996", "modified_by": "Administrator", "module": "Accounts", "name": "Accounts Settings", diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 33fbf1c0b9..d36e7b03f4 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -717,7 +717,9 @@ class AccountsController(TransactionBase): total_billed_amt = abs(total_billed_amt) max_allowed_amt = abs(max_allowed_amt) - if total_billed_amt - max_allowed_amt > 0.01: + role_allowed_to_over_bill = frappe.db.get_single_value('Accounts Settings', 'role_allowed_to_over_bill') + + if total_billed_amt - max_allowed_amt > 0.01 and role_allowed_to_over_bill not in frappe.get_roles(): frappe.throw(_("Cannot overbill for Item {0} in row {1} more than {2}. To allow over-billing, please set allowance in Accounts Settings") .format(item.item_code, item.idx, max_allowed_amt)) diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py index cdb6d244a6..5276da9720 100644 --- a/erpnext/controllers/status_updater.py +++ b/erpnext/controllers/status_updater.py @@ -201,10 +201,14 @@ class StatusUpdater(Document): get_allowance_for(item['item_code'], self.item_allowance, self.global_qty_allowance, self.global_amount_allowance, qty_or_amount) - overflow_percent = ((item[args['target_field']] - item[args['target_ref_field']]) / - item[args['target_ref_field']]) * 100 + role_allowed_to_over_deliver_receive = frappe.db.get_single_value('Stock Settings', 'role_allowed_to_over_deliver_receive') + role_allowed_to_over_bill = frappe.db.get_single_value('Accounts Settings', 'role_allowed_to_over_bill') + role = role_allowed_to_over_deliver_receive if qty_or_amount == 'qty' else role_allowed_to_over_bill - if overflow_percent - allowance > 0.01: + overflow_percent = ((item[args['target_field']] - item[args['target_ref_field']]) / + item[args['target_ref_field']]) * 100 + + if overflow_percent - allowance > 0.01 and role not in frappe.get_roles(): item['max_allowed'] = flt(item[args['target_ref_field']] * (100+allowance)/100) item['reduce_by'] = item[args['target_field']] - item['max_allowed'] diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.json b/erpnext/stock/doctype/stock_settings/stock_settings.json index 84af57b48d..f18eabc84b 100644 --- a/erpnext/stock/doctype/stock_settings/stock_settings.json +++ b/erpnext/stock/doctype/stock_settings/stock_settings.json @@ -13,6 +13,7 @@ "column_break_4", "valuation_method", "over_delivery_receipt_allowance", + "role_allowed_to_over_deliver_receive", "action_if_quality_inspection_is_not_submitted", "show_barcode_field", "clean_description_html", @@ -234,6 +235,13 @@ "fieldname": "disable_serial_no_and_batch_selector", "fieldtype": "Check", "label": "Disable Serial No And Batch Selector" + }, + { + "description": "Users with this role are allowed to over deliver/receive against orders above the allowance percentage", + "fieldname": "role_allowed_to_over_deliver_receive", + "fieldtype": "Link", + "label": "Role Allowed to Over Deliver/Receive", + "options": "Role" } ], "icon": "icon-cog", @@ -241,7 +249,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2021-01-18 13:15:38.352796", + "modified": "2021-03-11 18:48:14.513055", "modified_by": "Administrator", "module": "Stock", "name": "Stock Settings", From 1f8326bb1f8884b8efabb40c38d744ec03b35374 Mon Sep 17 00:00:00 2001 From: Hussain Nagaria Date: Mon, 19 Apr 2021 16:19:08 +0530 Subject: [PATCH 080/326] chore: Add Report to Dashboard --- erpnext/projects/workspace/projects/projects.json | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/erpnext/projects/workspace/projects/projects.json b/erpnext/projects/workspace/projects/projects.json index dbbd7e1458..bc2e71097f 100644 --- a/erpnext/projects/workspace/projects/projects.json +++ b/erpnext/projects/workspace/projects/projects.json @@ -15,6 +15,7 @@ "hide_custom": 0, "icon": "project", "idx": 0, + "is_default": 0, "is_standard": 1, "label": "Projects", "links": [ @@ -129,6 +130,16 @@ "onboard": 1, "type": "Link" }, + { + "dependencies": "Timesheet", + "hidden": 0, + "is_query_report": 1, + "label": "Employee Hours Utilization", + "link_to": "Employee Hours Utilization Based On Timesheet", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, { "dependencies": "Project", "hidden": 0, @@ -150,7 +161,7 @@ "type": "Link" } ], - "modified": "2020-12-01 13:38:37.856224", + "modified": "2021-04-19 16:17:30.142545", "modified_by": "Administrator", "module": "Projects", "name": "Projects", From 3b54b1e97588127d0a673f0a1cc769b78867c597 Mon Sep 17 00:00:00 2001 From: pateljannat Date: Mon, 19 Apr 2021 16:49:28 +0530 Subject: [PATCH 081/326] fix: report name and columns --- .../hr/doctype/hr_settings/hr_settings.json | 8 +- .../report/profitability/profitability.py | 162 ---------------- .../profitability/test_profitability.py | 51 ----- .../__init__.py | 0 .../project_profitability.js} | 8 +- .../project_profitability.json} | 25 ++- .../project_profitability.py | 174 ++++++++++++++++++ .../test_project_profitability.py | 51 +++++ .../projects/workspace/projects/projects.json | 6 +- erpnext/regional/india/utils.py | 2 +- 10 files changed, 255 insertions(+), 232 deletions(-) delete mode 100644 erpnext/projects/report/profitability/profitability.py delete mode 100644 erpnext/projects/report/profitability/test_profitability.py rename erpnext/projects/report/{profitability => project_profitability}/__init__.py (100%) rename erpnext/projects/report/{profitability/profitability.js => project_profitability/project_profitability.js} (84%) rename erpnext/projects/report/{profitability/profitability.json => project_profitability/project_profitability.json} (69%) create mode 100644 erpnext/projects/report/project_profitability/project_profitability.py create mode 100644 erpnext/projects/report/project_profitability/test_project_profitability.py diff --git a/erpnext/hr/doctype/hr_settings/hr_settings.json b/erpnext/hr/doctype/hr_settings/hr_settings.json index 4fa50c4852..35532291a5 100644 --- a/erpnext/hr/doctype/hr_settings/hr_settings.json +++ b/erpnext/hr/doctype/hr_settings/hr_settings.json @@ -10,7 +10,7 @@ "retirement_age", "emp_created_by", "column_break_4", - "default_working_hours", + "standard_working_hours", "stop_birthday_reminders", "expense_approver_mandatory_in_expense_claim", "leave_settings", @@ -147,16 +147,16 @@ }, { "default": "8", - "fieldname": "default_working_hours", + "fieldname": "standard_working_hours", "fieldtype": "Int", - "label": "Default Working Hours" + "label": "Standard Working Hours" } ], "icon": "fa fa-cog", "idx": 1, "issingle": 1, "links": [], - "modified": "2021-03-25 13:18:21.648077", + "modified": "2021-04-16 15:45:18.467699", "modified_by": "Administrator", "module": "HR", "name": "HR Settings", diff --git a/erpnext/projects/report/profitability/profitability.py b/erpnext/projects/report/profitability/profitability.py deleted file mode 100644 index 8ce2eb09ee..0000000000 --- a/erpnext/projects/report/profitability/profitability.py +++ /dev/null @@ -1,162 +0,0 @@ -# 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 import _ - -def execute(filters=None): - columns, data = [], [] - data = get_data(filters) - columns = get_columns() - charts = get_chart_data(data) - return columns, data, None, charts - -def get_data(filters): - conditions = get_conditions(filters) - default_working_hours = frappe.db.get_single_value("HR Settings", "default_working_hours") - sql = """ - select - *, - t.gross_pay * t.utilization as fractional_cost, - t.grand_total - t.gross_pay * t.utilization as profit - from - (select - si.customer_name,tabTimesheet.title,tabTimesheet.employee,si.grand_total,si.name as voucher_no, - ss.gross_pay,ss.total_working_days,tabTimesheet.end_date,tabTimesheet.total_billed_hours,tabTimesheet.name as timesheet, - tabTimesheet.total_billed_hours/(ss.total_working_days * {0}) as utilization - from - `tabSalary Slip Timesheet` as sst join `tabTimesheet` on tabTimesheet.name = sst.time_sheet - join `tabSales Invoice Timesheet` as sit on sit.time_sheet = tabTimesheet.name - join `tabSales Invoice` as si on si.name = sit.parent and si.status != "Cancelled" - join `tabSalary Slip` as ss on ss.name = sst.parent and ss.status != "Cancelled" """.format(default_working_hours) - if conditions: - sql += """ - where - {0}) as t""".format(conditions) - data = frappe.db.sql(sql,filters, as_dict=True) - return data - -def get_conditions(filters): - conditions = [] - if filters.get("company"): - conditions.append('tabTimesheet.company="{0}"'.format(filters.get("company"))) - if filters.get("customer_name"): - conditions.append("si.customer_name='{0}'".format(filters.get("customer_name"))) - if filters.get("start_date"): - conditions.append("tabTimesheet.start_date>='{0}'".format(filters.get("start_date"))) - if filters.get("end_date"): - conditions.append("tabTimesheet.end_date<='{0}'".format(filters.get("end_date"))) - if filters.get("employee"): - conditions.append("tabTimesheet.employee='{0}'".format(filters.get("employee"))) - - conditions = " and ".join(conditions) - return conditions - -def get_chart_data(data): - if not data: - return None - - labels = [] - utilization = [] - - for entry in data: - labels.append(entry.get("title") + " - " + str(entry.get("end_date"))) - utilization.append(entry.get("utilization")) - charts = { - 'data': { - 'labels': labels, - 'datasets': [ - { - 'name': 'Utilization', - 'values': utilization - } - ] - }, - 'type': 'bar', - 'colors': ['#84BDD5'] - } - return charts - -def get_columns(): - return [ - { - "fieldname": "customer_name", - "label": _("Customer"), - "fieldtype": "Link", - "options": "Customer", - "width": 150 - }, - { - "fieldname": "title", - "label": _("Name"), - "fieldtype": "Data", - "width": 120 - }, - { - "fieldname": "employee", - "label": _("Employee"), - "fieldtype": "Link", - "options": "Employee", - "width": 150 - }, - { - "fieldname": "voucher_no", - "label": _("Sales Invoice"), - "fieldtype": "Link", - "options": "Sales Invoice", - "width": 200 - }, - { - "fieldname": "timesheet", - "label": _("Timesheet"), - "fieldtype": "Link", - "options": "Timesheet", - "width": 150 - }, - { - "fieldname": "grand_total", - "label": _("Bill Amount"), - "fieldtype": "Currency", - "options": "currency", - "width": 120 - }, - { - "fieldname": "gross_pay", - "label": _("Cost"), - "fieldtype": "Currency", - "options": "currency", - "width": 120 - }, - { - "fieldname": "profit", - "label": _("Profit"), - "fieldtype": "Currency", - "options": "currency", - "width": 120 - }, - { - "fieldname": "end_date", - "label": _("End Date"), - "fieldtype": "Date", - "width": 120 - }, - { - "fieldname": "total_billed_hours", - "label": _("Total Billed Hours"), - "fieldtype": "Int", - "width": 100 - }, - { - "fieldname": "utilization", - "label": _("Utilization"), - "fieldtype": "Percentage", - "width": 120 - }, - { - "fieldname": "fractional_cost", - "label": _("Fractional Cost"), - "fieldtype": "Int", - "width": 100 - } - ] \ No newline at end of file diff --git a/erpnext/projects/report/profitability/test_profitability.py b/erpnext/projects/report/profitability/test_profitability.py deleted file mode 100644 index 64ab6787eb..0000000000 --- a/erpnext/projects/report/profitability/test_profitability.py +++ /dev/null @@ -1,51 +0,0 @@ -from __future__ import unicode_literals -import unittest -import frappe -from frappe.utils import getdate, nowdate -from erpnext.hr.doctype.employee.test_employee import make_employee -from erpnext.projects.doctype.timesheet.test_timesheet import make_salary_structure_for_timesheet, make_timesheet -from erpnext.projects.doctype.timesheet.timesheet import make_salary_slip, make_sales_invoice -from erpnext.projects.report.profitability.profitability import execute - -class TestProfitability(unittest.TestCase): - @classmethod - def setUp(self): - emp = make_employee("test_employee_9@salary.com", company="_Test Company") - if not frappe.db.exists("Salary Component", "Timesheet Component"): - frappe.get_doc({"doctype": "Salary Component", "salary_component": "Timesheet Component"}).insert() - make_salary_structure_for_timesheet(emp, company="_Test Company") - self.timesheet = make_timesheet(emp, simulate = True, billable=1) - self.salary_slip = make_salary_slip(self.timesheet.name) - self.salary_slip.submit() - self.sales_invoice = make_sales_invoice(self.timesheet.name, '_Test Item', '_Test Customer') - self.sales_invoice.due_date = nowdate() - self.sales_invoice.submit() - - def test_profitability(self): - filters = { - 'company': '_Test Company', - 'start_date': getdate(), - 'end_date': getdate() - } - - report = execute(filters) - expected_data = [ - { - "customer_name": "_Test Customer", - "title": "test_employee_9@salary.com", - "grand_total": 100.0, - "gross_pay": 78100.0, - "profit": -19425.0, - "total_billed_hours": 2.0, - "utilization": 0.25, - "fractional_cost": 19525.0, - "total_working_days": 1.0 - } - ] - for key in ["customer_name","title","grand_total","gross_pay","profit","total_billed_hours","utilization","fractional_cost","total_working_days"]: - self.assertEqual(expected_data[0].get(key), report[1][0].get(key)) - - def tearDown(self): - frappe.get_doc("Sales Invoice", self.sales_invoice.name).cancel() - frappe.get_doc("Salary Slip", self.salary_slip.name).cancel() - frappe.get_doc("Timesheet", self.timesheet.name).cancel() \ No newline at end of file diff --git a/erpnext/projects/report/profitability/__init__.py b/erpnext/projects/report/project_profitability/__init__.py similarity index 100% rename from erpnext/projects/report/profitability/__init__.py rename to erpnext/projects/report/project_profitability/__init__.py diff --git a/erpnext/projects/report/profitability/profitability.js b/erpnext/projects/report/project_profitability/project_profitability.js similarity index 84% rename from erpnext/projects/report/profitability/profitability.js rename to erpnext/projects/report/project_profitability/project_profitability.js index 6cb6e39d34..cdf7bfdc9f 100644 --- a/erpnext/projects/report/profitability/profitability.js +++ b/erpnext/projects/report/project_profitability/project_profitability.js @@ -2,7 +2,7 @@ // For license information, please see license.txt /* eslint-disable */ -frappe.query_reports["Profitability"] = { +frappe.query_reports["Project Profitability"] = { "filters": [ { "fieldname": "company", @@ -26,6 +26,12 @@ frappe.query_reports["Profitability"] = { "reqd": 1, "default": frappe.datetime.now_date() }, + { + "fieldname": "project", + "label": __("Project"), + "fieldtype": "Link", + "options": "Project" + }, { "fieldname": "customer_name", "label": __("Customer"), diff --git a/erpnext/projects/report/profitability/profitability.json b/erpnext/projects/report/project_profitability/project_profitability.json similarity index 69% rename from erpnext/projects/report/profitability/profitability.json rename to erpnext/projects/report/project_profitability/project_profitability.json index 4f91accf58..0b092cd2c0 100644 --- a/erpnext/projects/report/profitability/profitability.json +++ b/erpnext/projects/report/project_profitability/project_profitability.json @@ -1,7 +1,7 @@ { "add_total_row": 0, "columns": [], - "creation": "2021-03-18 10:19:40.124932", + "creation": "2021-04-16 15:50:28.914872", "disable_prepared_report": 0, "disabled": 0, "docstatus": 0, @@ -9,31 +9,36 @@ "filters": [], "idx": 0, "is_standard": "Yes", - "json": "{}", - "modified": "2021-03-18 10:20:15.559305", + "modified": "2021-04-16 15:50:48.490866", "modified_by": "Administrator", "module": "Projects", - "name": "Profitability", + "name": "Project Profitability", "owner": "Administrator", "prepared_report": 0, "ref_doctype": "Timesheet", - "report_name": "Profitability", + "report_name": "Project Profitability", "report_type": "Script Report", "roles": [ - { - "role": "Projects User" - }, { "role": "HR User" }, { - "role": "Manufacturing User" + "role": "Accounts User" }, { "role": "Employee" }, { - "role": "Accounts User" + "role": "Projects User" + }, + { + "role": "Manufacturing User" + }, + { + "role": "Employee Self Service" + }, + { + "role": "HR Manager" } ] } \ No newline at end of file diff --git a/erpnext/projects/report/project_profitability/project_profitability.py b/erpnext/projects/report/project_profitability/project_profitability.py new file mode 100644 index 0000000000..7a76213994 --- /dev/null +++ b/erpnext/projects/report/project_profitability/project_profitability.py @@ -0,0 +1,174 @@ +# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe + +def execute(filters=None): + columns, data = [], [] + data = get_data(filters) + columns = get_columns() + charts = get_chart_data(data) + return columns, data, None, charts + +def get_data(filters): + conditions = get_conditions(filters) + standard_working_hours = frappe.db.get_single_value('HR Settings', 'standard_working_hours') + sql = ''' + SELECT + * + FROM + (SELECT + si.customer_name,tabTimesheet.title, + tabTimesheet.employee,si.base_grand_total + si.name as voucher_no,ss.base_gross_pay,ss.total_working_days, + tabTimesheet.end_date,tabTimesheet.total_billed_hours, + tabTimesheet.name as timesheet, + tabTimesheet.total_billed_hours/(ss.total_working_days * {0}) as utilization + FROM + `tabSalary Slip Timesheet` as sst join `tabTimesheet` on tabTimesheet.name = sst.time_sheet + join `tabSales Invoice Timesheet` as sit on sit.time_sheet = tabTimesheet.name + join `tabSales Invoice` as si on si.name = sit.parent and si.status != 'Cancelled' + join `tabSalary Slip` as ss on ss.name = sst.parent and ss.status != 'Cancelled' '''.format(standard_working_hours) + if conditions: + sql += ''' + where + {0}) as t'''.format(conditions) + data = frappe.db.sql(sql,filters, as_dict=True) + data = perform_calculations(data) + return data + +def perform_calculations(data): + data.fractional_cost = data.base_gross_pay * data.utilization + data.profit = data.base_grand_total - data.base_gross_pay + return data + +def get_conditions(filters): + conditions = [] + if filters.get('company'): + conditions.append('tabTimesheet.company="{0}"'.format(filters.get('company'))) + if filters.get('customer_name'): + conditions.append('si.customer_name="{0}"'.format(filters.get('customer_name'))) + if filters.get('start_date'): + conditions.append('tabTimesheet.start_date>="{0}"'.format(filters.get('start_date'))) + if filters.get('end_date'): + conditions.append('tabTimesheet.end_date<="{0}"'.format(filters.get('end_date'))) + if filters.get('employee'): + conditions.append('tabTimesheet.employee="{0}"'.format(filters.get('employee'))) + + conditions = ' and '.join(conditions) + return conditions + +def get_chart_data(data): + if not data: + return None + + labels = [] + utilization = [] + + for entry in data: + labels.append(entry.get('title') + ' - ' + str(entry.get('end_date'))) + utilization.append(entry.get('utilization')) + charts = { + 'data': { + 'labels': labels, + 'datasets': [ + { + 'name': 'Utilization', + 'values': utilization + } + ] + }, + 'type': 'bar', + 'colors': ['#84BDD5'] + } + return charts + +def get_columns(): + return [ + { + 'fieldname': 'customer_name', + 'label': _('Customer'), + 'fieldtype': 'Link', + 'options': 'Customer', + 'width': 150 + }, + { + 'fieldname': 'employee', + 'label': _('Employee'), + 'fieldtype': 'Link', + 'options': 'Employee', + 'width': 150 + }, + { + 'fieldname': 'employee_name', + 'label': _('Employee Name'), + 'fieldtype': 'Data', + 'width': 120 + }, + { + 'fieldname': 'voucher_no', + 'label': _('Sales Invoice'), + 'fieldtype': 'Link', + 'options': 'Sales Invoice', + 'width': 200 + }, + { + 'fieldname': 'timesheet', + 'label': _('Timesheet'), + 'fieldtype': 'Link', + 'options': 'Timesheet', + 'width': 150 + }, + { + 'fieldname': 'grand_total', + 'label': _('Bill Amount'), + 'fieldtype': 'Currency', + 'options': 'currency', + 'width': 120 + }, + { + 'fieldname': 'gross_pay', + 'label': _('Cost'), + 'fieldtype': 'Currency', + 'options': 'currency', + 'width': 120 + }, + { + 'fieldname': 'profit', + 'label': _('Profit'), + 'fieldtype': 'Currency', + 'options': 'currency', + 'width': 120 + }, + { + 'fieldname': 'utilization', + 'label': _('Utilization'), + 'fieldtype': 'Percentage', + 'width': 120 + }, + { + 'fieldname': 'fractional_cost', + 'label': _('Fractional Cost'), + 'fieldtype': 'Int', + 'width': 100 + }, + { + 'fieldname': 'total_billed_hours', + 'label': _('Total Billed Hours'), + 'fieldtype': 'Int', + 'width': 100 + }, + { + 'fieldname': 'start_date', + 'label': _('Start Date'), + 'fieldtype': 'Date', + 'width': 120 + }, + { + 'fieldname': 'end_date', + 'label': _('End Date'), + 'fieldtype': 'Date', + 'width': 120 + } + ] diff --git a/erpnext/projects/report/project_profitability/test_project_profitability.py b/erpnext/projects/report/project_profitability/test_project_profitability.py new file mode 100644 index 0000000000..7036547e40 --- /dev/null +++ b/erpnext/projects/report/project_profitability/test_project_profitability.py @@ -0,0 +1,51 @@ +from __future__ import unicode_literals +import unittest +import frappe +from frappe.utils import getdate, nowdate +from erpnext.hr.doctype.employee.test_employee import make_employee +from erpnext.projects.doctype.timesheet.test_timesheet import make_salary_structure_for_timesheet, make_timesheet +from erpnext.projects.doctype.timesheet.timesheet import make_salary_slip, make_sales_invoice +from erpnext.projects.report.project_profitability.project_profitability import execute + +class TestProjectProfitability(unittest.TestCase): + @classmethod + def setUp(self): + emp = make_employee('test_employee_9@salary.com', company='_Test Company') + if not frappe.db.exists('Salary Component', 'Timesheet Component'): + frappe.get_doc({'doctype': 'Salary Component', 'salary_component': 'Timesheet Component'}).insert() + make_salary_structure_for_timesheet(emp, company='_Test Company') + self.timesheet = make_timesheet(emp, simulate = True, billable=1) + self.salary_slip = make_salary_slip(self.timesheet.name) + self.salary_slip.submit() + self.sales_invoice = make_sales_invoice(self.timesheet.name, '_Test Item', '_Test Customer') + self.sales_invoice.due_date = nowdate() + self.sales_invoice.submit() + + def test_project_profitability(self): + filters = { + 'company': '_Test Company', + 'start_date': getdate(), + 'end_date': getdate() + } + + report = execute(filters) + expected_data = [ + { + 'customer_name': '_Test Customer', + 'title': 'test_employee_9@salary.com', + 'grand_total': 100.0, + 'gross_pay': 78100.0, + 'profit': -19425.0, + 'total_billed_hours': 2.0, + 'utilization': 0.25, + 'fractional_cost': 19525.0, + 'total_working_days': 1.0 + } + ] + for key in ['customer_name','title','grand_total','gross_pay','profit','total_billed_hours','utilization','fractional_cost','total_working_days']: + self.assertEqual(expected_data[0].get(key), report[1][0].get(key)) + + def tearDown(self): + frappe.get_doc('Sales Invoice', self.sales_invoice.name).cancel() + frappe.get_doc('Salary Slip', self.salary_slip.name).cancel() + frappe.get_doc('Timesheet', self.timesheet.name).cancel() \ No newline at end of file diff --git a/erpnext/projects/workspace/projects/projects.json b/erpnext/projects/workspace/projects/projects.json index 8703ffb756..621c4bb52f 100644 --- a/erpnext/projects/workspace/projects/projects.json +++ b/erpnext/projects/workspace/projects/projects.json @@ -134,8 +134,8 @@ "dependencies": "Timesheet, Sales Invoice, Salary Slip", "hidden": 0, "is_query_report": 1, - "label": "Profitability", - "link_to": "Profitability", + "label": "Project Profitability", + "link_to": "Project Profitability", "link_type": "Report", "onboard": 0, "type": "Link" @@ -161,7 +161,7 @@ "type": "Link" } ], - "modified": "2021-03-25 13:25:17.609608", + "modified": "2021-04-16 16:27:16.548780", "modified_by": "Administrator", "module": "Projects", "name": "Projects", diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index 3637de438c..ff4ac07041 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -159,7 +159,7 @@ def validate_document_name(doc, method=None): # Date was chosen as start of next FY to avoid irritating current users. if country != "India" or getdate(doc.posting_date) < getdate("2021-04-01"): return - + print(doc.name) if len(doc.name) > 16: frappe.throw(_("Maximum length of document number should be 16 characters as per GST rules. Please change the naming series.")) From 135b852cf01e891e64c56289349cfcd54d124b26 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 19 Apr 2021 17:27:26 +0530 Subject: [PATCH 082/326] fix: Use party account currency --- .../process_statement_of_accounts.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py index 3cbf59448a..33c32ebce2 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py @@ -8,6 +8,7 @@ from frappe.model.document import Document from erpnext.accounts.report.general_ledger.general_ledger import execute as get_soa from erpnext.accounts.report.accounts_receivable_summary.accounts_receivable_summary import execute as get_ageing from erpnext import get_company_currency +from erpnext.accounts.party import get_party_account_currency from frappe.core.doctype.communication.email import make from frappe.utils.print_format import report_to_pdf @@ -68,7 +69,8 @@ def get_report_pdf(doc, consolidated=True): 'account': doc.account if doc.account else None, 'party_type': 'Customer', 'party': [entry.customer], - 'presentation_currency': doc.currency or get_company_currency(doc.company), + 'presentation_currency': get_party_account_currency('Customer', entry.customer, doc.company) \ + or doc.currency or get_company_currency(doc.company), 'group_by': doc.group_by, 'currency': doc.currency, 'cost_center': [cc.cost_center_name for cc in doc.cost_center], From 047044f9759cf4e9de933612d451a6b52eaeb980 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 19 Apr 2021 18:27:37 +0530 Subject: [PATCH 083/326] fix: Add amend perm for loan and system manager for loan doctypes --- erpnext/loan_management/doctype/loan/loan.json | 3 ++- .../doctype/loan_application/loan_application.json | 5 ++++- .../doctype/loan_disbursement/loan_disbursement.json | 4 +++- .../doctype/loan_interest_accrual/loan_interest_accrual.json | 4 +++- .../doctype/loan_repayment/loan_repayment.json | 4 +++- .../doctype/loan_security_pledge/loan_security_pledge.json | 4 +++- .../loan_security_unpledge/loan_security_unpledge.json | 4 +++- erpnext/loan_management/doctype/loan_type/loan_type.json | 3 ++- .../doctype/loan_write_off/loan_write_off.json | 4 +++- 9 files changed, 26 insertions(+), 9 deletions(-) diff --git a/erpnext/loan_management/doctype/loan/loan.json b/erpnext/loan_management/doctype/loan/loan.json index 4f8ceb0de8..c9f23ca4df 100644 --- a/erpnext/loan_management/doctype/loan/loan.json +++ b/erpnext/loan_management/doctype/loan/loan.json @@ -360,13 +360,14 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2021-04-10 09:28:21.946972", + "modified": "2021-04-19 18:10:32.360818", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan", "owner": "Administrator", "permissions": [ { + "amend": 1, "cancel": 1, "create": 1, "delete": 1, diff --git a/erpnext/loan_management/doctype/loan_application/loan_application.json b/erpnext/loan_management/doctype/loan_application/loan_application.json index a353a7740d..f91fa07235 100644 --- a/erpnext/loan_management/doctype/loan_application/loan_application.json +++ b/erpnext/loan_management/doctype/loan_application/loan_application.json @@ -212,15 +212,17 @@ "read_only": 1 } ], + "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2020-03-01 10:21:44.413353", + "modified": "2021-04-19 18:24:40.119647", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan Application", "owner": "Administrator", "permissions": [ { + "amend": 1, "cancel": 1, "create": 1, "delete": 1, @@ -235,6 +237,7 @@ "write": 1 }, { + "amend": 1, "create": 1, "delete": 1, "email": 1, diff --git a/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.json b/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.json index 662c626b8d..7811d56a75 100644 --- a/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.json +++ b/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.json @@ -154,13 +154,14 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2021-04-10 10:03:41.502210", + "modified": "2021-04-19 18:09:32.175355", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan Disbursement", "owner": "Administrator", "permissions": [ { + "amend": 1, "cancel": 1, "create": 1, "delete": 1, @@ -175,6 +176,7 @@ "write": 1 }, { + "amend": 1, "cancel": 1, "create": 1, "delete": 1, diff --git a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.json b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.json index 185bf7a666..30e2328442 100644 --- a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.json +++ b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.json @@ -185,13 +185,14 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2021-01-10 00:15:21.544140", + "modified": "2021-04-19 18:26:38.871889", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan Interest Accrual", "owner": "Administrator", "permissions": [ { + "amend": 1, "cancel": 1, "create": 1, "delete": 1, @@ -206,6 +207,7 @@ "write": 1 }, { + "amend": 1, "cancel": 1, "create": 1, "delete": 1, diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json index 8fbf233be5..6479853246 100644 --- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json +++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json @@ -248,13 +248,14 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2021-04-10 10:00:31.859076", + "modified": "2021-04-19 18:10:00.935364", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan Repayment", "owner": "Administrator", "permissions": [ { + "amend": 1, "cancel": 1, "create": 1, "delete": 1, @@ -269,6 +270,7 @@ "write": 1 }, { + "amend": 1, "cancel": 1, "create": 1, "delete": 1, 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 7dd5725e2e..18bd4aea78 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 @@ -160,13 +160,14 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2020-09-04 22:38:19.894488", + "modified": "2021-04-19 18:23:16.953305", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan Security Pledge", "owner": "Administrator", "permissions": [ { + "amend": 1, "cancel": 1, "create": 1, "delete": 1, @@ -181,6 +182,7 @@ "write": 1 }, { + "amend": 1, "cancel": 1, "create": 1, "delete": 1, diff --git a/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.json b/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.json index 2e2b2518d2..92923bbf25 100644 --- a/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.json +++ b/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.json @@ -126,13 +126,14 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2020-09-04 22:39:57.756146", + "modified": "2021-04-19 18:12:01.401744", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan Security Unpledge", "owner": "Administrator", "permissions": [ { + "amend": 1, "cancel": 1, "create": 1, "delete": 1, @@ -147,6 +148,7 @@ "write": 1 }, { + "amend": 1, "cancel": 1, "create": 1, "delete": 1, diff --git a/erpnext/loan_management/doctype/loan_type/loan_type.json b/erpnext/loan_management/doctype/loan_type/loan_type.json index 3ef53044c2..c0a5d2cda1 100644 --- a/erpnext/loan_management/doctype/loan_type/loan_type.json +++ b/erpnext/loan_management/doctype/loan_type/loan_type.json @@ -154,13 +154,14 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2021-01-17 06:51:26.082879", + "modified": "2021-04-19 18:10:57.368490", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan Type", "owner": "Administrator", "permissions": [ { + "amend": 1, "cancel": 1, "create": 1, "delete": 1, diff --git a/erpnext/loan_management/doctype/loan_write_off/loan_write_off.json b/erpnext/loan_management/doctype/loan_write_off/loan_write_off.json index 4617a62f5b..4ca9ef174c 100644 --- a/erpnext/loan_management/doctype/loan_write_off/loan_write_off.json +++ b/erpnext/loan_management/doctype/loan_write_off/loan_write_off.json @@ -116,13 +116,14 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2020-10-26 07:13:43.663924", + "modified": "2021-04-19 18:11:27.759862", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan Write Off", "owner": "Administrator", "permissions": [ { + "amend": 1, "cancel": 1, "create": 1, "delete": 1, @@ -137,6 +138,7 @@ "write": 1 }, { + "amend": 1, "cancel": 1, "create": 1, "delete": 1, From ebbcc90d89539fd9dd50478d42f25809241aabfb Mon Sep 17 00:00:00 2001 From: Meike Nedwidek Date: Mon, 19 Apr 2021 15:28:37 +0200 Subject: [PATCH 084/326] :sparkles: add check field for subscription invoices if they should be submitted automatically --- .../doctype/subscription/subscription.json | 145 +++++------------- .../doctype/subscription/subscription.py | 4 +- 2 files changed, 45 insertions(+), 104 deletions(-) diff --git a/erpnext/accounts/doctype/subscription/subscription.json b/erpnext/accounts/doctype/subscription/subscription.json index e80df2ab88..c4e4be7f78 100644 --- a/erpnext/accounts/doctype/subscription/subscription.json +++ b/erpnext/accounts/doctype/subscription/subscription.json @@ -36,6 +36,7 @@ "additional_discount_percentage", "additional_discount_amount", "sb_3", + "submit_invoice", "invoices", "accounting_dimensions_section", "cost_center", @@ -45,9 +46,7 @@ { "allow_on_submit": 1, "fieldname": "cb_1", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "fieldname": "status", @@ -55,97 +54,73 @@ "label": "Status", "no_copy": 1, "options": "\nTrialling\nActive\nPast Due Date\nCancelled\nUnpaid\nCompleted", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "subscription_period", "fieldtype": "Section Break", - "label": "Subscription Period", - "show_days": 1, - "show_seconds": 1 + "label": "Subscription Period" }, { "fieldname": "cancelation_date", "fieldtype": "Date", "label": "Cancelation Date", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "allow_on_submit": 1, "fieldname": "trial_period_start", "fieldtype": "Date", "label": "Trial Period Start Date", - "set_only_once": 1, - "show_days": 1, - "show_seconds": 1 + "set_only_once": 1 }, { "depends_on": "eval:doc.trial_period_start", "fieldname": "trial_period_end", "fieldtype": "Date", "label": "Trial Period End Date", - "set_only_once": 1, - "show_days": 1, - "show_seconds": 1 + "set_only_once": 1 }, { "fieldname": "column_break_11", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "fieldname": "current_invoice_start", "fieldtype": "Date", "label": "Current Invoice Start Date", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "current_invoice_end", "fieldtype": "Date", "label": "Current Invoice End Date", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "default": "0", "description": "Number of days that the subscriber has to pay invoices generated by this subscription", "fieldname": "days_until_due", "fieldtype": "Int", - "label": "Days Until Due", - "show_days": 1, - "show_seconds": 1 + "label": "Days Until Due" }, { "default": "0", "fieldname": "cancel_at_period_end", "fieldtype": "Check", - "label": "Cancel At End Of Period", - "show_days": 1, - "show_seconds": 1 + "label": "Cancel At End Of Period" }, { "default": "0", "fieldname": "generate_invoice_at_period_start", "fieldtype": "Check", - "label": "Generate Invoice At Beginning Of Period", - "show_days": 1, - "show_seconds": 1 + "label": "Generate Invoice At Beginning Of Period" }, { "allow_on_submit": 1, "fieldname": "sb_4", "fieldtype": "Section Break", - "label": "Plans", - "show_days": 1, - "show_seconds": 1 + "label": "Plans" }, { "allow_on_submit": 1, @@ -153,84 +128,62 @@ "fieldtype": "Table", "label": "Plans", "options": "Subscription Plan Detail", - "reqd": 1, - "show_days": 1, - "show_seconds": 1 + "reqd": 1 }, { "depends_on": "eval:['Customer', 'Supplier'].includes(doc.party_type)", "fieldname": "sb_1", "fieldtype": "Section Break", - "label": "Taxes", - "show_days": 1, - "show_seconds": 1 + "label": "Taxes" }, { "fieldname": "sb_2", "fieldtype": "Section Break", - "label": "Discounts", - "show_days": 1, - "show_seconds": 1 + "label": "Discounts" }, { "fieldname": "apply_additional_discount", "fieldtype": "Select", "label": "Apply Additional Discount On", - "options": "\nGrand Total\nNet Total", - "show_days": 1, - "show_seconds": 1 + "options": "\nGrand Total\nNet Total" }, { "fieldname": "cb_2", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "fieldname": "additional_discount_percentage", "fieldtype": "Percent", - "label": "Additional DIscount Percentage", - "show_days": 1, - "show_seconds": 1 + "label": "Additional DIscount Percentage" }, { "collapsible": 1, "fieldname": "additional_discount_amount", "fieldtype": "Currency", - "label": "Additional DIscount Amount", - "show_days": 1, - "show_seconds": 1 + "label": "Additional DIscount Amount" }, { "depends_on": "eval:doc.invoices", "fieldname": "sb_3", "fieldtype": "Section Break", - "label": "Invoices", - "show_days": 1, - "show_seconds": 1 + "label": "Invoices" }, { "collapsible": 1, "fieldname": "invoices", "fieldtype": "Table", "label": "Invoices", - "options": "Subscription Invoice", - "show_days": 1, - "show_seconds": 1 + "options": "Subscription Invoice" }, { "collapsible": 1, "fieldname": "accounting_dimensions_section", "fieldtype": "Section Break", - "label": "Accounting Dimensions", - "show_days": 1, - "show_seconds": 1 + "label": "Accounting Dimensions" }, { "fieldname": "dimension_col_break", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "fieldname": "party_type", @@ -238,9 +191,7 @@ "label": "Party Type", "options": "DocType", "reqd": 1, - "set_only_once": 1, - "show_days": 1, - "show_seconds": 1 + "set_only_once": 1 }, { "fieldname": "party", @@ -249,27 +200,21 @@ "label": "Party", "options": "party_type", "reqd": 1, - "set_only_once": 1, - "show_days": 1, - "show_seconds": 1 + "set_only_once": 1 }, { "depends_on": "eval:doc.party_type === 'Customer'", "fieldname": "sales_tax_template", "fieldtype": "Link", "label": "Sales Taxes and Charges Template", - "options": "Sales Taxes and Charges Template", - "show_days": 1, - "show_seconds": 1 + "options": "Sales Taxes and Charges Template" }, { "depends_on": "eval:doc.party_type === 'Supplier'", "fieldname": "purchase_tax_template", "fieldtype": "Link", "label": "Purchase Taxes and Charges Template", - "options": "Purchase Taxes and Charges Template", - "show_days": 1, - "show_seconds": 1 + "options": "Purchase Taxes and Charges Template" }, { "default": "0", @@ -277,55 +222,49 @@ "fieldname": "follow_calendar_months", "fieldtype": "Check", "label": "Follow Calendar Months", - "set_only_once": 1, - "show_days": 1, - "show_seconds": 1 + "set_only_once": 1 }, { "default": "0", "description": "New invoices will be generated as per schedule even if current invoices are unpaid or past due date", "fieldname": "generate_new_invoices_past_due_date", "fieldtype": "Check", - "label": "Generate New Invoices Past Due Date", - "show_days": 1, - "show_seconds": 1 + "label": "Generate New Invoices Past Due Date" }, { "fieldname": "end_date", "fieldtype": "Date", "label": "Subscription End Date", - "set_only_once": 1, - "show_days": 1, - "show_seconds": 1 + "set_only_once": 1 }, { "fieldname": "start_date", "fieldtype": "Date", "label": "Subscription Start Date", - "set_only_once": 1, - "show_days": 1, - "show_seconds": 1 + "set_only_once": 1 }, { "fieldname": "cost_center", "fieldtype": "Link", "label": "Cost Center", - "options": "Cost Center", - "show_days": 1, - "show_seconds": 1 + "options": "Cost Center" }, { "fieldname": "company", "fieldtype": "Link", "label": "Company", - "options": "Company", - "show_days": 1, - "show_seconds": 1 + "options": "Company" + }, + { + "default": "1", + "fieldname": "submit_invoice", + "fieldtype": "Check", + "label": "Submit Invoice Automatically" } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2021-02-09 15:44:20.024789", + "modified": "2021-04-19 15:24:27.550797", "modified_by": "Administrator", "module": "Accounts", "name": "Subscription", diff --git a/erpnext/accounts/doctype/subscription/subscription.py b/erpnext/accounts/doctype/subscription/subscription.py index 826044a407..f19bae7465 100644 --- a/erpnext/accounts/doctype/subscription/subscription.py +++ b/erpnext/accounts/doctype/subscription/subscription.py @@ -383,7 +383,9 @@ class Subscription(Document): invoice.flags.ignore_mandatory = True invoice.save() - invoice.submit() + + if self.submit_invoice == 1: + invoice.submit() return invoice From 923fb008c7a0f7125dda3e6e0a66c7500cfcc2c5 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 19 Apr 2021 21:09:47 +0530 Subject: [PATCH 085/326] fix: remove non-standard module cards from Home Workspace (#25391) --- erpnext/setup/workspace/home/home.json | 170 +------------------------ 1 file changed, 1 insertion(+), 169 deletions(-) diff --git a/erpnext/setup/workspace/home/home.json b/erpnext/setup/workspace/home/home.json index 305456b266..1576d5a399 100644 --- a/erpnext/setup/workspace/home/home.json +++ b/erpnext/setup/workspace/home/home.json @@ -248,177 +248,9 @@ "link_type": "DocType", "onboard": 1, "type": "Link" - }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Healthcare", - "onboard": 0, - "type": "Card Break" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Patient", - "link_to": "Patient", - "link_type": "DocType", - "onboard": 1, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Diagnosis", - "link_to": "Diagnosis", - "link_type": "DocType", - "onboard": 1, - "type": "Link" - }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Education", - "onboard": 0, - "type": "Card Break" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Student", - "link_to": "Student", - "link_type": "DocType", - "onboard": 1, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Instructor", - "link_to": "Instructor", - "link_type": "DocType", - "onboard": 1, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Course", - "link_to": "Course", - "link_type": "DocType", - "onboard": 1, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Room", - "link_to": "Room", - "link_type": "DocType", - "onboard": 1, - "type": "Link" - }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Non Profit", - "onboard": 0, - "type": "Card Break" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Donor", - "link_to": "Donor", - "link_type": "DocType", - "onboard": 1, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Member", - "link_to": "Member", - "link_type": "DocType", - "onboard": 1, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Volunteer", - "link_to": "Volunteer", - "link_type": "DocType", - "onboard": 1, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Chapter", - "link_to": "Chapter", - "link_type": "DocType", - "onboard": 1, - "type": "Link" - }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Agriculture", - "onboard": 0, - "type": "Card Break" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Location", - "link_to": "Location", - "link_type": "DocType", - "onboard": 1, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Crop", - "link_to": "Crop", - "link_type": "DocType", - "onboard": 1, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Crop Cycle", - "link_to": "Crop Cycle", - "link_type": "DocType", - "onboard": 1, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Fertilizer", - "link_to": "Fertilizer", - "link_type": "DocType", - "onboard": 1, - "type": "Link" } ], - "modified": "2021-03-16 15:59:58.416154", + "modified": "2021-04-19 15:48:44.089927", "modified_by": "Administrator", "module": "Setup", "name": "Home", From c22168237353aeb7da4153acdfb64e0470c337fb Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 19 Apr 2021 21:11:27 +0530 Subject: [PATCH 086/326] fix: Bulk Salary Structure Assignment (#25389) --- erpnext/payroll/doctype/salary_structure/salary_structure.js | 2 -- erpnext/payroll/doctype/salary_structure/salary_structure.py | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/erpnext/payroll/doctype/salary_structure/salary_structure.js b/erpnext/payroll/doctype/salary_structure/salary_structure.js index b539b1b8a9..e00bd87084 100755 --- a/erpnext/payroll/doctype/salary_structure/salary_structure.js +++ b/erpnext/payroll/doctype/salary_structure/salary_structure.js @@ -133,8 +133,6 @@ frappe.ui.form.on('Salary Structure', { title: __("Assign to Employees"), fields: [ {fieldname: "sec_break", fieldtype: "Section Break", label: __("Filter Employees By (Optional)")}, - {fieldname: "company", fieldtype: "Link", options: "Company", label: __("Company"), default: frm.doc.company, read_only:1}, - {fieldname: "currency", fieldtype: "Link", options: "Currency", label: __("Currency"), default: frm.doc.currency, read_only:1}, {fieldname: "grade", fieldtype: "Link", options: "Employee Grade", label: __("Employee Grade")}, {fieldname:'department', fieldtype:'Link', options: 'Department', label: __('Department')}, {fieldname:'designation', fieldtype:'Link', options: 'Designation', label: __('Designation')}, diff --git a/erpnext/payroll/doctype/salary_structure/salary_structure.py b/erpnext/payroll/doctype/salary_structure/salary_structure.py index 352c1804f0..58c445f8a9 100644 --- a/erpnext/payroll/doctype/salary_structure/salary_structure.py +++ b/erpnext/payroll/doctype/salary_structure/salary_structure.py @@ -88,7 +88,7 @@ class SalaryStructure(Document): return employees @frappe.whitelist() - def assign_salary_structure(self, grade=None, department=None, designation=None,employee=None, + def assign_salary_structure(self, grade=None, department=None, designation=None, employee=None, payroll_payable_account=None, from_date=None, base=None, variable=None, income_tax_slab=None): employees = self.get_employees(company= self.company, grade= grade,department= department,designation= designation,name=employee) From 846200d06905b3b6bf507b4bd2354607b592eb9d Mon Sep 17 00:00:00 2001 From: Marica Date: Mon, 19 Apr 2021 22:20:01 +0530 Subject: [PATCH 087/326] fix: Ignore Customer Group Perm on AlL Products page (#25397) --- erpnext/accounts/doctype/pricing_rule/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py index b91a7a5bd2..d23b952bdc 100644 --- a/erpnext/accounts/doctype/pricing_rule/utils.py +++ b/erpnext/accounts/doctype/pricing_rule/utils.py @@ -173,7 +173,7 @@ def _get_tree_conditions(args, parenttype, table, allow_blank=True): if parenttype in ["Customer Group", "Item Group", "Territory"]: parent_field = "parent_{0}".format(frappe.scrub(parenttype)) root_name = frappe.db.get_list(parenttype, - {"is_group": 1, parent_field: ("is", "not set")}, "name", as_list=1) + {"is_group": 1, parent_field: ("is", "not set")}, "name", as_list=1, ignore_permissions=True) if root_name and root_name[0][0]: parent_groups.append(root_name[0][0]) From b91fcbd70edf085b853336e314c99a4354f7d79d Mon Sep 17 00:00:00 2001 From: Marica Date: Mon, 19 Apr 2021 22:21:07 +0530 Subject: [PATCH 088/326] fix: Ignore Customer Group Perm on AlL Products page (#25396) --- erpnext/accounts/doctype/pricing_rule/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py index b91a7a5bd2..d23b952bdc 100644 --- a/erpnext/accounts/doctype/pricing_rule/utils.py +++ b/erpnext/accounts/doctype/pricing_rule/utils.py @@ -173,7 +173,7 @@ def _get_tree_conditions(args, parenttype, table, allow_blank=True): if parenttype in ["Customer Group", "Item Group", "Territory"]: parent_field = "parent_{0}".format(frappe.scrub(parenttype)) root_name = frappe.db.get_list(parenttype, - {"is_group": 1, parent_field: ("is", "not set")}, "name", as_list=1) + {"is_group": 1, parent_field: ("is", "not set")}, "name", as_list=1, ignore_permissions=True) if root_name and root_name[0][0]: parent_groups.append(root_name[0][0]) From 125c3d64a9b2392144426b506055b38b781237c1 Mon Sep 17 00:00:00 2001 From: Marica Date: Tue, 20 Apr 2021 12:53:22 +0530 Subject: [PATCH 089/326] fix: Cashier query in POS Opening/Closing Entry (#25398) --- .../accounts/doctype/pos_closing_entry/pos_closing_entry.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py index a05e5984f5..949211d35a 100644 --- a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py +++ b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py @@ -89,8 +89,8 @@ class POSClosingEntry(StatusUpdater): @frappe.whitelist() @frappe.validate_and_sanitize_search_inputs def get_cashiers(doctype, txt, searchfield, start, page_len, filters): - cashiers_list = frappe.get_all("POS Profile User", filters=filters, fields=['user']) - return [c['user'] for c in cashiers_list] + cashiers_list = frappe.get_all("POS Profile User", filters=filters, fields=['user'], as_list=1) + return [c for c in cashiers_list] @frappe.whitelist() def get_pos_invoices(start, end, pos_profile, user): From 0278646fd8cb95357745f8e1379d7a810e548df3 Mon Sep 17 00:00:00 2001 From: Marica Date: Tue, 20 Apr 2021 12:53:33 +0530 Subject: [PATCH 090/326] fix: Cashier query in POS Opening/Closing Entry (#25399) --- .../accounts/doctype/pos_closing_entry/pos_closing_entry.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py index a05e5984f5..949211d35a 100644 --- a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py +++ b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py @@ -89,8 +89,8 @@ class POSClosingEntry(StatusUpdater): @frappe.whitelist() @frappe.validate_and_sanitize_search_inputs def get_cashiers(doctype, txt, searchfield, start, page_len, filters): - cashiers_list = frappe.get_all("POS Profile User", filters=filters, fields=['user']) - return [c['user'] for c in cashiers_list] + cashiers_list = frappe.get_all("POS Profile User", filters=filters, fields=['user'], as_list=1) + return [c for c in cashiers_list] @frappe.whitelist() def get_pos_invoices(start, end, pos_profile, user): From 524b81abd95692332f3d5d067845ea7329fddba2 Mon Sep 17 00:00:00 2001 From: Rohan Bansal Date: Fri, 2 Apr 2021 15:36:50 +0530 Subject: [PATCH 091/326] feat: manage Python 3 compatiblity with dependencies --- .github/workflows/ci-tests.yml | 7 +++---- requirements.txt | 26 ++++++++++++-------------- 2 files changed, 15 insertions(+), 18 deletions(-) diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml index 78c2f5a187..824b74e013 100644 --- a/.github/workflows/ci-tests.yml +++ b/.github/workflows/ci-tests.yml @@ -85,10 +85,9 @@ jobs: run: | cp ~/frappe-bench/sites/.coverage ${GITHUB_WORKSPACE} cd ${GITHUB_WORKSPACE} - pip install coveralls==2.2.0 - pip install coverage==4.5.4 - coveralls + pip install coveralls==3.0.1 + pip install coverage==5.5 + coveralls --service=github env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_TOKEN }} - diff --git a/requirements.txt b/requirements.txt index 5a352364b6..377fd7df6c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,15 +1,13 @@ -braintree==3.57.1 frappe -gocardless-pro==1.11.0 -googlemaps==3.1.1 -pandas>=1.0.5 -plaid-python>=7.0.0 -pycountry==19.8.18 -PyGithub==1.44.1 -python-stdnum==1.12 -python-youtube==0.6.0 -taxjar==1.9.0 -tweepy==3.8.0 -Unidecode==1.1.1 -WooCommerce==2.1.1 -pycryptodome==3.9.8 +gocardless-pro~=1.22.0 +googlemaps # used in ERPNext, but dependency is defined in Frappe +pandas~=1.1.5 +plaid-python~=7.2.1 +pycountry~=20.7.3 +PyGithub~=1.54.1 +python-stdnum~=1.16 +python-youtube~=0.8.0 +taxjar~=1.9.2 +tweepy~=3.10.0 +Unidecode~=1.2.0 +WooCommerce~=3.0.0 From 704ec132b7b5a8353906f77332496a1f92ba27c6 Mon Sep 17 00:00:00 2001 From: Syed Mujeer Hashmi Date: Tue, 20 Apr 2021 16:39:46 +0530 Subject: [PATCH 092/326] feat:Inpatient Occupancy Table Editable for Healthcare Admin (#24989) Make Admitted datetime and Discharge Datetime editable along with Inaptient Occupancy Service Unit, Check_In and Check_out time. Signed-off-by: Syed Mujeer Hashmi Co-authored-by: Rucha Mahabal --- .../inpatient_occupancy.json | 252 ++++-------------- .../inpatient_record/inpatient_record.json | 39 ++- 2 files changed, 90 insertions(+), 201 deletions(-) diff --git a/erpnext/healthcare/doctype/inpatient_occupancy/inpatient_occupancy.json b/erpnext/healthcare/doctype/inpatient_occupancy/inpatient_occupancy.json index 818f125e77..3fa98b6678 100644 --- a/erpnext/healthcare/doctype/inpatient_occupancy/inpatient_occupancy.json +++ b/erpnext/healthcare/doctype/inpatient_occupancy/inpatient_occupancy.json @@ -1,206 +1,64 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2018-07-12 12:07:36.932333", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", + "actions": [], + "creation": "2018-07-12 12:07:36.932333", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "service_unit", + "check_in", + "left", + "check_out", + "invoiced" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "service_unit", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Healthcare Service Unit", - "length": 0, - "no_copy": 0, - "options": "Healthcare Service Unit", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "service_unit", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Healthcare Service Unit", + "options": "Healthcare Service Unit", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "check_in", - "fieldtype": "Datetime", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Check In", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "check_in", + "fieldtype": "Datetime", + "in_list_view": 1, + "label": "Check In" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "left", - "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": "Left", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "0", + "fieldname": "left", + "fieldtype": "Check", + "label": "Left", + "read_only": 1, + "search_index": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "check_out", - "fieldtype": "Datetime", - "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": "Check Out", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "check_out", + "fieldtype": "Datetime", + "label": "Check Out" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "0", - "fieldname": "invoiced", - "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": "Invoiced", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "default": "0", + "fieldname": "invoiced", + "fieldtype": "Check", + "label": "Invoiced", + "read_only": 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": 1, - "max_attachments": 0, - "modified": "2018-11-04 03:33:26.958713", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Inpatient Occupancy", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "restrict_to_domain": "Healthcare", - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-03-18 15:08:54.634132", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Inpatient Occupancy", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "restrict_to_domain": "Healthcare", + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.json b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.json index aaf0e855d4..0e1c2ba766 100644 --- a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.json +++ b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.json @@ -185,7 +185,7 @@ "fieldtype": "Datetime", "in_list_view": 1, "label": "Admitted Datetime", - "read_only": 1 + "permlevel": 2 }, { "depends_on": "eval:(doc.expected_length_of_stay > 0)", @@ -312,7 +312,7 @@ "fieldname": "inpatient_occupancies", "fieldtype": "Table", "options": "Inpatient Occupancy", - "read_only": 1 + "permlevel": 2 }, { "fieldname": "btn_transfer", @@ -407,12 +407,12 @@ "fieldname": "discharge_datetime", "fieldtype": "Datetime", "label": "Discharge Date", - "read_only": 1 + "permlevel": 2 } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2021-03-18 14:44:11.689956", + "modified": "2021-03-18 15:59:17.318988", "modified_by": "Administrator", "module": "Healthcare", "name": "Inpatient Record", @@ -465,6 +465,37 @@ "read": 1, "report": 1, "role": "Nursing User" + }, + { + "email": 1, + "export": 1, + "permlevel": 2, + "print": 1, + "read": 1, + "report": 1, + "role": "Healthcare Administrator", + "share": 1, + "write": 1 + }, + { + "email": 1, + "export": 1, + "permlevel": 2, + "print": 1, + "read": 1, + "report": 1, + "role": "Physician", + "share": 1 + }, + { + "email": 1, + "export": 1, + "permlevel": 2, + "print": 1, + "read": 1, + "report": 1, + "role": "Nursing User", + "share": 1 } ], "restrict_to_domain": "Healthcare", From cbdcfb4873f5b63e6215df9aef86a788f097b70b Mon Sep 17 00:00:00 2001 From: pateljannat Date: Tue, 20 Apr 2021 17:53:41 +0530 Subject: [PATCH 093/326] fix: multicurrency --- erpnext/hooks.py | 1 - .../project_profitability.js | 12 +- .../project_profitability.py | 281 +++++++++++------- .../test_project_profitability.py | 14 +- 4 files changed, 186 insertions(+), 122 deletions(-) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index bb6cd8bdc2..af357cd331 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -262,7 +262,6 @@ doc_events = { ], "on_trash": "erpnext.regional.check_deletion_permission", "validate": [ - "erpnext.regional.india.utils.validate_document_name", "erpnext.regional.india.utils.update_taxable_values" ] }, diff --git a/erpnext/projects/report/project_profitability/project_profitability.js b/erpnext/projects/report/project_profitability/project_profitability.js index cdf7bfdc9f..13ae19bb29 100644 --- a/erpnext/projects/report/project_profitability/project_profitability.js +++ b/erpnext/projects/report/project_profitability/project_profitability.js @@ -26,12 +26,6 @@ frappe.query_reports["Project Profitability"] = { "reqd": 1, "default": frappe.datetime.now_date() }, - { - "fieldname": "project", - "label": __("Project"), - "fieldtype": "Link", - "options": "Project" - }, { "fieldname": "customer_name", "label": __("Customer"), @@ -43,6 +37,12 @@ frappe.query_reports["Project Profitability"] = { "label": __("Employee"), "fieldtype": "Link", "options": "Employee" + }, + { + "fieldname": "project", + "label": __("Project"), + "fieldtype": "Link", + "options": "Project" } ] }; diff --git a/erpnext/projects/report/project_profitability/project_profitability.py b/erpnext/projects/report/project_profitability/project_profitability.py index 7a76213994..405c6fd344 100644 --- a/erpnext/projects/report/project_profitability/project_profitability.py +++ b/erpnext/projects/report/project_profitability/project_profitability.py @@ -2,7 +2,12 @@ # For license information, please see license.txt from __future__ import unicode_literals -# import frappe +import frappe +from frappe import _ +from erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment import get_employee_currency +from erpnext.accounts.utils import get_currency_precision +from frappe.utils import flt +from erpnext.setup.utils import get_exchange_rate def execute(filters=None): columns, data = [], [] @@ -12,51 +17,96 @@ def execute(filters=None): return columns, data, None, charts def get_data(filters): - conditions = get_conditions(filters) - standard_working_hours = frappe.db.get_single_value('HR Settings', 'standard_working_hours') - sql = ''' - SELECT - * - FROM - (SELECT - si.customer_name,tabTimesheet.title, - tabTimesheet.employee,si.base_grand_total - si.name as voucher_no,ss.base_gross_pay,ss.total_working_days, - tabTimesheet.end_date,tabTimesheet.total_billed_hours, - tabTimesheet.name as timesheet, - tabTimesheet.total_billed_hours/(ss.total_working_days * {0}) as utilization - FROM - `tabSalary Slip Timesheet` as sst join `tabTimesheet` on tabTimesheet.name = sst.time_sheet - join `tabSales Invoice Timesheet` as sit on sit.time_sheet = tabTimesheet.name - join `tabSales Invoice` as si on si.name = sit.parent and si.status != 'Cancelled' - join `tabSalary Slip` as ss on ss.name = sst.parent and ss.status != 'Cancelled' '''.format(standard_working_hours) - if conditions: - sql += ''' - where - {0}) as t'''.format(conditions) - data = frappe.db.sql(sql,filters, as_dict=True) - data = perform_calculations(data) + data = get_rows(filters) + data = handle_multi_currency(data, filters) + data = calculate_cost_and_profit(data) return data -def perform_calculations(data): - data.fractional_cost = data.base_gross_pay * data.utilization - data.profit = data.base_grand_total - data.base_gross_pay +def get_rows(filters): + conditions = get_conditions(filters) + standard_working_hours = frappe.db.get_single_value("HR Settings", "standard_working_hours") + sql = """ + SELECT + * + FROM + (SELECT + si.customer_name,si.base_grand_total, + si.name as voucher_no,tabTimesheet.employee, + tabTimesheet.title as employee_name,tabTimesheet.parent_project as project, + tabTimesheet.start_date,tabTimesheet.end_date, + tabTimesheet.total_billed_hours,tabTimesheet.name as timesheet, + ss.base_gross_pay,ss.total_working_days, + tabTimesheet.total_billed_hours/(ss.total_working_days * {0}) as utilization + FROM + `tabSalary Slip Timesheet` as sst join `tabTimesheet` on tabTimesheet.name = sst.time_sheet + join `tabSales Invoice Timesheet` as sit on sit.time_sheet = tabTimesheet.name + join `tabSales Invoice` as si on si.name = sit.parent and si.status != "Cancelled" + join `tabSalary Slip` as ss on ss.name = sst.parent and ss.status != "Cancelled" """.format(standard_working_hours) + if conditions: + sql += """ + WHERE + {0}) as t""".format(conditions) + return frappe.db.sql(sql,filters, as_dict=True) + +def handle_multi_currency(data, filters): + currency_precision = get_currency_precision() or 2 + company_currency = frappe.get_cached_value("Company", filters.get("company"), "default_currency") + + for row in data: + row.currency = company_currency + + if filters.get("employee"): + party_currency = get_employee_currency(row.employee) + + if filters.get("customer_name"): + party_currency = frappe.db.get_value("Customer", row.customer_name, ["default_currency"]) + + if party_currency and party_currency != company_currency: + exchange_rate = get_exchange_rate(company_currency, party_currency) + row.currency = party_currency + + row.base_grand_total = flt(flt(row.base_grand_total) * + flt(exchange_rate), currency_precision) + + row.base_gross_pay = flt(flt(row.base_gross_pay) * + flt(exchange_rate), currency_precision) + + row.profit = flt(flt(row.profit) * + flt(exchange_rate), currency_precision) + + row.fractional_cost = flt(flt(row.fractional_cost) * + flt(exchange_rate), currency_precision) + + return data + +def calculate_cost_and_profit(data): + for row in data: + row.fractional_cost = row.base_gross_pay * row.utilization + row.profit = row.base_grand_total - row.base_gross_pay * row.utilization return data def get_conditions(filters): conditions = [] - if filters.get('company'): - conditions.append('tabTimesheet.company="{0}"'.format(filters.get('company'))) - if filters.get('customer_name'): - conditions.append('si.customer_name="{0}"'.format(filters.get('customer_name'))) - if filters.get('start_date'): - conditions.append('tabTimesheet.start_date>="{0}"'.format(filters.get('start_date'))) - if filters.get('end_date'): - conditions.append('tabTimesheet.end_date<="{0}"'.format(filters.get('end_date'))) - if filters.get('employee'): - conditions.append('tabTimesheet.employee="{0}"'.format(filters.get('employee'))) + + if filters.get("company"): + conditions.append("tabTimesheet.company='{0}'".format(filters.get("company"))) + + if filters.get("start_date"): + conditions.append("tabTimesheet.start_date>='{0}'".format(filters.get("start_date"))) + + if filters.get("end_date"): + conditions.append("tabTimesheet.end_date<='{0}'".format(filters.get("end_date"))) + + if filters.get("customer_name"): + conditions.append("si.customer_name='{0}'".format(filters.get("customer_name"))) + + if filters.get("employee"): + conditions.append("tabTimesheet.employee='{0}'".format(filters.get("employee"))) + + if filters.get("project"): + conditions.append("tabTimesheet.parent_project='{0}'".format(filters.get("project"))) - conditions = ' and '.join(conditions) + conditions = " and ".join(conditions) return conditions def get_chart_data(data): @@ -67,108 +117,123 @@ def get_chart_data(data): utilization = [] for entry in data: - labels.append(entry.get('title') + ' - ' + str(entry.get('end_date'))) - utilization.append(entry.get('utilization')) + labels.append(entry.get("employee_name") + " - " + str(entry.get("end_date"))) + utilization.append(entry.get("utilization")) + charts = { - 'data': { - 'labels': labels, - 'datasets': [ + "data": { + "labels": labels, + "datasets": [ { - 'name': 'Utilization', - 'values': utilization + "name": "Utilization", + "values": utilization } ] }, - 'type': 'bar', - 'colors': ['#84BDD5'] + "type": "bar", + "colors": ["#84BDD5"] } return charts def get_columns(): return [ { - 'fieldname': 'customer_name', - 'label': _('Customer'), - 'fieldtype': 'Link', - 'options': 'Customer', - 'width': 150 + "fieldname": "customer_name", + "label": _("Customer"), + "fieldtype": "Link", + "options": "Customer", + "width": 150 }, { - 'fieldname': 'employee', - 'label': _('Employee'), - 'fieldtype': 'Link', - 'options': 'Employee', - 'width': 150 + "fieldname": "employee", + "label": _("Employee"), + "fieldtype": "Link", + "options": "Employee", + "width": 130 }, { - 'fieldname': 'employee_name', - 'label': _('Employee Name'), - 'fieldtype': 'Data', - 'width': 120 + "fieldname": "employee_name", + "label": _("Employee Name"), + "fieldtype": "Data", + "width": 120 }, { - 'fieldname': 'voucher_no', - 'label': _('Sales Invoice'), - 'fieldtype': 'Link', - 'options': 'Sales Invoice', - 'width': 200 + "fieldname": "voucher_no", + "label": _("Sales Invoice"), + "fieldtype": "Link", + "options": "Sales Invoice", + "width": 180 }, { - 'fieldname': 'timesheet', - 'label': _('Timesheet'), - 'fieldtype': 'Link', - 'options': 'Timesheet', - 'width': 150 + "fieldname": "timesheet", + "label": _("Timesheet"), + "fieldtype": "Link", + "options": "Timesheet", + "width": 130 }, { - 'fieldname': 'grand_total', - 'label': _('Bill Amount'), - 'fieldtype': 'Currency', - 'options': 'currency', - 'width': 120 + "fieldname": "project", + "label": _("Project"), + "fieldtype": "Link", + "options": "Project", + "width": 100 }, { - 'fieldname': 'gross_pay', - 'label': _('Cost'), - 'fieldtype': 'Currency', - 'options': 'currency', - 'width': 120 + "fieldname": "base_grand_total", + "label": _("Bill Amount"), + "fieldtype": "Currency", + "options": "currency", + "width": 100 }, { - 'fieldname': 'profit', - 'label': _('Profit'), - 'fieldtype': 'Currency', - 'options': 'currency', - 'width': 120 + "fieldname": "base_gross_pay", + "label": _("Cost"), + "fieldtype": "Currency", + "options": "currency", + "width": 100 }, { - 'fieldname': 'utilization', - 'label': _('Utilization'), - 'fieldtype': 'Percentage', - 'width': 120 + "fieldname": "profit", + "label": _("Profit"), + "fieldtype": "Currency", + "options": "currency", + "width": 100 }, { - 'fieldname': 'fractional_cost', - 'label': _('Fractional Cost'), - 'fieldtype': 'Int', - 'width': 100 + "fieldname": "utilization", + "label": _("Utilization"), + "fieldtype": "Percentage", + "width": 120 }, { - 'fieldname': 'total_billed_hours', - 'label': _('Total Billed Hours'), - 'fieldtype': 'Int', - 'width': 100 + "fieldname": "fractional_cost", + "label": _("Fractional Cost"), + "fieldtype": "Int", + "width": 100 }, { - 'fieldname': 'start_date', - 'label': _('Start Date'), - 'fieldtype': 'Date', - 'width': 120 + "fieldname": "total_billed_hours", + "label": _("Total Billed Hours"), + "fieldtype": "Int", + "width": 100 }, { - 'fieldname': 'end_date', - 'label': _('End Date'), - 'fieldtype': 'Date', - 'width': 120 + "fieldname": "start_date", + "label": _("Start Date"), + "fieldtype": "Date", + "width": 120 + }, + { + "fieldname": "end_date", + "label": _("End Date"), + "fieldtype": "Date", + "width": 120 + }, + { + "label": _("Currency"), + "fieldname": "currency", + "fieldtype": "Link", + "options": "Currency", + "width": 100 } - ] + ] \ No newline at end of file diff --git a/erpnext/projects/report/project_profitability/test_project_profitability.py b/erpnext/projects/report/project_profitability/test_project_profitability.py index 7036547e40..96659bc606 100644 --- a/erpnext/projects/report/project_profitability/test_project_profitability.py +++ b/erpnext/projects/report/project_profitability/test_project_profitability.py @@ -32,9 +32,9 @@ class TestProjectProfitability(unittest.TestCase): expected_data = [ { 'customer_name': '_Test Customer', - 'title': 'test_employee_9@salary.com', - 'grand_total': 100.0, - 'gross_pay': 78100.0, + 'employee_name': 'test_employee_9@salary.com', + 'base_grand_total': 100.0, + 'base_gross_pay': 78100.0, 'profit': -19425.0, 'total_billed_hours': 2.0, 'utilization': 0.25, @@ -42,10 +42,10 @@ class TestProjectProfitability(unittest.TestCase): 'total_working_days': 1.0 } ] - for key in ['customer_name','title','grand_total','gross_pay','profit','total_billed_hours','utilization','fractional_cost','total_working_days']: + for key in ['customer_name','employee_name','base_grand_total','base_gross_pay','profit','total_billed_hours','utilization','fractional_cost','total_working_days']: self.assertEqual(expected_data[0].get(key), report[1][0].get(key)) def tearDown(self): - frappe.get_doc('Sales Invoice', self.sales_invoice.name).cancel() - frappe.get_doc('Salary Slip', self.salary_slip.name).cancel() - frappe.get_doc('Timesheet', self.timesheet.name).cancel() \ No newline at end of file + frappe.get_doc("Sales Invoice", self.sales_invoice.name).cancel() + frappe.get_doc("Salary Slip", self.salary_slip.name).cancel() + frappe.get_doc("Timesheet", self.timesheet.name).cancel() \ No newline at end of file From 66250351d2e8cd6aa6d02b81bc3e1540243fdd60 Mon Sep 17 00:00:00 2001 From: Ernesto Ruiz Date: Tue, 20 Apr 2021 14:35:14 -0600 Subject: [PATCH 094/326] fix: Add transtlation function to strings Add transtlation function to strings --- erpnext/payroll/doctype/salary_structure/salary_structure.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/payroll/doctype/salary_structure/salary_structure.js b/erpnext/payroll/doctype/salary_structure/salary_structure.js index e00bd87084..44368a69fb 100755 --- a/erpnext/payroll/doctype/salary_structure/salary_structure.js +++ b/erpnext/payroll/doctype/salary_structure/salary_structure.js @@ -16,11 +16,11 @@ frappe.ui.form.on('Salary Structure', { onload: function(frm) { let help_button = $(` - Condition and Formula Help + ${__(Condition and Formula Help)} `).click(()=>{ let d = new frappe.ui.Dialog({ - title: 'Condition and Formula Help', + title: __('Condition and Formula Help'), fields: [ { fieldname: 'msg_wrapper', From cb92ead0616efa1ea7dd8d12605f5c8ddf2f5723 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Wed, 21 Apr 2021 11:22:35 +0530 Subject: [PATCH 095/326] fix: Added Change Log --- erpnext/change_log/v13/v13_1_0.md | 129 ++++++++++++++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 erpnext/change_log/v13/v13_1_0.md diff --git a/erpnext/change_log/v13/v13_1_0.md b/erpnext/change_log/v13/v13_1_0.md new file mode 100644 index 0000000000..d991034e36 --- /dev/null +++ b/erpnext/change_log/v13/v13_1_0.md @@ -0,0 +1,129 @@ +# Version 13.1.0 Release Notes + +### Features + +- Recursive pricing rule ([#24922](https://github.com/frappe/erpnext/pull/24922)) +- Discount configuration on early payments ([#24586](https://github.com/frappe/erpnext/pull/24586)) +- Bulk e-invoice generation ([#24969](https://github.com/frappe/erpnext/pull/24969)) +- Employee Self Service ([#24408](https://github.com/frappe/erpnext/pull/24408)) +- Share doc with employee approvers if they don't have access ([#25190](https://github.com/frappe/erpnext/pull/25190)) +- Price margin in buying ([#24685](https://github.com/frappe/erpnext/pull/24685)) +- Allow changing Work Stations in Work Order & Job Card ([#24897](https://github.com/frappe/erpnext/pull/24897)) +- Add document type field for e-invoicing (Italy) ([#25256](https://github.com/frappe/erpnext/pull/25256)) +- Add checkbox for disabling leave notification in HR Settings ([#24877](https://github.com/frappe/erpnext/pull/24877)) +- Enhancements in Material Request Plan Item in Production Plan ([#25025](https://github.com/frappe/erpnext/pull/25025)) + + +### Fixes and Enhancements +- Mode of payments disappear on loading draft pos invoice ([#24917](https://github.com/frappe/erpnext/pull/24917)) +- Sales order not saving due type mismatch in promo scheme (#24748) ([#25222](https://github.com/frappe/erpnext/pull/25222)) +- Zero amount completed delivery notes being shown in Sales Invoice get items ([#25317](https://github.com/frappe/erpnext/pull/25317)) +- Incorrect status creating PR from PO after creating PI ([#25109](https://github.com/frappe/erpnext/pull/25109)) +- Precision and formatted document for stock level in item dashboard. ([#24921](https://github.com/frappe/erpnext/pull/24921)) +- Precision issues while allocating advance amount ([#25086](https://github.com/frappe/erpnext/pull/25086)) +- Round off final tax amount instead of current tax amount ([#25188](https://github.com/frappe/erpnext/pull/25188)) +- Redesign fixes ([#24896](https://github.com/frappe/erpnext/pull/24896)) +- TDS check getting checked after reload ([#24972](https://github.com/frappe/erpnext/pull/24972)) +- Github Action not failing when tests fail ([#24867](https://github.com/frappe/erpnext/pull/24867)) +- Calculate 80g certificate amount on validate for memberships ([#24925](https://github.com/frappe/erpnext/pull/24925)) +- Purchase from registered composition dealer ([#25040](https://github.com/frappe/erpnext/pull/25040)) +- Reduce number of queries for checking if future SL entry exists ([#24881](https://github.com/frappe/erpnext/pull/24881)) +- Remove unwanted parameter in calculate_rate_and_amount ([#24883](https://github.com/frappe/erpnext/pull/24883)) +- Membership renewal validation ([#24963](https://github.com/frappe/erpnext/pull/24963)) +- Not able to save material request ([#25112](https://github.com/frappe/erpnext/pull/25112)) +- POS print receipt ([#25330](https://github.com/frappe/erpnext/pull/25330)) +- Supplier was not able to Submit RFQ due to insufficient permission ([#24622](https://github.com/frappe/erpnext/pull/24622)) +- Unequal debit and credit issue on RCM Invoice ([#24836](https://github.com/frappe/erpnext/pull/24836)) +- Picked Qty conversion from Stock Qty to Qty while creating DN from Pick List ([#25105](https://github.com/frappe/erpnext/pull/25105)) +- Salary Structure object has no attribute set_totals ([#25113](https://github.com/frappe/erpnext/pull/25113)) +- Incorrect Nil Exempt and Non GST amount in GSTR3B report ([#24916](https://github.com/frappe/erpnext/pull/24916)) +- Add method for regional round off account back ([#24893](https://github.com/frappe/erpnext/pull/24893)) +- Employee profile pic upload access for erpnext user ([#25022](https://github.com/frappe/erpnext/pull/25022)) +- Make filters for payroll entry ([#25386](https://github.com/frappe/erpnext/pull/25386)) +- Fix dynamically changing grid properties ([#25310](https://github.com/frappe/erpnext/pull/25310)) +- Consider paid repayment entries in subsequent loan repayments ([#25271](https://github.com/frappe/erpnext/pull/25271)) +- Allow duplicate additional salaries ([#24842](https://github.com/frappe/erpnext/pull/24842)) +- Object referencing the same address issue ([#25159](https://github.com/frappe/erpnext/pull/25159)) +- Validating party currency with doc currency ([#24318](https://github.com/frappe/erpnext/pull/24318)) +- Non Profit fixes ([#25060](https://github.com/frappe/erpnext/pull/25060)) +- Additional Salary component amount not getting set ([#25356](https://github.com/frappe/erpnext/pull/25356)) +- Allow user to update exchange rate in Multi-currency LCV ([#24912](https://github.com/frappe/erpnext/pull/24912)) +- Allow creating stock entry based on work order for customer provided items ([#24885](https://github.com/frappe/erpnext/pull/24885)) +- Create property setters for shorter naming series on setup ([#25128](https://github.com/frappe/erpnext/pull/25128)) +- Add GST category field in Delivery Note ([#25053](https://github.com/frappe/erpnext/pull/25053)) +- Ignore Permission for Leave Ledger Entry ([#25172](https://github.com/frappe/erpnext/pull/25172)) +- Pending shortfall update on processing loan security shortfall ([#24971](https://github.com/frappe/erpnext/pull/24971)) +- Added flag for dont_fetch_price_list_rate in transaction ([#25041](https://github.com/frappe/erpnext/pull/25041)) +- Exchange Rate not getting set in Salary Slip ([#25004](https://github.com/frappe/erpnext/pull/25004)) +- Repost not completed backdated transactions ([#24980](https://github.com/frappe/erpnext/pull/24980)) +- frappe.whitelist for doc methods ([#25230](https://github.com/frappe/erpnext/pull/25230)) +- Opportunity-quotation mapping order status ([#25001](https://github.com/frappe/erpnext/pull/25001)) +- GST on freight charge in e-invoicing ([#25000](https://github.com/frappe/erpnext/pull/25000)) +- Role to override maintain same rate check in transactions ([#25193](https://github.com/frappe/erpnext/pull/25193)) +- Added blank option for status in report related to issue ([#25082](https://github.com/frappe/erpnext/pull/25082)) +- Cashier query in POS Opening/Closing Entry ([#25399](https://github.com/frappe/erpnext/pull/25399)) +- Lead Source's module ([#24583](https://github.com/frappe/erpnext/pull/24583)) +- Hide alt tag if item is not shown in website ([#24937](https://github.com/frappe/erpnext/pull/24937)) +- Ignore Customer Group Perm on All Products page ([#25397](https://github.com/frappe/erpnext/pull/25397)) +- Give first preference to loan security on repayment ([#25212](https://github.com/frappe/erpnext/pull/25212)) +- Add shortfall ratio in Loan Security Shortfall ([#25138](https://github.com/frappe/erpnext/pull/25138)) +- Condition for SLA status banner ([#25261](https://github.com/frappe/erpnext/pull/25261)) +- Component amount calculation based on formula with abbr not working ([#25117](https://github.com/frappe/erpnext/pull/25117)) +- Remove gst name validation for purchase Invoice ([#25235](https://github.com/frappe/erpnext/pull/25235)) +- Do not fetch stopped MR in production plan ([#25063](https://github.com/frappe/erpnext/pull/25063)) +- Backport missing commits to develop branch ([#25305](https://github.com/frappe/erpnext/pull/25305)) +- UOM length unit in global setup list is empty ([#24855](https://github.com/frappe/erpnext/pull/24855)) +- Round total quantity in job card ([#25240](https://github.com/frappe/erpnext/pull/25240)) +- Default total_estimated_cost to zero ([#24939](https://github.com/frappe/erpnext/pull/24939)) +- Serial no refresh issue ([#25127](https://github.com/frappe/erpnext/pull/25127)) +- Correct calculation for discount amount when margin is set ([#25179](https://github.com/frappe/erpnext/pull/25179)) +- Get correct holiday list when calculating dates; test fixes ([#24901](https://github.com/frappe/erpnext/pull/24901)) +- POS print receipt ([#24924](https://github.com/frappe/erpnext/pull/24924)) +- Condition for setting agreement status ([#25255](https://github.com/frappe/erpnext/pull/25255)) +- Loan Repayment entry cancellation on salary slip cancel ([#24879](https://github.com/frappe/erpnext/pull/24879)) +- Add company validation for e-invoicing ([#25349](https://github.com/frappe/erpnext/pull/25349)) +- Query values incorrectly escaped while back updating Quality Inspection ([#25118](https://github.com/frappe/erpnext/pull/25118)) +- Update Bin via Update Item on Purchase/Sales Order ([#23509](https://github.com/frappe/erpnext/pull/23509)) +- Declare data before assigning ([#25287](https://github.com/frappe/erpnext/pull/25287)) +- Do not set standard link in Sales Invoice as custom ([#25096](https://github.com/frappe/erpnext/pull/25096)) +- Hide serial and batch selector in Stock Entry ([#25107](https://github.com/frappe/erpnext/pull/25107)) +- Taxable value including Freight and Forwarding charges in GSTR-1 Report ([#25290](https://github.com/frappe/erpnext/pull/25290)) +- Remove nonexistent method from pick list ([#25279](https://github.com/frappe/erpnext/pull/25279)) +- Allow zero valuation in stock reconciliation ([#24888](https://github.com/frappe/erpnext/pull/24888)) +- Place of supply of e-invoicing ([#25148](https://github.com/frappe/erpnext/pull/25148)) +- Delivery note print error ([#25080](https://github.com/frappe/erpnext/pull/25080)) +- Fix Payment references from disappearing on adding Cost Center in Payment Entry ([#24831](https://github.com/frappe/erpnext/pull/24831)) +- Company field in Warehouse ([#25196](https://github.com/frappe/erpnext/pull/25196)) +- Available employee for selection ([#25378](https://github.com/frappe/erpnext/pull/25378)) +- Cannot set qty to less than zero ([#25258](https://github.com/frappe/erpnext/pull/25258)) +- Don't delete mode of payment account details while deleting comp… ([#25217](https://github.com/frappe/erpnext/pull/25217)) +- Exclude current doc while validation. ([#24914](https://github.com/frappe/erpnext/pull/24914)) +- POS Opening Entry with empty balance detail rows ([#24876](https://github.com/frappe/erpnext/pull/24876)) +- Unable to submit stock entry ([#25033](https://github.com/frappe/erpnext/pull/25033)) +- BOM cost test case ([#25242](https://github.com/frappe/erpnext/pull/25242)) +- Filter for employees in salary slip ([#25361](https://github.com/frappe/erpnext/pull/25361)) +- Added correct path in hooks ([#24862](https://github.com/frappe/erpnext/pull/24862)) +- Patch regional fields for old companies ([#24988](https://github.com/frappe/erpnext/pull/24988)) +- consolidated sales invoice posting date ([#25119](https://github.com/frappe/erpnext/pull/25119)) +- Don't set "Company:company:default_currency" as default for currency link fields ([#25095](https://github.com/frappe/erpnext/pull/25095)) +- Healthcare lab module rename fields ([#25276](https://github.com/frappe/erpnext/pull/25276)) +- Error message compensatory leave request ([#25206](https://github.com/frappe/erpnext/pull/25206)) +- Adding company link to e invoice settings patch condition ([#25301](https://github.com/frappe/erpnext/pull/25301)) +- Membership and Donation API fixes ([#24900](https://github.com/frappe/erpnext/pull/24900)) +- Set correct ack no. on irn generation ([#25251](https://github.com/frappe/erpnext/pull/25251)) +- Report Issue Summary fix for zero issues ([#24934](https://github.com/frappe/erpnext/pull/24934)) +- Validation msg for TransDocNo e-invoicing ([#25121](https://github.com/frappe/erpnext/pull/25121)) +- Correct state code for 'Other Territory' ([#24993](https://github.com/frappe/erpnext/pull/24993)) +- Commit individual SLE rename for large datasets (develop) ([#25084](https://github.com/frappe/erpnext/pull/25084)) +- Remove shipping address GSTIN validation for e-invoice ([#25153](https://github.com/frappe/erpnext/pull/25153)) +- Period list for exponential smoothing forecasting report ([#24982](https://github.com/frappe/erpnext/pull/24982)) +- Customer creation from shopping cart ([#25136](https://github.com/frappe/erpnext/pull/25136)) +- Simplified logic for additional salary ([#24824](https://github.com/frappe/erpnext/pull/24824)) +- Item wise tax rate for consolidated POS invoice ([#25029](https://github.com/frappe/erpnext/pull/25029)) +- Column width in Recruitment analytics report ([#25003](https://github.com/frappe/erpnext/pull/25003)) +- Filter Bank Account drop-down list in Bank Reconciliation Tool ([#24873](https://github.com/frappe/erpnext/pull/24873)) +- Payroll issues ([#24540](https://github.com/frappe/erpnext/pull/24540)) +- PO not created against all selected suppliers (drop shipping) ([#24863](https://github.com/frappe/erpnext/pull/24863)) +- Can't multiply sequence by non-int of type 'float' ([#25092](https://github.com/frappe/erpnext/pull/25092)) +- Make Discharge Schedule Date as Datetime ([#24940](https://github.com/frappe/erpnext/pull/24940)) +- Serial no trim issue ([#24949](https://github.com/frappe/erpnext/pull/24949)) From 039a5a829ddc7a7ef034665c7050f06277c7da8b Mon Sep 17 00:00:00 2001 From: pateljannat Date: Wed, 21 Apr 2021 12:32:25 +0530 Subject: [PATCH 096/326] fix: dynamic values for tests --- .../test_project_profitability.py | 35 +++++++++++-------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/erpnext/projects/report/project_profitability/test_project_profitability.py b/erpnext/projects/report/project_profitability/test_project_profitability.py index 96659bc606..251b71da59 100644 --- a/erpnext/projects/report/project_profitability/test_project_profitability.py +++ b/erpnext/projects/report/project_profitability/test_project_profitability.py @@ -29,21 +29,26 @@ class TestProjectProfitability(unittest.TestCase): } report = execute(filters) - expected_data = [ - { - 'customer_name': '_Test Customer', - 'employee_name': 'test_employee_9@salary.com', - 'base_grand_total': 100.0, - 'base_gross_pay': 78100.0, - 'profit': -19425.0, - 'total_billed_hours': 2.0, - 'utilization': 0.25, - 'fractional_cost': 19525.0, - 'total_working_days': 1.0 - } - ] - for key in ['customer_name','employee_name','base_grand_total','base_gross_pay','profit','total_billed_hours','utilization','fractional_cost','total_working_days']: - self.assertEqual(expected_data[0].get(key), report[1][0].get(key)) + + row = report[1][0] + timesheet = frappe.get_doc("Timesheet", self.timesheet.name) + + self.assertEqual(self.sales_invoice.customer, row.customer_name) + self.assertEqual(timesheet.title, row.employee_name) + self.assertEqual(self.sales_invoice.base_grand_total, row.base_grand_total) + self.assertEqual(self.salary_slip.base_gross_pay, row.base_gross_pay) + self.assertEqual(timesheet.total_billed_hours, row.total_billed_hours) + self.assertEqual(self.salary_slip.total_working_days, row.total_working_days) + + standard_working_hours = frappe.db.get_single_value("HR Settings", "standard_working_hours") + utilization = timesheet.total_billed_hours/(self.salary_slip.total_working_days * standard_working_hours) + self.assertEqual(utilization, row.utilization) + + profit = self.sales_invoice.base_grand_total - self.salary_slip.base_gross_pay * utilization + self.assertEqual(profit, row.profit) + + fractional_cost = self.salary_slip.base_gross_pay * utilization + self.assertEqual(fractional_cost, row.fractional_cost) def tearDown(self): frappe.get_doc("Sales Invoice", self.sales_invoice.name).cancel() From c3b7c1b998d3012f08ff986774c2e2801f74aadf Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Wed, 21 Apr 2021 12:54:49 +0550 Subject: [PATCH 097/326] bumped to version 13.1.0 --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 199a183e47..4da0605370 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -5,7 +5,7 @@ import frappe from erpnext.hooks import regional_overrides from frappe.utils import getdate -__version__ = '13.0.0-dev' +__version__ = '13.1.0' def get_default_company(user=None): '''Get default company for user''' From 6d3305c4461053a6d978beef379ca0f090fc10fa Mon Sep 17 00:00:00 2001 From: Afshan Date: Wed, 21 Apr 2021 15:34:58 +0530 Subject: [PATCH 098/326] fix: added total column --- .../dimension_wise_accounts_balance_report.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.py b/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.py index 7f38770a32..de7ed4926e 100644 --- a/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.py +++ b/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.py @@ -120,6 +120,7 @@ def prepare_data(accounts, filters, parent_children_map, company_currency, dimen for d in accounts: has_value = False + total = 0 row = { "account": d.name, "parent_account": d.parent_account, @@ -137,8 +138,10 @@ def prepare_data(accounts, filters, parent_children_map, company_currency, dimen if abs(row[frappe.scrub(item)]) >= 0.005: # ignore zero values has_value = True + total += flt(d.get(frappe.scrub(item), 0.0), 3) row["has_value"] = has_value + row["total"] = total data.append(row) return data @@ -199,5 +202,12 @@ def get_columns(dimension_items_list, accumulated_values=1, company=None): "options": "currency", "width": 150 }) + columns.append({ + "fieldname": "total", + "label": "Total", + "fieldtype": "Currency", + "options": "currency", + "width": 150 + }) return columns From 485464d1f49172debd439fd50917fba2744b2526 Mon Sep 17 00:00:00 2001 From: pateljannat Date: Wed, 21 Apr 2021 18:04:58 +0530 Subject: [PATCH 099/326] fix: added back a hook --- erpnext/hooks.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index af357cd331..bb6cd8bdc2 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -262,6 +262,7 @@ doc_events = { ], "on_trash": "erpnext.regional.check_deletion_permission", "validate": [ + "erpnext.regional.india.utils.validate_document_name", "erpnext.regional.india.utils.update_taxable_values" ] }, From 090177494dbc4bd6477c7783ee5eaa6113726658 Mon Sep 17 00:00:00 2001 From: Ernesto Ruiz Date: Wed, 21 Apr 2021 09:04:06 -0600 Subject: [PATCH 100/326] fix: Make strings translatable Make strings translatable --- .../projects/report/project_summary/project_summary.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/projects/report/project_summary/project_summary.py b/erpnext/projects/report/project_summary/project_summary.py index ea7f1ab2e7..2c7bb49cfb 100644 --- a/erpnext/projects/report/project_summary/project_summary.py +++ b/erpnext/projects/report/project_summary/project_summary.py @@ -131,25 +131,25 @@ def get_report_summary(data): { "value": avg_completion, "indicator": "Green" if avg_completion > 50 else "Red", - "label": "Average Completion", + "label": _("Average Completion"), "datatype": "Percent", }, { "value": total, "indicator": "Blue", - "label": "Total Tasks", + "label": _("Total Tasks"), "datatype": "Int", }, { "value": completed, "indicator": "Green", - "label": "Completed Tasks", + "label": _("Completed Tasks"), "datatype": "Int", }, { "value": total_overdue, "indicator": "Green" if total_overdue == 0 else "Red", - "label": "Overdue Tasks", + "label": _("Overdue Tasks"), "datatype": "Int", } ] From 157b3882098a0d1b72edf48a46ad364ae481befd Mon Sep 17 00:00:00 2001 From: Alan <2.alan.tom@gmail.com> Date: Wed, 21 Apr 2021 21:00:22 +0530 Subject: [PATCH 101/326] fix: change subcontracted item display (#25425) --- erpnext/public/js/utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index fd98f17ac1..19c9073090 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -712,7 +712,7 @@ erpnext.utils.map_current_doc = function(opts) { } frappe.form.link_formatters['Item'] = function(value, doc) { - if (doc && value && doc.item_name && doc.item_name !== value) { + if (doc && value && doc.item_name && doc.item_name !== value && doc.item_code === value) { return value + ': ' + doc.item_name; } else if (!value && doc.doctype && doc.item_name) { // format blank value in child table From 7f932cfb30f32224cbf500efa86ea7d87462b543 Mon Sep 17 00:00:00 2001 From: Mohammad Hasnain Mohsin Rajan Date: Thu, 22 Apr 2021 04:53:09 +0530 Subject: [PATCH 102/326] fix: display reconcile tool when closing balance 0 (#25417) Co-authored-by: Saqib --- .../bank_reconciliation_tool/bank_reconciliation_tool.js | 3 +-- .../bank_reconciliation_tool.json | 9 ++++----- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.js b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.js index 10f660a140..f7d471b725 100644 --- a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.js +++ b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.js @@ -78,8 +78,7 @@ frappe.ui.form.on("Bank Reconciliation Tool", { if ( frm.doc.bank_account && frm.doc.bank_statement_from_date && - frm.doc.bank_statement_to_date && - frm.doc.bank_statement_closing_balance + frm.doc.bank_statement_to_date ) { frm.trigger("render_chart"); frm.trigger("render"); diff --git a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.json b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.json index 4837db3b86..b643e6e091 100644 --- a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.json +++ b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.json @@ -39,13 +39,13 @@ "depends_on": "eval: doc.bank_account", "fieldname": "bank_statement_from_date", "fieldtype": "Date", - "label": "Bank Statement From Date" + "label": "From Date" }, { "depends_on": "eval: doc.bank_statement_from_date", "fieldname": "bank_statement_to_date", "fieldtype": "Date", - "label": "Bank Statement To Date" + "label": "To Date" }, { "fieldname": "column_break_2", @@ -63,11 +63,10 @@ "depends_on": "eval: doc.bank_statement_to_date", "fieldname": "bank_statement_closing_balance", "fieldtype": "Currency", - "label": "Bank Statement Closing Balance", + "label": "Closing Balance", "options": "Currency" }, { - "depends_on": "eval: doc.bank_statement_closing_balance", "fieldname": "section_break_1", "fieldtype": "Section Break", "label": "Reconcile" @@ -90,7 +89,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2021-02-02 01:35:53.043578", + "modified": "2021-04-21 11:13:49.831769", "modified_by": "Administrator", "module": "Accounts", "name": "Bank Reconciliation Tool", From ba6ad7f5be20b9d23346a439d1e0092857553d66 Mon Sep 17 00:00:00 2001 From: pateljannat Date: Thu, 22 Apr 2021 11:19:44 +0530 Subject: [PATCH 103/326] fix: escape for format and msg for working hours if not set --- .../project_profitability/project_profitability.py | 13 +++++++++---- erpnext/regional/india/utils.py | 2 +- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/erpnext/projects/report/project_profitability/project_profitability.py b/erpnext/projects/report/project_profitability/project_profitability.py index 405c6fd344..7703b81cf7 100644 --- a/erpnext/projects/report/project_profitability/project_profitability.py +++ b/erpnext/projects/report/project_profitability/project_profitability.py @@ -25,6 +25,11 @@ def get_data(filters): def get_rows(filters): conditions = get_conditions(filters) standard_working_hours = frappe.db.get_single_value("HR Settings", "standard_working_hours") + if not standard_working_hours: + hr_settings = "HR Settings" + frappe.msgprint(_("The metrics for this report are calculated based on the Standard Working Hours. Please set Standard Working Hours in {0}.").format(hr_settings)) + return [] + sql = """ SELECT * @@ -89,7 +94,7 @@ def get_conditions(filters): conditions = [] if filters.get("company"): - conditions.append("tabTimesheet.company='{0}'".format(filters.get("company"))) + conditions.append("tabTimesheet.company={0}".format(frappe.db.escape(filters.get("company")))) if filters.get("start_date"): conditions.append("tabTimesheet.start_date>='{0}'".format(filters.get("start_date"))) @@ -98,13 +103,13 @@ def get_conditions(filters): conditions.append("tabTimesheet.end_date<='{0}'".format(filters.get("end_date"))) if filters.get("customer_name"): - conditions.append("si.customer_name='{0}'".format(filters.get("customer_name"))) + conditions.append("si.customer_name={0}".format(frappe.db.escape(filters.get("customer_name")))) if filters.get("employee"): - conditions.append("tabTimesheet.employee='{0}'".format(filters.get("employee"))) + conditions.append("tabTimesheet.employee={0}".format(frappe.db.escape(filters.get("employee")))) if filters.get("project"): - conditions.append("tabTimesheet.parent_project='{0}'".format(filters.get("project"))) + conditions.append("tabTimesheet.parent_project={0}".format(frappe.db.escape(filters.get("project")))) conditions = " and ".join(conditions) return conditions diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index 0c757e962d..6338056698 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -161,7 +161,7 @@ def validate_document_name(doc, method=None): # Date was chosen as start of next FY to avoid irritating current users. if country != "India" or getdate(doc.posting_date) < getdate("2021-04-01"): return - print(doc.name) + if len(doc.name) > 16: frappe.throw(_("Maximum length of document number should be 16 characters as per GST rules. Please change the naming series.")) From b0e160ff7838442270d6918ef1aa4a6668e3b7f4 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Thu, 22 Apr 2021 13:23:50 +0530 Subject: [PATCH 104/326] fix: ignore fraction difference while making round off gl entry --- erpnext/accounts/general_ledger.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index dac0c216c8..f18de020fc 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -170,11 +170,11 @@ def round_off_debit_credit(gl_map): else: allowance = .5 - if abs(debit_credit_diff) >= allowance: + if abs(debit_credit_diff) > allowance: frappe.throw(_("Debit and Credit not equal for {0} #{1}. Difference is {2}.") .format(gl_map[0].voucher_type, gl_map[0].voucher_no, debit_credit_diff)) - elif abs(debit_credit_diff) >= (1.0 / (10**precision)): + elif abs(debit_credit_diff) > (1.0 / (10**precision)): make_round_off_gle(gl_map, debit_credit_diff, precision) def make_round_off_gle(gl_map, debit_credit_diff, precision): From 19b73968d75f8976bd0dd47310919be320c42433 Mon Sep 17 00:00:00 2001 From: Saqib Date: Thu, 22 Apr 2021 13:38:20 +0530 Subject: [PATCH 105/326] fix(pos): validations & minor ui issues (#25351) * fix: pos print format * fix: stock validation on pos invoice creation --- .../pos_closing_entry/pos_closing_entry.py | 20 ------ .../doctype/pos_invoice/pos_invoice.py | 68 +++++++++++-------- .../doctype/pos_invoice/test_pos_invoice.py | 30 ++++++++ .../page/point_of_sale/pos_past_order_list.js | 2 +- .../gst_pos_invoice/gst_pos_invoice.json | 5 +- .../print_format/pos_invoice/pos_invoice.json | 5 +- 6 files changed, 76 insertions(+), 54 deletions(-) diff --git a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py index 949211d35a..1065168a50 100644 --- a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py +++ b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py @@ -16,28 +16,8 @@ class POSClosingEntry(StatusUpdater): if frappe.db.get_value("POS Opening Entry", self.pos_opening_entry, "status") != "Open": frappe.throw(_("Selected POS Opening Entry should be open."), title=_("Invalid Opening Entry")) - self.validate_pos_closing() self.validate_pos_invoices() - def validate_pos_closing(self): - user = frappe.db.sql(""" - SELECT name FROM `tabPOS Closing Entry` - WHERE - user = %(user)s AND docstatus = 1 AND pos_profile = %(profile)s AND - (period_start_date between %(start)s and %(end)s OR period_end_date between %(start)s and %(end)s) - """, { - 'user': self.user, - 'profile': self.pos_profile, - 'start': self.period_start_date, - 'end': self.period_end_date - }) - - if user: - bold_already_exists = frappe.bold(_("already exists")) - bold_user = frappe.bold(self.user) - frappe.throw(_("POS Closing Entry {} against {} between selected period") - .format(bold_already_exists, bold_user), title=_("Invalid Period")) - def validate_pos_invoices(self): invalid_rows = [] for d in self.pos_transactions: diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py index e614459252..1e6a3d1b3b 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py @@ -96,30 +96,45 @@ class POSInvoice(SalesInvoice): if paid_amt and pay.amount != paid_amt: return frappe.throw(_("Payment related to {0} is not completed").format(pay.mode_of_payment)) + def validate_pos_reserved_serial_nos(self, item): + serial_nos = get_serial_nos(item.serial_no) + filters = {"item_code": item.item_code, "warehouse": item.warehouse} + if item.batch_no: + filters["batch_no"] = item.batch_no + + reserved_serial_nos = get_pos_reserved_serial_nos(filters) + invalid_serial_nos = [s for s in serial_nos if s in reserved_serial_nos] + + bold_invalid_serial_nos = frappe.bold(', '.join(invalid_serial_nos)) + if len(invalid_serial_nos) == 1: + frappe.throw(_("Row #{}: Serial No. {} has already been transacted into another POS Invoice. Please select valid serial no.") + .format(item.idx, bold_invalid_serial_nos), title=_("Item Unavailable")) + elif invalid_serial_nos: + frappe.throw(_("Row #{}: Serial Nos. {} has already been transacted into another POS Invoice. Please select valid serial no.") + .format(item.idx, bold_invalid_serial_nos), title=_("Item Unavailable")) + + def validate_delivered_serial_nos(self, item): + serial_nos = get_serial_nos(item.serial_no) + delivered_serial_nos = frappe.db.get_list('Serial No', { + 'item_code': item.item_code, + 'name': ['in', serial_nos], + 'sales_invoice': ['is', 'set'] + }, pluck='name') + + if delivered_serial_nos: + bold_delivered_serial_nos = frappe.bold(', '.join(delivered_serial_nos)) + frappe.throw(_("Row #{}: Serial No. {} has already been transacted into another Sales Invoice. Please select valid serial no.") + .format(item.idx, bold_delivered_serial_nos), title=_("Item Unavailable")) + def validate_stock_availablility(self): if self.is_return: return - allow_negative_stock = frappe.db.get_value('Stock Settings', None, 'allow_negative_stock') - error_msg = [] + allow_negative_stock = frappe.db.get_single_value('Stock Settings', 'allow_negative_stock') for d in self.get('items'): - msg = "" if d.serial_no: - filters = { "item_code": d.item_code, "warehouse": d.warehouse } - if d.batch_no: - filters["batch_no"] = d.batch_no - reserved_serial_nos = get_pos_reserved_serial_nos(filters) - serial_nos = get_serial_nos(d.serial_no) - invalid_serial_nos = [s for s in serial_nos if s in reserved_serial_nos] - - bold_invalid_serial_nos = frappe.bold(', '.join(invalid_serial_nos)) - if len(invalid_serial_nos) == 1: - msg = (_("Row #{}: Serial No. {} has already been transacted into another POS Invoice. Please select valid serial no.") - .format(d.idx, bold_invalid_serial_nos)) - elif invalid_serial_nos: - msg = (_("Row #{}: Serial Nos. {} has already been transacted into another POS Invoice. Please select valid serial no.") - .format(d.idx, bold_invalid_serial_nos)) - + self.validate_pos_reserved_serial_nos(d) + self.validate_delivered_serial_nos(d) else: if allow_negative_stock: return @@ -127,15 +142,11 @@ class POSInvoice(SalesInvoice): available_stock = get_stock_availability(d.item_code, d.warehouse) item_code, warehouse, qty = frappe.bold(d.item_code), frappe.bold(d.warehouse), frappe.bold(d.qty) if flt(available_stock) <= 0: - msg = (_('Row #{}: Item Code: {} is not available under warehouse {}.').format(d.idx, item_code, warehouse)) + frappe.throw(_('Row #{}: Item Code: {} is not available under warehouse {}.') + .format(d.idx, item_code, warehouse), title=_("Item Unavailable")) elif flt(available_stock) < flt(d.qty): - msg = (_('Row #{}: Stock quantity not enough for Item Code: {} under warehouse {}. Available quantity {}.') - .format(d.idx, item_code, warehouse, qty)) - if msg: - error_msg.append(msg) - - if error_msg: - frappe.throw(error_msg, title=_("Item Unavailable"), as_list=True) + frappe.throw(_('Row #{}: Stock quantity not enough for Item Code: {} under warehouse {}. Available quantity {}.') + .format(d.idx, item_code, warehouse, available_stock), title=_("Item Unavailable")) def validate_serialised_or_batched_item(self): error_msg = [] @@ -202,9 +213,8 @@ class POSInvoice(SalesInvoice): for d in self.get("items"): is_stock_item = frappe.get_cached_value("Item", d.get("item_code"), "is_stock_item") if not is_stock_item: - frappe.throw(_("Row #{}: Item {} is a non stock item. You can only include stock items in a POS Invoice. ").format( - d.idx, frappe.bold(d.item_code) - ), title=_("Invalid Item")) + frappe.throw(_("Row #{}: Item {} is a non stock item. You can only include stock items in a POS Invoice. ") + .format(d.idx, frappe.bold(d.item_code)), title=_("Invalid Item")) def validate_mode_of_payment(self): if len(self.payments) == 0: diff --git a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py index 6d388c4aaa..6172796129 100644 --- a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py @@ -10,10 +10,12 @@ from erpnext.accounts.doctype.pos_invoice.pos_invoice import make_sales_return from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt from erpnext.stock.doctype.item.test_item import make_item +from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice class TestPOSInvoice(unittest.TestCase): @classmethod def setUpClass(cls): + make_stock_entry(target="_Test Warehouse - _TC", item_code="_Test Item", qty=800, basic_rate=100) frappe.db.sql("delete from `tabTax Rule`") def tearDown(self): @@ -320,6 +322,34 @@ class TestPOSInvoice(unittest.TestCase): self.assertRaises(frappe.ValidationError, pos2.insert) + def test_delivered_serialized_item_transaction(self): + from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item + from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos + + se = make_serialized_item(company='_Test Company', + target_warehouse="Stores - _TC", cost_center='Main - _TC', expense_account='Cost of Goods Sold - _TC') + + serial_nos = get_serial_nos(se.get("items")[0].serial_no) + + si = create_sales_invoice(company='_Test Company', debit_to='Debtors - _TC', + account_for_change_amount='Cash - _TC', warehouse='Stores - _TC', income_account='Sales - _TC', + expense_account='Cost of Goods Sold - _TC', cost_center='Main - _TC', + item=se.get("items")[0].item_code, rate=1000, do_not_save=1) + + si.get("items")[0].serial_no = serial_nos[0] + si.insert() + si.submit() + + pos2 = create_pos_invoice(company='_Test Company', debit_to='Debtors - _TC', + account_for_change_amount='Cash - _TC', warehouse='Stores - _TC', income_account='Sales - _TC', + expense_account='Cost of Goods Sold - _TC', cost_center='Main - _TC', + item=se.get("items")[0].item_code, rate=1000, do_not_save=1) + + pos2.get("items")[0].serial_no = serial_nos[0] + pos2.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - _TC', 'amount': 1000}) + + self.assertRaises(frappe.ValidationError, pos2.insert) + def test_loyalty_points(self): from erpnext.accounts.doctype.loyalty_program.test_loyalty_program import create_records from erpnext.accounts.doctype.loyalty_program.loyalty_program import get_loyalty_program_details_with_points diff --git a/erpnext/selling/page/point_of_sale/pos_past_order_list.js b/erpnext/selling/page/point_of_sale/pos_past_order_list.js index ec392313f5..70c7dc2adf 100644 --- a/erpnext/selling/page/point_of_sale/pos_past_order_list.js +++ b/erpnext/selling/page/point_of_sale/pos_past_order_list.js @@ -105,7 +105,7 @@ erpnext.PointOfSale.PastOrderList = class { - ${invoice.customer} + ${frappe.ellipsis(invoice.customer, 20)}
diff --git a/erpnext/selling/print_format/gst_pos_invoice/gst_pos_invoice.json b/erpnext/selling/print_format/gst_pos_invoice/gst_pos_invoice.json index 9094a07bcc..9d1b196cf0 100644 --- a/erpnext/selling/print_format/gst_pos_invoice/gst_pos_invoice.json +++ b/erpnext/selling/print_format/gst_pos_invoice/gst_pos_invoice.json @@ -1,4 +1,5 @@ { + "absolute_value": 0, "align_labels_right": 0, "creation": "2017-08-08 12:33:04.773099", "custom_format": 1, @@ -7,10 +8,10 @@ "docstatus": 0, "doctype": "Print Format", "font": "Default", - "html": "\n\n{% if letter_head %}\n {{ letter_head }}\n{% endif %}\n

\n\t{{ doc.company }}
\n\t{% if doc.company_address_display %}\n\t\t{% set company_address = doc.company_address_display.replace(\"\\n\", \" \").replace(\"
\", \" \") %}\n\t\t{% if \"GSTIN\" not in company_address %}\n\t\t\t{{ company_address }}\n\t\t\t{{ _(\"GSTIN\") }}:{{ doc.company_gstin }}\n\t\t{% else %}\n\t\t\t{{ company_address.replace(\"GSTIN\", \"
GSTIN\") }}\n\t\t{% endif %}\n\t{% endif %}\n\t
\n\t{% if doc.docstatus == 0 %}\n\t\t{{ doc.status + \" \"+ (doc.select_print_heading or _(\"Invoice\")) }}
\n\t{% else %}\n\t\t{{ doc.select_print_heading or _(\"Invoice\") }}
\n\t{% endif %}\n

\n

\n\t{{ _(\"Receipt No\") }}: {{ doc.name }}
\n\t{{ _(\"Date\") }}: {{ doc.get_formatted(\"posting_date\") }}
\n\t{% if doc.grand_total > 50000 %}\n\t\t{% set customer_address = doc.address_display.replace(\"\\n\", \" \").replace(\"
\", \" \") %}\n\t\t{{ _(\"Customer\") }}:
\n\t\t{{ doc.customer_name }}
\n\t\t{{ customer_address }}\n\t{% endif %}\n

\n\n
\n\n\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\n\t\n\t\t{%- for item in doc.items -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{%- endfor -%}\n\t\n
{{ _(\"Item\") }}{{ _(\"Qty\") }}{{ _(\"Amount\") }}
\n\t\t\t\t{{ item.item_code }}\n\t\t\t\t{%- if item.item_name != item.item_code -%}\n\t\t\t\t\t
{{ item.item_name }}\n\t\t\t\t{%- endif -%}\n\t\t\t\t{%- if item.gst_hsn_code -%}\n\t\t\t\t\t
{{ _(\"HSN/SAC\") }}: {{ item.gst_hsn_code }}\n\t\t\t\t{%- endif -%}\n\t\t\t\t{%- if item.serial_no -%}\n\t\t\t\t\t
{{ _(\"SR.No\") }}:
\n\t\t\t\t\t{{ item.serial_no | replace(\"\\n\", \", \") }}\n\t\t\t\t{%- endif -%}\n\t\t\t
{{ item.qty }}
@ {{ item.rate }}
{{ item.get_formatted(\"amount\") }}
\n\n\t\n\t\t\n\t\t\t{% if doc.flags.show_inclusive_tax_in_print %}\n\t\t\t\t\n\t\t\t\t\n\t\t\t{% else %}\n\t\t\t\t\n\t\t\t\t\n\t\t\t{% endif %}\n\t\t\n\t\t{%- for row in doc.taxes -%}\n\t\t {%- if (not row.included_in_print_rate or doc.flags.show_inclusive_tax_in_print) and row.tax_amount != 0 -%}\n\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t {%- endif -%}\n\t\t{%- endfor -%}\n\t\t{%- if doc.discount_amount -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{%- endif -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{%- if doc.rounded_total -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{%- endif -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t{%- if doc.change_amount -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t{%- endif -%}\n\t\n
\n\t\t\t\t\t{{ _(\"Total Excl. Tax\") }}\n\t\t\t\t\n\t\t\t\t\t{{ doc.get_formatted(\"net_total\", doc) }}\n\t\t\t\t\n\t\t\t\t\t{{ _(\"Total\") }}\n\t\t\t\t\n\t\t\t\t\t{{ doc.get_formatted(\"total\", doc) }}\n\t\t\t\t
\n\t\t\t\t\t{% if '%' in row.description %}\n\t\t\t\t\t {{ row.description }}\n\t\t\t\t\t{% else %}\n\t\t\t\t\t {{ row.description }}@{{ row.rate }}%\n\t\t\t\t\t{% endif %}\n\t\t\t\t\n\t\t\t\t\t{{ row.get_formatted(\"tax_amount\", doc) }}\n\t\t\t\t
\n\t\t\t\t{{ _(\"Discount\") }}\n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"discount_amount\") }}\n\t\t\t
\n\t\t\t\t{{ _(\"Grand Total\") }}\n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"grand_total\") }}\n\t\t\t
\n\t\t\t\t{{ _(\"Rounded Total\") }}\n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"rounded_total\") }}\n\t\t\t
\n\t\t\t\t{{ _(\"Paid Amount\") }}\n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"paid_amount\") }}\n\t\t\t
\n\t\t\t\t{{ _(\"Change Amount\") }}\n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"change_amount\") }}\n\t\t\t
\n

{{ doc.terms or \"\" }}

\n

{{ _(\"Thank you, please visit again.\") }}

", + "html": "\n\n{% if letter_head %}\n {{ letter_head }}\n{% endif %}\n

\n\t{{ doc.company }}
\n\t{% if doc.company_address_display %}\n\t\t{% set company_address = doc.company_address_display.replace(\"\\n\", \" \").replace(\"
\", \" \") %}\n\t\t{% if \"GSTIN\" not in company_address %}\n\t\t\t{{ company_address }}\n\t\t\t{{ _(\"GSTIN\") }}:{{ doc.company_gstin }}\n\t\t{% else %}\n\t\t\t{{ company_address.replace(\"GSTIN\", \"
GSTIN\") }}\n\t\t{% endif %}\n\t{% endif %}\n\t
\n\t{% if doc.docstatus == 0 %}\n\t\t{{ doc.status + \" \"+ (doc.select_print_heading or _(\"Invoice\")) }}
\n\t{% else %}\n\t\t{{ doc.select_print_heading or _(\"Invoice\") }}
\n\t{% endif %}\n

\n\n

\n\t{{ _(\"Receipt No\") }}: {{ doc.name }}
\n\t{{ _(\"Cashier\") }}: {{ doc.owner }}
\n\t{{ _(\"Date\") }}: {{ doc.get_formatted(\"posting_date\") }}
\n\t{{ _(\"Time\") }}: {{ doc.get_formatted(\"posting_time\") }}
\n\t{% if doc.grand_total > 50000 %}\n\t\t{% set customer_address = doc.address_display.replace(\"\\n\", \" \").replace(\"
\", \" \") %}\n\t\t{{ _(\"Customer\") }}:
\n\t\t{{ doc.customer_name }}
\n\t\t{{ customer_address }}\n\t{% endif %}\n

\n\n
\n\n\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\n\t\n\t\t{%- for item in doc.items -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{%- endfor -%}\n\t\n
{{ _(\"Item\") }}{{ _(\"Qty\") }}{{ _(\"Amount\") }}
\n\t\t\t\t{{ item.item_code }}\n\t\t\t\t{%- if item.item_name != item.item_code -%}\n\t\t\t\t\t
{{ item.item_name }}\n\t\t\t\t{%- endif -%}\n\t\t\t\t{%- if item.gst_hsn_code -%}\n\t\t\t\t\t
{{ _(\"HSN/SAC\") }}: {{ item.gst_hsn_code }}\n\t\t\t\t{%- endif -%}\n\t\t\t\t{%- if item.serial_no -%}\n\t\t\t\t\t
{{ _(\"SR.No\") }}:
\n\t\t\t\t\t{{ item.serial_no | replace(\"\\n\", \", \") }}\n\t\t\t\t{%- endif -%}\n\t\t\t
{{ item.qty }}
@ {{ item.rate }}
{{ item.get_formatted(\"amount\") }}
\n\n\t\n\t\t\n\t\t\t{% if doc.flags.show_inclusive_tax_in_print %}\n\t\t\t\t\n\t\t\t\t\n\t\t\t{% else %}\n\t\t\t\t\n\t\t\t\t\n\t\t\t{% endif %}\n\t\t\n\t\t{%- for row in doc.taxes -%}\n\t\t {%- if (not row.included_in_print_rate or doc.flags.show_inclusive_tax_in_print) and row.tax_amount != 0 -%}\n\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t {%- endif -%}\n\t\t{%- endfor -%}\n\t\t{%- if doc.discount_amount -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{%- endif -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{%- if doc.rounded_total -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{%- endif -%}\n\t\t{%- for row in doc.payments -%}\n\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t{%- endfor -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t{%- if doc.change_amount -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t{%- endif -%}\n\t\n
\n\t\t\t\t\t{{ _(\"Total Excl. Tax\") }}\n\t\t\t\t\n\t\t\t\t\t{{ doc.get_formatted(\"net_total\", doc) }}\n\t\t\t\t\n\t\t\t\t\t{{ _(\"Total\") }}\n\t\t\t\t\n\t\t\t\t\t{{ doc.get_formatted(\"total\", doc) }}\n\t\t\t\t
\n\t\t\t\t\t{% if '%' in row.description %}\n\t\t\t\t\t {{ row.description }}\n\t\t\t\t\t{% else %}\n\t\t\t\t\t {{ row.description }}@{{ row.rate }}%\n\t\t\t\t\t{% endif %}\n\t\t\t\t\n\t\t\t\t\t{{ row.get_formatted(\"tax_amount\", doc) }}\n\t\t\t\t
\n\t\t\t\t{{ _(\"Discount\") }}\n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"discount_amount\") }}\n\t\t\t
\n\t\t\t\t{{ _(\"Grand Total\") }}\n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"grand_total\") }}\n\t\t\t
\n\t\t\t\t{{ _(\"Rounded Total\") }}\n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"rounded_total\") }}\n\t\t\t
\n\t\t\t\t {{ row.mode_of_payment }}\n\t\t\t\t\n\t\t\t\t\t{{ row.get_formatted(\"amount\", doc) }}\n\t\t\t\t
\n\t\t\t\t{{ _(\"Paid Amount\") }}\n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"paid_amount\") }}\n\t\t\t
\n\t\t\t\t{{ _(\"Change Amount\") }}\n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"change_amount\") }}\n\t\t\t
\n

{{ doc.terms or \"\" }}

\n

{{ _(\"Thank you, please visit again.\") }}

", "idx": 0, "line_breaks": 0, - "modified": "2020-04-29 16:47:02.743246", + "modified": "2021-04-15 15:26:04.396169", "modified_by": "Administrator", "module": "Selling", "name": "GST POS Invoice", diff --git a/erpnext/selling/print_format/pos_invoice/pos_invoice.json b/erpnext/selling/print_format/pos_invoice/pos_invoice.json index 99094ed9b0..6c01e26587 100644 --- a/erpnext/selling/print_format/pos_invoice/pos_invoice.json +++ b/erpnext/selling/print_format/pos_invoice/pos_invoice.json @@ -1,4 +1,5 @@ { + "absolute_value": 0, "align_labels_right": 0, "creation": "2011-12-21 11:08:55", "custom_format": 1, @@ -6,10 +7,10 @@ "doc_type": "POS Invoice", "docstatus": 0, "doctype": "Print Format", - "html": "\n\n{% if letter_head %}\n {{ letter_head }}\n{% endif %}\n\n

\n\t{{ doc.company }}
\n\t{{ doc.select_print_heading or _(\"Invoice\") }}
\n

\n

\n\t{{ _(\"Receipt No\") }}: {{ doc.name }}
\n\t{{ _(\"Date\") }}: {{ doc.get_formatted(\"posting_date\") }}
\n\t{{ _(\"Customer\") }}: {{ doc.customer_name }}\n

\n\n
\n\n\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\n\t\n\t\t{%- for item in doc.items -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{%- endfor -%}\n\t\n
{{ _(\"Item\") }}{{ _(\"Qty\") }}{{ _(\"Amount\") }}
\n\t\t\t\t{{ item.item_code }}\n\t\t\t\t{%- if item.item_name != item.item_code -%}\n\t\t\t\t\t
{{ item.item_name }}\n\t\t\t\t{%- endif -%}\n\t\t\t\t{%- if item.serial_no -%}\n\t\t\t\t\t
{{ _(\"SR.No\") }}:
\n\t\t\t\t\t{{ item.serial_no | replace(\"\\n\", \", \") }}\n\t\t\t\t{%- endif -%}\n\t\t\t
{{ item.qty }}
@ {{ item.get_formatted(\"rate\") }}
{{ item.get_formatted(\"amount\") }}
\n\n\t\n\t\t\n\t\t\t{% if doc.flags.show_inclusive_tax_in_print %}\n\t\t\t\t\n\t\t\t\t\n\t\t\t{% else %}\n\t\t\t\t\n\t\t\t\t\n\t\t\t{% endif %}\n\t\t\n\t\t{%- for row in doc.taxes -%}\n\t\t {%- if not row.included_in_print_rate or doc.flags.show_inclusive_tax_in_print -%}\n\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t {%- endif -%}\n\t\t{%- endfor -%}\n\n\t\t{%- if doc.discount_amount -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{%- endif -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{%- if doc.rounded_total -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{%- endif -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{%- if doc.change_amount -%}\n\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t{%- endif -%}\n\t\n
\n\t\t\t\t\t{{ _(\"Total Excl. Tax\") }}\n\t\t\t\t\n\t\t\t\t\t{{ doc.get_formatted(\"net_total\", doc) }}\n\t\t\t\t\n\t\t\t\t\t{{ _(\"Total\") }}\n\t\t\t\t\n\t\t\t\t\t{{ doc.get_formatted(\"total\", doc) }}\n\t\t\t\t
\n\t\t\t\t {% if '%' in row.description %}\n\t\t\t\t\t {{ row.description }}\n\t\t\t\t\t{% else %}\n\t\t\t\t\t {{ row.description }}@{{ row.rate }}%\n\t\t\t\t\t{% endif %}\n\t\t\t\t\n\t\t\t\t\t{{ row.get_formatted(\"tax_amount\", doc) }}\n\t\t\t\t
\n\t\t\t\t{{ _(\"Discount\") }}\n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"discount_amount\") }}\n\t\t\t
\n\t\t\t\t{{ _(\"Grand Total\") }}\n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"grand_total\") }}\n\t\t\t
\n\t\t\t\t{{ _(\"Rounded Total\") }}\n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"rounded_total\") }}\n\t\t\t
\n\t\t\t\t{{ _(\"Paid Amount\") }}\n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"paid_amount\") }}\n\t\t\t
\n\t\t\t\t\t{{ _(\"Change Amount\") }}\n\t\t\t\t\n\t\t\t\t\t{{ doc.get_formatted(\"change_amount\") }}\n\t\t\t\t
\n
\n

{{ doc.terms or \"\" }}

\n

{{ _(\"Thank you, please visit again.\") }}

", + "html": "\n\n{% if letter_head %}\n {{ letter_head }}\n{% endif %}\n\n

\n\t{{ doc.company }}
\n\t{{ doc.select_print_heading or _(\"Invoice\") }}
\n

\n

\n\t{{ _(\"Receipt No\") }}: {{ doc.name }}
\n\t{{ _(\"Cashier\") }}: {{ doc.owner }}
\n\t{{ _(\"Customer\") }}: {{ doc.customer_name }}
\n\t{{ _(\"Date\") }}: {{ doc.get_formatted(\"posting_date\") }}
\n\t{{ _(\"Time\") }}: {{ doc.get_formatted(\"posting_time\") }}
\n

\n\n
\n\n\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\n\t\n\t\t{%- for item in doc.items -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{%- endfor -%}\n\t\n
{{ _(\"Item\") }}{{ _(\"Qty\") }}{{ _(\"Amount\") }}
\n\t\t\t\t{{ item.item_code }}\n\t\t\t\t{%- if item.item_name != item.item_code -%}\n\t\t\t\t\t
{{ item.item_name }}\n\t\t\t\t{%- endif -%}\n\t\t\t\t{%- if item.serial_no -%}\n\t\t\t\t\t
{{ _(\"SR.No\") }}:
\n\t\t\t\t\t{{ item.serial_no | replace(\"\\n\", \", \") }}\n\t\t\t\t{%- endif -%}\n\t\t\t
{{ item.qty }}
@ {{ item.get_formatted(\"rate\") }}
{{ item.get_formatted(\"amount\") }}
\n\n\t\n\t\t\n\t\t\t{% if doc.flags.show_inclusive_tax_in_print %}\n\t\t\t\t\n\t\t\t\t\n\t\t\t{% else %}\n\t\t\t\t\n\t\t\t\t\n\t\t\t{% endif %}\n\t\t\n\t\t{%- for row in doc.taxes -%}\n\t\t {%- if not row.included_in_print_rate or doc.flags.show_inclusive_tax_in_print -%}\n\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t {%- endif -%}\n\t\t{%- endfor -%}\n\n\t\t{%- if doc.discount_amount -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{%- endif -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{%- if doc.rounded_total -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{%- endif -%}\n\t\t{%- for row in doc.payments -%}\n\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t{%- endfor -%}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{%- if doc.change_amount -%}\n\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t{%- endif -%}\n\t\n
\n\t\t\t\t\t{{ _(\"Total Excl. Tax\") }}\n\t\t\t\t\n\t\t\t\t\t{{ doc.get_formatted(\"net_total\", doc) }}\n\t\t\t\t\n\t\t\t\t\t{{ _(\"Total\") }}\n\t\t\t\t\n\t\t\t\t\t{{ doc.get_formatted(\"total\", doc) }}\n\t\t\t\t
\n\t\t\t\t {% if '%' in row.description %}\n\t\t\t\t\t {{ row.description }}\n\t\t\t\t\t{% else %}\n\t\t\t\t\t {{ row.description }}@{{ row.rate }}%\n\t\t\t\t\t{% endif %}\n\t\t\t\t\n\t\t\t\t\t{{ row.get_formatted(\"tax_amount\", doc) }}\n\t\t\t\t
\n\t\t\t\t{{ _(\"Discount\") }}\n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"discount_amount\") }}\n\t\t\t
\n\t\t\t\t{{ _(\"Grand Total\") }}\n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"grand_total\") }}\n\t\t\t
\n\t\t\t\t{{ _(\"Rounded Total\") }}\n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"rounded_total\") }}\n\t\t\t
\n\t\t\t\t {{ row.mode_of_payment }}\n\t\t\t\t\n\t\t\t\t\t{{ row.get_formatted(\"amount\", doc) }}\n\t\t\t\t
\n\t\t\t\t{{ _(\"Paid Amount\") }}\n\t\t\t\n\t\t\t\t{{ doc.get_formatted(\"paid_amount\") }}\n\t\t\t
\n\t\t\t\t\t{{ _(\"Change Amount\") }}\n\t\t\t\t\n\t\t\t\t\t{{ doc.get_formatted(\"change_amount\") }}\n\t\t\t\t
\n
\n

{{ doc.terms or \"\" }}

\n

{{ _(\"Thank you, please visit again.\") }}

", "idx": 1, "line_breaks": 0, - "modified": "2020-04-29 16:45:58.942375", + "modified": "2021-04-15 15:23:28.867135", "modified_by": "Administrator", "module": "Selling", "name": "POS Invoice", From 2004fec8eb3aa03d933fe5fdc6f217f5ec7c70e5 Mon Sep 17 00:00:00 2001 From: pateljannat Date: Thu, 22 Apr 2021 14:52:36 +0530 Subject: [PATCH 106/326] fix: removed multicurrency --- .../project_profitability.py | 32 ------------------- 1 file changed, 32 deletions(-) diff --git a/erpnext/projects/report/project_profitability/project_profitability.py b/erpnext/projects/report/project_profitability/project_profitability.py index 7703b81cf7..0051c47458 100644 --- a/erpnext/projects/report/project_profitability/project_profitability.py +++ b/erpnext/projects/report/project_profitability/project_profitability.py @@ -18,7 +18,6 @@ def execute(filters=None): def get_data(filters): data = get_rows(filters) - data = handle_multi_currency(data, filters) data = calculate_cost_and_profit(data) return data @@ -53,37 +52,6 @@ def get_rows(filters): {0}) as t""".format(conditions) return frappe.db.sql(sql,filters, as_dict=True) -def handle_multi_currency(data, filters): - currency_precision = get_currency_precision() or 2 - company_currency = frappe.get_cached_value("Company", filters.get("company"), "default_currency") - - for row in data: - row.currency = company_currency - - if filters.get("employee"): - party_currency = get_employee_currency(row.employee) - - if filters.get("customer_name"): - party_currency = frappe.db.get_value("Customer", row.customer_name, ["default_currency"]) - - if party_currency and party_currency != company_currency: - exchange_rate = get_exchange_rate(company_currency, party_currency) - row.currency = party_currency - - row.base_grand_total = flt(flt(row.base_grand_total) * - flt(exchange_rate), currency_precision) - - row.base_gross_pay = flt(flt(row.base_gross_pay) * - flt(exchange_rate), currency_precision) - - row.profit = flt(flt(row.profit) * - flt(exchange_rate), currency_precision) - - row.fractional_cost = flt(flt(row.fractional_cost) * - flt(exchange_rate), currency_precision) - - return data - def calculate_cost_and_profit(data): for row in data: row.fractional_cost = row.base_gross_pay * row.utilization From 3847452b60d6c0856e97cce0d84f3d05b1cc9ee6 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Thu, 22 Apr 2021 16:11:01 +0530 Subject: [PATCH 107/326] fix: Laboratory Module patch (#25436) --- .../healthcare_lab_module_rename_doctypes.py | 41 ++++++++++++++----- 1 file changed, 31 insertions(+), 10 deletions(-) diff --git a/erpnext/patches/v13_0/healthcare_lab_module_rename_doctypes.py b/erpnext/patches/v13_0/healthcare_lab_module_rename_doctypes.py index a78f802574..028c61976c 100644 --- a/erpnext/patches/v13_0/healthcare_lab_module_rename_doctypes.py +++ b/erpnext/patches/v13_0/healthcare_lab_module_rename_doctypes.py @@ -38,16 +38,37 @@ def execute(): """.format(doctype), {'parentfield': parentfield}) # copy renamed child table fields (fields were already renamed in old doctype json, hence sql) - frappe.db.sql("""UPDATE `tabNormal Test Result` SET lab_test_name = test_name""") - frappe.db.sql("""UPDATE `tabNormal Test Result` SET lab_test_event = test_event""") - frappe.db.sql("""UPDATE `tabNormal Test Result` SET lab_test_uom = test_uom""") - frappe.db.sql("""UPDATE `tabNormal Test Result` SET lab_test_comment = test_comment""") - frappe.db.sql("""UPDATE `tabNormal Test Template` SET lab_test_event = test_event""") - frappe.db.sql("""UPDATE `tabNormal Test Template` SET lab_test_uom = test_uom""") - frappe.db.sql("""UPDATE `tabDescriptive Test Result` SET lab_test_particulars = test_particulars""") - frappe.db.sql("""UPDATE `tabLab Test Group Template` SET lab_test_template = test_template""") - frappe.db.sql("""UPDATE `tabLab Test Group Template` SET lab_test_description = test_description""") - frappe.db.sql("""UPDATE `tabLab Test Group Template` SET lab_test_rate = test_rate""") + rename_fields = { + 'lab_test_name': 'test_name', + 'lab_test_event': 'test_event', + 'lab_test_uom': 'test_uom', + 'lab_test_comment': 'test_comment' + } + + for new, old in rename_fields.items(): + if frappe.db.has_column('Normal Test Result', old): + frappe.db.sql("""UPDATE `tabNormal Test Result` SET %(new)s = %(old)s""", { + 'new': new, 'old': old}) + + if frappe.db.has_column('Normal Test Template', 'test_event'): + frappe.db.sql("""UPDATE `tabNormal Test Template` SET lab_test_event = test_event""") + + if frappe.db.has_column('Normal Test Template', 'test_uom'): + frappe.db.sql("""UPDATE `tabNormal Test Template` SET lab_test_uom = test_uom""") + + if frappe.db.has_column('Descriptive Test Result', 'test_particulars'): + frappe.db.sql("""UPDATE `tabDescriptive Test Result` SET lab_test_particulars = test_particulars""") + + rename_fields = { + 'lab_test_template': 'test_template', + 'lab_test_description': 'test_description', + 'lab_test_rate': 'test_rate' + } + + for new, old in rename_fields.items(): + if frappe.db.has_column('Lab Test Group Template', old): + frappe.db.sql("""UPDATE `tabLab Test Group Template` SET %(new)s = %(old)s""", { + 'new': new, 'old': old}) # rename field frappe.reload_doc('healthcare', 'doctype', 'lab_test') From e3b8057eb4fc79f5b7fcc1b85a1c4a141cbc7859 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Thu, 22 Apr 2021 16:11:17 +0530 Subject: [PATCH 108/326] fix: Laboratory Module patch (#25431) --- .../healthcare_lab_module_rename_doctypes.py | 41 ++++++++++++++----- 1 file changed, 31 insertions(+), 10 deletions(-) diff --git a/erpnext/patches/v13_0/healthcare_lab_module_rename_doctypes.py b/erpnext/patches/v13_0/healthcare_lab_module_rename_doctypes.py index a78f802574..028c61976c 100644 --- a/erpnext/patches/v13_0/healthcare_lab_module_rename_doctypes.py +++ b/erpnext/patches/v13_0/healthcare_lab_module_rename_doctypes.py @@ -38,16 +38,37 @@ def execute(): """.format(doctype), {'parentfield': parentfield}) # copy renamed child table fields (fields were already renamed in old doctype json, hence sql) - frappe.db.sql("""UPDATE `tabNormal Test Result` SET lab_test_name = test_name""") - frappe.db.sql("""UPDATE `tabNormal Test Result` SET lab_test_event = test_event""") - frappe.db.sql("""UPDATE `tabNormal Test Result` SET lab_test_uom = test_uom""") - frappe.db.sql("""UPDATE `tabNormal Test Result` SET lab_test_comment = test_comment""") - frappe.db.sql("""UPDATE `tabNormal Test Template` SET lab_test_event = test_event""") - frappe.db.sql("""UPDATE `tabNormal Test Template` SET lab_test_uom = test_uom""") - frappe.db.sql("""UPDATE `tabDescriptive Test Result` SET lab_test_particulars = test_particulars""") - frappe.db.sql("""UPDATE `tabLab Test Group Template` SET lab_test_template = test_template""") - frappe.db.sql("""UPDATE `tabLab Test Group Template` SET lab_test_description = test_description""") - frappe.db.sql("""UPDATE `tabLab Test Group Template` SET lab_test_rate = test_rate""") + rename_fields = { + 'lab_test_name': 'test_name', + 'lab_test_event': 'test_event', + 'lab_test_uom': 'test_uom', + 'lab_test_comment': 'test_comment' + } + + for new, old in rename_fields.items(): + if frappe.db.has_column('Normal Test Result', old): + frappe.db.sql("""UPDATE `tabNormal Test Result` SET %(new)s = %(old)s""", { + 'new': new, 'old': old}) + + if frappe.db.has_column('Normal Test Template', 'test_event'): + frappe.db.sql("""UPDATE `tabNormal Test Template` SET lab_test_event = test_event""") + + if frappe.db.has_column('Normal Test Template', 'test_uom'): + frappe.db.sql("""UPDATE `tabNormal Test Template` SET lab_test_uom = test_uom""") + + if frappe.db.has_column('Descriptive Test Result', 'test_particulars'): + frappe.db.sql("""UPDATE `tabDescriptive Test Result` SET lab_test_particulars = test_particulars""") + + rename_fields = { + 'lab_test_template': 'test_template', + 'lab_test_description': 'test_description', + 'lab_test_rate': 'test_rate' + } + + for new, old in rename_fields.items(): + if frappe.db.has_column('Lab Test Group Template', old): + frappe.db.sql("""UPDATE `tabLab Test Group Template` SET %(new)s = %(old)s""", { + 'new': new, 'old': old}) # rename field frappe.reload_doc('healthcare', 'doctype', 'lab_test') From 3d4acf9e5cc1e388ec7a2e07ed4c78dca56be422 Mon Sep 17 00:00:00 2001 From: Saqib Date: Thu, 22 Apr 2021 16:16:03 +0530 Subject: [PATCH 109/326] fix: permission error after submitting exchange rate revaluation (#25432) --- .../exchange_rate_revaluation.js | 24 ++++++++----------- .../exchange_rate_revaluation.py | 17 +++++++++++++ 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.js b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.js index 1092f4c8f1..b7b6020caa 100644 --- a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.js +++ b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.js @@ -21,21 +21,17 @@ frappe.ui.form.on('Exchange Rate Revaluation', { refresh: function(frm) { if(frm.doc.docstatus==1) { - frappe.db.get_value("Journal Entry Account", { - 'reference_type': 'Exchange Rate Revaluation', - 'reference_name': frm.doc.name, - 'docstatus': 1 - }, "sum(debit) as sum", (r) =>{ - let total_amt = 0; - frm.doc.accounts.forEach(d=> { - total_amt = total_amt + d['new_balance_in_base_currency']; - }); - if(total_amt !== r.sum) { - frm.add_custom_button(__('Journal Entry'), function() { - return frm.events.make_jv(frm); - }, __('Create')); + frappe.call({ + method: 'check_journal_entry_condition', + doc: frm.doc, + callback: function(r) { + if (r.message) { + frm.add_custom_button(__('Journal Entry'), function() { + return frm.events.make_jv(frm); + }, __('Create')); + } } - }, 'Journal Entry'); + }); } }, diff --git a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py index c1b8ba70ba..56193216a2 100644 --- a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py +++ b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py @@ -27,6 +27,23 @@ class ExchangeRateRevaluation(Document): if not (self.company and self.posting_date): frappe.throw(_("Please select Company and Posting Date to getting entries")) + @frappe.whitelist() + def check_journal_entry_condition(self): + total_debit = frappe.db.get_value("Journal Entry Account", { + 'reference_type': 'Exchange Rate Revaluation', + 'reference_name': self.name, + 'docstatus': 1 + }, "sum(debit) as sum") + + total_amt = 0 + for d in self.accounts: + total_amt = total_amt + d.new_balance_in_base_currency + + if total_amt != total_debit: + return True + + return False + @frappe.whitelist() def get_accounts_data(self, account=None): accounts = [] From 84ef7419da0e117466cac6170bade3ec29c23caf Mon Sep 17 00:00:00 2001 From: pateljannat Date: Thu, 22 Apr 2021 16:31:16 +0530 Subject: [PATCH 110/326] fix: sider --- .../report/project_profitability/project_profitability.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/erpnext/projects/report/project_profitability/project_profitability.py b/erpnext/projects/report/project_profitability/project_profitability.py index 0051c47458..593ce1b22b 100644 --- a/erpnext/projects/report/project_profitability/project_profitability.py +++ b/erpnext/projects/report/project_profitability/project_profitability.py @@ -4,10 +4,6 @@ from __future__ import unicode_literals import frappe from frappe import _ -from erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment import get_employee_currency -from erpnext.accounts.utils import get_currency_precision -from frappe.utils import flt -from erpnext.setup.utils import get_exchange_rate def execute(filters=None): columns, data = [], [] From eed60bf035ddce257e7dd1a0402100cff2f3cfa5 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 22 Apr 2021 17:11:22 +0530 Subject: [PATCH 111/326] fix: Backward compatibility for GSTR-1 report --- erpnext/regional/report/gstr_1/gstr_1.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py index b637fb47b3..808fd3a2cc 100644 --- a/erpnext/regional/report/gstr_1/gstr_1.py +++ b/erpnext/regional/report/gstr_1/gstr_1.py @@ -199,7 +199,7 @@ class Gstr1Report(object): self.item_tax_rate = frappe._dict() items = frappe.db.sql(""" - select item_code, parent, taxable_value, item_tax_rate + select item_code, parent, taxable_value, base_net_amount, item_tax_rate from `tab%s Item` where parent in (%s) """ % (self.doctype, ', '.join(['%s']*len(self.invoices))), tuple(self.invoices), as_dict=1) @@ -207,7 +207,7 @@ class Gstr1Report(object): for d in items: if d.item_code not in self.invoice_items.get(d.parent, {}): self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code, - sum(i.get('taxable_value', 0) for i in items + sum((i.get('taxable_value', 0) or i.get('base_net_amount', 0)) for i in items if i.item_code == d.item_code and i.parent == d.parent)) item_tax_rate = {} From c21ce42559edba387f9959f2b502dc493afb1fc0 Mon Sep 17 00:00:00 2001 From: Anoop <3326959+akurungadam@users.noreply.github.com> Date: Thu, 22 Apr 2021 22:44:44 +0530 Subject: [PATCH 112/326] fix(patch): lab module patch fix (#25447) * fix(patch): lab module patch fix * fix: param Co-authored-by: Rucha Mahabal --- .../v13_0/healthcare_lab_module_rename_doctypes.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/patches/v13_0/healthcare_lab_module_rename_doctypes.py b/erpnext/patches/v13_0/healthcare_lab_module_rename_doctypes.py index 028c61976c..9af0a8dbef 100644 --- a/erpnext/patches/v13_0/healthcare_lab_module_rename_doctypes.py +++ b/erpnext/patches/v13_0/healthcare_lab_module_rename_doctypes.py @@ -47,8 +47,8 @@ def execute(): for new, old in rename_fields.items(): if frappe.db.has_column('Normal Test Result', old): - frappe.db.sql("""UPDATE `tabNormal Test Result` SET %(new)s = %(old)s""", { - 'new': new, 'old': old}) + frappe.db.sql("""UPDATE `tabNormal Test Result` SET {} = {}""" + .format(new, old)) if frappe.db.has_column('Normal Test Template', 'test_event'): frappe.db.sql("""UPDATE `tabNormal Test Template` SET lab_test_event = test_event""") @@ -67,8 +67,8 @@ def execute(): for new, old in rename_fields.items(): if frappe.db.has_column('Lab Test Group Template', old): - frappe.db.sql("""UPDATE `tabLab Test Group Template` SET %(new)s = %(old)s""", { - 'new': new, 'old': old}) + frappe.db.sql("""UPDATE `tabLab Test Group Template` SET {} = {}""" + .format(new, old)) # rename field frappe.reload_doc('healthcare', 'doctype', 'lab_test') From 373e248e9fac5500462fb97d6c80c721f0e3ba6a Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Thu, 22 Apr 2021 22:47:39 +0530 Subject: [PATCH 113/326] fix(patch): lab module patch fix (#25447) (#25449) * fix(patch): lab module patch fix * fix: param Co-authored-by: Rucha Mahabal Co-authored-by: Anoop <3326959+akurungadam@users.noreply.github.com> --- .../v13_0/healthcare_lab_module_rename_doctypes.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/patches/v13_0/healthcare_lab_module_rename_doctypes.py b/erpnext/patches/v13_0/healthcare_lab_module_rename_doctypes.py index 028c61976c..9af0a8dbef 100644 --- a/erpnext/patches/v13_0/healthcare_lab_module_rename_doctypes.py +++ b/erpnext/patches/v13_0/healthcare_lab_module_rename_doctypes.py @@ -47,8 +47,8 @@ def execute(): for new, old in rename_fields.items(): if frappe.db.has_column('Normal Test Result', old): - frappe.db.sql("""UPDATE `tabNormal Test Result` SET %(new)s = %(old)s""", { - 'new': new, 'old': old}) + frappe.db.sql("""UPDATE `tabNormal Test Result` SET {} = {}""" + .format(new, old)) if frappe.db.has_column('Normal Test Template', 'test_event'): frappe.db.sql("""UPDATE `tabNormal Test Template` SET lab_test_event = test_event""") @@ -67,8 +67,8 @@ def execute(): for new, old in rename_fields.items(): if frappe.db.has_column('Lab Test Group Template', old): - frappe.db.sql("""UPDATE `tabLab Test Group Template` SET %(new)s = %(old)s""", { - 'new': new, 'old': old}) + frappe.db.sql("""UPDATE `tabLab Test Group Template` SET {} = {}""" + .format(new, old)) # rename field frappe.reload_doc('healthcare', 'doctype', 'lab_test') From 8787ebf83d7a3ca217e130d8c8eec62d36410a3e Mon Sep 17 00:00:00 2001 From: meike289 <63092915+meike289@users.noreply.github.com> Date: Fri, 23 Apr 2021 08:06:29 +0200 Subject: [PATCH 114/326] Apply suggestions from code review Co-authored-by: Ankush Menat --- erpnext/accounts/doctype/subscription/subscription.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/subscription/subscription.py b/erpnext/accounts/doctype/subscription/subscription.py index f19bae7465..175710c9f8 100644 --- a/erpnext/accounts/doctype/subscription/subscription.py +++ b/erpnext/accounts/doctype/subscription/subscription.py @@ -384,7 +384,7 @@ class Subscription(Document): invoice.flags.ignore_mandatory = True invoice.save() - if self.submit_invoice == 1: + if self.submit_invoice: invoice.submit() return invoice From 68b514b1f5c1d8221415b39a55b299082dd10d9c Mon Sep 17 00:00:00 2001 From: Revant Nandgaonkar Date: Fri, 23 Apr 2021 13:06:13 +0530 Subject: [PATCH 115/326] docs: add docker repo link in README --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index bb592ae75c..708668a1a5 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,10 @@ ERPNext is built on the [Frappe Framework](https://github.com/frappe/frappe), a --- +### Containerized Installation + +Use docker to deploy ERPNext in production or for development of frappe framework apps. See https://github.com/frappe/frappe_docker for more details. + ### Full Install The Easy Way: our install script for bench will install all dependencies (e.g. MariaDB). See https://github.com/frappe/bench for more details. From 52ea6b126bb68e72780ffef4d79c2e8dafd3f96e Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Fri, 23 Apr 2021 14:14:47 +0530 Subject: [PATCH 116/326] Revert "fix: email digest user not found" This reverts commit 188657d05a9f0199de898094bbe033e51d52b930. --- erpnext/setup/doctype/email_digest/email_digest.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/erpnext/setup/doctype/email_digest/email_digest.py b/erpnext/setup/doctype/email_digest/email_digest.py index ac55fdfdb8..8c97322a71 100644 --- a/erpnext/setup/doctype/email_digest/email_digest.py +++ b/erpnext/setup/doctype/email_digest/email_digest.py @@ -50,8 +50,12 @@ class EmailDigest(Document): recipients = list(filter(lambda r: r in valid_users, self.recipient_list.split("\n"))) + original_user = frappe.session.user + if recipients: for user_id in recipients: + frappe.set_user(user_id) + frappe.set_user_lang(user_id) msg_for_this_recipient = self.get_msg_html() if msg_for_this_recipient: frappe.sendmail( @@ -62,6 +66,9 @@ class EmailDigest(Document): reference_name = self.name, unsubscribe_message = _("Unsubscribe from this Email Digest")) + frappe.set_user(original_user) + frappe.set_user_lang(original_user) + def get_msg_html(self): """Build email digest content""" frappe.flags.ignore_account_permission = True From d552fe6778f71e940f4acdb594afcf79fe5757c2 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Fri, 23 Apr 2021 14:46:52 +0530 Subject: [PATCH 117/326] feat: base payment amount in payment schedule --- .../payment_schedule/payment_schedule.json | 36 +++++++++++-- .../purchase_invoice/test_purchase_invoice.py | 2 +- erpnext/controllers/accounts_controller.py | 53 ++++++++++++------- erpnext/public/js/controllers/transaction.js | 5 +- 4 files changed, 70 insertions(+), 26 deletions(-) diff --git a/erpnext/accounts/doctype/payment_schedule/payment_schedule.json b/erpnext/accounts/doctype/payment_schedule/payment_schedule.json index 39eda331db..e9f8ba29dc 100644 --- a/erpnext/accounts/doctype/payment_schedule/payment_schedule.json +++ b/erpnext/accounts/doctype/payment_schedule/payment_schedule.json @@ -20,10 +20,13 @@ "discount", "section_break_9", "payment_amount", + "outstanding", + "paid_amount", "discounted_amount", "column_break_3", - "outstanding", - "paid_amount" + "base_payment_amount", + "base_outstanding", + "base_paid_amount" ], "fields": [ { @@ -65,6 +68,7 @@ "fieldtype": "Currency", "in_list_view": 1, "label": "Payment Amount", + "options": "currency", "reqd": 1 }, { @@ -77,7 +81,8 @@ "depends_on": "paid_amount", "fieldname": "paid_amount", "fieldtype": "Currency", - "label": "Paid Amount" + "label": "Paid Amount", + "options": "currency" }, { "fieldname": "column_break_3", @@ -96,6 +101,7 @@ "fieldname": "outstanding", "fieldtype": "Currency", "label": "Outstanding", + "options": "currency", "read_only": 1 }, { @@ -144,12 +150,34 @@ { "fieldname": "section_break_4", "fieldtype": "Section Break" + }, + { + "fieldname": "base_payment_amount", + "fieldtype": "Currency", + "label": "Payment Amount (Company Currency)", + "options": "Company:company:default_currency" + }, + { + "default": "0", + "fieldname": "base_outstanding", + "fieldtype": "Currency", + "label": "Outstanding (Company Currency)", + "options": "Company:company:default_currency", + "read_only": 1 + }, + { + "default": "0", + "fieldname": "base_paid_amount", + "fieldtype": "Currency", + "label": "Paid Amount (Company Currency)", + "options": "Company:company:default_currency", + "read_only": 1 } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-04-12 15:39:47.363609", + "modified": "2021-04-23 13:55:59.548043", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Schedule", diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 50492f50b5..66be11ff23 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -397,7 +397,7 @@ class TestPurchaseInvoice(unittest.TestCase): pi.update({ "payment_schedule": get_payment_terms("_Test Payment Term Template", - pi.posting_date, pi.grand_total) + pi.posting_date, pi.grand_total, pi.base_grand_total) }) pi.save() diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index d36e7b03f4..c51d8283e5 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -904,20 +904,24 @@ class AccountsController(TransactionBase): date = self.get("due_date") due_date = date or posting_date - if party_account_currency == self.company_currency: - grand_total = self.get("base_rounded_total") or self.base_grand_total - else: - grand_total = self.get("rounded_total") or self.grand_total + base_grand_total = self.get("base_rounded_total") or self.base_grand_total + grand_total = self.get("rounded_total") or self.grand_total if self.doctype in ("Sales Invoice", "Purchase Invoice"): + base_grand_total = base_grand_total - flt(self.base_write_off_amount) grand_total = grand_total - flt(self.write_off_amount) if self.get("total_advance"): - grand_total -= self.get("total_advance") + if party_account_currency == self.company_currency: + base_grand_total -= self.get("total_advance") + grand_total = flt(base_grand_total / self.get("conversion_rate"), self.precision("grand_total")) + else: + grand_total -= self.get("total_advance") + base_grand_total = flt(grand_total * self.get("conversion_rate"), self.precision("base_grand_total")) if not self.get("payment_schedule"): if self.get("payment_terms_template"): - data = get_payment_terms(self.payment_terms_template, posting_date, grand_total) + data = get_payment_terms(self.payment_terms_template, posting_date, grand_total, base_grand_total) for item in data: self.append("payment_schedule", item) else: @@ -927,7 +931,9 @@ class AccountsController(TransactionBase): for d in self.get("payment_schedule"): if d.invoice_portion: d.payment_amount = flt(grand_total * flt(d.invoice_portion / 100), d.precision('payment_amount')) + d.base_payment_amount = flt(base_grand_total * flt(d.invoice_portion / 100), d.precision('payment_amount')) d.outstanding = d.payment_amount + d.base_outstanding = d.base_payment_amount def set_due_date(self): due_dates = [d.due_date for d in self.get("payment_schedule") if d.due_date] @@ -963,22 +969,28 @@ class AccountsController(TransactionBase): if self.get("payment_schedule"): total = 0 + base_total = 0 for d in self.get("payment_schedule"): total += flt(d.payment_amount) + base_total += flt(d.base_payment_amount) - if party_account_currency == self.company_currency: - total = flt(total, self.precision("base_grand_total")) - grand_total = flt(self.get("base_rounded_total") or self.base_grand_total, self.precision('base_grand_total')) - else: - total = flt(total, self.precision("grand_total")) - grand_total = flt(self.get("rounded_total") or self.grand_total, self.precision('grand_total')) - - if self.get("total_advance"): - grand_total -= self.get("total_advance") + base_grand_total = self.get("base_rounded_total") or self.base_grand_total + grand_total = self.get("rounded_total") or self.grand_total if self.doctype in ("Sales Invoice", "Purchase Invoice"): + base_grand_total = base_grand_total - flt(self.base_write_off_amount) grand_total = grand_total - flt(self.write_off_amount) - if total != flt(grand_total, self.precision("grand_total")): + + if self.get("total_advance"): + if party_account_currency == self.company_currency: + base_grand_total -= self.get("total_advance") + grand_total = flt(base_grand_total / self.get("conversion_rate"), self.precision("grand_total")) + else: + grand_total -= self.get("total_advance") + base_grand_total = flt(grand_total * self.get("conversion_rate"), self.precision("base_grand_total")) + + if total != flt(grand_total, self.precision("grand_total")) or \ + base_total != flt(base_grand_total, self.precision("base_grand_total")): frappe.throw(_("Total Payment Amount in Payment Schedule must be equal to Grand / Rounded Total")) def is_rounded_total_disabled(self): @@ -1218,7 +1230,7 @@ def update_invoice_status(): @frappe.whitelist() -def get_payment_terms(terms_template, posting_date=None, grand_total=None, bill_date=None): +def get_payment_terms(terms_template, posting_date=None, grand_total=None, base_grand_total=None, bill_date=None): if not terms_template: return @@ -1226,14 +1238,14 @@ def get_payment_terms(terms_template, posting_date=None, grand_total=None, bill_ schedule = [] for d in terms_doc.get("terms"): - term_details = get_payment_term_details(d, posting_date, grand_total, bill_date) + term_details = get_payment_term_details(d, posting_date, grand_total, base_grand_total, bill_date) schedule.append(term_details) return schedule @frappe.whitelist() -def get_payment_term_details(term, posting_date=None, grand_total=None, bill_date=None): +def get_payment_term_details(term, posting_date=None, grand_total=None, base_grand_total=None, bill_date=None): term_details = frappe._dict() if isinstance(term, text_type): term = frappe.get_doc("Payment Term", term) @@ -1242,10 +1254,11 @@ def get_payment_term_details(term, posting_date=None, grand_total=None, bill_dat term_details.description = term.description term_details.invoice_portion = term.invoice_portion term_details.payment_amount = flt(term.invoice_portion) * flt(grand_total) / 100 + term_details.base_payment_amount = flt(term.invoice_portion) * flt(base_grand_total) / 100 term_details.discount_type = term.discount_type term_details.discount = term.discount - # term_details.discounted_amount = flt(grand_total) * (term.discount / 100) if term.discount_type == 'Percentage' else discount term_details.outstanding = term_details.payment_amount + term_details.base_outstanding = term_details.base_payment_amount term_details.mode_of_payment = term.mode_of_payment if bill_date: diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index a0398e718f..258262aed3 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1995,6 +1995,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ terms_template: doc.payment_terms_template, posting_date: posting_date, grand_total: doc.rounded_total || doc.grand_total, + base_grand_total: doc.base_rounded_total || doc.base_grand_total, bill_date: doc.bill_date }, callback: function(r) { @@ -2009,13 +2010,15 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ payment_term: function(doc, cdt, cdn) { var row = locals[cdt][cdn]; if(row.payment_term) { + debugger; frappe.call({ method: "erpnext.controllers.accounts_controller.get_payment_term_details", args: { term: row.payment_term, bill_date: this.frm.doc.bill_date, posting_date: this.frm.doc.posting_date || this.frm.doc.transaction_date, - grand_total: this.frm.doc.rounded_total || this.frm.doc.grand_total + grand_total: this.frm.doc.rounded_total || this.frm.doc.grand_total, + base_grand_total: this.frm.doc.base_rounded_total || this.frm.doc.base_grand_total }, callback: function(r) { if(r.message && !r.exc) { From c5c9f9a9416250d8ed4fe72d9b738ba586aa54b4 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Fri, 23 Apr 2021 15:34:58 +0530 Subject: [PATCH 118/326] feat: set dynamic labels for payment schedule fields --- erpnext/public/js/controllers/transaction.js | 54 +++++++++++++++++--- 1 file changed, 46 insertions(+), 8 deletions(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 258262aed3..dc731ca213 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -640,6 +640,10 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ let key = item.name; me.apply_rule_on_other_items({key: item}); } + }, + () => { + var company_currency = me.get_company_currency(); + me.update_item_grid_labels(company_currency); } ]); } @@ -1321,11 +1325,9 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ change_grid_labels: function(company_currency) { var me = this; - this.frm.set_currency_labels(["base_rate", "base_net_rate", "base_price_list_rate", "base_amount", "base_net_amount", "base_rate_with_margin"], - company_currency, "items"); + this.update_item_grid_labels(company_currency); - this.frm.set_currency_labels(["rate", "net_rate", "price_list_rate", "amount", "net_amount", "stock_uom_rate", "rate_with_margin"], - this.frm.doc.currency, "items"); + this.toggle_item_grid_columns(company_currency); if(this.frm.fields_dict["operations"]) { this.frm.set_currency_labels(["operating_cost", "hour_rate"], this.frm.doc.currency, "operations"); @@ -1360,6 +1362,42 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ this.frm.doc.party_account_currency, "advances"); } + this.update_payment_schedule_grid_labels(company_currency); + + // set labels + var $wrapper = $(this.frm.wrapper); + }, + + update_item_grid_labels: function(company_currency) { + this.frm.set_currency_labels([ + "base_rate", "base_net_rate", "base_price_list_rate", + "base_amount", "base_net_amount", "base_rate_with_margin" + ], company_currency, "items"); + + this.frm.set_currency_labels([ + "rate", "net_rate", "price_list_rate", "amount", + "net_amount", "stock_uom_rate", "rate_with_margin" + ], this.frm.doc.currency, "items"); + }, + + update_payment_schedule_grid_labels: function(company_currency) { + const me = this; + if(this.frm.fields_dict["payment_schedule"]) { + this.frm.set_currency_labels(["base_payment_amount", "base_outstanding", "base_paid_amount"], + company_currency, "payment_schedule"); + this.frm.set_currency_labels(["payment_amount", "outstanding", "paid_amount"], + this.frm.doc.currency, "payment_schedule"); + + var schedule_grid = this.frm.fields_dict["payment_schedule"].grid; + $.each(["base_payment_amount", "base_outstanding", "base_paid_amount"], function(i, fname) { + if(frappe.meta.get_docfield(schedule_grid.doctype, fname)) + schedule_grid.set_column_disp(fname, me.frm.doc.currency != company_currency); + }); + } + }, + + toggle_item_grid_columns: function(company_currency) { + const me = this; // toggle columns var item_grid = this.frm.fields_dict["items"].grid; $.each(["base_rate", "base_price_list_rate", "base_amount", "base_rate_with_margin"], function(i, fname) { @@ -1379,9 +1417,6 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ if(frappe.meta.get_docfield(item_grid.doctype, fname)) item_grid.set_column_disp(fname, (show && (me.frm.doc.currency != company_currency))); }); - - // set labels - var $wrapper = $(this.frm.wrapper); }, recalculate: function() { @@ -2001,6 +2036,8 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ callback: function(r) { if(r.message && !r.exc) { me.frm.set_value("payment_schedule", r.message); + const company_currency = me.get_company_currency(); + this.update_payment_schedule_grid_labels(company_currency); } } }) @@ -2010,7 +2047,6 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ payment_term: function(doc, cdt, cdn) { var row = locals[cdt][cdn]; if(row.payment_term) { - debugger; frappe.call({ method: "erpnext.controllers.accounts_controller.get_payment_term_details", args: { @@ -2024,6 +2060,8 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ if(r.message && !r.exc) { for (var d in r.message) { frappe.model.set_value(cdt, cdn, d, r.message[d]); + const company_currency = me.get_company_currency(); + this.update_payment_schedule_grid_labels(company_currency); } } } From 62d2df58156b53ed3520444ca305eee838dbc659 Mon Sep 17 00:00:00 2001 From: gavin Date: Fri, 23 Apr 2021 16:31:13 +0530 Subject: [PATCH 119/326] docs: Add Frappe reference in Containerized Install section --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 708668a1a5..0a556f57b4 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ ERPNext is built on the [Frappe Framework](https://github.com/frappe/frappe), a ### Containerized Installation -Use docker to deploy ERPNext in production or for development of frappe framework apps. See https://github.com/frappe/frappe_docker for more details. +Use docker to deploy ERPNext in production or for development of [Frappe](https://github.com/frappe/frappe) apps. See https://github.com/frappe/frappe_docker for more details. ### Full Install From dc599e01643cd53fcacd4bee805061b01bd526ac Mon Sep 17 00:00:00 2001 From: Anuja P Date: Fri, 23 Apr 2021 19:24:53 +0530 Subject: [PATCH 120/326] fix: to update Allocated amount after Paid Amount is changed --- .../doctype/payment_entry/payment_entry.js | 32 ++++++++----------- 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index c2e804e441..936c538e20 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -582,7 +582,7 @@ frappe.ui.form.on('Payment Entry', { } if(frm.doc.payment_type == "Receive") - frm.events.allocate_party_amount_against_ref_docs(frm, frm.doc.paid_amount); + frm.events.allocate_party_amount_against_ref_docs(frm, frm.doc.paid_amount, 1); else frm.events.set_unallocated_amount(frm); }, @@ -606,9 +606,9 @@ frappe.ui.form.on('Payment Entry', { {fieldtype:"Float", label: __("Less Than Amount"), fieldname:"outstanding_amt_less_than"}, {fieldtype:"Section Break"}, {fieldtype:"Link", label:__("Cost Center"), fieldname:"cost_center", options:"Cost Center", - "get_query": function() { - return { - "filters": {"company": frm.doc.company} + "get_query": function() { + return { + "filters": {"company": frm.doc.company} } } }, @@ -743,7 +743,7 @@ frappe.ui.form.on('Payment Entry', { }); }, - allocate_party_amount_against_ref_docs: function(frm, paid_amount) { + allocate_party_amount_against_ref_docs: function(frm, paid_amount, paid_amount_change) { var total_positive_outstanding_including_order = 0; var total_negative_outstanding = 0; var total_deductions = frappe.utils.sum($.map(frm.doc.deductions || [], @@ -800,22 +800,18 @@ frappe.ui.form.on('Payment Entry', { //If allocate payment amount checkbox is unchecked, set zero to allocate amount row.allocated_amount = 0; - } else if (frappe.flags.allocate_payment_amount != 0 && !row.allocated_amount) { + } else if (frappe.flags.allocate_payment_amount != 0 && (!row.allocated_amount || paid_amount_change)) { if (row.outstanding_amount > 0 && allocated_positive_outstanding > 0) { - if (row.outstanding_amount >= allocated_positive_outstanding) { - row.allocated_amount = allocated_positive_outstanding; - } else { - row.allocated_amount = row.outstanding_amount; - } - + row.allocated_amount = (row.outstanding_amount >= allocated_positive_outstanding) ? + allocated_positive_outstanding : row.outstanding_amount; allocated_positive_outstanding -= flt(row.allocated_amount); - } else if (row.outstanding_amount < 0 && allocated_negative_outstanding) { - if (Math.abs(row.outstanding_amount) >= allocated_negative_outstanding) { - row.allocated_amount = -1*allocated_negative_outstanding; - } else { - row.allocated_amount = row.outstanding_amount; - }; + } else if (row.outstanding_amount > 0 && allocated_positive_outstanding == 0) { + row.allocated_amount = null; + + } else if (row.outstanding_amount < 0 && allocated_negative_outstanding) { + row.allocated_amount = (Math.abs(row.outstanding_amount) >= allocated_negative_outstanding) ? + -1*allocated_negative_outstanding : row.outstanding_amount; allocated_negative_outstanding -= Math.abs(flt(row.allocated_amount)); } } From 184317d5bd7ebc9a6ce4ffaf353e6f7ebfb674c5 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Fri, 23 Apr 2021 20:03:50 +0530 Subject: [PATCH 121/326] fix(HR): Permission error while adding weekly holidays (#25450) --- erpnext/hr/doctype/holiday_list/holiday_list.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/hr/doctype/holiday_list/holiday_list.py b/erpnext/hr/doctype/holiday_list/holiday_list.py index 6df7bc88c0..67630a0abe 100644 --- a/erpnext/hr/doctype/holiday_list/holiday_list.py +++ b/erpnext/hr/doctype/holiday_list/holiday_list.py @@ -16,6 +16,7 @@ class HolidayList(Document): self.validate_days() self.total_holidays = len(self.holidays) + @frappe.whitelist() def get_weekly_off_dates(self): self.validate_values() date_list = self.get_weekly_off_date_list(self.from_date, self.to_date) From e63f3b2f36fe3cbfec6278b59902974f00f6b906 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Fri, 23 Apr 2021 20:33:42 +0530 Subject: [PATCH 122/326] fix: standard working hours message and link for HR Settings --- .../project_profitability/project_profitability.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/erpnext/projects/report/project_profitability/project_profitability.py b/erpnext/projects/report/project_profitability/project_profitability.py index 593ce1b22b..23e8f79b45 100644 --- a/erpnext/projects/report/project_profitability/project_profitability.py +++ b/erpnext/projects/report/project_profitability/project_profitability.py @@ -21,11 +21,13 @@ def get_rows(filters): conditions = get_conditions(filters) standard_working_hours = frappe.db.get_single_value("HR Settings", "standard_working_hours") if not standard_working_hours: - hr_settings = "HR Settings" - frappe.msgprint(_("The metrics for this report are calculated based on the Standard Working Hours. Please set Standard Working Hours in {0}.").format(hr_settings)) + msg = _("The metrics for this report are calculated based on the Standard Working Hours. Please set {0} in {1}.").format( + frappe.bold("Standard Working Hours"), frappe.utils.get_link_to_form("HR Settings", "HR Settings")) + + frappe.msgprint(msg) return [] - sql = """ + sql = """ SELECT * FROM @@ -74,7 +76,7 @@ def get_conditions(filters): if filters.get("project"): conditions.append("tabTimesheet.parent_project={0}".format(frappe.db.escape(filters.get("project")))) - + conditions = " and ".join(conditions) return conditions From 4a805b5622cde87a9436113e1d7f9d1dcf71172b Mon Sep 17 00:00:00 2001 From: Walstan Baptista <38958184+walstanb@users.noreply.github.com> Date: Sat, 24 Apr 2021 14:23:08 +0530 Subject: [PATCH 123/326] chore: frappe.whitelist for doc methods (#25465) --- erpnext/accounts/doctype/journal_entry/journal_entry.py | 1 + .../doctype/amazon_mws_settings/amazon_mws_settings.py | 4 +++- erpnext/healthcare/doctype/therapy_type/therapy_type.py | 1 + erpnext/hr/doctype/holiday_list/holiday_list.py | 1 + .../doctype/leave_control_panel/leave_control_panel.py | 1 + .../import_supplier_invoice/import_supplier_invoice.py | 3 ++- erpnext/selling/doctype/sms_center/sms_center.py | 2 ++ erpnext/setup/doctype/naming_series/naming_series.py | 1 + .../stock/doctype/purchase_receipt/purchase_receipt.py | 1 + erpnext/stock/doctype/stock_entry/stock_entry.py | 9 +++++++-- 10 files changed, 20 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index ff2c8c29b4..fefab82efc 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -592,6 +592,7 @@ class JournalEntry(AccountsController): self.validate_total_debit_and_credit() + @frappe.whitelist() def get_outstanding_invoices(self): self.set('accounts', []) total = 0 diff --git a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_settings.py b/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_settings.py index 899b7ffe13..9c59840149 100644 --- a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_settings.py +++ b/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_settings.py @@ -17,10 +17,12 @@ class AmazonMWSSettings(Document): else: self.enable_sync = 0 + @frappe.whitelist() def get_products_details(self): if self.enable_amazon == 1: frappe.enqueue('erpnext.erpnext_integrations.doctype.amazon_mws_settings.amazon_methods.get_products_details') + @frappe.whitelist() def get_order_details(self): if self.enable_amazon == 1: after_date = dateutil.parser.parse(self.after_date).strftime("%Y-%m-%d") @@ -40,4 +42,4 @@ def setup_custom_fields(): fieldtype='Data', insert_after='title', read_only=1, print_hide=1)] } - create_custom_fields(custom_fields) \ No newline at end of file + create_custom_fields(custom_fields) diff --git a/erpnext/healthcare/doctype/therapy_type/therapy_type.py b/erpnext/healthcare/doctype/therapy_type/therapy_type.py index 6c825b8a58..3f6a36a968 100644 --- a/erpnext/healthcare/doctype/therapy_type/therapy_type.py +++ b/erpnext/healthcare/doctype/therapy_type/therapy_type.py @@ -50,6 +50,7 @@ class TherapyType(Document): self.db_set('change_in_item', 0) + @frappe.whitelist() def add_exercises(self): exercises = self.get_exercises_for_body_parts() last_idx = max([cint(d.idx) for d in self.get('exercises')] or [0,]) diff --git a/erpnext/hr/doctype/holiday_list/holiday_list.py b/erpnext/hr/doctype/holiday_list/holiday_list.py index 67630a0abe..8af8cea605 100644 --- a/erpnext/hr/doctype/holiday_list/holiday_list.py +++ b/erpnext/hr/doctype/holiday_list/holiday_list.py @@ -62,6 +62,7 @@ class HolidayList(Document): return date_list + @frappe.whitelist() def clear_table(self): self.set('holidays', []) diff --git a/erpnext/hr/doctype/leave_control_panel/leave_control_panel.py b/erpnext/hr/doctype/leave_control_panel/leave_control_panel.py index 57e61b5e08..74014020fc 100644 --- a/erpnext/hr/doctype/leave_control_panel/leave_control_panel.py +++ b/erpnext/hr/doctype/leave_control_panel/leave_control_panel.py @@ -29,6 +29,7 @@ class LeaveControlPanel(Document): frappe.throw(_("{0} is required").format(self.meta.get_label(f))) self.validate_from_to_dates('from_date', 'to_date') + @frappe.whitelist() def allocate_leave(self): self.validate_values() leave_allocated_for = [] diff --git a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py index 31a7545a0d..cc6b907bc1 100644 --- a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py +++ b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py @@ -124,6 +124,7 @@ class ImportSupplierInvoice(Document): if disc_line.find("Percentuale"): invoices_args["total_discount"] += flt((flt(disc_line.Percentuale.text) / 100) * (rate * qty)) + @frappe.whitelist() def process_file_data(self): self.status = "Processing File Data" self.save() @@ -400,4 +401,4 @@ def get_full_path(file_name): elif not self.file_url: frappe.throw(_("There is some problem with the file url: {0}").format(file_path)) - return file_path \ No newline at end of file + return file_path diff --git a/erpnext/selling/doctype/sms_center/sms_center.py b/erpnext/selling/doctype/sms_center/sms_center.py index bb6ba1ffce..d142d16248 100644 --- a/erpnext/selling/doctype/sms_center/sms_center.py +++ b/erpnext/selling/doctype/sms_center/sms_center.py @@ -12,6 +12,7 @@ from frappe.model.document import Document from frappe.core.doctype.sms_settings.sms_settings import send_sms class SMSCenter(Document): + @frappe.whitelist() def create_receiver_list(self): rec, where_clause = '', '' if self.send_to == 'All Customer Contact': @@ -73,6 +74,7 @@ class SMSCenter(Document): return receiver_nos + @frappe.whitelist() def send_sms(self): receiver_list = [] if not self.message: diff --git a/erpnext/setup/doctype/naming_series/naming_series.py b/erpnext/setup/doctype/naming_series/naming_series.py index c4f1de14e4..373b0a58c9 100644 --- a/erpnext/setup/doctype/naming_series/naming_series.py +++ b/erpnext/setup/doctype/naming_series/naming_series.py @@ -159,6 +159,7 @@ class NamingSeries(Document): if frappe.db.get_value('Series', series, 'name', order_by="name") == None: frappe.db.sql("insert into tabSeries (name, current) values (%s, 0)", (series)) + @frappe.whitelist() def update_series_start(self): if self.prefix: prefix = self.parse_naming_series() diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 5d7597b2db..d8d8310733 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -221,6 +221,7 @@ class PurchaseReceipt(BuyingController): self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry', 'Repost Item Valuation') self.delete_auto_created_batches() + @frappe.whitelist() def get_current_stock(self): for d in self.get('supplied_items'): if self.supplier_warehouse: diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index f8ac400a8e..48cfa51041 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -398,8 +398,12 @@ class StockEntry(StockController): and item_code = %s and ifnull(s_warehouse,'')='' """ % (", ".join(["%s" * len(other_ste)]), "%s"), args)[0][0] if fg_qty_already_entered and fg_qty_already_entered >= qty: - frappe.throw(_("Stock Entries already created for Work Order ") - + self.work_order + ":" + ", ".join(other_ste), DuplicateEntryForWorkOrderError) + frappe.throw( + _("Stock Entries already created for Work Order {0}: {1}").format( + self.work_order, ", ".join(other_ste) + ), + DuplicateEntryForWorkOrderError, + ) def set_actual_qty(self): allow_negative_stock = cint(frappe.db.get_value("Stock Settings", None, "allow_negative_stock")) @@ -435,6 +439,7 @@ class StockEntry(StockController): if transferred_serial_no: d.serial_no = transferred_serial_no + @frappe.whitelist() def get_stock_and_rate(self): """ Updates rate and availability of all the items. From 2a25c7505efbba95393a4a0b6ed6916510885227 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Sun, 25 Apr 2021 20:29:14 +0530 Subject: [PATCH 124/326] fix: report column widths --- .../project_profitability.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/erpnext/projects/report/project_profitability/project_profitability.py b/erpnext/projects/report/project_profitability/project_profitability.py index 23e8f79b45..5ad2d85232 100644 --- a/erpnext/projects/report/project_profitability/project_profitability.py +++ b/erpnext/projects/report/project_profitability/project_profitability.py @@ -133,14 +133,14 @@ def get_columns(): "label": _("Sales Invoice"), "fieldtype": "Link", "options": "Sales Invoice", - "width": 180 + "width": 120 }, { "fieldname": "timesheet", "label": _("Timesheet"), "fieldtype": "Link", "options": "Timesheet", - "width": 130 + "width": 120 }, { "fieldname": "project", @@ -174,37 +174,37 @@ def get_columns(): "fieldname": "utilization", "label": _("Utilization"), "fieldtype": "Percentage", - "width": 120 + "width": 100 }, { "fieldname": "fractional_cost", "label": _("Fractional Cost"), "fieldtype": "Int", - "width": 100 + "width": 120 }, { "fieldname": "total_billed_hours", "label": _("Total Billed Hours"), "fieldtype": "Int", - "width": 100 + "width": 150 }, { "fieldname": "start_date", "label": _("Start Date"), "fieldtype": "Date", - "width": 120 + "width": 100 }, { "fieldname": "end_date", "label": _("End Date"), "fieldtype": "Date", - "width": 120 + "width": 100 }, { "label": _("Currency"), "fieldname": "currency", "fieldtype": "Link", "options": "Currency", - "width": 100 + "width": 80 } ] \ No newline at end of file From 0decc2b66a060586e4a88a5356bf89754e196c1f Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 26 Apr 2021 11:00:54 +0530 Subject: [PATCH 125/326] fix: validate Standard Working Hours - use standard working hours in metrics calculation --- .../hr/doctype/hr_settings/hr_settings.json | 3 +- ...ee_hours_utilization_based_on_timesheet.py | 34 ++++++++++++------- 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/erpnext/hr/doctype/hr_settings/hr_settings.json b/erpnext/hr/doctype/hr_settings/hr_settings.json index 35532291a5..3db6c239ef 100644 --- a/erpnext/hr/doctype/hr_settings/hr_settings.json +++ b/erpnext/hr/doctype/hr_settings/hr_settings.json @@ -146,7 +146,6 @@ "label": "Send Leave Notification" }, { - "default": "8", "fieldname": "standard_working_hours", "fieldtype": "Int", "label": "Standard Working Hours" @@ -156,7 +155,7 @@ "idx": 1, "issingle": 1, "links": [], - "modified": "2021-04-16 15:45:18.467699", + "modified": "2021-04-26 10:52:56.192773", "modified_by": "Administrator", "module": "HR", "name": "HR Settings", diff --git a/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py index 48eb7b4bb0..7337a03eb4 100644 --- a/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py +++ b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py @@ -19,13 +19,22 @@ class EmployeeHoursReport: self.to_date = getdate(self.filters.to_date) self.validate_dates() - + self.validate_standard_working_hours() + def validate_dates(self): self.day_span = (self.to_date - self.from_date).days if self.day_span <= 0: frappe.throw(_('From Date must come before To Date')) + def validate_standard_working_hours(self): + self.standard_working_hours = frappe.db.get_single_value('HR Settings', 'standard_working_hours') + if not self.standard_working_hours: + msg = _('The metrics for this report are calculated based on the Standard Working Hours. Please set {0} in {1}.').format( + frappe.bold('Standard Working Hours'), frappe.utils.get_link_to_form('HR Settings', 'HR Settings')) + + frappe.throw(msg) + def run(self): self.generate_columns() self.generate_data() @@ -47,7 +56,7 @@ class EmployeeHoursReport: 'label': _('Department'), 'options': 'Department', 'fieldname': 'department', - 'fieldtype': 'Link', + 'fieldtype': 'Link', 'width': 170 }, { @@ -81,7 +90,7 @@ class EmployeeHoursReport: 'width': 200 } ] - + def generate_data(self): self.generate_filtered_time_logs() self.generate_stats_by_employee() @@ -108,7 +117,7 @@ class EmployeeHoursReport: for emp, data in self.stats_by_employee.items(): if data['department'] == self.filters.department: filtered_data[emp] = data - + # Update stats self.stats_by_employee = filtered_data @@ -126,8 +135,8 @@ class EmployeeHoursReport: self.filtered_time_logs = frappe.db.sql(''' SELECT tt.employee AS employee, ttd.hours AS hours, ttd.billable AS billable, ttd.project AS project - FROM `tabTimesheet Detail` AS ttd - JOIN `tabTimesheet` AS tt + FROM `tabTimesheet Detail` AS ttd + JOIN `tabTimesheet` AS tt ON ttd.parent = tt.name WHERE tt.employee IS NOT NULL AND tt.start_date BETWEEN '{0}' AND '{1}' @@ -161,10 +170,9 @@ class EmployeeHoursReport: self.stats_by_employee[emp]['department'] = emp_dept self.stats_by_employee[emp]['employee_name'] = emp_name - + def calculate_utilizations(self): - # (9.0) Will be fetched from HR settings - TOTAL_HOURS = flt(9.0 * self.day_span, 2) + TOTAL_HOURS = flt(self.standard_working_hours * self.day_span, 2) for emp, data in iteritems(self.stats_by_employee): data['total_hours'] = TOTAL_HOURS data['untracked_hours'] = flt(TOTAL_HOURS - data['billed_hours'] - data['non_billed_hours'], 2) @@ -172,14 +180,14 @@ class EmployeeHoursReport: # To handle overtime edge-case if data['untracked_hours'] < 0: data['untracked_hours'] = 0.0 - + data['per_util'] = flt(((data['billed_hours'] + data['non_billed_hours']) / TOTAL_HOURS) * 100, 2) - + def generate_report_summary(self): self.report_summary = [] if not self.data: - return + return avg_utilization = 0.0 total_billed, total_non_billed = 0.0, 0.0 @@ -233,7 +241,7 @@ class EmployeeHoursReport: billed_hours.append(row.get('billed_hours')) non_billed_hours.append(row.get('non_billed_hours')) untracked_hours.append(row.get('untracked_hours')) - + self.chart = { 'data': { 'labels': labels[:30], From b9d4719285baabdb031bdce649947fd5b49a73fc Mon Sep 17 00:00:00 2001 From: Shadrak Gurupnor <30501401+shadrak98@users.noreply.github.com> Date: Mon, 26 Apr 2021 11:14:07 +0530 Subject: [PATCH 126/326] fix: circular loop in project task (#25454) * fix: circular loop in project task --- erpnext/projects/doctype/task/task.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/projects/doctype/task/task.js b/erpnext/projects/doctype/task/task.js index 002ddb2f40..6a9d2d1424 100644 --- a/erpnext/projects/doctype/task/task.js +++ b/erpnext/projects/doctype/task/task.js @@ -32,7 +32,8 @@ frappe.ui.form.on("Task", { frm.set_query("parent_task", function () { let filters = { - "is_group": 1 + "is_group": 1, + "name": ["!=", frm.doc.name] }; if (frm.doc.project) filters["project"] = frm.doc.project; return { From 2a1a4723712b6cae1c15c88c3a1e3bff4f6ea7ad Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 26 Apr 2021 11:18:32 +0530 Subject: [PATCH 127/326] chore: clean-up - set default date filters for last month instead of year - added abbreviations for columns to use them in the metrics labels --- ...ee_hours_utilization_based_on_timesheet.js | 4 +-- ...ee_hours_utilization_based_on_timesheet.py | 34 +++++++++---------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.js b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.js index b11a1fc97f..9a30b99f9b 100644 --- a/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.js +++ b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.js @@ -16,14 +16,14 @@ frappe.query_reports["Employee Hours Utilization Based On Timesheet"] = { fieldname: "from_date", label: __("From Date"), fieldtype: "Date", - default: frappe.defaults.get_global_default("year_start_date"), + default: frappe.datetime.add_months(frappe.datetime.get_today(), -1), reqd: 1 }, { fieldname:"to_date", label: __("To Date"), fieldtype: "Date", - default: frappe.defaults.get_global_default("year_end_date"), + default: frappe.datetime.now_date(), reqd: 1 }, { diff --git a/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py index 7337a03eb4..b38da1416f 100644 --- a/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py +++ b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py @@ -57,34 +57,34 @@ class EmployeeHoursReport: 'options': 'Department', 'fieldname': 'department', 'fieldtype': 'Link', + 'width': 120 + }, + { + 'label': _('Total Hours (T)'), + 'fieldname': 'total_hours', + 'fieldtype': 'Float', + 'width': 120 + }, + { + 'label': _('Billed Hours (B)'), + 'fieldname': 'billed_hours', + 'fieldtype': 'Float', 'width': 170 }, { - 'label': _('Total Hours'), - 'fieldname': 'total_hours', - 'fieldtype': 'Float', - 'width': 150 - }, - { - 'label': _('Billed Hours'), - 'fieldname': 'billed_hours', - 'fieldtype': 'Float', - 'width': 150 - }, - { - 'label': _('Non-Billed Hours'), + 'label': _('Non-Billed Hours (NB)'), 'fieldname': 'non_billed_hours', 'fieldtype': 'Float', - 'width': 150 + 'width': 170 }, { - 'label': _('Untracked Hours'), + 'label': _('Untracked Hours (U)'), 'fieldname': 'untracked_hours', 'fieldtype': 'Float', - 'width': 150 + 'width': 170 }, { - 'label': _('% Utilization (Billed Hours + Non-Billed Hours / Total Hours)'), + 'label': _('% Utilization (B + NB) / T'), 'fieldname': 'per_util', 'fieldtype': 'Percentage', 'width': 200 From f74d8d2e167decd1155cae6b469024f280407316 Mon Sep 17 00:00:00 2001 From: Anuja P Date: Mon, 26 Apr 2021 11:31:51 +0530 Subject: [PATCH 128/326] fix: suggested changes --- erpnext/accounts/doctype/payment_entry/payment_entry.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index 936c538e20..830a7f25c0 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -801,14 +801,11 @@ frappe.ui.form.on('Payment Entry', { row.allocated_amount = 0; } else if (frappe.flags.allocate_payment_amount != 0 && (!row.allocated_amount || paid_amount_change)) { - if (row.outstanding_amount > 0 && allocated_positive_outstanding > 0) { + if (row.outstanding_amount > 0 && allocated_positive_outstanding >= 0) { row.allocated_amount = (row.outstanding_amount >= allocated_positive_outstanding) ? allocated_positive_outstanding : row.outstanding_amount; allocated_positive_outstanding -= flt(row.allocated_amount); - } else if (row.outstanding_amount > 0 && allocated_positive_outstanding == 0) { - row.allocated_amount = null; - } else if (row.outstanding_amount < 0 && allocated_negative_outstanding) { row.allocated_amount = (Math.abs(row.outstanding_amount) >= allocated_negative_outstanding) ? -1*allocated_negative_outstanding : row.outstanding_amount; From 4716ebea5a5e7588f4d0d0440dbefaf7a342cab9 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 26 Apr 2021 11:40:26 +0530 Subject: [PATCH 129/326] feat: Add % Utilization (Billed Only) - Added billed utilization metric in report summary --- ...ee_hours_utilization_based_on_timesheet.py | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py index b38da1416f..842fd4d314 100644 --- a/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py +++ b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py @@ -88,6 +88,12 @@ class EmployeeHoursReport: 'fieldname': 'per_util', 'fieldtype': 'Percentage', 'width': 200 + }, + { + 'label': _('% Utilization (B / T)'), + 'fieldname': 'per_util_billed_only', + 'fieldtype': 'Percentage', + 'width': 200 } ] @@ -182,6 +188,7 @@ class EmployeeHoursReport: data['untracked_hours'] = 0.0 data['per_util'] = flt(((data['billed_hours'] + data['non_billed_hours']) / TOTAL_HOURS) * 100, 2) + data['per_util_billed_only'] = flt((data['billed_hours'] / TOTAL_HOURS) * 100, 2) def generate_report_summary(self): self.report_summary = [] @@ -190,11 +197,13 @@ class EmployeeHoursReport: return avg_utilization = 0.0 + avg_utilization_billed_only = 0.0 total_billed, total_non_billed = 0.0, 0.0 total_untracked = 0.0 for row in self.data: avg_utilization += row['per_util'] + avg_utilization_billed_only += row['per_util_billed_only'] total_billed += row['billed_hours'] total_non_billed += row['non_billed_hours'] total_untracked += row['untracked_hours'] @@ -202,12 +211,21 @@ class EmployeeHoursReport: avg_utilization /= len(self.data) avg_utilization = flt(avg_utilization, 2) + avg_utilization_billed_only /= len(self.data) + avg_utilization_billed_only = flt(avg_utilization_billed_only, 2) + THRESHOLD_PERCENTAGE = 70.0 self.report_summary = [ { 'value': f'{avg_utilization}%', 'indicator': 'Red' if avg_utilization < THRESHOLD_PERCENTAGE else 'Green', - 'label': _('Average Utilization'), + 'label': _('Avg Utilization'), + 'datatype': 'Percentage' + }, + { + 'value': f'{avg_utilization_billed_only}%', + 'indicator': 'Red' if avg_utilization_billed_only < THRESHOLD_PERCENTAGE else 'Green', + 'label': _('Avg Utilization (Billed Only)'), 'datatype': 'Percentage' }, { @@ -219,11 +237,6 @@ class EmployeeHoursReport: 'value': total_non_billed, 'label': _('Total Non-Billed Hours'), 'datatype': 'Float' - }, - { - 'value': total_untracked, - 'label': _('Total Untracked Hours'), - 'datatype': 'Float' } ] From 968ec110f304e8c8f1710ddccef972c35d1cef73 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 26 Apr 2021 14:51:00 +0530 Subject: [PATCH 130/326] fix: Linting and translation issues --- .../process_statement_of_accounts.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py index 33c32ebce2..b6149e89f5 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py @@ -4,13 +4,13 @@ from __future__ import unicode_literals import frappe +from frappe import _ from frappe.model.document import Document from erpnext.accounts.report.general_ledger.general_ledger import execute as get_soa from erpnext.accounts.report.accounts_receivable_summary.accounts_receivable_summary import execute as get_ageing from erpnext import get_company_currency from erpnext.accounts.party import get_party_account_currency -from frappe.core.doctype.communication.email import make from frappe.utils.print_format import report_to_pdf from frappe.utils.pdf import get_pdf from frappe.utils import today, add_days, add_months, getdate, format_date @@ -31,7 +31,7 @@ class ProcessStatementOfAccounts(Document): validate_template(self.body) if not self.customers: - frappe.throw(frappe._('Customers not selected.')) + frappe.throw(_('Customers not selected.')) if self.enable_auto_email: self.to_date = self.start_date @@ -60,6 +60,8 @@ def get_report_pdf(doc, consolidated=True): aging[0]['ageing_based_on'] = doc.ageing_based_on tax_id = frappe.get_doc('Customer', entry.customer).tax_id + presentation_currency = get_party_account_currency('Customer', entry.customer, doc.company) \ + or doc.currency or get_company_currency(doc.company) filters= frappe._dict({ 'from_date': doc.from_date, @@ -69,8 +71,7 @@ def get_report_pdf(doc, consolidated=True): 'account': doc.account if doc.account else None, 'party_type': 'Customer', 'party': [entry.customer], - 'presentation_currency': get_party_account_currency('Customer', entry.customer, doc.company) \ - or doc.currency or get_company_currency(doc.company), + 'presentation_currency': presentation_currency, 'group_by': doc.group_by, 'currency': doc.currency, 'cost_center': [cc.cost_center_name for cc in doc.cost_center], @@ -170,7 +171,7 @@ def fetch_customers(customer_collection, collection_name, primary_mandatory): if customer_collection == 'Sales Person': customers = get_customers_based_on_sales_person(collection_name) if not bool(customers): - frappe.throw('No Customers found with selected options.') + frappe.throw(_('No Customers found with selected options.')) else: if customer_collection == 'Sales Partner': customers = frappe.get_list('Customer', fields=['name', 'email_id'], \ @@ -202,14 +203,14 @@ def get_customer_emails(customer_name, primary_mandatory, billing_and_primary=Tr if len(billing_email) == 0 or (billing_email[0][0] is None): if billing_and_primary: - frappe.throw('No billing email found for customer: '+ customer_name) + frappe.throw(_("No billing email found for customer: {0}").format(customer_name)) else: return '' if billing_and_primary: primary_email = frappe.get_value('Customer', customer_name, 'email_id') if primary_email is None and int(primary_mandatory): - frappe.throw('No primary email found for customer: '+ customer_name) + frappe.throw(_("No primary email found for customer: {0}").format(customer_name)) return [primary_email or '', billing_email[0][0]] else: return billing_email[0][0] or '' From 4c8d15b487df963875509956dbcb5df332db4aea Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Mon, 26 Apr 2021 15:24:34 +0530 Subject: [PATCH 131/326] fix: Incorrect GL Entry validation (#25474) * fix: Incorrect GL Entry validation * fix: Add validation for party accounts * fix: Scrub labels * fix: Translation --- erpnext/accounts/general_ledger.py | 3 ++- erpnext/controllers/accounts_controller.py | 21 ++++++++++++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index dac0c216c8..85bff10fb4 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -18,7 +18,8 @@ def make_gl_entries(gl_map, cancel=False, adv_adj=False, merge_entries=True, upd gl_map = process_gl_map(gl_map, merge_entries) if gl_map and len(gl_map) > 1: save_entries(gl_map, adv_adj, update_outstanding, from_repost) - else: + # Post GL Map proccess there may no be any GL Entries + elif gl_map: frappe.throw(_("Incorrect number of General Ledger Entries found. You might have selected a wrong Account in the transaction.")) else: make_reverse_gl_entries(gl_map, adv_adj=adv_adj, update_outstanding=update_outstanding) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index d36e7b03f4..97bcb00210 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -90,6 +90,8 @@ class AccountsController(TransactionBase): self.ensure_supplier_is_not_blocked() self.validate_date_with_fiscal_year() + self.validate_party_accounts() + self.validate_inter_company_reference() self.set_incoming_rate() @@ -233,6 +235,23 @@ class AccountsController(TransactionBase): validate_fiscal_year(self.get(date_field), self.fiscal_year, self.company, self.meta.get_label(date_field), self) + def validate_party_accounts(self): + if self.doctype not in ('Sales Invoice', 'Purchase Invoice'): + return + + if self.doctype == 'Sales Invoice': + party_account_field = 'debit_to' + item_field = 'income_account' + else: + party_account_field = 'credit_to' + item_field = 'expense_account' + + for item in self.get('items'): + if item.get(item_field) == self.get(party_account_field): + frappe.throw(_("Row {0}: {1} {2} cannot be same as {3} (Party Account) {4}").format(item.idx, + frappe.bold(frappe.unscrub(item_field)), item.get(item_field), + frappe.bold(frappe.unscrub(party_account_field)), self.get(party_account_field))) + def validate_inter_company_reference(self): if self.doctype not in ('Purchase Invoice', 'Purchase Receipt', 'Purchase Order'): return @@ -240,7 +259,7 @@ class AccountsController(TransactionBase): if self.is_internal_transfer(): if not (self.get('inter_company_reference') or self.get('inter_company_invoice_reference') or self.get('inter_company_order_reference')): - msg = _("Internal Sale or Delivery Reference missing. ") + msg = _("Internal Sale or Delivery Reference missing.") msg += _("Please create purchase from internal sale or delivery document itself") frappe.throw(msg, title=_("Internal Sales Reference Missing")) From c2de4f6b3513043f13d7c28cb3d6711f8c021be4 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 26 Apr 2021 15:28:17 +0530 Subject: [PATCH 132/326] chore: update snyk (#25477) --- package.json | 2 +- yarn.lock | 3606 ++++++++++++++++++++++++++++++++++---------------- 2 files changed, 2476 insertions(+), 1132 deletions(-) diff --git a/package.json b/package.json index d12661b5cc..c9ee7a622c 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "url": "https://github.com/frappe/erpnext/issues" }, "devDependencies": { - "snyk": "^1.290.1" + "snyk": "^1.518.0" }, "dependencies": { "onscan.js": "^1.5.2" diff --git a/yarn.lock b/yarn.lock index e5a2da1e40..0a2ac1affc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,241 +2,622 @@ # yarn lockfile v1 -"@snyk/cli-interface@1.5.0": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@snyk/cli-interface/-/cli-interface-1.5.0.tgz#b9dbe6ebfb86e67ffabf29d4e0d28a52670ac456" - integrity sha512-+Qo+IO3YOXWgazlo+CKxOuWFLQQdaNCJ9cSfhFQd687/FuesaIxWdInaAdfpsLScq0c6M1ieZslXgiZELSzxbg== +"@arcanis/slice-ansi@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@arcanis/slice-ansi/-/slice-ansi-1.0.2.tgz#35331e41a1062e3c53c01ad2ec1555c5c1959d8f" + integrity sha512-lDL63z0W/L/WTgqrwVOuNyMAsTv+pvjybd21z9SWdStmQoXT59E/iVWwat3gYjcdTNBf6oHAMoyFm8dtjpXEYw== dependencies: - tslib "^1.9.3" + grapheme-splitter "^1.0.4" -"@snyk/cli-interface@2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@snyk/cli-interface/-/cli-interface-2.2.0.tgz#5536bc913917c623d16d727f9f3759521a916026" - integrity sha512-sA7V2JhgqJB9z5uYotgQc5iNDv//y+Mdm39rANxmFjtZMSYJZHkP80arzPjw1mB5ni/sWec7ieYUUFeySZBfVg== +"@deepcode/dcignore@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@deepcode/dcignore/-/dcignore-1.0.2.tgz#39e4a3df7dde8811925330506e4bb3fbf3c288d8" + integrity sha512-DPgxtHuJwBORpqRkPXzzOT+uoPRVJmaN7LR+pmeL6DQM90kj6G6GFUH1i/YpRH8NbML8ZGEDwB9f9u4UwD2pzg== + +"@nodelib/fs.scandir@2.1.4": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz#d4b3549a5db5de2683e0c1071ab4f140904bbf69" + integrity sha512-33g3pMJk3bg5nXbL/+CY6I2eJDzZAni49PfJnL5fghPTggPvBd/pFNSgJsdAgWptuFu7qq/ERvOYFlhvsLTCKA== dependencies: - tslib "^1.9.3" + "@nodelib/fs.stat" "2.0.4" + run-parallel "^1.1.9" -"@snyk/cli-interface@2.3.0": - version "2.3.0" - resolved "https://registry.yarnpkg.com/@snyk/cli-interface/-/cli-interface-2.3.0.tgz#9d38f815a5cf2be266006954c2a058463d531e08" - integrity sha512-ecbylK5Ol2ySb/WbfPj0s0GuLQR+KWKFzUgVaoNHaSoN6371qRWwf2uVr+hPUP4gXqCai21Ug/RDArfOhlPwrQ== +"@nodelib/fs.stat@2.0.4", "@nodelib/fs.stat@^2.0.2": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.4.tgz#a3f2dd61bab43b8db8fa108a121cfffe4c676655" + integrity sha512-IYlHJA0clt2+Vg7bccq+TzRdJvv19c2INqBSsoOLp1je7xjtr7J26+WXR72MCdvU9q1qTzIWDfhMf+DRvQJK4Q== + +"@nodelib/fs.walk@^1.2.3": + version "1.2.6" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.6.tgz#cce9396b30aa5afe9e3756608f5831adcb53d063" + integrity sha512-8Broas6vTtW4GIXTAHDoE32hnN2M5ykgCpWGbuXHQ15vEMqr23pB76e/GZcYsZCHALv50ktd24qhEyKr6wBtow== dependencies: - tslib "^1.9.3" + "@nodelib/fs.scandir" "2.1.4" + fastq "^1.6.0" -"@snyk/cli-interface@2.3.1", "@snyk/cli-interface@^2.0.3": +"@octetstream/promisify@2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@octetstream/promisify/-/promisify-2.0.2.tgz#29ac3bd7aefba646db670227f895d812c1a19615" + integrity sha512-7XHoRB61hxsz8lBQrjC1tq/3OEIgpvGWg6DKAdwi7WRzruwkmsdwmOoUXbU4Dtd4RSOMDwed0SkP3y8UlMt1Bg== + +"@open-policy-agent/opa-wasm@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@open-policy-agent/opa-wasm/-/opa-wasm-1.2.0.tgz#481b766093f70b00efefbee1e4192f375fd34ca2" + integrity sha512-CtUBTnzvDrT0NASa8IuGQTxFGgt2vxbLnMYuTA+uDFxOcA4uK4mGFgrhHJtxUZnWHiwemOvKKSY3BMCo7qiAsQ== + dependencies: + sprintf-js "^1.1.2" + utf8 "^3.0.0" + +"@sindresorhus/is@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" + integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ== + +"@sindresorhus/is@^2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-2.1.1.tgz#ceff6a28a5b4867c2dd4a1ba513de278ccbe8bb1" + integrity sha512-/aPsuoj/1Dw/kzhkgz+ES6TxG0zfTMGLwuK2ZG00k/iJzYHTLCE8mVU8EPqEOp/lmxPoq1C1C9RYToRKb2KEfg== + +"@sindresorhus/is@^4.0.0": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.0.1.tgz#d26729db850fa327b7cacc5522252194404226f5" + integrity sha512-Qm9hBEBu18wt1PO2flE7LPb30BHMQt1eQgbV76YntdNk73XZGpn3izvGTYxbGgzXKgbCjiia0uxTd3aTNQrY/g== + +"@snyk/cli-interface@2.11.0", "@snyk/cli-interface@^2.11.0", "@snyk/cli-interface@^2.9.1", "@snyk/cli-interface@^2.9.2": + version "2.11.0" + resolved "https://registry.yarnpkg.com/@snyk/cli-interface/-/cli-interface-2.11.0.tgz#9df68c8cd54de5dff69f0ab797a188541d9c8965" + integrity sha512-T3xfDqrEFKclHGdJx4/5+D5F7e76/99f33guE4RTlVITBhy7VVnjz4t/NDr3UYqcC0MgAmiC4bSVYHnlshuwJw== + dependencies: + "@types/graphlib" "^2" + +"@snyk/cli-interface@^2.0.3": version "2.3.1" resolved "https://registry.yarnpkg.com/@snyk/cli-interface/-/cli-interface-2.3.1.tgz#73f2f4bd717b9f03f096ede3ff5830eb8d2f3716" integrity sha512-JZvsmhDXSyjv1dkc12lPI3tNTNYlIaOiIQMYFg2RgqF3QmWjTyBUgRZcF7LoKyufHtS4dIudM6k1aHBpSaDrhw== dependencies: tslib "^1.9.3" -"@snyk/cocoapods-lockfile-parser@3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@snyk/cocoapods-lockfile-parser/-/cocoapods-lockfile-parser-3.0.0.tgz#514b744cedd9d3d3efb2a5d06fce1662fec2ff1a" - integrity sha512-AebCc+v9vtOL9tFkU4/tommgVsXxqdx6t45kCkBW+FC4PaYvfYEg9Eg/9GqlY9+nFrLFo/uTr+E/aR0AF/KqYA== +"@snyk/cloud-config-parser@^1.9.2": + version "1.9.2" + resolved "https://registry.yarnpkg.com/@snyk/cloud-config-parser/-/cloud-config-parser-1.9.2.tgz#e6c8e575db8527b33cf1ba766f86e1b3414cf6e1" + integrity sha512-m8Y2+3l4fxj96QMrTfiCEaXgCpDkCkJIX/5wv0V0RHuxpUiyh+KxC2yJ8Su4wybBj6v6hB9hB7h5/L+Gy4V4PA== dependencies: - "@snyk/dep-graph" "^1.11.0" - "@snyk/ruby-semver" "^2.0.4" - "@types/js-yaml" "^3.12.1" - core-js "^3.2.0" - js-yaml "^3.13.1" - source-map-support "^0.5.7" - tslib "^1.9.3" - -"@snyk/composer-lockfile-parser@1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@snyk/composer-lockfile-parser/-/composer-lockfile-parser-1.2.0.tgz#62c6d88c6a39c55dda591854f5380923a993182f" - integrity sha512-kZT+HTqgNcQMeoE5NM9M3jj463M8zI7ZxqZXLw9WoyVs5JTt9g0qFWxIG1cNwZdGVI+y7tzZbNWw9BlMD1vCCQ== - dependencies: - lodash "^4.17.13" - -"@snyk/configstore@3.2.0-rc1", "@snyk/configstore@^3.2.0-rc1": - version "3.2.0-rc1" - resolved "https://registry.yarnpkg.com/@snyk/configstore/-/configstore-3.2.0-rc1.tgz#385c050d11926a26d0335a4b3be9e55f90f6e0ac" - integrity sha512-CV3QggFY8BY3u8PdSSlUGLibqbqCG1zJRmGM2DhnhcxQDRRPTGTP//l7vJphOVsUP1Oe23+UQsj7KRWpRUZiqg== - dependencies: - dot-prop "^5.2.0" - graceful-fs "^4.1.2" - make-dir "^1.0.0" - unique-string "^1.0.0" - write-file-atomic "^2.0.0" - xdg-basedir "^3.0.0" - -"@snyk/dep-graph@1.13.1": - version "1.13.1" - resolved "https://registry.yarnpkg.com/@snyk/dep-graph/-/dep-graph-1.13.1.tgz#45721f7e21136b62d1cdd99b3319e717d9071dfb" - integrity sha512-Ww2xvm5UQgrq9eV0SdTBCh+w/4oI2rCx5vn1IOSeypaR0CO4p+do1vm3IDZ2ugg4jLSfHP8+LiD6ORESZMkQ2w== - dependencies: - graphlib "^2.1.5" - lodash "^4.7.14" - object-hash "^1.3.1" - semver "^6.0.0" - source-map-support "^0.5.11" - tslib "^1.9.3" - -"@snyk/dep-graph@^1.11.0", "@snyk/dep-graph@^1.13.1": - version "1.15.0" - resolved "https://registry.yarnpkg.com/@snyk/dep-graph/-/dep-graph-1.15.0.tgz#67bf790bc9f0eee36ad7dad053465cdd928ce223" - integrity sha512-GdF/dvqfKRVHqQio/tSkR4GRpAqIglLPEDZ+XlV7jT5btq9+Fxq2h25Lmm/a7sw+ODTOOqNhTF9y8ASc9VIhww== - dependencies: - graphlib "^2.1.5" - lodash "^4.7.14" - object-hash "^1.3.1" - semver "^6.0.0" - source-map-support "^0.5.11" + esprima "^4.0.1" tslib "^1.10.0" + yaml-js "^0.3.0" + +"@snyk/cocoapods-lockfile-parser@3.6.2": + version "3.6.2" + resolved "https://registry.yarnpkg.com/@snyk/cocoapods-lockfile-parser/-/cocoapods-lockfile-parser-3.6.2.tgz#803ae9466f408c48ba7c5a8ec51b9dbac6f633b3" + integrity sha512-ca2JKOnSRzYHJkhOB9gYmdRZHmd02b/uBd/S0D5W+L9nIMS7sUBV5jfhKwVgrYPIpVNIc0XCI9rxK4TfkQRpiA== + dependencies: + "@snyk/dep-graph" "^1.23.1" + "@types/js-yaml" "^3.12.1" + js-yaml "^3.13.1" + tslib "^1.10.0" + +"@snyk/code-client@3.4.1": + version "3.4.1" + resolved "https://registry.yarnpkg.com/@snyk/code-client/-/code-client-3.4.1.tgz#b9d025897cd586e0aef903162ac0407d0bffc3cd" + integrity sha512-XJ7tUdX1iQyzN/BmHac7p+Oyw1SyTcqSkCNExwBJxyQdlnUAKK6QKIWLXS81tTpZ79FgCdT+0fdS0AjsyS99eA== + dependencies: + "@deepcode/dcignore" "^1.0.2" + "@snyk/fast-glob" "^3.2.6-patch" + "@types/flat-cache" "^2.0.0" + "@types/lodash.chunk" "^4.2.6" + "@types/lodash.omit" "^4.5.6" + "@types/lodash.union" "^4.6.6" + "@types/micromatch" "^4.0.1" + "@types/sarif" "^2.1.3" + "@types/uuid" "^8.3.0" + axios "^0.21.1" + ignore "^5.1.8" + lodash.chunk "^4.2.0" + lodash.omit "^4.5.0" + lodash.union "^4.6.0" + micromatch "^4.0.2" + queue "^6.0.1" + uuid "^8.3.2" + +"@snyk/composer-lockfile-parser@^1.4.1": + version "1.4.1" + resolved "https://registry.yarnpkg.com/@snyk/composer-lockfile-parser/-/composer-lockfile-parser-1.4.1.tgz#2f7c93ad367520322b16d9490a208fec08445e0e" + integrity sha512-wNANv235j95NFsQuODIXCiQZ9kcyg9fz92Kg1zoGvaP3kN/ma7fgCnvQL/dyml6iouQJR5aZovjhrrfEFoKtiQ== + dependencies: + lodash.findkey "^4.6.0" + lodash.get "^4.4.2" + lodash.invert "^4.3.0" + lodash.isempty "^4.4.0" + +"@snyk/dep-graph@^1.19.3", "@snyk/dep-graph@^1.21.0", "@snyk/dep-graph@^1.23.0", "@snyk/dep-graph@^1.23.1", "@snyk/dep-graph@^1.27.1", "@snyk/dep-graph@^1.28.0": + version "1.28.0" + resolved "https://registry.yarnpkg.com/@snyk/dep-graph/-/dep-graph-1.28.0.tgz#d68c0576cb3562c6e819ca8a8c7ac29ee11d9776" + integrity sha512-Oup9nAvb558jdNvbZah/vaBtOtCcizkdeS+OBQeBIqIffyer4mc4juSn4b1SFjCpu7AG7piio8Lj8k1B9ps6Tg== + dependencies: + event-loop-spinner "^2.1.0" + lodash.clone "^4.5.0" + lodash.constant "^3.0.0" + lodash.filter "^4.6.0" + lodash.foreach "^4.5.0" + lodash.isempty "^4.4.0" + lodash.isequal "^4.5.0" + lodash.isfunction "^3.0.9" + lodash.isundefined "^3.0.1" + lodash.keys "^4.2.0" + lodash.map "^4.6.0" + lodash.reduce "^4.6.0" + lodash.size "^4.2.0" + lodash.transform "^4.6.0" + lodash.union "^4.6.0" + lodash.values "^4.3.0" + object-hash "^2.0.3" + semver "^7.0.0" + tslib "^1.13.0" + +"@snyk/docker-registry-v2-client@1.13.9": + version "1.13.9" + resolved "https://registry.yarnpkg.com/@snyk/docker-registry-v2-client/-/docker-registry-v2-client-1.13.9.tgz#54c2e3071de58fc6fc12c5fef5eaeae174ecda12" + integrity sha512-DIFLEhr8m1GrAwsLGInJmpcQMacjuhf3jcbpQTR+LeMvZA9IuKq+B7kqw2O2FzMiHMZmUb5z+tV+BR7+IUHkFQ== + dependencies: + needle "^2.5.0" + parse-link-header "^1.0.1" + tslib "^1.10.0" + +"@snyk/fast-glob@^3.2.6-patch": + version "3.2.6-patch" + resolved "https://registry.yarnpkg.com/@snyk/fast-glob/-/fast-glob-3.2.6-patch.tgz#a0866bedb17f95255e4050dad08daeaff0f4caa8" + integrity sha512-E/Pfdze/WFfxwyuTFcfhQN1SwyUsc43yuCoW63RVBCaxTD6OzhVD2Pvc/Sy7BjiWUfmelzyKkIBpoow8zZX7Zg== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + "@snyk/glob-parent" "^5.1.2-patch.1" + merge2 "^1.3.0" + micromatch "^4.0.2" + picomatch "^2.2.1" + +"@snyk/fix@1.554.0": + version "1.554.0" + resolved "https://registry.yarnpkg.com/@snyk/fix/-/fix-1.554.0.tgz#7ae786882e0ffea5e7f10d0b41e3d593b65555c4" + integrity sha512-q2eRVStgspPeI2wZ2EQGLpiWZMRg7o+4tsCk6m/kHZgQGDN4Bb7L3xslFW3OgF0+ZksYSaHl2cW2HmGiLRaYcA== + dependencies: + "@snyk/dep-graph" "^1.21.0" + chalk "4.1.0" + debug "^4.3.1" + ora "5.3.0" + p-map "^4.0.0" + strip-ansi "6.0.0" "@snyk/gemfile@1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@snyk/gemfile/-/gemfile-1.2.0.tgz#919857944973cce74c650e5428aaf11bcd5c0457" integrity sha512-nI7ELxukf7pT4/VraL4iabtNNMz8mUo7EXlqCFld8O5z6mIMLX9llps24iPpaIZOwArkY3FWA+4t+ixyvtTSIA== -"@snyk/ruby-semver@^2.0.4": - version "2.0.4" - resolved "https://registry.yarnpkg.com/@snyk/ruby-semver/-/ruby-semver-2.0.4.tgz#457686ea7a4d60b10efddde99587efb3a53ba884" - integrity sha512-ceMD4CBS3qtAg+O0BUvkKdsheUNCqi+/+Rju243Ul8PsUgZnXmGiqfk/2z7DCprRQnxUTra4+IyeDQT7wAheCQ== +"@snyk/glob-parent@^5.1.2-patch.1": + version "5.1.2-patch.1" + resolved "https://registry.yarnpkg.com/@snyk/glob-parent/-/glob-parent-5.1.2-patch.1.tgz#87733b4ab282043fa7915200bc94cb391df6d44f" + integrity sha512-OkUPdHgxIWKAAzceG1nraNA0kgI+eS0I9wph8tll9UL0slD2mIWSj4mAqroGovaEXm8nHedoUfuDRGEb6wnzCQ== dependencies: - lodash "^4.17.14" + is-glob "^4.0.1" -"@snyk/snyk-cocoapods-plugin@2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@snyk/snyk-cocoapods-plugin/-/snyk-cocoapods-plugin-2.0.1.tgz#be8660c854d551a56baa9d072bb4ae7f188cc1cd" - integrity sha512-XVkvaMvMzQ3miJi/YZmsRJSAUfDloYhfg6pXPgzAeAugB4p+cNi01Z68pT62ypB8U/Ugh1Xx2pb9aoOFqBbSjA== +"@snyk/graphlib@2.1.9-patch.3", "@snyk/graphlib@^2.1.9-patch.3": + version "2.1.9-patch.3" + resolved "https://registry.yarnpkg.com/@snyk/graphlib/-/graphlib-2.1.9-patch.3.tgz#b8edb2335af1978db7f3cb1f28f5d562960acf23" + integrity sha512-bBY9b9ulfLj0v2Eer0yFYa3syVeIxVKl2EpxSrsVeT4mjA0CltZyHsF0JjoaGXP27nItTdJS5uVsj1NA+3aE+Q== dependencies: - "@snyk/cli-interface" "1.5.0" - "@snyk/cocoapods-lockfile-parser" "3.0.0" - "@snyk/dep-graph" "^1.13.1" + lodash.clone "^4.5.0" + lodash.constant "^3.0.0" + lodash.filter "^4.6.0" + lodash.foreach "^4.5.0" + lodash.has "^4.5.2" + lodash.isempty "^4.4.0" + lodash.isfunction "^3.0.9" + lodash.isundefined "^3.0.1" + lodash.keys "^4.2.0" + lodash.map "^4.6.0" + lodash.reduce "^4.6.0" + lodash.size "^4.2.0" + lodash.transform "^4.6.0" + lodash.union "^4.6.0" + lodash.values "^4.3.0" + +"@snyk/inquirer@^7.3.3-patch": + version "7.3.3-patch" + resolved "https://registry.yarnpkg.com/@snyk/inquirer/-/inquirer-7.3.3-patch.tgz#ef84d531724c53b755e8dd454e1a3c2ccdcfc0bf" + integrity sha512-aWiQSOacH2lOpJ1ard9ErABcH4tdJogdr+mg1U67iZJOPO9n2gFgAwz1TQJDyPkv4/A5mh4hT2rg03Uq+KBn2Q== + dependencies: + ansi-escapes "^4.2.1" + chalk "^4.1.0" + cli-cursor "^3.1.0" + cli-width "^3.0.0" + external-editor "^3.0.3" + figures "^3.0.0" + lodash.assign "^4.2.0" + lodash.assignin "^4.2.0" + lodash.clone "^4.5.0" + lodash.defaults "^4.2.0" + lodash.filter "^4.6.0" + lodash.find "^4.6.0" + lodash.findindex "^4.6.0" + lodash.flatten "^4.4.0" + lodash.isboolean "^3.0.3" + lodash.isfunction "^3.0.9" + lodash.isnumber "^3.0.3" + lodash.isplainobject "^4.0.6" + lodash.isstring "^4.0.1" + lodash.last "^3.0.0" + lodash.map "^4.6.0" + lodash.omit "^4.5.0" + lodash.set "^4.3.2" + lodash.sum "^4.0.2" + lodash.uniq "^4.5.0" + mute-stream "0.0.8" + run-async "^2.4.0" + rxjs "^6.6.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + through "^2.3.6" + +"@snyk/java-call-graph-builder@1.19.1": + version "1.19.1" + resolved "https://registry.yarnpkg.com/@snyk/java-call-graph-builder/-/java-call-graph-builder-1.19.1.tgz#1d579d782df3bb5f9d5171cc35180596cd90aa8b" + integrity sha512-bxjHef5Qm3pNc+BrFlxMudmSSbOjA395ZqBddc+dvsFHoHeyNbiY56Y1JSGUlTgjRM+PKNPBiCuELTSMaROeZg== + dependencies: + "@snyk/graphlib" "2.1.9-patch.3" + ci-info "^2.0.0" + debug "^4.1.1" + glob "^7.1.6" + jszip "^3.2.2" + needle "^2.3.3" + progress "^2.0.3" + snyk-config "^4.0.0-rc.2" source-map-support "^0.5.7" + temp-dir "^2.0.0" + tmp "^0.2.1" tslib "^1.9.3" + xml-js "^1.6.11" -"@snyk/update-notifier@^2.5.1-rc2": - version "2.5.1-rc2" - resolved "https://registry.yarnpkg.com/@snyk/update-notifier/-/update-notifier-2.5.1-rc2.tgz#14bf816114b5698a255289d7170157f254202fad" - integrity sha512-dlled3mfpnAt3cQb5hxkFiqfPCj4Yk0xV8Yl5P8PeVv1pUmO7vI4Ka4Mjs4r6CYM5f9kZhviFPQQcWOIDlMRcw== +"@snyk/java-call-graph-builder@1.20.0": + version "1.20.0" + resolved "https://registry.yarnpkg.com/@snyk/java-call-graph-builder/-/java-call-graph-builder-1.20.0.tgz#ffca734cf7ce276a69277963149358190eaac3e5" + integrity sha512-NX8bpIu7oG5cuSSm6WvtxqcCuJs2gRjtKhtuSeF1p5TYXyESs3FXQ0nHjfY90LiyTTc+PW/UBq6SKbBA6bCBww== dependencies: - "@snyk/configstore" "3.2.0-rc1" - boxen "^1.3.0" - chalk "^2.3.2" - import-lazy "^2.1.0" - is-ci "^1.0.10" - is-installed-globally "^0.1.0" - is-npm "^1.0.0" - latest-version "^3.1.0" - semver-diff "^2.0.0" - xdg-basedir "^3.0.0" + "@snyk/graphlib" "2.1.9-patch.3" + ci-info "^2.0.0" + debug "^4.1.1" + glob "^7.1.6" + jszip "^3.2.2" + needle "^2.3.3" + progress "^2.0.3" + snyk-config "^4.0.0-rc.2" + source-map-support "^0.5.7" + temp-dir "^2.0.0" + tmp "^0.2.1" + tslib "^1.9.3" + xml-js "^1.6.11" -"@types/agent-base@^4.2.0": - version "4.2.0" - resolved "https://registry.yarnpkg.com/@types/agent-base/-/agent-base-4.2.0.tgz#00644e8b395b40e1bf50aaf1d22cabc1200d5051" - integrity sha512-8mrhPstU+ZX0Ugya8tl5DsDZ1I5ZwQzbL/8PA0z8Gj0k9nql7nkaMzmPVLj+l/nixWaliXi+EBiLA8bptw3z7Q== - dependencies: - "@types/events" "*" - "@types/node" "*" - -"@types/bunyan@*": - version "1.8.6" - resolved "https://registry.yarnpkg.com/@types/bunyan/-/bunyan-1.8.6.tgz#6527641cca30bedec5feb9ab527b7803b8000582" - integrity sha512-YiozPOOsS6bIuz31ilYqR5SlLif4TBWsousN2aCWLi5233nZSX19tFbcQUPdR7xJ8ypPyxkCGNxg0CIV5n9qxQ== +"@snyk/mix-parser@^1.1.1": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@snyk/mix-parser/-/mix-parser-1.3.2.tgz#930de1d9c3a91e20660751f78c3e6f6a88ac5b2b" + integrity sha512-0Aq9vcgmjH0d9Gk5q0k6l4ZOvSHPf6/BCQGDVOpKp0hwOkXWnpDOLLPxL+uBCktuH9zTYQFB0aTk91kQImZqmA== dependencies: + "@snyk/dep-graph" "^1.28.0" + tslib "^2.0.0" + +"@snyk/rpm-parser@^2.0.0": + version "2.2.1" + resolved "https://registry.yarnpkg.com/@snyk/rpm-parser/-/rpm-parser-2.2.1.tgz#b61ccf5478684b203576bd2be68de434ccbb0069" + integrity sha512-OAON0bPf3c5fgM/GK9DX0aZErB6SnuRyYlPH0rqI1TXGsKrYnVELhaE6ctNbEfPTQuY9r6q0vM+UYDaFM/YliA== + dependencies: + event-loop-spinner "^2.0.0" + +"@snyk/snyk-cocoapods-plugin@2.5.2": + version "2.5.2" + resolved "https://registry.yarnpkg.com/@snyk/snyk-cocoapods-plugin/-/snyk-cocoapods-plugin-2.5.2.tgz#cd724fcd637cb3af76187bf7254819b6079489f6" + integrity sha512-WHhnwyoGOhjFOjBXqUfszD84SErrtjHjium/4xFbqKpEE+yuwxs8OwV/S29BtxhYiGtjpD1azv5QtH30VUMl0A== + dependencies: + "@snyk/cli-interface" "^2.11.0" + "@snyk/cocoapods-lockfile-parser" "3.6.2" + "@snyk/dep-graph" "^1.23.1" + source-map-support "^0.5.7" + tslib "^2.0.0" + +"@snyk/snyk-docker-pull@3.2.3": + version "3.2.3" + resolved "https://registry.yarnpkg.com/@snyk/snyk-docker-pull/-/snyk-docker-pull-3.2.3.tgz#9743ea624098c7abd0f95c438c76067530494f4b" + integrity sha512-hiFiSmWGLc2tOI7FfgIhVdFzO2f69im8O6p3OV4xEZ/Ss1l58vwtqudItoswsk7wj/azRlgfBW8wGu2MjoudQg== + dependencies: + "@snyk/docker-registry-v2-client" "1.13.9" + child-process "^1.0.2" + tar-stream "^2.1.2" + tmp "^0.1.0" + +"@snyk/snyk-hex-plugin@1.1.4": + version "1.1.4" + resolved "https://registry.yarnpkg.com/@snyk/snyk-hex-plugin/-/snyk-hex-plugin-1.1.4.tgz#4a5b1684cecc1a557ec1a9f5f8646683ae89f0da" + integrity sha512-kLfFGckSmyKe667UGPyWzR/H7/Trkt4fD8O/ktElOx1zWgmivpLm0Symb4RCfEmz9irWv+N6zIKRrfSNdytcPQ== + dependencies: + "@snyk/dep-graph" "^1.28.0" + "@snyk/mix-parser" "^1.1.1" + debug "^4.3.1" + tmp "^0.0.33" + tslib "^2.0.0" + upath "2.0.1" + +"@szmarczak/http-timer@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-1.1.2.tgz#b1665e2c461a2cd92f4c1bbf50d5454de0d4b421" + integrity sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA== + dependencies: + defer-to-connect "^1.0.1" + +"@szmarczak/http-timer@^4.0.5": + version "4.0.5" + resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-4.0.5.tgz#bfbd50211e9dfa51ba07da58a14cdfd333205152" + integrity sha512-PyRA9sm1Yayuj5OIoJ1hGt2YISX45w9WcFbh6ddT0Z/0yaFxOtGLInr4jUfU1EAFVs0Yfyfev4RNwBlUaHdlDQ== + dependencies: + defer-to-connect "^2.0.0" + +"@types/braces@*": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/braces/-/braces-3.0.0.tgz#7da1c0d44ff1c7eb660a36ec078ea61ba7eb42cb" + integrity sha512-TbH79tcyi9FHwbyboOKeRachRq63mSuWYXOflsNO9ZyE5ClQ/JaozNKl+aWUq87qPNsXasXxi2AbgfwIJ+8GQw== + +"@types/cacheable-request@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@types/cacheable-request/-/cacheable-request-6.0.1.tgz#5d22f3dded1fd3a84c0bbeb5039a7419c2c91976" + integrity sha512-ykFq2zmBGOCbpIXtoVbz4SKY5QriWPh3AjyU4G74RYbtt5yOc5OfaY75ftjg7mikMOla1CTGpX3lLbuJh8DTrQ== + dependencies: + "@types/http-cache-semantics" "*" + "@types/keyv" "*" "@types/node" "*" + "@types/responselike" "*" "@types/debug@^4.1.4": version "4.1.5" resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.5.tgz#b14efa8852b7768d898906613c23f688713e02cd" integrity sha512-Q1y515GcOdTHgagaVFhHnIFQ38ygs/kmxdNpvpou+raI9UO3YZcHDngBSYKQklcKlvA7iuQlmIKbzvmxcOE9CQ== -"@types/events@*": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7" - integrity sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g== +"@types/emscripten@^1.38.0": + version "1.39.4" + resolved "https://registry.yarnpkg.com/@types/emscripten/-/emscripten-1.39.4.tgz#d61990c0cee72c4e475de737a140b51fe925a2c8" + integrity sha512-k3LLVMFrdNA9UCvMDPWMbFrGPNb+GcPyw29ktJTo1RCN7RmxFG5XzPZcPKRlnLuLT/FRm8wp4ohvDwNY7GlROQ== + +"@types/flat-cache@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@types/flat-cache/-/flat-cache-2.0.0.tgz#64e5d3b426c392b603a208a55bdcc7d920ce6e57" + integrity sha512-fHeEsm9hvmZ+QHpw6Fkvf19KIhuqnYLU6vtWLjd5BsMd/qVi7iTkMioDZl0mQmfNRA1A6NwvhrSRNr9hGYZGww== + +"@types/graphlib@^2": + version "2.1.7" + resolved "https://registry.yarnpkg.com/@types/graphlib/-/graphlib-2.1.7.tgz#e6a47a4f43511f5bad30058a669ce5ce93bfd823" + integrity sha512-K7T1n6U2HbTYu+SFHlBjz/RH74OA2D/zF1qlzn8uXbvB4uRg7knOM85ugS2bbXI1TXMh7rLqk4OVRwIwEBaixg== + +"@types/http-cache-semantics@*": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.0.tgz#9140779736aa2655635ee756e2467d787cfe8a2a" + integrity sha512-c3Xy026kOF7QOTn00hbIllV1dLR9hG9NkSrLQgCVs8NF6sBU+VGWjD3wLPhmh1TYAc7ugCFsvHYMN4VcBN1U1A== "@types/js-yaml@^3.12.1": version "3.12.2" resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-3.12.2.tgz#a35a1809c33a68200fb6403d1ad708363c56470a" integrity sha512-0CFu/g4mDSNkodVwWijdlr8jH7RoplRWNgovjFLEZeT+QEbbZXjBmCe3HwaWheAlCbHwomTwzZoSedeOycABug== +"@types/keyv@*": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@types/keyv/-/keyv-3.1.1.tgz#e45a45324fca9dab716ab1230ee249c9fb52cfa7" + integrity sha512-MPtoySlAZQ37VoLaPcTHCu1RWJ4llDkULYZIzOYxlhxBqYPB0RsRlmMU0R6tahtFe27mIdkHV+551ZWV4PLmVw== + dependencies: + "@types/node" "*" + +"@types/lodash.chunk@^4.2.6": + version "4.2.6" + resolved "https://registry.yarnpkg.com/@types/lodash.chunk/-/lodash.chunk-4.2.6.tgz#9d35f05360b0298715d7f3d9efb34dd4f77e5d2a" + integrity sha512-SPlusB7jxXyGcTXYcUdWr7WmhArO/rmTq54VN88iKMxGUhyg79I4Q8n4riGn3kjaTjOJrVlHhxgX/d7woak5BQ== + dependencies: + "@types/lodash" "*" + +"@types/lodash.omit@^4.5.6": + version "4.5.6" + resolved "https://registry.yarnpkg.com/@types/lodash.omit/-/lodash.omit-4.5.6.tgz#f2a9518259e481a48ff7ec423420fa8fd58933e2" + integrity sha512-KXPpOSNX2h0DAG2w7ajpk7TXvWF28ZHs5nJhOJyP0BQHkehgr948RVsToItMme6oi0XJkp19CbuNXkIX8FiBlQ== + dependencies: + "@types/lodash" "*" + +"@types/lodash.union@^4.6.6": + version "4.6.6" + resolved "https://registry.yarnpkg.com/@types/lodash.union/-/lodash.union-4.6.6.tgz#2f77f2088326ed147819e9e384182b99aae8d4b0" + integrity sha512-Wu0ZEVNcyCz8eAn6TlUbYWZoGbH9E+iOHxAZbwUoCEXdUiy6qpcz5o44mMXViM4vlPLLCPlkAubEP1gokoSZaw== + dependencies: + "@types/lodash" "*" + +"@types/lodash@*": + version "4.14.168" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.168.tgz#fe24632e79b7ade3f132891afff86caa5e5ce008" + integrity sha512-oVfRvqHV/V6D1yifJbVRU3TMp8OT6o6BG+U9MkwuJ3U8/CsDHvalRpsxBqivn71ztOFZBTfJMvETbqHiaNSj7Q== + +"@types/micromatch@^4.0.1": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@types/micromatch/-/micromatch-4.0.1.tgz#9381449dd659fc3823fd2a4190ceacc985083bc7" + integrity sha512-my6fLBvpY70KattTNzYOK6KU1oR1+UCz9ug/JbcF5UrEmeCt9P7DV2t7L8+t18mMPINqGQCE4O8PLOPbI84gxw== + dependencies: + "@types/braces" "*" + "@types/node@*": version "13.5.3" resolved "https://registry.yarnpkg.com/@types/node/-/node-13.5.3.tgz#37f1f539b7535b9fb4ef77d59db1847a837b7f17" integrity sha512-ZPnWX9PW992w6DUsz3JIXHaSb5v7qmKCVzC3km6SxcDGxk7zmLfYaCJTbktIa5NeywJkkZDhGldKqDIvC5DRrA== -"@types/node@^6.14.4": - version "6.14.9" - resolved "https://registry.yarnpkg.com/@types/node/-/node-6.14.9.tgz#733583e21ef0eab85a9737dfafbaa66345a92ef0" - integrity sha512-leP/gxHunuazPdZaCvsCefPQxinqUDsCxCR5xaDUrY2MkYxQRFZZwU5e7GojyYsGB7QVtCi7iVEl/hoFXQYc+w== +"@types/node@^13.7.0": + version "13.13.50" + resolved "https://registry.yarnpkg.com/@types/node/-/node-13.13.50.tgz#bc8ebf70c392a98ffdba7aab9b46989ea96c1c62" + integrity sha512-y7kkh+hX/0jZNxMyBR/6asG0QMSaPSzgeVK63dhWHl4QAXCQB8lExXmzLL6SzmOgKHydtawpMnNhlDbv7DXPEA== -"@types/restify@^4.3.6": - version "4.3.6" - resolved "https://registry.yarnpkg.com/@types/restify/-/restify-4.3.6.tgz#5da5889b65c34c33937a67686bab591325dde806" - integrity sha512-4l4f0EXnleXQttlhRCXtTuJ8UelsKiAKIK2AAEd2epBHu41aEbM0U2z6E5tUrNwlbxz7qaNBISduGMeg+G3PaA== +"@types/responselike@*", "@types/responselike@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@types/responselike/-/responselike-1.0.0.tgz#251f4fe7d154d2bad125abe1b429b23afd262e29" + integrity sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA== dependencies: - "@types/bunyan" "*" "@types/node" "*" -"@types/semver@^5.5.0": - version "5.5.0" - resolved "https://registry.yarnpkg.com/@types/semver/-/semver-5.5.0.tgz#146c2a29ee7d3bae4bf2fcb274636e264c813c45" - integrity sha512-41qEJgBH/TWgo5NFSvBCJ1qkoi3Q6ONSF2avrHq1LVEZfYpdHmj0y9SuTK+u9ZhG1sYQKBL1AWXKyLWP4RaUoQ== +"@types/sarif@^2.1.3": + version "2.1.3" + resolved "https://registry.yarnpkg.com/@types/sarif/-/sarif-2.1.3.tgz#1f9c16033f1461536ac014284920350109614c02" + integrity sha512-zf+EoIplTkQW2TV2mwtJtlI0g540Z3Rs9tX9JqRAtyjnDCqkP+eMTgWCj3PGNbQpi+WXAjvC3Ou/dvvX2sLK4w== -"@types/xml2js@0.4.3": - version "0.4.3" - resolved "https://registry.yarnpkg.com/@types/xml2js/-/xml2js-0.4.3.tgz#2f41bfc74d5a4022511721f872ed395a210ad3b7" - integrity sha512-Pv2HGRE4gWLs31In7nsyXEH4uVVsd0HNV9i2dyASvtDIlOtSTr1eczPLDpdEuyv5LWH5LT20GIXwPjkshKWI1g== +"@types/semver@^7.1.0": + version "7.3.4" + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.4.tgz#43d7168fec6fa0988bb1a513a697b29296721afb" + integrity sha512-+nVsLKlcUCeMzD2ufHEYuJ9a2ovstb6Dp52A5VsoKxDXgvE051XgHI/33I1EymwkRGQkwnA0LkhnUzituGs4EQ== + +"@types/treeify@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@types/treeify/-/treeify-1.0.0.tgz#f04743cb91fc38254e8585d692bd92503782011c" + integrity sha512-ONpcZAEYlbPx4EtJwfTyCDQJGUpKf4sEcuySdCVjK5Fj/3vHp5HII1fqa1/+qrsLnpYELCQTfVW/awsGJePoIg== + +"@types/uuid@^8.3.0": + version "8.3.0" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.0.tgz#215c231dff736d5ba92410e6d602050cce7e273f" + integrity sha512-eQ9qFW/fhfGJF8WKHGEHZEyVWfZxrT+6CLIJGBcZPfxUh/+BnEj+UCGYMlr9qZuX/2AltsvwrGqp0LhEW8D0zQ== + +"@yarnpkg/core@^2.4.0": + version "2.4.0" + resolved "https://registry.yarnpkg.com/@yarnpkg/core/-/core-2.4.0.tgz#b5d8cc7ee2ddb022816c7afa3f83c3ee3d317c80" + integrity sha512-FYjcPNTfDfMKLFafQPt49EY28jnYC82Z2S7oMwLPUh144BL8v8YXzb4aCnFyi5nFC5h2kcrJfZh7+Pm/qvCqGw== dependencies: - "@types/events" "*" - "@types/node" "*" + "@arcanis/slice-ansi" "^1.0.2" + "@types/semver" "^7.1.0" + "@types/treeify" "^1.0.0" + "@yarnpkg/fslib" "^2.4.0" + "@yarnpkg/json-proxy" "^2.1.0" + "@yarnpkg/libzip" "^2.2.1" + "@yarnpkg/parsers" "^2.3.0" + "@yarnpkg/pnp" "^2.3.2" + "@yarnpkg/shell" "^2.4.1" + binjumper "^0.1.4" + camelcase "^5.3.1" + chalk "^3.0.0" + ci-info "^2.0.0" + clipanion "^2.6.2" + cross-spawn "7.0.3" + diff "^4.0.1" + globby "^11.0.1" + got "^11.7.0" + json-file-plus "^3.3.1" + lodash "^4.17.15" + micromatch "^4.0.2" + mkdirp "^0.5.1" + p-limit "^2.2.0" + pluralize "^7.0.0" + pretty-bytes "^5.1.0" + semver "^7.1.2" + stream-to-promise "^2.2.0" + tar-stream "^2.0.1" + treeify "^1.1.0" + tslib "^1.13.0" + tunnel "^0.0.6" -"@yarnpkg/lockfile@^1.0.2": +"@yarnpkg/fslib@^2.1.0", "@yarnpkg/fslib@^2.4.0": + version "2.4.0" + resolved "https://registry.yarnpkg.com/@yarnpkg/fslib/-/fslib-2.4.0.tgz#a265b737cd089ef293ad964e06c143f5efd411a9" + integrity sha512-CwffYY9owtl3uImNOn1K4jl5iIb/L16a9UZ9Q3lkBARk6tlUsPrNFX00eoUlFcLn49TTfd3zdN6higloGCyncw== + dependencies: + "@yarnpkg/libzip" "^2.2.1" + tslib "^1.13.0" + +"@yarnpkg/json-proxy@^2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@yarnpkg/json-proxy/-/json-proxy-2.1.0.tgz#362a161678cd7dda74b47b4fc848a2f1730d16cd" + integrity sha512-rOgCg2DkyviLgr80mUMTt9vzdf5RGOujQB26yPiXjlz4WNePLBshKlTNG9rKSoKQSOYEQcw6cUmosfOKDatrCw== + dependencies: + "@yarnpkg/fslib" "^2.1.0" + tslib "^1.13.0" + +"@yarnpkg/libzip@^2.2.1": + version "2.2.1" + resolved "https://registry.yarnpkg.com/@yarnpkg/libzip/-/libzip-2.2.1.tgz#61c9b8b2499ee6bd9c4fcbf8248f68e07bd89948" + integrity sha512-AYDJXrkzayoDd3ZlVgFJ+LyDX+Zj/cki3vxIpcYxejtgkl3aquVWOxlC0DD9WboBWsJFIP1MjrUbchLyh++/7A== + dependencies: + "@types/emscripten" "^1.38.0" + tslib "^1.13.0" + +"@yarnpkg/lockfile@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31" integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ== +"@yarnpkg/parsers@^2.3.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@yarnpkg/parsers/-/parsers-2.3.0.tgz#7b9564c6df02f4921d5cfe8287c4b648e93ea84b" + integrity sha512-qgz0QUgOvnhtF92kaluIhIIKBUHlYlHUBQxqh5v9+sxEQvUeF6G6PKiFlzo3E6O99XwvNEGpVu1xZPoSGyGscQ== + dependencies: + js-yaml "^3.10.0" + tslib "^1.13.0" + +"@yarnpkg/pnp@^2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@yarnpkg/pnp/-/pnp-2.3.2.tgz#9a052a06bf09c9f0b7c31e0867a7e725cb6401ed" + integrity sha512-JdwHu1WBCISqJEhIwx6Hbpe8MYsYbkGMxoxolkDiAeJ9IGEe08mQcbX1YmUDV1ozSWlm9JZE90nMylcDsXRFpA== + dependencies: + "@types/node" "^13.7.0" + "@yarnpkg/fslib" "^2.4.0" + tslib "^1.13.0" + +"@yarnpkg/shell@^2.4.1": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@yarnpkg/shell/-/shell-2.4.1.tgz#abc557f8924987c9c382703e897433a82780265d" + integrity sha512-oNNJkH8ZI5uwu0dMkJf737yMSY1WXn9gp55DqSA5wAOhKvV5DJTXFETxkVgBQhO6Bow9tMGSpvowTMD/oAW/9g== + dependencies: + "@yarnpkg/fslib" "^2.4.0" + "@yarnpkg/parsers" "^2.3.0" + clipanion "^2.6.2" + cross-spawn "7.0.3" + fast-glob "^3.2.2" + micromatch "^4.0.2" + stream-buffers "^3.0.2" + tslib "^1.13.0" + abbrev@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== -agent-base@4, agent-base@^4.2.0, agent-base@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee" - integrity sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg== +aggregate-error@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" + integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA== dependencies: - es6-promisify "^5.0.0" + clean-stack "^2.0.0" + indent-string "^4.0.0" -agent-base@~4.2.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.1.tgz#d89e5999f797875674c07d87f260fc41e83e8ca9" - integrity sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg== +ansi-align@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-3.0.0.tgz#b536b371cf687caaef236c18d3e21fe3797467cb" + integrity sha512-ZpClVKqXN3RGBmKibdfWzqCY4lnjEuoNzU5T0oEFpfd/z5qJHVarukridD4juLO2FXMiwUQxr9WqQtaYa8XRYw== dependencies: - es6-promisify "^5.0.0" + string-width "^3.0.0" -ansi-align@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-2.0.0.tgz#c36aeccba563b89ceb556f3690f0b1d9e3547f7f" - integrity sha1-w2rsy6VjuJzrVW82kPCx2eNUf38= - dependencies: - string-width "^2.0.0" - -ansi-escapes@3.2.0, ansi-escapes@^3.2.0: +ansi-escapes@3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b" integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ== -ansi-regex@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" - integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= - -ansi-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" - integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= +ansi-escapes@^4.2.1: + version "4.3.2" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" + integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== + dependencies: + type-fest "^0.21.3" ansi-regex@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== +ansi-regex@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" + integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg== + ansi-styles@^3.2.0, ansi-styles@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" @@ -244,11 +625,23 @@ ansi-styles@^3.2.0, ansi-styles@^3.2.1: dependencies: color-convert "^1.9.0" +ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + ansicolors@^0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/ansicolors/-/ansicolors-0.3.2.tgz#665597de86a9ffe3aa9bfbe6cae5c6ea426b4979" integrity sha1-ZlWX3oap/+Oqm/vmyuXG6kJrSXk= +any-promise@^1.1.0, any-promise@~1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" + integrity sha1-q8av7tzqUugJzcA3au0845Y10X8= + archy@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/archy/-/archy-1.0.0.tgz#f9c8c13757cc1dd7bc379ac77b2c62a5c2868c40" @@ -261,26 +654,57 @@ argparse@^1.0.7: dependencies: sprintf-js "~1.0.2" +array-union@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" + integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== + asap@~2.0.3: version "2.0.6" resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= -ast-types@0.x.x: - version "0.13.2" - resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.13.2.tgz#df39b677a911a83f3a049644fb74fdded23cea48" - integrity sha512-uWMHxJxtfj/1oZClOxDEV1sQ1HCDkA4MG8Gr69KKeBjEVH0R84WlejZ0y2DcwyBlpAEMltmVYkVgqfLFb2oyiA== +asn1@~0.2.0: + version "0.2.4" + resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" + integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg== + dependencies: + safer-buffer "~2.1.0" -async@^1.4.0: - version "1.5.2" - resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" - integrity sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo= +async@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/async/-/async-3.2.0.tgz#b3a2685c5ebb641d3de02d161002c60fc9f85720" + integrity sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw== + +axios@^0.21.1: + version "0.21.1" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8" + integrity sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA== + dependencies: + follow-redirects "^1.10.0" balanced-match@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= +base64-js@^1.3.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + +bcrypt-pbkdf@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" + integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4= + dependencies: + tweetnacl "^0.14.3" + +binjumper@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/binjumper/-/binjumper-0.1.4.tgz#4acc0566832714bd6508af6d666bd9e5e21fc7f8" + integrity sha512-Gdxhj+U295tIM6cO4bJO1jsvSjBVHNpj2o/OwW7pqDEtaqF6KdOxjtbo93jMMKAkP7+u09+bV8DhSqjIv4qR3w== + bl@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/bl/-/bl-3.0.1.tgz#1cbb439299609e419b5a74d7fce2f8b37d8e5c6f" @@ -288,18 +712,33 @@ bl@^3.0.0: dependencies: readable-stream "^3.0.1" -boxen@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/boxen/-/boxen-1.3.0.tgz#55c6c39a8ba58d9c61ad22cd877532deb665a20b" - integrity sha512-TNPjfTr432qx7yOjQyaXm3dSR0MH9vXp7eT1BFSl/C51g+EFnOR9hTg1IreahGBmDNCehscshe45f+C1TBZbLw== +bl@^4.0.3: + version "4.1.0" + resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" + integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== dependencies: - ansi-align "^2.0.0" - camelcase "^4.0.0" - chalk "^2.0.1" - cli-boxes "^1.0.0" - string-width "^2.0.0" - term-size "^1.2.0" - widest-line "^2.0.0" + buffer "^5.5.0" + inherits "^2.0.4" + readable-stream "^3.4.0" + +boolean@^3.0.1: + version "3.0.3" + resolved "https://registry.yarnpkg.com/boolean/-/boolean-3.0.3.tgz#0fee0c9813b66bef25a8a6a904bb46736d05f024" + integrity sha512-EqrTKXQX6Z3A2nRmMEIlAIfjQOgFnVO2nqZGpbcsPnYGWBwpFqzlrozU1dy+S2iqfYDLh26ef4KrgTxu9xQrxA== + +boxen@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/boxen/-/boxen-4.2.0.tgz#e411b62357d6d6d36587c8ac3d5d974daa070e64" + integrity sha512-eB4uT9RGzg2odpER62bBwSLvUeGC+WbRjjyyFhGsKnc8wp/m0+hQsMUvUe3H2V0D5vw0nBdO1hCJoZo5mKeuIQ== + dependencies: + ansi-align "^3.0.0" + camelcase "^5.3.1" + chalk "^3.0.0" + cli-boxes "^2.2.0" + string-width "^4.1.0" + term-size "^2.1.0" + type-fest "^0.8.1" + widest-line "^3.1.0" brace-expansion@^1.1.7: version "1.1.11" @@ -309,41 +748,86 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" +braces@^3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + +browserify-zlib@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.1.4.tgz#bb35f8a519f600e0fa6b8485241c979d0141fb2d" + integrity sha1-uzX4pRn2AOD6a4SFJByXnQFB+y0= + dependencies: + pako "~0.2.0" + buffer-from@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== -bytes@3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" - integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg== - -camelcase@^2.0.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f" - integrity sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8= - -camelcase@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd" - integrity sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0= - -capture-stack-trace@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz#a6c0bbe1f38f3aa0b92238ecb6ff42c344d4135d" - integrity sha512-mYQLZnx5Qt1JgB1WEiMCf2647plpGeQ2NMR/5L0HNZzGQo4fuSPnK+wjfPnKZV0aiJDgzmWqqkV/g7JD+DW0qw== - -chalk@^2.0.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.1.tgz#18c49ab16a037b6eb0152cc83e3471338215b66e" - integrity sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ== +buffer@^5.5.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" + integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== dependencies: - ansi-styles "^3.2.1" - escape-string-regexp "^1.0.5" - supports-color "^5.3.0" + base64-js "^1.3.1" + ieee754 "^1.1.13" -chalk@^2.3.2, chalk@^2.4.2: +cacheable-lookup@^5.0.3: + version "5.0.4" + resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz#5a6b865b2c44357be3d5ebc2a467b032719a7005" + integrity sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA== + +cacheable-request@^6.0.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-6.1.0.tgz#20ffb8bd162ba4be11e9567d823db651052ca912" + integrity sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg== + dependencies: + clone-response "^1.0.2" + get-stream "^5.1.0" + http-cache-semantics "^4.0.0" + keyv "^3.0.0" + lowercase-keys "^2.0.0" + normalize-url "^4.1.0" + responselike "^1.0.2" + +cacheable-request@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-7.0.1.tgz#062031c2856232782ed694a257fa35da93942a58" + integrity sha512-lt0mJ6YAnsrBErpTMWeu5kl/tg9xMAWjavYTN6VQXM1A/teBITuNcccXsCxF0tDQQJf9DfAaX5O4e0zp0KlfZw== + dependencies: + clone-response "^1.0.2" + get-stream "^5.1.0" + http-cache-semantics "^4.0.0" + keyv "^4.0.0" + lowercase-keys "^2.0.0" + normalize-url "^4.1.0" + responselike "^2.0.0" + +call-bind@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" + integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== + dependencies: + function-bind "^1.1.1" + get-intrinsic "^1.0.2" + +camelcase@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + +chalk@4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" + integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -352,56 +836,90 @@ chalk@^2.3.2, chalk@^2.4.2: escape-string-regexp "^1.0.5" supports-color "^5.3.0" +chalk@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" + integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chalk@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.1.tgz#c80b3fab28bf6371e6863325eee67e618b77e6ad" + integrity sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + chardet@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== -ci-info@^1.5.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.6.0.tgz#2ca20dbb9ceb32d4524a683303313f0304b1e497" - integrity sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A== +child-process@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/child-process/-/child-process-1.0.2.tgz#98974dc7ed1ee4c6229f8e305fa7313a6885a7f2" + integrity sha1-mJdNx+0e5MYin44wX6cxOmiFp/I= -cli-boxes@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-1.0.0.tgz#4fa917c3e59c94a004cd61f8ee509da651687143" - integrity sha1-T6kXw+WclKAEzWH47lCdplFocUM= +chownr@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" + integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== -cli-cursor@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" - integrity sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU= +ci-info@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" + integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== + +clean-stack@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" + integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== + +cli-boxes@^2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-2.2.1.tgz#ddd5035d25094fce220e9cab40a45840a440318f" + integrity sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw== + +cli-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" + integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== dependencies: - restore-cursor "^2.0.0" + restore-cursor "^3.1.0" cli-spinner@0.2.10: version "0.2.10" resolved "https://registry.yarnpkg.com/cli-spinner/-/cli-spinner-0.2.10.tgz#f7d617a36f5c47a7bc6353c697fc9338ff782a47" integrity sha512-U0sSQ+JJvSLi1pAYuJykwiA8Dsr15uHEy85iCJ6A+0DjVxivr3d+N2Wjvodeg89uP5K6TswFkKBfAD7B3YSn/Q== -cli-width@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639" - integrity sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk= +cli-spinners@^2.5.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.6.0.tgz#36c7dc98fb6a9a76bd6238ec3f77e2425627e939" + integrity sha512-t+4/y50K/+4xcCRosKkA7W4gTr1MySvLV0q+PxmG7FJ5g+66ChKurYjxBCjHggHH3HA5Hh9cy+lcUGWDqVH+4Q== -cliui@^3.0.3: - version "3.2.0" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d" - integrity sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0= +cli-width@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6" + integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw== + +clipanion@^2.6.2: + version "2.6.2" + resolved "https://registry.yarnpkg.com/clipanion/-/clipanion-2.6.2.tgz#820e7440812052442455b248f927b187ed732f71" + integrity sha512-0tOHJNMF9+4R3qcbBL+4IxLErpaYSYvzs10aXuECDbZdJOuJHdagJMAqvLdeaUQTI/o2uSCDRpet6ywDiKOAYw== + +clone-response@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b" + integrity sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws= dependencies: - string-width "^1.0.1" - strip-ansi "^3.0.1" - wrap-ansi "^2.0.0" + mimic-response "^1.0.0" -co@^4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" - integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ= - -code-point-at@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" - integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= +clone@^1.0.2: + version "1.0.4" + resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" + integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4= color-convert@^1.9.0: version "1.9.3" @@ -410,41 +928,58 @@ color-convert@^1.9.0: dependencies: color-name "1.1.3" +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + color-name@1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= -core-js@^3.2.0: - version "3.6.4" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.6.4.tgz#440a83536b458114b9cb2ac1580ba377dc470647" - integrity sha512-4paDGScNgZP2IXXilaffL9X7968RuvwlkK3xWtZRVqgd8SYNiVKRJvkFd1aqqEuPfN7E68ZHEp9hDj6lHj4Hyw== +configstore@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/configstore/-/configstore-5.0.1.tgz#d365021b5df4b98cdd187d6a3b0e3f6a7cc5ed96" + integrity sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA== + dependencies: + dot-prop "^5.2.0" + graceful-fs "^4.1.2" + make-dir "^3.0.0" + unique-string "^2.0.0" + write-file-atomic "^3.0.0" + xdg-basedir "^4.0.0" + +core-js@^3.6.5: + version "3.11.0" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.11.0.tgz#05dac6aa70c0a4ad842261f8957b961d36eb8926" + integrity sha512-bd79DPpx+1Ilh9+30aT5O1sgpQd4Ttg8oqkqi51ZzhedMM1omD2e6IOF48Z/DzDCZ2svp49tN/3vneTK6ZBkXw== core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= -create-error-class@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/create-error-class/-/create-error-class-3.0.2.tgz#06be7abef947a3f14a30fd610671d401bca8b7b6" - integrity sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y= +cross-spawn@7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== dependencies: - capture-stack-trace "^1.0.0" - -cross-spawn@^5.0.1: - version "5.1.0" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" - integrity sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk= - dependencies: - lru-cache "^4.0.1" - shebang-command "^1.2.0" - which "^1.2.9" + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" cross-spawn@^6.0.0: version "6.0.5" @@ -457,84 +992,108 @@ cross-spawn@^6.0.0: shebang-command "^1.2.0" which "^1.2.9" -crypto-random-string@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-1.0.0.tgz#a230f64f568310e1498009940790ec99545bca7e" - integrity sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4= +crypto-random-string@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" + integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== -data-uri-to-buffer@1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-1.2.0.tgz#77163ea9c20d8641b4707e8f18abdf9a78f34835" - integrity sha512-vKQ9DTQPN1FLYiiEEOQ6IBGFqvjCa5rSK3cWMy/Nespm5d/x3dGFT9UBZnkLxCwua/IXBi2TYnwTEpsOvhC4UQ== - -debug@2: - version "2.6.9" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" - integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== - dependencies: - ms "2.0.0" - -debug@3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" - integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== - dependencies: - ms "2.0.0" - -debug@4, debug@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" - integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== - dependencies: - ms "^2.1.1" - -debug@^3.1.0, debug@^3.2.5, debug@^3.2.6: +debug@^3.1.0, debug@^3.2.6: version "3.2.6" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== dependencies: ms "^2.1.1" -decamelize@^1.1.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" - integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= +debug@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" + integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== + dependencies: + ms "^2.1.1" + +debug@^4.2.0, debug@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" + integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== + dependencies: + ms "2.1.2" + +decompress-response@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3" + integrity sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M= + dependencies: + mimic-response "^1.0.0" + +decompress-response@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" + integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ== + dependencies: + mimic-response "^3.1.0" deep-extend@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== -deep-is@~0.1.3: - version "0.1.3" - resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" - integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= - -degenerator@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/degenerator/-/degenerator-1.0.4.tgz#fcf490a37ece266464d9cc431ab98c5819ced095" - integrity sha1-/PSQo37OJmRk2cxDGrmMWBnO0JU= +defaults@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.3.tgz#c656051e9817d9ff08ed881477f3fe4019f3ef7d" + integrity sha1-xlYFHpgX2f8I7YgUd/P+QBnz730= dependencies: - ast-types "0.x.x" - escodegen "1.x.x" - esprima "3.x.x" + clone "^1.0.2" -depd@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" - integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= +defer-to-connect@^1.0.1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-1.1.3.tgz#331ae050c08dcf789f8c83a7b81f0ed94f4ac591" + integrity sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ== + +defer-to-connect@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-2.0.1.tgz#8016bdb4143e4632b77a3449c6236277de520587" + integrity sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg== + +define-properties@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" + integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== + dependencies: + object-keys "^1.0.12" + +detect-node@^2.0.4: + version "2.0.5" + resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.5.tgz#9d270aa7eaa5af0b72c4c9d9b814e7f4ce738b79" + integrity sha512-qi86tE6hRcFHy8jI1m2VG+LaPUR1LhqDa5G8tVjuUXmOrpuAgqsA1pN0+ldgr3aKUH+QLI9hCY/OcRYisERejw== diff@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== -dockerfile-ast@0.0.18: - version "0.0.18" - resolved "https://registry.yarnpkg.com/dockerfile-ast/-/dockerfile-ast-0.0.18.tgz#94a0ba84eb9b3e9fb7bd6beae0ea7eb6dcbca75a" - integrity sha512-SEp95qCox1KAzf8BBtjHoBDD0a7/eNlZJ6fgDf9RxqeSEDwLuEN9YjdZ/tRlkrYLxXR4i+kqZzS4eDRSqs8VKQ== +dir-glob@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" + integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== dependencies: - vscode-languageserver-types "^3.5.0" + path-type "^4.0.0" + +docker-modem@2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/docker-modem/-/docker-modem-2.1.3.tgz#15432225f63db02eb5de4bb9a621b7293e5f264d" + integrity sha512-cwaRptBmYZwu/FyhGcqBm2MzXA77W2/E6eVkpOZVDk6PkI9Bjj84xPrXiHMA+OWjzNy+DFjgKh8Q+1hMR7/OHg== + dependencies: + debug "^4.1.1" + readable-stream "^3.5.0" + split-ca "^1.0.1" + ssh2 "^0.8.7" + +dockerfile-ast@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/dockerfile-ast/-/dockerfile-ast-0.2.0.tgz#13cc4a6fe3aea30a4104622b30f49a0fe3a5c038" + integrity sha512-iQyp12k1A4tF3sEfLAq2wfFPKdpoiGTJeuiu2Y1bdEqIZu0DfSSL2zm0fk7a/UHeQkngnYaRRGuON+C+2LO1Fw== + dependencies: + vscode-languageserver-types "^3.16.0" dot-prop@^5.2.0: version "5.2.0" @@ -543,22 +1102,40 @@ dot-prop@^5.2.0: dependencies: is-obj "^2.0.0" -dotnet-deps-parser@4.9.0: - version "4.9.0" - resolved "https://registry.yarnpkg.com/dotnet-deps-parser/-/dotnet-deps-parser-4.9.0.tgz#d14f9f92ae9a64062cd215c8863d1e77e80236f0" - integrity sha512-V0O+7pI7Ei+iL5Kgy6nYq1UTwzrpqci5K/zf8cXyP5RWBSQBUl/JOE9I67zLUkKiwOdfPhbMQgcRj/yGA+NL1A== +dotnet-deps-parser@5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/dotnet-deps-parser/-/dotnet-deps-parser-5.0.0.tgz#5115c442cbefea59e4fb9f9ed8fa4863a0f3186d" + integrity sha512-1l9K4UnQQHSfKgeHeLrxnB53AidCZqPyf9dkRL4/fZl8//NPiiDD43zHtgylw8DHlO7gvM8+O5a0UPHesNYZKw== dependencies: - "@types/xml2js" "0.4.3" - lodash "^4.17.11" + lodash.isempty "^4.4.0" + lodash.set "^4.3.2" + lodash.uniq "^4.5.0" source-map-support "^0.5.7" tslib "^1.10.0" - xml2js "0.4.19" + xml2js "0.4.23" duplexer3@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI= +duplexify@^3.5.0, duplexify@^3.6.0: + version "3.7.1" + resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.7.1.tgz#2a4df5317f6ccfd91f86d6fd25d8d8a103b88309" + integrity sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g== + dependencies: + end-of-stream "^1.0.0" + inherits "^2.0.1" + readable-stream "^2.0.0" + stream-shift "^1.0.0" + +elfy@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/elfy/-/elfy-1.0.0.tgz#7a1c86af7d41e0a568cbb4a3fa5b685648d9efcd" + integrity sha512-4Kp3AA94jC085IJox+qnvrZ3PudqTi4gQNvIoTZfJJ9IqkRuCoqP60vCVYlIg00c5aYusi5Wjh2bf0cHYt+6gQ== + dependencies: + endian-reader "^0.3.0" + email-validator@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/email-validator/-/email-validator-2.0.4.tgz#b8dfaa5d0dae28f1b03c95881d904d4e40bfe7ed" @@ -569,81 +1146,61 @@ emoji-regex@^7.0.1: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== -end-of-stream@^1.1.0, end-of-stream@^1.4.1: +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +end-of-stream@^1.0.0, end-of-stream@^1.1.0, end-of-stream@^1.4.1: version "1.4.4" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== dependencies: once "^1.4.0" -es6-promise@^4.0.3: - version "4.2.8" - resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a" - integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w== - -es6-promisify@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203" - integrity sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM= +end-of-stream@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.1.0.tgz#e9353258baa9108965efc41cb0ef8ade2f3cfb07" + integrity sha1-6TUyWLqpEIll78QcsO+K3i88+wc= dependencies: - es6-promise "^4.0.3" + once "~1.3.0" + +endian-reader@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/endian-reader/-/endian-reader-0.3.0.tgz#84eca436b80aed0d0639c47291338b932efe50a0" + integrity sha1-hOykNrgK7Q0GOcRykTOLky7+UKA= + +es6-error@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/es6-error/-/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d" + integrity sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg== + +escape-goat@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/escape-goat/-/escape-goat-2.1.1.tgz#1b2dc77003676c457ec760b2dc68edb648188675" + integrity sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q== escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= -escodegen@1.x.x: - version "1.13.0" - resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.13.0.tgz#c7adf9bd3f3cc675bb752f202f79a720189cab29" - integrity sha512-eYk2dCkxR07DsHA/X2hRBj0CFAZeri/LyDMc0C8JT1Hqi6JnVpMhJ7XFITbb0+yZS3lVkaPL2oCkZ3AVmeVbMw== - dependencies: - esprima "^4.0.1" - estraverse "^4.2.0" - esutils "^2.0.2" - optionator "^0.8.1" - optionalDependencies: - source-map "~0.6.1" - -esprima@3.x.x: - version "3.1.3" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633" - integrity sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM= +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== esprima@^4.0.0, esprima@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== -estraverse@^4.2.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" - integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== - -esutils@^2.0.2: - version "2.0.3" - resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" - integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== - -event-loop-spinner@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/event-loop-spinner/-/event-loop-spinner-1.1.0.tgz#96de9c70e6e2b0b3e257b0901e25e792e3c9c8d0" - integrity sha512-YVFs6dPpZIgH665kKckDktEVvSBccSYJmoZUfhNUdv5d3Xv+Q+SKF4Xis1jolq9aBzuW1ZZhQh/m/zU/TPdDhw== +event-loop-spinner@^2.0.0, event-loop-spinner@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/event-loop-spinner/-/event-loop-spinner-2.1.0.tgz#75f501d585105c6d57f174073b39af1b6b3a1567" + integrity sha512-RJ10wL8/F9AlfBgRCvYctJIXSb9XkVmSCK3GGUvPD3dJrvTjDeDT0tmhcbEC6I2NEjNM9xD38HQJ4F/f/gb4VQ== dependencies: - tslib "^1.10.0" - -execa@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777" - integrity sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c= - dependencies: - cross-spawn "^5.0.1" - get-stream "^3.0.0" - is-stream "^1.1.0" - npm-run-path "^2.0.0" - p-finally "^1.0.0" - signal-exit "^3.0.0" - strip-eof "^1.0.0" + tslib "^2.1.0" execa@^1.0.0: version "1.0.0" @@ -658,11 +1215,6 @@ execa@^1.0.0: signal-exit "^3.0.0" strip-eof "^1.0.0" -extend@~3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" - integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== - external-editor@^3.0.3: version "3.1.0" resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" @@ -672,81 +1224,97 @@ external-editor@^3.0.3: iconv-lite "^0.4.24" tmp "^0.0.33" -fast-levenshtein@~2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" - integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= +fast-glob@^3.1.1, fast-glob@^3.2.2: + version "3.2.5" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.5.tgz#7939af2a656de79a4f1901903ee8adcaa7cb9661" + integrity sha512-2DtFcgT68wiTTiwZ2hNdJfcHNke9XOfnwmBRWXhmeKM8rF0TGwmC/Qto3S7RoZKp5cilZbxzO5iTNTQsJ+EeDg== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.0" + merge2 "^1.3.0" + micromatch "^4.0.2" + picomatch "^2.2.1" -figures@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" - integrity sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI= +fastq@^1.6.0: + version "1.11.0" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.11.0.tgz#bb9fb955a07130a918eb63c1f5161cc32a5d0858" + integrity sha512-7Eczs8gIPDrVzT+EksYBcupqMyxSHXXrHOLRRxU2/DicV8789MRBRR8+Hc2uWzUupOs4YS4JzBmBxjjCVBxD/g== + dependencies: + reusify "^1.0.4" + +figures@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" + integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg== dependencies: escape-string-regexp "^1.0.5" -file-uri-to-path@1: - version "1.0.0" - resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" - integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + +follow-redirects@^1.10.0: + version "1.14.0" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.0.tgz#f5d260f95c5f8c105894491feee5dc8993b402fe" + integrity sha512-0vRwd7RKQBTt+mgu87mtYeofLFZpTas2S9zY+jIeuLJMNvudIgF52nr19q40HOwH5RrhWIPuj9puybzSJiRrVg== fs-constants@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== +fs-minipass@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" + integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== + dependencies: + minipass "^3.0.0" + fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= -ftp@~0.3.10: - version "0.3.10" - resolved "https://registry.yarnpkg.com/ftp/-/ftp-0.3.10.tgz#9197d861ad8142f3e63d5a83bfe4c59f7330885d" - integrity sha1-kZfYYa2BQvPmPVqDv+TFn3MwiF0= +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +get-intrinsic@^1.0.2: + version "1.1.1" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6" + integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q== dependencies: - readable-stream "1.1.x" - xregexp "2.0.0" + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" -get-stream@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" - integrity sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ= - -get-stream@^4.0.0: +get-stream@^4.0.0, get-stream@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== dependencies: pump "^3.0.0" -get-uri@^2.0.0: - version "2.0.4" - resolved "https://registry.yarnpkg.com/get-uri/-/get-uri-2.0.4.tgz#d4937ab819e218d4cb5ae18e4f5962bef169cc6a" - integrity sha512-v7LT/s8kVjs+Tx0ykk1I+H/rbpzkHvuIq87LmeXptcf5sNWm9uQiwjNAt94SJPA1zOlCntmnOlJvVWKmzsxG8Q== +get-stream@^5.1.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" + integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== dependencies: - data-uri-to-buffer "1" - debug "2" - extend "~3.0.2" - file-uri-to-path "1" - ftp "~0.3.10" - readable-stream "2" + pump "^3.0.0" -git-up@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/git-up/-/git-up-4.0.1.tgz#cb2ef086653640e721d2042fe3104857d89007c0" - integrity sha512-LFTZZrBlrCrGCG07/dm1aCjjpL1z9L3+5aEeI9SBhAqSc+kiA9Or1bgZhQFNppJX6h/f5McrvJt1mQXTFm6Qrw== +glob-parent@^5.1.0: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== dependencies: - is-ssh "^1.3.0" - parse-url "^5.0.0" + is-glob "^4.0.1" -git-url-parse@11.1.2: - version "11.1.2" - resolved "https://registry.yarnpkg.com/git-url-parse/-/git-url-parse-11.1.2.tgz#aff1a897c36cc93699270587bea3dbcbbb95de67" - integrity sha512-gZeLVGY8QVKMIkckncX+iCq2/L8PlwncvDFKiWkBn9EtCfYDbliRTTp6qzyQ1VMdITUfq7293zDzfpjdiGASSQ== - dependencies: - git-up "^4.0.0" - -glob@^7.1.3: +glob@^7.1.3, glob@^7.1.6: version "7.1.6" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== @@ -758,91 +1326,182 @@ glob@^7.1.3: once "^1.3.0" path-is-absolute "^1.0.0" -global-dirs@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-0.1.1.tgz#b319c0dd4607f353f3be9cca4c72fc148c49f445" - integrity sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU= +global-agent@^2.1.12: + version "2.2.0" + resolved "https://registry.yarnpkg.com/global-agent/-/global-agent-2.2.0.tgz#566331b0646e6bf79429a16877685c4a1fbf76dc" + integrity sha512-+20KpaW6DDLqhG7JDiJpD1JvNvb8ts+TNl7BPOYcURqCrXqnN1Vf+XVOrkKJAFPqfX+oEhsdzOj1hLWkBTdNJg== dependencies: - ini "^1.3.4" + boolean "^3.0.1" + core-js "^3.6.5" + es6-error "^4.1.1" + matcher "^3.0.0" + roarr "^2.15.3" + semver "^7.3.2" + serialize-error "^7.0.1" -got@^6.7.1: - version "6.7.1" - resolved "https://registry.yarnpkg.com/got/-/got-6.7.1.tgz#240cd05785a9a18e561dc1b44b41c763ef1e8db0" - integrity sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA= +global-dirs@^2.0.1: + version "2.1.0" + resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-2.1.0.tgz#e9046a49c806ff04d6c1825e196c8f0091e8df4d" + integrity sha512-MG6kdOUh/xBnyo9cJFeIKkLEc1AyFq42QTU4XiX51i2NEdxLxLWXIjEjmqKeSuKR7pAZjTqUVoT2b2huxVLgYQ== dependencies: - create-error-class "^3.0.0" + ini "1.3.7" + +globalthis@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.2.tgz#2a235d34f4d8036219f7e34929b5de9e18166b8b" + integrity sha512-ZQnSFO1la8P7auIOQECnm0sSuoMeaSq0EEdXMBFF2QJO4uNcwbyhSgG3MruWNbFTqCLmxVwGOl7LZ9kASvHdeQ== + dependencies: + define-properties "^1.1.3" + +globby@^11.0.1: + version "11.0.3" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.3.tgz#9b1f0cb523e171dd1ad8c7b2a9fb4b644b9593cb" + integrity sha512-ffdmosjA807y7+lA1NM0jELARVmYul/715xiILEjo3hBLPTcirgQNnXECn5g3mtR8TOLCVbkfua1Hpen25/Xcg== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.1.1" + ignore "^5.1.4" + merge2 "^1.3.0" + slash "^3.0.0" + +got@11.4.0: + version "11.4.0" + resolved "https://registry.yarnpkg.com/got/-/got-11.4.0.tgz#1f0910310572af4efcc6890e1dacd7affb710b39" + integrity sha512-XysJZuZNVpaQ37Oo2LV90MIkPeYITehyy1A0QzO1JwOXm8EWuEf9eeGk2XuHePvLEGnm9AVOI37bHwD6KYyBtg== + dependencies: + "@sindresorhus/is" "^2.1.1" + "@szmarczak/http-timer" "^4.0.5" + "@types/cacheable-request" "^6.0.1" + "@types/responselike" "^1.0.0" + cacheable-lookup "^5.0.3" + cacheable-request "^7.0.1" + decompress-response "^6.0.0" + http2-wrapper "^1.0.0-beta.4.5" + lowercase-keys "^2.0.0" + p-cancelable "^2.0.0" + responselike "^2.0.0" + +got@^11.7.0: + version "11.8.2" + resolved "https://registry.yarnpkg.com/got/-/got-11.8.2.tgz#7abb3959ea28c31f3576f1576c1effce23f33599" + integrity sha512-D0QywKgIe30ODs+fm8wMZiAcZjypcCodPNuMz5H9Mny7RJ+IjJ10BdmGW7OM7fHXP+O7r6ZwapQ/YQmMSvB0UQ== + dependencies: + "@sindresorhus/is" "^4.0.0" + "@szmarczak/http-timer" "^4.0.5" + "@types/cacheable-request" "^6.0.1" + "@types/responselike" "^1.0.0" + cacheable-lookup "^5.0.3" + cacheable-request "^7.0.1" + decompress-response "^6.0.0" + http2-wrapper "^1.0.0-beta.5.2" + lowercase-keys "^2.0.0" + p-cancelable "^2.0.0" + responselike "^2.0.0" + +got@^9.6.0: + version "9.6.0" + resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85" + integrity sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q== + dependencies: + "@sindresorhus/is" "^0.14.0" + "@szmarczak/http-timer" "^1.1.2" + cacheable-request "^6.0.0" + decompress-response "^3.3.0" duplexer3 "^0.1.4" - get-stream "^3.0.0" - is-redirect "^1.0.0" - is-retry-allowed "^1.0.0" - is-stream "^1.0.0" - lowercase-keys "^1.0.0" - safe-buffer "^5.0.1" - timed-out "^4.0.0" - unzip-response "^2.0.1" - url-parse-lax "^1.0.0" - -graceful-fs@^4.1.11: - version "4.2.3" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423" - integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ== + get-stream "^4.1.0" + lowercase-keys "^1.0.1" + mimic-response "^1.0.1" + p-cancelable "^1.0.0" + to-readable-stream "^1.0.0" + url-parse-lax "^3.0.0" graceful-fs@^4.1.2: version "4.1.15" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00" integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA== -graphlib@^2.1.1, graphlib@^2.1.5: - version "2.1.8" - resolved "https://registry.yarnpkg.com/graphlib/-/graphlib-2.1.8.tgz#5761d414737870084c92ec7b5dbcb0592c9d35da" - integrity sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A== +grapheme-splitter@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz#9cf3a665c6247479896834af35cf1dbb4400767e" + integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ== + +gunzip-maybe@^1.4.2: + version "1.4.2" + resolved "https://registry.yarnpkg.com/gunzip-maybe/-/gunzip-maybe-1.4.2.tgz#b913564ae3be0eda6f3de36464837a9cd94b98ac" + integrity sha512-4haO1M4mLO91PW57BMsDFf75UmwoRX0GkdD+Faw+Lr+r/OZrOCS0pIBwOL1xCKQqnQzbNFGgK2V2CpBUPeFNTw== dependencies: - lodash "^4.17.15" + browserify-zlib "^0.1.4" + is-deflate "^1.0.0" + is-gzip "^1.0.0" + peek-stream "^1.1.0" + pumpify "^1.3.3" + through2 "^2.0.3" has-flag@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= -hosted-git-info@^2.7.1: - version "2.8.5" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.5.tgz#759cfcf2c4d156ade59b0b2dfabddc42a6b9c70c" - integrity sha512-kssjab8CvdXfcXMXVcvsXum4Hwdq9XGtRD3TteMEvEbq0LXyiNQr6AprqKqfeaDXze7SxWvRxdpwE6ku7ikLkg== +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== -http-errors@1.7.3: - version "1.7.3" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06" - integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw== - dependencies: - depd "~1.1.2" - inherits "2.0.4" - setprototypeof "1.1.1" - statuses ">= 1.5.0 < 2" - toidentifier "1.0.0" +has-symbols@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423" + integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw== -http-proxy-agent@^2.1.0: +has-yarn@^2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz#e4821beef5b2142a2026bd73926fe537631c5405" - integrity sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg== - dependencies: - agent-base "4" - debug "3.1.0" + resolved "https://registry.yarnpkg.com/has-yarn/-/has-yarn-2.1.0.tgz#137e11354a7b5bf11aa5cb649cf0c6f3ff2b2e77" + integrity sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw== -https-proxy-agent@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-3.0.1.tgz#b8c286433e87602311b01c8ea34413d856a4af81" - integrity sha512-+ML2Rbh6DAuee7d07tYGEKOEi2voWPUGan+ExdPbPW6Z3svq+JCqr0v8WmKPOkz1vOVykPCBSuobe7G8GJUtVg== +has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== dependencies: - agent-base "^4.3.0" - debug "^3.1.0" + function-bind "^1.1.1" -iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@^0.4.4: +hosted-git-info@^3.0.4, hosted-git-info@^3.0.7: + version "3.0.8" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-3.0.8.tgz#6e35d4cc87af2c5f816e4cb9ce350ba87a3f370d" + integrity sha512-aXpmwoOhRBrw6X3j0h5RloK4x1OzsxMPyxqIHyNfSe2pypkVTZFpEiRoSipPEPlMrh0HW/XsjkJ5WgnCirpNUw== + dependencies: + lru-cache "^6.0.0" + +http-cache-semantics@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" + integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ== + +http2-wrapper@^1.0.0-beta.4.5, http2-wrapper@^1.0.0-beta.5.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/http2-wrapper/-/http2-wrapper-1.0.3.tgz#b8f55e0c1f25d4ebd08b3b0c2c079f9590800b3d" + integrity sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg== + dependencies: + quick-lru "^5.1.1" + resolve-alpn "^1.0.0" + +iconv-lite@^0.4.24, iconv-lite@^0.4.4: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== dependencies: safer-buffer ">= 2.1.2 < 3" +ieee754@^1.1.13: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + +ignore@^5.1.4, ignore@^5.1.8: + version "5.1.8" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57" + integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw== + immediate@~3.0.5: version "3.0.6" resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" @@ -858,6 +1517,11 @@ imurmurhash@^0.1.4: resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= +indent-string@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" + integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== + inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" @@ -871,125 +1535,134 @@ inherits@2, inherits@~2.0.3: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= -inherits@2.0.4, inherits@^2.0.3, inherits@~2.0.1: +inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== -ini@^1.3.0, ini@^1.3.4, ini@~1.3.0: +ini@1.3.7: + version "1.3.7" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.7.tgz#a09363e1911972ea16d7a8851005d84cf09a9a84" + integrity sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ== + +ini@~1.3.0: version "1.3.5" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== -inquirer@^6.2.2: - version "6.5.2" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.5.2.tgz#ad50942375d036d327ff528c08bd5fab089928ca" - integrity sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ== - dependencies: - ansi-escapes "^3.2.0" - chalk "^2.4.2" - cli-cursor "^2.1.0" - cli-width "^2.0.0" - external-editor "^3.0.3" - figures "^2.0.0" - lodash "^4.17.12" - mute-stream "0.0.7" - run-async "^2.2.0" - rxjs "^6.4.0" - string-width "^2.1.0" - strip-ansi "^5.1.0" - through "^2.3.6" +is-callable@^1.1.5: + version "1.2.3" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.3.tgz#8b1e0500b73a1d76c70487636f368e519de8db8e" + integrity sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ== -invert-kv@^1.0.0: +is-ci@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c" + integrity sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w== + dependencies: + ci-info "^2.0.0" + +is-deflate@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6" - integrity sha1-EEqOSqym09jNFXqO+L+rLXo//bY= + resolved "https://registry.yarnpkg.com/is-deflate/-/is-deflate-1.0.0.tgz#c862901c3c161fb09dac7cdc7e784f80e98f2f14" + integrity sha1-yGKQHDwWH7CdrHzcfnhPgOmPLxQ= -ip@1.1.5, ip@^1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" - integrity sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo= +is-docker@^2.0.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" + integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== -is-ci@^1.0.10: - version "1.2.1" - resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.2.1.tgz#e3779c8ee17fccf428488f6e281187f2e632841c" - integrity sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg== - dependencies: - ci-info "^1.5.0" - -is-fullwidth-code-point@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" - integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs= - dependencies: - number-is-nan "^1.0.0" +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= is-fullwidth-code-point@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= -is-installed-globally@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.1.0.tgz#0dfd98f5a9111716dd535dda6492f67bf3d25a80" - integrity sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA= - dependencies: - global-dirs "^0.1.0" - is-path-inside "^1.0.0" +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== -is-npm@^1.0.0: +is-glob@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" + integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== + dependencies: + is-extglob "^2.1.1" + +is-gzip@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-1.0.0.tgz#f2fb63a65e4905b406c86072765a1a4dc793b9f4" - integrity sha1-8vtjpl5JBbQGyGBydloaTceTufQ= + resolved "https://registry.yarnpkg.com/is-gzip/-/is-gzip-1.0.0.tgz#6ca8b07b99c77998025900e555ced8ed80879a83" + integrity sha1-bKiwe5nHeZgCWQDlVc7Y7YCHmoM= + +is-installed-globally@^0.3.1: + version "0.3.2" + resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.3.2.tgz#fd3efa79ee670d1187233182d5b0a1dd00313141" + integrity sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g== + dependencies: + global-dirs "^2.0.1" + is-path-inside "^3.0.1" + +is-interactive@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-1.0.0.tgz#cea6e6ae5c870a7b0a0004070b7b587e0252912e" + integrity sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w== + +is-npm@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-4.0.0.tgz#c90dd8380696df87a7a6d823c20d0b12bbe3c84d" + integrity sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig== + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== is-obj@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== -is-path-inside@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-1.0.1.tgz#8ef5b7de50437a3fdca6b4e865ef7aa55cb48036" - integrity sha1-jvW33lBDej/cprToZe96pVy0gDY= - dependencies: - path-is-inside "^1.0.1" +is-path-inside@^3.0.1: + version "3.0.3" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" + integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== -is-promise@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" - integrity sha1-eaKp7OfwlugPNtKy87wWwf9L8/o= - -is-redirect@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-redirect/-/is-redirect-1.0.0.tgz#1d03dded53bd8db0f30c26e4f95d36fc7c87dc24" - integrity sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ= - -is-retry-allowed@^1.0.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz#d778488bd0a4666a3be8a1482b9f2baafedea8b4" - integrity sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg== - -is-ssh@^1.3.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/is-ssh/-/is-ssh-1.3.1.tgz#f349a8cadd24e65298037a522cf7520f2e81a0f3" - integrity sha512-0eRIASHZt1E68/ixClI8bp2YK2wmBPVWEismTs6M+M099jKgrzl/3E976zIbImSIob48N2/XGe9y7ZiYdImSlg== - dependencies: - protocols "^1.1.0" - -is-stream@^1.0.0, is-stream@^1.1.0: +is-stream@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= -is-wsl@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" - integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0= +is-typedarray@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= -isarray@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" - integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8= +is-unicode-supported@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" + integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== + +is-wsl@^2.1.1: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" + integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== + dependencies: + is-docker "^2.0.0" + +is-yarn-global@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/is-yarn-global/-/is-yarn-global-0.3.0.tgz#d502d3382590ea3004893746754c89139973e232" + integrity sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw== + +is@^3.2.1: + version "3.3.0" + resolved "https://registry.yarnpkg.com/is/-/is-3.3.0.tgz#61cff6dd3c4193db94a3d62582072b44e5645d79" + integrity sha512-nW24QBoPcFGGHJGUwnfpI7Yc5CdqWNdsyHQszVE/z2pKHXzh7FZ5GWhJqSyaQ9wMkQnsTx+kAI8bHlCX4tKdbg== isarray@~1.0.0: version "1.0.0" @@ -1001,6 +1674,14 @@ isexe@^2.0.0: resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= +js-yaml@^3.10.0: + version "3.14.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" + integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + js-yaml@^3.13.1: version "3.13.1" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" @@ -1009,37 +1690,72 @@ js-yaml@^3.13.1: argparse "^1.0.7" esprima "^4.0.0" -jszip@^3.1.5: - version "3.2.2" - resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.2.2.tgz#b143816df7e106a9597a94c77493385adca5bd1d" - integrity sha512-NmKajvAFQpbg3taXQXr/ccS2wcucR1AZ+NtyWp2Nq7HHVsXhcJFR8p0Baf32C2yVvBylFWVeKf+WI2AnvlPhpA== +json-buffer@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898" + integrity sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg= + +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + +json-file-plus@^3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/json-file-plus/-/json-file-plus-3.3.1.tgz#f4363806b82819ff8803d83d539d6a9edd2a5258" + integrity sha512-wo0q1UuiV5NsDPQDup1Km8IwEeqe+olr8tkWxeJq9Bjtcp7DZ0l+yrg28fSC3DEtrE311mhTZ54QGS6oiqnZEA== + dependencies: + is "^3.2.1" + node.extend "^2.0.0" + object.assign "^4.1.0" + promiseback "^2.0.2" + safer-buffer "^2.0.2" + +json-stringify-safe@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= + +jszip@3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.4.0.tgz#1a69421fa5f0bb9bc222a46bca88182fba075350" + integrity sha512-gZAOYuPl4EhPTXT0GjhI3o+ZAz3su6EhLrKUoAivcKqyqC7laS5JEv4XWZND9BgcDcF83vI85yGbDmDR6UhrIg== dependencies: lie "~3.3.0" pako "~1.0.2" readable-stream "~2.3.6" set-immediate-shim "~1.0.1" -latest-version@^3.1.0: +jszip@^3.2.2: + version "3.6.0" + resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.6.0.tgz#839b72812e3f97819cc13ac4134ffced95dd6af9" + integrity sha512-jgnQoG9LKnWO3mnVNBnfhkh0QknICd1FGSrXcgrl67zioyJ4wgx25o9ZqwNtrROSflGBCGYnJfjrIyRIby1OoQ== + dependencies: + lie "~3.3.0" + pako "~1.0.2" + readable-stream "~2.3.6" + set-immediate-shim "~1.0.1" + +keyv@^3.0.0: version "3.1.0" - resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-3.1.0.tgz#a205383fea322b33b5ae3b18abee0dc2f356ee15" - integrity sha1-ogU4P+oyKzO1rjsYq+4NwvNW7hU= + resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9" + integrity sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA== dependencies: - package-json "^4.0.0" + json-buffer "3.0.0" -lcid@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835" - integrity sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU= +keyv@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.0.3.tgz#4f3aa98de254803cafcd2896734108daa35e4254" + integrity sha512-zdGa2TOpSZPq5mU6iowDARnMBZgtCqJ11dJROFi6tg6kTn4nuUdU09lFyLFSaHrWqpIJ+EBq4E8/Dc0Vx5vLdA== dependencies: - invert-kv "^1.0.0" + json-buffer "3.0.1" -levn@~0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" - integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4= +latest-version@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-5.1.0.tgz#119dfe908fe38d15dfa43ecd13fa12ec8832face" + integrity sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA== dependencies: - prelude-ls "~1.1.2" - type-check "~0.3.2" + package-json "^6.3.0" lie@~3.3.0: version "3.3.0" @@ -1058,6 +1774,16 @@ lodash.assignin@^4.2.0: resolved "https://registry.yarnpkg.com/lodash.assignin/-/lodash.assignin-4.2.0.tgz#ba8df5fb841eb0a3e8044232b0e263a8dc6a28a2" integrity sha1-uo31+4QesKPoBEIysOJjqNxqKKI= +lodash.camelcase@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" + integrity sha1-soqmKIorn8ZRA1x3EfZathkDMaY= + +lodash.chunk@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.chunk/-/lodash.chunk-4.2.0.tgz#66e5ce1f76ed27b4303d8c6512e8d1216e8106bc" + integrity sha1-ZuXOH3btJ7QwPYxlEujRIW6BBrw= + lodash.clone@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.clone/-/lodash.clone-4.5.0.tgz#195870450f5a13192478df4bc3d23d2dea1907b6" @@ -1068,32 +1794,235 @@ lodash.clonedeep@^4.3.0, lodash.clonedeep@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8= +lodash.constant@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/lodash.constant/-/lodash.constant-3.0.0.tgz#bfe05cce7e515b3128925d6362138420bd624910" + integrity sha1-v+Bczn5RWzEokl1jYhOEIL1iSRA= + +lodash.defaults@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c" + integrity sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw= + +lodash.endswith@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/lodash.endswith/-/lodash.endswith-4.2.1.tgz#fed59ac1738ed3e236edd7064ec456448b37bc09" + integrity sha1-/tWawXOO0+I27dcGTsRWRIs3vAk= + +lodash.filter@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.filter/-/lodash.filter-4.6.0.tgz#668b1d4981603ae1cc5a6fa760143e480b4c4ace" + integrity sha1-ZosdSYFgOuHMWm+nYBQ+SAtMSs4= + +lodash.find@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.find/-/lodash.find-4.6.0.tgz#cb0704d47ab71789ffa0de8b97dd926fb88b13b1" + integrity sha1-ywcE1Hq3F4n/oN6Ll92Sb7iLE7E= + +lodash.findindex@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.findindex/-/lodash.findindex-4.6.0.tgz#a3245dee61fb9b6e0624b535125624bb69c11106" + integrity sha1-oyRd7mH7m24GJLU1ElYku2nBEQY= + +lodash.findkey@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.findkey/-/lodash.findkey-4.6.0.tgz#83058e903b51cbb759d09ccf546dea3ea39c4718" + integrity sha1-gwWOkDtRy7dZ0JzPVG3qPqOcRxg= + +lodash.flatmap@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.flatmap/-/lodash.flatmap-4.5.0.tgz#ef8cbf408f6e48268663345305c6acc0b778702e" + integrity sha1-74y/QI9uSCaGYzRTBcaswLd4cC4= + lodash.flatten@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f" integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8= +lodash.flattendeep@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2" + integrity sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI= + +lodash.foreach@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.foreach/-/lodash.foreach-4.5.0.tgz#1a6a35eace401280c7f06dddec35165ab27e3e53" + integrity sha1-Gmo16s5AEoDH8G3d7DUWWrJ+PlM= + lodash.get@^4.4.2: version "4.4.2" resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk= +lodash.groupby@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.groupby/-/lodash.groupby-4.6.0.tgz#0b08a1dcf68397c397855c3239783832df7403d1" + integrity sha1-Cwih3PaDl8OXhVwyOXg4Mt90A9E= + +lodash.has@^4.5.2: + version "4.5.2" + resolved "https://registry.yarnpkg.com/lodash.has/-/lodash.has-4.5.2.tgz#d19f4dc1095058cccbe2b0cdf4ee0fe4aa37c862" + integrity sha1-0Z9NwQlQWMzL4rDN9O4P5Ko3yGI= + +lodash.invert@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.invert/-/lodash.invert-4.3.0.tgz#8ffe20d4b616f56bea8f1aa0c6ebd80dcf742aee" + integrity sha1-j/4g1LYW9WvqjxqgxuvYDc90Ku4= + +lodash.isboolean@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" + integrity sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY= + +lodash.isempty@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.isempty/-/lodash.isempty-4.4.0.tgz#6f86cbedd8be4ec987be9aaf33c9684db1b31e7e" + integrity sha1-b4bL7di+TsmHvpqvM8loTbGzHn4= + +lodash.isequal@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" + integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA= + +lodash.isfunction@^3.0.9: + version "3.0.9" + resolved "https://registry.yarnpkg.com/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz#06de25df4db327ac931981d1bdb067e5af68d051" + integrity sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw== + +lodash.isnumber@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" + integrity sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w= + +lodash.isobject@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/lodash.isobject/-/lodash.isobject-3.0.2.tgz#3c8fb8d5b5bf4bf90ae06e14f2a530a4ed935e1d" + integrity sha1-PI+41bW/S/kK4G4U8qUwpO2TXh0= + +lodash.isplainobject@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" + integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs= + +lodash.isstring@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" + integrity sha1-1SfftUVuynzJu5XV2ur4i6VKVFE= + +lodash.isundefined@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/lodash.isundefined/-/lodash.isundefined-3.0.1.tgz#23ef3d9535565203a66cefd5b830f848911afb48" + integrity sha1-I+89lTVWUgOmbO/VuDD4SJEa+0g= + +lodash.keys@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-4.2.0.tgz#a08602ac12e4fb83f91fc1fb7a360a4d9ba35205" + integrity sha1-oIYCrBLk+4P5H8H7ejYKTZujUgU= + +lodash.last@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/lodash.last/-/lodash.last-3.0.0.tgz#242f663112dd4c6e63728c60a3c909d1bdadbd4c" + integrity sha1-JC9mMRLdTG5jcoxgo8kJ0b2tvUw= + +lodash.map@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.map/-/lodash.map-4.6.0.tgz#771ec7839e3473d9c4cde28b19394c3562f4f6d3" + integrity sha1-dx7Hg540c9nEzeKLGTlMNWL09tM= + +lodash.merge@^4.6.2: + version "4.6.2" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + +lodash.omit@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.omit/-/lodash.omit-4.5.0.tgz#6eb19ae5a1ee1dd9df0b969e66ce0b7fa30b5e60" + integrity sha1-brGa5aHuHdnfC5aeZs4Lf6MLXmA= + +lodash.orderby@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.orderby/-/lodash.orderby-4.6.0.tgz#e697f04ce5d78522f54d9338b32b81a3393e4eb3" + integrity sha1-5pfwTOXXhSL1TZM4syuBozk+TrM= + +lodash.reduce@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.reduce/-/lodash.reduce-4.6.0.tgz#f1ab6b839299ad48f784abbf476596f03b914d3b" + integrity sha1-8atrg5KZrUj3hKu/R2WW8DuRTTs= + lodash.set@^4.3.2: version "4.3.2" resolved "https://registry.yarnpkg.com/lodash.set/-/lodash.set-4.3.2.tgz#d8757b1da807dde24816b0d6a84bea1a76230b23" integrity sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM= -lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.7.14: +lodash.size@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.size/-/lodash.size-4.2.0.tgz#71fe75ed3eabdb2bcb73a1b0b4f51c392ee27b86" + integrity sha1-cf517T6r2yvLc6GwtPUcOS7ie4Y= + +lodash.sortby@^4.7.0: + version "4.7.0" + resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" + integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg= + +lodash.sum@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/lodash.sum/-/lodash.sum-4.0.2.tgz#ad90e397965d803d4f1ff7aa5b2d0197f3b4637b" + integrity sha1-rZDjl5ZdgD1PH/eqWy0Bl/O0Y3s= + +lodash.topairs@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.topairs/-/lodash.topairs-4.3.0.tgz#3b6deaa37d60fb116713c46c5f17ea190ec48d64" + integrity sha1-O23qo31g+xFnE8RsXxfqGQ7EjWQ= + +lodash.transform@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.transform/-/lodash.transform-4.6.0.tgz#12306422f63324aed8483d3f38332b5f670547a0" + integrity sha1-EjBkIvYzJK7YSD0/ODMrX2cFR6A= + +lodash.union@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.union/-/lodash.union-4.6.0.tgz#48bb5088409f16f1821666641c44dd1aaae3cd88" + integrity sha1-SLtQiECfFvGCFmZkHETdGqrjzYg= + +lodash.uniq@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" + integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= + +lodash.upperfirst@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz#1365edf431480481ef0d1c68957a5ed99d49f7ce" + integrity sha1-E2Xt9DFIBIHvDRxolXpe2Z1J984= + +lodash.values@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.values/-/lodash.values-4.3.0.tgz#a3a6c2b0ebecc5c2cba1c17e6e620fe81b53d347" + integrity sha1-o6bCsOvsxcLLocF+bmIP6BtT00c= + +lodash@^4.17.15: version "4.17.19" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b" integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ== -lowercase-keys@^1.0.0: +log-symbols@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" + integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== + dependencies: + chalk "^4.1.0" + is-unicode-supported "^0.1.0" + +lowercase-keys@^1.0.0, lowercase-keys@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA== -lru-cache@^4.0.0, lru-cache@^4.0.1: +lowercase-keys@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" + integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== + +lru-cache@^4.0.0: version "4.1.5" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd" integrity sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g== @@ -1108,22 +2037,67 @@ lru-cache@^5.1.1: dependencies: yallist "^3.0.2" +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + macos-release@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/macos-release/-/macos-release-2.3.0.tgz#eb1930b036c0800adebccd5f17bc4c12de8bb71f" integrity sha512-OHhSbtcviqMPt7yfw5ef5aghS2jzFVKEFyCJndQt2YpSQ9qRVSEv2axSJI1paVThEu+FFGs584h/1YhxjVqajA== -make-dir@^1.0.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c" - integrity sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ== +make-dir@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" + integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== dependencies: - pify "^3.0.0" + semver "^6.0.0" -mimic-fn@^1.0.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" - integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ== +matcher@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/matcher/-/matcher-3.0.0.tgz#bd9060f4c5b70aa8041ccc6f80368760994f30ca" + integrity sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng== + dependencies: + escape-string-regexp "^4.0.0" + +merge2@^1.3.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +micromatch@4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259" + integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q== + dependencies: + braces "^3.0.1" + picomatch "^2.0.5" + +micromatch@^4.0.2: + version "4.0.4" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9" + integrity sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg== + dependencies: + braces "^3.0.1" + picomatch "^2.2.3" + +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +mimic-response@^1.0.0, mimic-response@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" + integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== + +mimic-response@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" + integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== minimatch@^3.0.4: version "3.0.4" @@ -1132,59 +2106,79 @@ minimatch@^3.0.4: dependencies: brace-expansion "^1.1.7" -minimist@^1.2.0: +minimist@^1.2.0, minimist@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== -ms@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= +minipass@^3.0.0: + version "3.1.3" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.3.tgz#7d42ff1f39635482e15f9cdb53184deebd5815fd" + integrity sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg== + dependencies: + yallist "^4.0.0" + +minizlib@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" + integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== + dependencies: + minipass "^3.0.0" + yallist "^4.0.0" + +mkdirp@^0.5.1: + version "0.5.5" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" + integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== + dependencies: + minimist "^1.2.5" + +mkdirp@^1.0.3, mkdirp@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== ms@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== -mute-stream@0.0.7: - version "0.0.7" - resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" - integrity sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s= +mute-stream@0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" + integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== -nconf@^0.10.0: - version "0.10.0" - resolved "https://registry.yarnpkg.com/nconf/-/nconf-0.10.0.tgz#da1285ee95d0a922ca6cee75adcf861f48205ad2" - integrity sha512-fKiXMQrpP7CYWJQzKkPPx9hPgmq+YLDyxcG9N8RpiE9FoCkCbzD0NyW0YhE3xn3Aupe7nnDeIx4PFzYehpHT9Q== - dependencies: - async "^1.4.0" - ini "^1.3.0" - secure-keys "^1.0.0" - yargs "^3.19.0" - -needle@^2.2.4, needle@^2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/needle/-/needle-2.4.0.tgz#6833e74975c444642590e15a750288c5f939b57c" - integrity sha512-4Hnwzr3mi5L97hMYeNl8wRW/Onhy4nUKR/lVemJ8gJedxxUyBLm9kkrDColJvoSfwi0jCNhD+xCdOtiGDQiRZg== +needle@2.6.0, needle@^2.3.3, needle@^2.5.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/needle/-/needle-2.6.0.tgz#24dbb55f2509e2324b4a99d61f413982013ccdbe" + integrity sha512-KKYdza4heMsEfSWD7VPUIz3zX2XDwOyX2d+geb4vrERZMT5RMU6ujjaD+I5Yr54uZxQ2w6XRTAhHBbSCyovZBg== dependencies: debug "^3.2.6" iconv-lite "^0.4.4" sax "^1.2.4" -netmask@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/netmask/-/netmask-1.0.6.tgz#20297e89d86f6f6400f250d9f4f6b4c1945fcd35" - integrity sha1-ICl+idhvb2QA8lDZ9Pa0wZRfzTU= - nice-try@^1.0.4: version "1.0.5" resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== -normalize-url@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-3.3.0.tgz#b2e1c4dc4f7c6d57743df733a4f5978d18650559" - integrity sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg== +node.extend@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/node.extend/-/node.extend-2.0.2.tgz#b4404525494acc99740f3703c496b7d5182cc6cc" + integrity sha512-pDT4Dchl94/+kkgdwyS2PauDFjZG0Hk0IcHIB+LkW27HLDtdoeMxHTxZh39DYbPP8UflWXWj9JcdDozF+YDOpQ== + dependencies: + has "^1.0.3" + is "^3.2.1" + +normalize-url@^4.1.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.0.tgz#453354087e6ca96957bd8f5baf753f5982142129" + integrity sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ== npm-run-path@^2.0.0: version "2.0.2" @@ -1193,15 +2187,25 @@ npm-run-path@^2.0.0: dependencies: path-key "^2.0.0" -number-is-nan@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" - integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= +object-hash@^2.0.3: + version "2.1.1" + resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-2.1.1.tgz#9447d0279b4fcf80cff3259bf66a1dc73afabe09" + integrity sha512-VOJmgmS+7wvXf8CjbQmimtCnEx3IAoLxI3fp2fbWehxrWBcAQFbk+vcwb6vzR0VZv/eNCJ/27j151ZTwqW/JeQ== -object-hash@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-1.3.1.tgz#fde452098a951cb145f039bb7d455449ddc126df" - integrity sha512-OSuu/pU4ENM9kmREg0BdNrUDIl1heYa4mBZacJc+vVWz4GtAwu7jO8s4AIt2aGRUTqxykpWzI3Oqnsm13tTMDA== +object-keys@^1.0.12, object-keys@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + +object.assign@^4.1.0: + version "4.1.2" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940" + integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + has-symbols "^1.0.1" + object-keys "^1.1.1" once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" @@ -1210,43 +2214,46 @@ once@^1.3.0, once@^1.3.1, once@^1.4.0: dependencies: wrappy "1" -onetime@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4" - integrity sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ= +once@~1.3.0: + version "1.3.3" + resolved "https://registry.yarnpkg.com/once/-/once-1.3.3.tgz#b2e261557ce4c314ec8304f3fa82663e4297ca20" + integrity sha1-suJhVXzkwxTsgwTz+oJmPkKXyiA= dependencies: - mimic-fn "^1.0.0" + wrappy "1" + +onetime@^5.1.0: + version "5.1.2" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" onscan.js@^1.5.2: version "1.5.2" resolved "https://registry.yarnpkg.com/onscan.js/-/onscan.js-1.5.2.tgz#14ed636e5f4c3f0a78bacbf9a505dad3140ee341" integrity sha512-9oGYy2gXYRjvXO9GYqqVca0VuCTAmWhbmX3egBSBP13rXiMNb+dKPJzKFEeECGqPBpf0m40Zoo+GUQ7eCackdw== -opn@^5.5.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/opn/-/opn-5.5.0.tgz#fc7164fab56d235904c51c3b27da6758ca3b9bfc" - integrity sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA== +open@^7.0.3: + version "7.4.2" + resolved "https://registry.yarnpkg.com/open/-/open-7.4.2.tgz#b8147e26dcf3e426316c730089fd71edd29c2321" + integrity sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q== dependencies: - is-wsl "^1.1.0" + is-docker "^2.0.0" + is-wsl "^2.1.1" -optionator@^0.8.1: - version "0.8.3" - resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" - integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== +ora@5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/ora/-/ora-5.3.0.tgz#fb832899d3a1372fe71c8b2c534bbfe74961bb6f" + integrity sha512-zAKMgGXUim0Jyd6CXK9lraBnD3H5yPGBPPOkC23a2BG6hsm4Zu6OQSjQuEtV0BHDf4aKHcUFvJiGRrFuW3MG8g== dependencies: - deep-is "~0.1.3" - fast-levenshtein "~2.0.6" - levn "~0.3.0" - prelude-ls "~1.1.2" - type-check "~0.3.2" - word-wrap "~1.2.3" - -os-locale@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-1.4.0.tgz#20f9f17ae29ed345e8bde583b13d2009803c14d9" - integrity sha1-IPnxeuKe00XoveWDsT0gCYA8FNk= - dependencies: - lcid "^1.0.0" + bl "^4.0.3" + chalk "^4.1.0" + cli-cursor "^3.1.0" + cli-spinners "^2.5.0" + is-interactive "^1.0.0" + log-symbols "^4.0.0" + strip-ansi "^6.0.0" + wcwidth "^1.0.1" os-name@^3.0.0: version "3.1.0" @@ -1261,134 +2268,164 @@ os-tmpdir@~1.0.2: resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= +p-cancelable@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc" + integrity sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw== + +p-cancelable@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-2.1.0.tgz#4d51c3b91f483d02a0d300765321fca393d758dd" + integrity sha512-HAZyB3ZodPo+BDpb4/Iu7Jv4P6cSazBz9ZM0ChhEXp70scx834aWCEjQRwgt41UzzejUAPdbqqONfRWTPYrPAQ== + p-finally@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= +p-limit@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + p-map@2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/p-map/-/p-map-2.1.0.tgz#310928feef9c9ecc65b68b17693018a665cea175" integrity sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw== -pac-proxy-agent@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/pac-proxy-agent/-/pac-proxy-agent-3.0.1.tgz#115b1e58f92576cac2eba718593ca7b0e37de2ad" - integrity sha512-44DUg21G/liUZ48dJpUSjZnFfZro/0K5JTyFYLBcmh9+T6Ooi4/i4efwUiEy0+4oQusCBqWdhv16XohIj1GqnQ== +p-map@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" + integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== dependencies: - agent-base "^4.2.0" - debug "^4.1.1" - get-uri "^2.0.0" - http-proxy-agent "^2.1.0" - https-proxy-agent "^3.0.0" - pac-resolver "^3.0.0" - raw-body "^2.2.0" - socks-proxy-agent "^4.0.1" + aggregate-error "^3.0.0" -pac-resolver@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/pac-resolver/-/pac-resolver-3.0.0.tgz#6aea30787db0a891704deb7800a722a7615a6f26" - integrity sha512-tcc38bsjuE3XZ5+4vP96OfhOugrX+JcnpUbhfuc4LuXBLQhoTthOstZeoQJBDnQUDYzYmdImKsbz0xSl1/9qeA== - dependencies: - co "^4.6.0" - degenerator "^1.0.4" - ip "^1.1.5" - netmask "^1.0.6" - thunkify "^2.1.2" +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== -package-json@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/package-json/-/package-json-4.0.1.tgz#8869a0401253661c4c4ca3da6c2121ed555f5eed" - integrity sha1-iGmgQBJTZhxMTKPabCEh7VVfXu0= +package-json@^6.3.0: + version "6.5.0" + resolved "https://registry.yarnpkg.com/package-json/-/package-json-6.5.0.tgz#6feedaca35e75725876d0b0e64974697fed145b0" + integrity sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ== dependencies: - got "^6.7.1" - registry-auth-token "^3.0.1" - registry-url "^3.0.3" - semver "^5.1.0" + got "^9.6.0" + registry-auth-token "^4.0.0" + registry-url "^5.0.0" + semver "^6.2.0" + +pako@~0.2.0: + version "0.2.9" + resolved "https://registry.yarnpkg.com/pako/-/pako-0.2.9.tgz#f3f7522f4ef782348da8161bad9ecfd51bf83a75" + integrity sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU= pako@~1.0.2: version "1.0.11" resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== -parse-path@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/parse-path/-/parse-path-4.0.1.tgz#0ec769704949778cb3b8eda5e994c32073a1adff" - integrity sha512-d7yhga0Oc+PwNXDvQ0Jv1BuWkLVPXcAoQ/WREgd6vNNoKYaW52KI+RdOFjI63wjkmps9yUE8VS4veP+AgpQ/hA== +parse-link-header@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parse-link-header/-/parse-link-header-1.0.1.tgz#bedfe0d2118aeb84be75e7b025419ec8a61140a7" + integrity sha1-vt/g0hGK64S+deewJUGeyKYRQKc= dependencies: - is-ssh "^1.3.0" - protocols "^1.4.0" - -parse-url@^5.0.0: - version "5.0.1" - resolved "https://registry.yarnpkg.com/parse-url/-/parse-url-5.0.1.tgz#99c4084fc11be14141efa41b3d117a96fcb9527f" - integrity sha512-flNUPP27r3vJpROi0/R3/2efgKkyXqnXwyP1KQ2U0SfFRgdizOdWfvrrvJg1LuOoxs7GQhmxJlq23IpQ/BkByg== - dependencies: - is-ssh "^1.3.0" - normalize-url "^3.3.0" - parse-path "^4.0.0" - protocols "^1.4.0" + xtend "~4.0.1" path-is-absolute@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= -path-is-inside@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" - integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM= - path-key@^2.0.0, path-key@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= -pify@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" - integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY= +path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== -prelude-ls@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" - integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== -prepend-http@^1.0.1: - version "1.0.4" - resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" - integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw= +peek-stream@^1.1.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/peek-stream/-/peek-stream-1.1.3.tgz#3b35d84b7ccbbd262fff31dc10da56856ead6d67" + integrity sha512-FhJ+YbOSBb9/rIl2ZeE/QHEsWn7PqNYt8ARAY3kIgNGOk13g9FGyIY6JIl/xB/3TFRVoTv5as0l11weORrTekA== + dependencies: + buffer-from "^1.0.0" + duplexify "^3.5.0" + through2 "^2.0.3" + +picomatch@^2.0.5, picomatch@^2.2.1, picomatch@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.3.tgz#465547f359ccc206d3c48e46a1bcb89bf7ee619d" + integrity sha512-KpELjfwcCDUb9PeigTs2mBJzXUPzAuP2oPcA989He8Rte0+YUAjw1JVedDhuTKPkHjSYzMN3npC9luThGYEKdg== + +pluralize@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-7.0.0.tgz#298b89df8b93b0221dbf421ad2b1b1ea23fc6777" + integrity sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow== + +prepend-http@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" + integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc= + +pretty-bytes@^5.1.0: + version "5.6.0" + resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb" + integrity sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg== process-nextick-args@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa" integrity sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw== -"promise@>=3.2 <8": +progress@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" + integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== + +promise-deferred@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/promise-deferred/-/promise-deferred-2.0.3.tgz#b99c9588820798501862a593d49cece51d06fd7f" + integrity sha512-n10XaoznCzLfyPFOlEE8iurezHpxrYzyjgq/1eW9Wk1gJwur/N7BdBmjJYJpqMeMcXK4wEbzo2EvZQcqjYcKUQ== + dependencies: + promise "^7.3.1" + +promise-fs@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/promise-fs/-/promise-fs-2.1.1.tgz#0b725a592c165ff16157d1f13640ba390637e557" + integrity sha512-43p7e4QzAQ3w6eyN0+gbBL7jXiZFWLWYITg9wIObqkBySu/a5K1EDcQ/S6UyB/bmiZWDA4NjTbcopKLTaKcGSw== + dependencies: + "@octetstream/promisify" "2.0.2" + +promise-queue@^2.2.5: + version "2.2.5" + resolved "https://registry.yarnpkg.com/promise-queue/-/promise-queue-2.2.5.tgz#2f6f5f7c0f6d08109e967659c79b88a9ed5e93b4" + integrity sha1-L29ffA9tCBCelnZZx5uIqe1ek7Q= + +"promise@>=3.2 <8", promise@^7.3.1: version "7.3.1" resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" integrity sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg== dependencies: asap "~2.0.3" -protocols@^1.1.0, protocols@^1.4.0: - version "1.4.7" - resolved "https://registry.yarnpkg.com/protocols/-/protocols-1.4.7.tgz#95f788a4f0e979b291ffefcf5636ad113d037d32" - integrity sha512-Fx65lf9/YDn3hUX08XUc0J8rSux36rEsyiv21ZGUC1mOyeM3lTRpZLcrm8aAolzS4itwVfm7TAPyxC2E5zd6xg== - -proxy-agent@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/proxy-agent/-/proxy-agent-3.1.1.tgz#7e04e06bf36afa624a1540be247b47c970bd3014" - integrity sha512-WudaR0eTsDx33O3EJE16PjBRZWcX8GqCEeERw1W3hZJgH/F2a46g7jty6UGty6NeJ4CKQy8ds2CJPMiyeqaTvw== +promiseback@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/promiseback/-/promiseback-2.0.3.tgz#bd468d86930e8cd44bfc3292de9a6fbafb6378e6" + integrity sha512-VZXdCwS0ppVNTIRfNsCvVwJAaP2b+pxQF7lM8DMWfmpNWyTxB6O5YNbzs+8z0ki/KIBHKHk308NTIl4kJUem3w== dependencies: - agent-base "^4.2.0" - debug "4" - http-proxy-agent "^2.1.0" - https-proxy-agent "^3.0.0" - lru-cache "^5.1.1" - pac-proxy-agent "^3.0.1" - proxy-from-env "^1.0.0" - socks-proxy-agent "^4.0.1" + is-callable "^1.1.5" + promise-deferred "^2.0.3" proxy-from-env@^1.0.0: version "1.0.0" @@ -1400,6 +2437,14 @@ pseudomap@^1.0.2: resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM= +pump@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909" + integrity sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + pump@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" @@ -1408,17 +2453,40 @@ pump@^3.0.0: end-of-stream "^1.1.0" once "^1.3.1" -raw-body@^2.2.0: - version "2.4.1" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.1.tgz#30ac82f98bb5ae8c152e67149dac8d55153b168c" - integrity sha512-9WmIKF6mkvA0SLmA2Knm9+qj89e+j1zqgyn8aXGd7+nAduPoqgI9lO57SAZNn/Byzo5P7JhXTyg9PzaJbH73bA== +pumpify@^1.3.3: + version "1.5.1" + resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.5.1.tgz#36513be246ab27570b1a374a5ce278bfd74370ce" + integrity sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ== dependencies: - bytes "3.1.0" - http-errors "1.7.3" - iconv-lite "0.4.24" - unpipe "1.0.0" + duplexify "^3.6.0" + inherits "^2.0.3" + pump "^2.0.0" -rc@^1.0.1, rc@^1.1.6: +pupa@^2.0.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/pupa/-/pupa-2.1.1.tgz#f5e8fd4afc2c5d97828faa523549ed8744a20d62" + integrity sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A== + dependencies: + escape-goat "^2.0.0" + +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + +queue@^6.0.1: + version "6.0.2" + resolved "https://registry.yarnpkg.com/queue/-/queue-6.0.2.tgz#b91525283e2315c7553d2efa18d83e76432fed65" + integrity sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA== + dependencies: + inherits "~2.0.3" + +quick-lru@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932" + integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA== + +rc@^1.2.8: version "1.2.8" resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== @@ -1428,17 +2496,7 @@ rc@^1.0.1, rc@^1.1.6: minimist "^1.2.0" strip-json-comments "~2.0.1" -readable-stream@1.1.x: - version "1.1.14" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" - integrity sha1-fPTFTvZI44EwhMY23SB54WbAgdk= - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.1" - isarray "0.0.1" - string_decoder "~0.10.x" - -readable-stream@2, readable-stream@~2.3.6: +readable-stream@^2.0.0, readable-stream@~2.3.6: version "2.3.7" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== @@ -1451,7 +2509,7 @@ readable-stream@2, readable-stream@~2.3.6: string_decoder "~1.1.1" util-deprecate "~1.0.1" -readable-stream@^3.0.1, readable-stream@^3.1.1: +readable-stream@^3.0.1, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.5.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== @@ -1460,29 +2518,52 @@ readable-stream@^3.0.1, readable-stream@^3.1.1: string_decoder "^1.1.1" util-deprecate "^1.0.1" -registry-auth-token@^3.0.1: - version "3.4.0" - resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-3.4.0.tgz#d7446815433f5d5ed6431cd5dca21048f66b397e" - integrity sha512-4LM6Fw8eBQdwMYcES4yTnn2TqIasbXuwDx3um+QRs7S55aMKCBKBxvPXl2RiUjHwuJLTyYfxSpmfSAjQpcuP+A== +registry-auth-token@^4.0.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-4.2.1.tgz#6d7b4006441918972ccd5fedcd41dc322c79b250" + integrity sha512-6gkSb4U6aWJB4SF2ZvLb76yCBjcvufXBqvvEx1HbmKPkutswjW1xNVRY0+daljIYRbogN7O0etYSlbiaEQyMyw== dependencies: - rc "^1.1.6" - safe-buffer "^5.0.1" + rc "^1.2.8" -registry-url@^3.0.3: - version "3.1.0" - resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-3.1.0.tgz#3d4ef870f73dde1d77f0cf9a381432444e174942" - integrity sha1-PU74cPc93h138M+aOBQyRE4XSUI= +registry-url@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-5.1.0.tgz#e98334b50d5434b81136b44ec638d9c2009c5009" + integrity sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw== dependencies: - rc "^1.0.1" + rc "^1.2.8" -restore-cursor@^2.0.0: +resolve-alpn@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/resolve-alpn/-/resolve-alpn-1.1.2.tgz#30b60cfbb0c0b8dc897940fe13fe255afcdd4d28" + integrity sha512-8OyfzhAtA32LVUsJSke3auIyINcwdh5l3cvYKdKO0nvsYSKuiLfTM5i78PJswFPT8y6cPW+L1v6/hE95chcpDA== + +responselike@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7" + integrity sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec= + dependencies: + lowercase-keys "^1.0.0" + +responselike@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" - integrity sha1-n37ih/gv0ybU/RYpI9YhKe7g368= + resolved "https://registry.yarnpkg.com/responselike/-/responselike-2.0.0.tgz#26391bcc3174f750f9a79eacc40a12a5c42d7723" + integrity sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw== dependencies: - onetime "^2.0.0" + lowercase-keys "^2.0.0" + +restore-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" + integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== + dependencies: + onetime "^5.1.0" signal-exit "^3.0.2" +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + rimraf@^2.6.3: version "2.7.1" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" @@ -1490,21 +2571,45 @@ rimraf@^2.6.3: dependencies: glob "^7.1.3" -run-async@^2.2.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0" - integrity sha1-A3GrSuC91yDUFm19/aZP96RFpsA= +rimraf@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== dependencies: - is-promise "^2.1.0" + glob "^7.1.3" -rxjs@^6.4.0: - version "6.5.4" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.4.tgz#e0777fe0d184cec7872df147f303572d414e211c" - integrity sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q== +roarr@^2.15.3: + version "2.15.4" + resolved "https://registry.yarnpkg.com/roarr/-/roarr-2.15.4.tgz#f5fe795b7b838ccfe35dc608e0282b9eba2e7afd" + integrity sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A== + dependencies: + boolean "^3.0.1" + detect-node "^2.0.4" + globalthis "^1.0.1" + json-stringify-safe "^5.0.1" + semver-compare "^1.0.0" + sprintf-js "^1.1.2" + +run-async@^2.4.0: + version "2.4.1" + resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" + integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ== + +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + +rxjs@^6.6.0: + version "6.6.7" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9" + integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ== dependencies: tslib "^1.9.0" -safe-buffer@^5.0.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1: +safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== @@ -1514,7 +2619,7 @@ safe-buffer@~5.2.0: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== -"safer-buffer@>= 2.1.2 < 3": +"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@~2.1.0: version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== @@ -1524,43 +2629,52 @@ sax@>=0.6.0, sax@^1.2.4: resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== -secure-keys@^1.0.0: +semver-compare@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/secure-keys/-/secure-keys-1.0.0.tgz#f0c82d98a3b139a8776a8808050b824431087fca" - integrity sha1-8MgtmKOxOah3aogIBQuCRDEIf8o= + resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc" + integrity sha1-De4hahyUGrN+nvsXiPavxf9VN/w= -semver-diff@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-2.1.0.tgz#4bbb8437c8d37e4b0cf1a68fd726ec6d645d6d36" - integrity sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY= +semver-diff@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-3.1.1.tgz#05f77ce59f325e00e2706afd67bb506ddb1ca32b" + integrity sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg== dependencies: - semver "^5.0.3" - -semver@^5.0.3, semver@^5.1.0, semver@^5.5.1: - version "5.7.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" - integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + semver "^6.3.0" semver@^5.5.0: version "5.6.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004" integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg== -semver@^6.0.0, semver@^6.1.0, semver@^6.1.2: +semver@^5.5.1: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + +semver@^6.0.0, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== +semver@^7.0.0, semver@^7.1.2, semver@^7.3.2, semver@^7.3.4: + version "7.3.5" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" + integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== + dependencies: + lru-cache "^6.0.0" + +serialize-error@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/serialize-error/-/serialize-error-7.0.1.tgz#f1360b0447f61ffb483ec4157c737fab7d778e18" + integrity sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw== + dependencies: + type-fest "^0.13.1" + set-immediate-shim@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61" integrity sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E= -setprototypeof@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" - integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw== - shebang-command@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" @@ -1568,167 +2682,244 @@ shebang-command@^1.2.0: dependencies: shebang-regex "^1.0.0" +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + shebang-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + signal-exit@^3.0.0, signal-exit@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0= -smart-buffer@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.1.0.tgz#91605c25d91652f4661ea69ccf45f1b331ca21ba" - integrity sha512-iVICrxOzCynf/SNaBQCw34eM9jROU/s5rzIhpOvzhzuYHfJR/DhZfDkXiZSgKXfgv26HT3Yni3AV/DGw0cGnnw== +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== -snyk-config@^2.2.1: - version "2.2.3" - resolved "https://registry.yarnpkg.com/snyk-config/-/snyk-config-2.2.3.tgz#8e09bb98602ad044954d30a9fc1695ab5b6042fa" - integrity sha512-9NjxHVMd1U1LFw66Lya4LXgrsFUiuRiL4opxfTFo0LmMNzUoU5Bk/p0zDdg3FE5Wg61r4fP2D8w+QTl6M8CGiw== - dependencies: - debug "^3.1.0" - lodash "^4.17.15" - nconf "^0.10.0" - -snyk-docker-plugin@1.38.0: - version "1.38.0" - resolved "https://registry.yarnpkg.com/snyk-docker-plugin/-/snyk-docker-plugin-1.38.0.tgz#afe0ac316e461b200bcd0063295a3f8bd3655e93" - integrity sha512-43HbJj6QatuL2BNG+Uq2Taa73wdfSQSID8FJWW4q5/LYgd9D+RtdiE4lAMwxqYYbvThU9uuza4epuF/B1CAlYw== +snyk-config@4.0.0, snyk-config@^4.0.0-rc.2: + version "4.0.0" + resolved "https://registry.yarnpkg.com/snyk-config/-/snyk-config-4.0.0.tgz#21d459f19087991246cc07a7ffb4501dce6f4159" + integrity sha512-E6jNe0oUjjzVASWBOAc/mA23DhbzABDF9MI6UZvl0gylh2NSXSXw2/LjlqMNOKL2c1qkbSkzLOdIX5XACoLCAQ== dependencies: + async "^3.2.0" debug "^4.1.1" - dockerfile-ast "0.0.18" - event-loop-spinner "^1.1.0" - semver "^6.1.0" - tar-stream "^2.1.0" - tslib "^1" + lodash.merge "^4.6.2" + minimist "^1.2.5" -snyk-go-parser@1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/snyk-go-parser/-/snyk-go-parser-1.3.1.tgz#427387507578baf008a3e73828e0e53ed8c796f3" - integrity sha512-jrFRfIk6yGHFeipGD66WV9ei/A/w/lIiGqI80w1ndMbg6D6M5pVNbK7ngDTmo4GdHrZDYqx/VBGBsUm2bol3Rg== - dependencies: - toml "^3.0.0" - tslib "^1.9.3" - -snyk-go-plugin@1.11.1: - version "1.11.1" - resolved "https://registry.yarnpkg.com/snyk-go-plugin/-/snyk-go-plugin-1.11.1.tgz#cd7c73c42bd3cf5faa2a90a54cd7c6db926fea5d" - integrity sha512-IsNi7TmpHoRHzONOWJTT8+VYozQJnaJpKgnYNQjzNm2JlV8bDGbdGQ1a8LcEoChxnJ8v8aMZy7GTiQyGGABtEQ== +snyk-cpp-plugin@2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/snyk-cpp-plugin/-/snyk-cpp-plugin-2.2.1.tgz#55891511a43a6448e5a7c836a94f66f70fa705eb" + integrity sha512-NFwVLMCqKTocY66gcim0ukF6e31VRDJqDapg5sy3vCHqlD1OCNUXSK/aI4VQEEndDrsnFmQepsL5KpEU0dDRIQ== dependencies: + "@snyk/dep-graph" "^1.19.3" + chalk "^4.1.0" debug "^4.1.1" - graphlib "^2.1.1" - snyk-go-parser "1.3.1" - tmp "0.0.33" - tslib "^1.10.0" + hosted-git-info "^3.0.7" + tslib "^2.0.0" -snyk-gradle-plugin@3.2.4: - version "3.2.4" - resolved "https://registry.yarnpkg.com/snyk-gradle-plugin/-/snyk-gradle-plugin-3.2.4.tgz#c1ff1dfbbe3c1a254d0da54a91c3f59c1b5582ca" - integrity sha512-XmS1gl7uZNHP9HP5RaPuRXW3VjkbdWe+EgSOlvmspztkubIOIainqc87k7rIJ6u3tLBhqsZK8b5ru0/E9Q69hQ== +snyk-docker-plugin@4.19.3: + version "4.19.3" + resolved "https://registry.yarnpkg.com/snyk-docker-plugin/-/snyk-docker-plugin-4.19.3.tgz#14569f25c52a3fc71a20f80f5beac4ccdc326c11" + integrity sha512-5WkXyT7uY5NrTOvEqxeMqb6dDcskT3c/gbHUTOyPuvE6tMut+OOYK8RRXbwZFeLzpS8asq4e1R7U7syYG3VXwg== dependencies: - "@snyk/cli-interface" "2.3.0" - "@types/debug" "^4.1.4" + "@snyk/dep-graph" "^1.21.0" + "@snyk/rpm-parser" "^2.0.0" + "@snyk/snyk-docker-pull" "3.2.3" chalk "^2.4.2" debug "^4.1.1" - tmp "0.0.33" - tslib "^1.9.3" + docker-modem "2.1.3" + dockerfile-ast "0.2.0" + elfy "^1.0.0" + event-loop-spinner "^2.0.0" + gunzip-maybe "^1.4.2" + mkdirp "^1.0.4" + semver "^7.3.4" + snyk-nodejs-lockfile-parser "1.30.2" + tar-stream "^2.1.0" + tmp "^0.2.1" + tslib "^1" + uuid "^8.2.0" -snyk-module@1.9.1, snyk-module@^1.6.0, snyk-module@^1.9.1: - version "1.9.1" - resolved "https://registry.yarnpkg.com/snyk-module/-/snyk-module-1.9.1.tgz#b2a78f736600b0ab680f1703466ed7309c980804" - integrity sha512-A+CCyBSa4IKok5uEhqT+hV/35RO6APFNLqk9DRRHg7xW2/j//nPX8wTSZUPF8QeRNEk/sX+6df7M1y6PBHGSHA== +snyk-go-parser@1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/snyk-go-parser/-/snyk-go-parser-1.4.1.tgz#df16a5fbd7a517ee757268ef081abc33506c8857" + integrity sha512-StU3uHB85VMEkcgXta63M0Fgd+9cs5sMCjQXTBoYTdE4dxarPn7U67yCuwkRRdZdny1ZXtzfY8LKns9i0+dy9w== dependencies: - debug "^3.1.0" - hosted-git-info "^2.7.1" + toml "^3.0.0" + tslib "^1.10.0" -snyk-mvn-plugin@2.8.0: - version "2.8.0" - resolved "https://registry.yarnpkg.com/snyk-mvn-plugin/-/snyk-mvn-plugin-2.8.0.tgz#20c4201debd99928ade099fd426d13bd17b2cc85" - integrity sha512-Jt6lsVOFOYj7rp0H2IWz/BZS9xxaO0jEFTAoafLCocJIWWuGhPpVocCqmh/hrYAdKY9gS4gVOViMJ3EvcC1r1Q== - dependencies: - "@snyk/cli-interface" "2.3.1" - debug "^4.1.1" - lodash "^4.17.15" - needle "^2.4.0" - tmp "^0.1.0" - tslib "1.9.3" - -snyk-nodejs-lockfile-parser@1.17.0: +snyk-go-plugin@1.17.0: version "1.17.0" - resolved "https://registry.yarnpkg.com/snyk-nodejs-lockfile-parser/-/snyk-nodejs-lockfile-parser-1.17.0.tgz#709e1d8c83faccae3bfdac5c10620dcedbf8c4ac" - integrity sha512-i4GAYFj9TJLOQ8F+FbIJuJWdGymi8w/XcrEX0FzXk7DpYUCY3mWibyKhw8RasfYBx5vLwUzEvRMaQuc2EwlyfA== + resolved "https://registry.yarnpkg.com/snyk-go-plugin/-/snyk-go-plugin-1.17.0.tgz#56d0c92d7def29ba4c3c2030c5830093e3b0dd26" + integrity sha512-1jAYPRgMapO2BYL+HWsUq5gsAiDGmI0Pn7omc0lk24tcUOMhUB+1hb0u9WBMNzHvXBjevBkjOctjpnt2hMKN6Q== dependencies: - "@yarnpkg/lockfile" "^1.0.2" - graphlib "^2.1.5" - lodash "^4.17.14" - p-map "2.1.0" - source-map-support "^0.5.7" - tslib "^1.9.3" - uuid "^3.3.2" + "@snyk/dep-graph" "^1.23.1" + "@snyk/graphlib" "2.1.9-patch.3" + debug "^4.1.1" + snyk-go-parser "1.4.1" + tmp "0.2.1" + tslib "^1.10.0" -snyk-nuget-plugin@1.16.0: - version "1.16.0" - resolved "https://registry.yarnpkg.com/snyk-nuget-plugin/-/snyk-nuget-plugin-1.16.0.tgz#241c6c8a417429c124c3ebf6db314a14eb8eed89" - integrity sha512-OEusK3JKKpR4Yto5KwuqjQGgb9wAhmDqBWSQomWdtKQVFrzn5B6BMzOFikUzmeMTnUGGON7gurQBLXeZZLhRqg== +snyk-gradle-plugin@3.14.2: + version "3.14.2" + resolved "https://registry.yarnpkg.com/snyk-gradle-plugin/-/snyk-gradle-plugin-3.14.2.tgz#898b051f679e681b6d859f0ca84a500ac028af7d" + integrity sha512-l/nivKDZz7e2wymrwP6g2WQD8qgaYeE22SnbZrfIpwGolif81U28A9FsRedwkxKyB/shrM0vGEoD3c3zI8NLBw== dependencies: - debug "^3.1.0" - dotnet-deps-parser "4.9.0" - jszip "^3.1.5" - lodash "^4.17.14" - snyk-paket-parser "1.5.0" + "@snyk/cli-interface" "2.11.0" + "@snyk/dep-graph" "^1.28.0" + "@snyk/java-call-graph-builder" "1.20.0" + "@types/debug" "^4.1.4" + chalk "^3.0.0" + debug "^4.1.1" + tmp "0.2.1" + tslib "^2.0.0" + +snyk-module@3.1.0, snyk-module@^3.0.0, snyk-module@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/snyk-module/-/snyk-module-3.1.0.tgz#3e088ff473ddf0d4e253a46ea6749d76d8e6e7ba" + integrity sha512-HHuOYEAACpUpkFgU8HT57mmxmonaJ4O3YADoSkVhnhkmJ+AowqZyJOau703dYHNrq2DvQ7qYw81H7yyxS1Nfjw== + dependencies: + debug "^4.1.1" + hosted-git-info "^3.0.4" + +snyk-mvn-plugin@2.25.3: + version "2.25.3" + resolved "https://registry.yarnpkg.com/snyk-mvn-plugin/-/snyk-mvn-plugin-2.25.3.tgz#fb7f6fa1d565b9f07c032e8b34e6308c310b2a27" + integrity sha512-JAxOThX51JDbgMMjp3gQDVi07G9VgTYSF06QC7f5LNA0zoXNr743e2rm78RGw5bqE3JRjZxEghiLHPPuvS5DDg== + dependencies: + "@snyk/cli-interface" "2.11.0" + "@snyk/dep-graph" "^1.23.1" + "@snyk/java-call-graph-builder" "1.19.1" + debug "^4.1.1" + glob "^7.1.6" + needle "^2.5.0" + tmp "^0.1.0" + tslib "1.11.1" + +snyk-nodejs-lockfile-parser@1.30.2: + version "1.30.2" + resolved "https://registry.yarnpkg.com/snyk-nodejs-lockfile-parser/-/snyk-nodejs-lockfile-parser-1.30.2.tgz#8dbb64c42382aeaf4488c36e48c1e48eb75a1584" + integrity sha512-wI3VXVYO/ok0uaQm5i+Koo4rKBNilYC/QRIQFlyGbZXf+WBdRcTBKVDfTy8uNfUhMRSGzd84lNclMnetU9Y+vw== + dependencies: + "@snyk/graphlib" "2.1.9-patch.3" + "@yarnpkg/lockfile" "^1.1.0" + event-loop-spinner "^2.0.0" + got "11.4.0" + lodash.clonedeep "^4.5.0" + lodash.flatmap "^4.5.0" + lodash.isempty "^4.4.0" + lodash.set "^4.3.2" + lodash.topairs "^4.3.0" + p-map "2.1.0" + snyk-config "^4.0.0-rc.2" tslib "^1.9.3" + uuid "^8.3.0" + yaml "^1.9.2" + +snyk-nodejs-lockfile-parser@1.32.0: + version "1.32.0" + resolved "https://registry.yarnpkg.com/snyk-nodejs-lockfile-parser/-/snyk-nodejs-lockfile-parser-1.32.0.tgz#2e25ea8622ef03ae7457a93ae70e156d6c46c2ef" + integrity sha512-FdYa/7NibnJPqBfobyw5jgI1/rd0LpMZf2W4WYYLRc2Hz7LZjKAByPjIX6qoA+lB9SC7yk5HYwWj2n4Fbg/DDw== + dependencies: + "@snyk/graphlib" "2.1.9-patch.3" + "@yarnpkg/core" "^2.4.0" + "@yarnpkg/lockfile" "^1.1.0" + event-loop-spinner "^2.0.0" + got "11.4.0" + lodash.clonedeep "^4.5.0" + lodash.flatmap "^4.5.0" + lodash.isempty "^4.4.0" + lodash.set "^4.3.2" + lodash.topairs "^4.3.0" + p-map "2.1.0" + snyk-config "^4.0.0-rc.2" + tslib "^1.9.3" + uuid "^8.3.0" + yaml "^1.9.2" + +snyk-nuget-plugin@1.21.1: + version "1.21.1" + resolved "https://registry.yarnpkg.com/snyk-nuget-plugin/-/snyk-nuget-plugin-1.21.1.tgz#a79bbc65456823a1148119873226afb0e4907ec8" + integrity sha512-nRtedIvrow5ODqOKkQWolKrxn8ZoNL3iNJGuW0jNhwv+/9K0XE1UORM5F1ENAsd+nzCSO/kiYAXCc5CNK8HWEw== + dependencies: + debug "^4.1.1" + dotnet-deps-parser "5.0.0" + jszip "3.4.0" + snyk-paket-parser "1.6.0" + tslib "^1.11.2" xml2js "^0.4.17" -snyk-paket-parser@1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/snyk-paket-parser/-/snyk-paket-parser-1.5.0.tgz#a0e96888d9d304b1ae6203a0369971575f099548" - integrity sha512-1CYMPChJ9D9LBy3NLqHyv8TY7pR/LMISSr08LhfFw/FpfRZ+gTH8W6bbxCmybAYrOFNCqZkRprqOYDqZQFHipA== +snyk-paket-parser@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/snyk-paket-parser/-/snyk-paket-parser-1.6.0.tgz#f70c423b33d31484c8c4cae74bb7f5deb9bbc382" + integrity sha512-6htFynjBe/nakclEHUZ1A3j5Eu32/0pNve5Qm4MFn3YQmJgj7UcAO8hdyK3QfzEY29/kAv/rkJQg+SKshn+N9Q== dependencies: tslib "^1.9.3" -snyk-php-plugin@1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/snyk-php-plugin/-/snyk-php-plugin-1.7.0.tgz#cf1906ed8a10db134c803be3d6e4be0cbdc5fe33" - integrity sha512-mDe90xkqSEVrpx1ZC7ItqCOc6fZCySbE+pHVI+dAPUmf1C1LSWZrZVmAVeo/Dw9sJzJfzmcdAFQl+jZP8/uV0A== +snyk-php-plugin@1.9.2: + version "1.9.2" + resolved "https://registry.yarnpkg.com/snyk-php-plugin/-/snyk-php-plugin-1.9.2.tgz#282ef733060aab49da23e1fb2d2dd1af8f71f7cd" + integrity sha512-IQcdsQBqqXVRY5DatlI7ASy4flbhtU2V7cr4P2rK9rkFnVHO6LHcitwKXVZa9ocdOmpZDzk7U6iwHJkVFcR6OA== dependencies: - "@snyk/cli-interface" "2.2.0" - "@snyk/composer-lockfile-parser" "1.2.0" - tslib "1.9.3" + "@snyk/cli-interface" "^2.9.1" + "@snyk/composer-lockfile-parser" "^1.4.1" + tslib "1.11.1" -snyk-policy@1.13.5: - version "1.13.5" - resolved "https://registry.yarnpkg.com/snyk-policy/-/snyk-policy-1.13.5.tgz#c5cf262f759879a65ab0810dd58d59c8ec7e9e47" - integrity sha512-KI6GHt+Oj4fYKiCp7duhseUj5YhyL/zJOrrJg0u6r59Ux9w8gmkUYT92FHW27ihwuT6IPzdGNEuy06Yv2C9WaQ== +snyk-poetry-lockfile-parser@^1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/snyk-poetry-lockfile-parser/-/snyk-poetry-lockfile-parser-1.1.6.tgz#bab5a279c103cbcca8eb86ab87717b115592881e" + integrity sha512-MoekbWOZPj9umfukjk2bd2o3eRj0OyO+58sxq9crMtHmTlze4h0/Uj4+fb0JFPBOtBO3c2zwbA+dvFQmpKoOTA== dependencies: - debug "^3.1.0" + "@snyk/cli-interface" "^2.9.2" + "@snyk/dep-graph" "^1.23.0" + debug "^4.2.0" + toml "^3.0.0" + tslib "^2.0.0" + +snyk-policy@1.19.0: + version "1.19.0" + resolved "https://registry.yarnpkg.com/snyk-policy/-/snyk-policy-1.19.0.tgz#0cbc442d9503970fb3afea938f57d57993a914ad" + integrity sha512-XYjhOTRPFA7NfDUsH6uH1fbML2OgSFsqdUPbud7x01urNP9CHXgUgAD4NhKMi3dVQK+7IdYadWt0wrFWw4y+qg== + dependencies: + debug "^4.1.1" email-validator "^2.0.4" js-yaml "^3.13.1" lodash.clonedeep "^4.5.0" + promise-fs "^2.1.1" semver "^6.0.0" - snyk-module "^1.9.1" - snyk-resolve "^1.0.1" - snyk-try-require "^1.3.1" - then-fs "^2.0.0" + snyk-module "^3.0.0" + snyk-resolve "^1.1.0" + snyk-try-require "^2.0.0" -snyk-python-plugin@1.17.0: - version "1.17.0" - resolved "https://registry.yarnpkg.com/snyk-python-plugin/-/snyk-python-plugin-1.17.0.tgz#9bc38ba3c799c3cbef7676a1081f52608690d254" - integrity sha512-EKdVOUlvhiVpXA5TeW8vyxYVqbITAfT+2AbL2ZRiiUNLP5ae+WiNYaPy7aB5HAS9IKBKih+IH8Ag65Xu1IYSYA== +snyk-python-plugin@1.19.8: + version "1.19.8" + resolved "https://registry.yarnpkg.com/snyk-python-plugin/-/snyk-python-plugin-1.19.8.tgz#9e4dfa8ed7e16ef2752f934b786d2e033de62ce0" + integrity sha512-LMKVnv0J4X/qHMoKB17hMND0abWtm9wdgI4xVzrOcf2Vtzs3J87trRhwLxQA2lMoBW3gcjtTeBUvNKaxikSVeQ== dependencies: "@snyk/cli-interface" "^2.0.3" + snyk-poetry-lockfile-parser "^1.1.6" tmp "0.0.33" -snyk-resolve-deps@4.4.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/snyk-resolve-deps/-/snyk-resolve-deps-4.4.0.tgz#ef20fb578a4c920cc262fb73dd292ff21215f52d" - integrity sha512-aFPtN8WLqIk4E1ulMyzvV5reY1Iksz+3oPnUVib1jKdyTHymmOIYF7z8QZ4UUr52UsgmrD9EA/dq7jpytwFoOQ== +snyk-resolve-deps@4.7.2: + version "4.7.2" + resolved "https://registry.yarnpkg.com/snyk-resolve-deps/-/snyk-resolve-deps-4.7.2.tgz#11e7051110dadd8756819594bb30e6b88777a8b4" + integrity sha512-Bmtr7QdRL2b3Js+mPDmvXbkprOpzO8aUFXqR0nJKAOlUVQqZ84yiuT0n/mssEiJJ0vP+k0kZvTeiTwgio4KZRg== dependencies: - "@types/node" "^6.14.4" - "@types/semver" "^5.5.0" ansicolors "^0.3.2" - debug "^3.2.5" + debug "^4.1.1" lodash.assign "^4.2.0" lodash.assignin "^4.2.0" lodash.clone "^4.5.0" @@ -1737,13 +2928,21 @@ snyk-resolve-deps@4.4.0: lodash.set "^4.3.2" lru-cache "^4.0.0" semver "^5.5.1" - snyk-module "^1.6.0" + snyk-module "^3.1.0" snyk-resolve "^1.0.0" snyk-tree "^1.0.0" snyk-try-require "^1.1.1" then-fs "^2.0.0" -snyk-resolve@1.0.1, snyk-resolve@^1.0.0, snyk-resolve@^1.0.1: +snyk-resolve@1.1.0, snyk-resolve@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/snyk-resolve/-/snyk-resolve-1.1.0.tgz#52740cb01ba477851086855f9857b3a44296ee0e" + integrity sha512-OZMF8I8TOu0S58Z/OS9mr8jkEzGAPByCsAkrWlcmZgPaE0RsxVKVIFPhbMNy/JlYswgGDYYIEsNw+e0j1FnTrw== + dependencies: + debug "^4.1.1" + promise-fs "^2.1.1" + +snyk-resolve@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/snyk-resolve/-/snyk-resolve-1.0.1.tgz#eaa4a275cf7e2b579f18da5b188fe601b8eed9ab" integrity sha512-7+i+LLhtBo1Pkth01xv+RYJU8a67zmJ8WFFPvSxyCjdlKIcsps4hPQFebhz+0gC5rMemlaeIV6cqwqUf9PEDpw== @@ -1769,7 +2968,7 @@ snyk-tree@^1.0.0: dependencies: archy "^1.0.0" -snyk-try-require@1.3.1, snyk-try-require@^1.1.1, snyk-try-require@^1.3.1: +snyk-try-require@1.3.1, snyk-try-require@^1.1.1: version "1.3.1" resolved "https://registry.yarnpkg.com/snyk-try-require/-/snyk-try-require-1.3.1.tgz#6e026f92e64af7fcccea1ee53d524841e418a212" integrity sha1-bgJvkuZK9/zM6h7lPVJIQeQYohI= @@ -1779,74 +2978,90 @@ snyk-try-require@1.3.1, snyk-try-require@^1.1.1, snyk-try-require@^1.3.1: lru-cache "^4.0.0" then-fs "^2.0.0" -snyk@^1.290.1: - version "1.290.2" - resolved "https://registry.yarnpkg.com/snyk/-/snyk-1.290.2.tgz#a5e36e735a8083464263abdb266b6c9b3d46de7f" - integrity sha512-siieHkSY/b3Yw1Gf84L07j65m2Bht1PamAbX3cmZ1zAzsUxfXpqZq5W9PlAp5z1d0Tp1vxsQmXw6UGW0K1Tq1Q== +snyk-try-require@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/snyk-try-require/-/snyk-try-require-2.0.1.tgz#076ae9bc505d64d28389452ce19fcac28f26655a" + integrity sha512-VCOfFIvqLMXgCXEdooQgu3A40XYIFBnj0X8Y01RJ5iAbu08b4WKGN/uAKaRVF30dABS4EcjsalmCO+YlKUPEIA== dependencies: - "@snyk/cli-interface" "2.3.0" - "@snyk/configstore" "^3.2.0-rc1" - "@snyk/dep-graph" "1.13.1" + debug "^4.1.1" + lodash.clonedeep "^4.3.0" + lru-cache "^5.1.1" + +snyk@^1.518.0: + version "1.564.0" + resolved "https://registry.yarnpkg.com/snyk/-/snyk-1.564.0.tgz#c8c511128351f8b8fe239b010b6799f40bb659c5" + integrity sha512-Fh4YusvJ9XdQfyz8JH9J8mBbipfgGLiF60MW9DYhQgP6h8z5uckAfd+S/uFMwPOVOIoe00fFo7aCLxFUuPcVJQ== + dependencies: + "@open-policy-agent/opa-wasm" "^1.2.0" + "@snyk/cli-interface" "2.11.0" + "@snyk/cloud-config-parser" "^1.9.2" + "@snyk/code-client" "3.4.1" + "@snyk/dep-graph" "^1.27.1" + "@snyk/fix" "1.554.0" "@snyk/gemfile" "1.2.0" - "@snyk/snyk-cocoapods-plugin" "2.0.1" - "@snyk/update-notifier" "^2.5.1-rc2" - "@types/agent-base" "^4.2.0" - "@types/restify" "^4.3.6" + "@snyk/graphlib" "^2.1.9-patch.3" + "@snyk/inquirer" "^7.3.3-patch" + "@snyk/snyk-cocoapods-plugin" "2.5.2" + "@snyk/snyk-hex-plugin" "1.1.4" abbrev "^1.1.1" ansi-escapes "3.2.0" chalk "^2.4.2" cli-spinner "0.2.10" - debug "^3.1.0" + configstore "^5.0.1" + debug "^4.1.1" diff "^4.0.1" - git-url-parse "11.1.2" - glob "^7.1.3" - inquirer "^6.2.2" - lodash "^4.17.14" - needle "^2.2.4" - opn "^5.5.0" + global-agent "^2.1.12" + lodash.assign "^4.2.0" + lodash.camelcase "^4.3.0" + lodash.clonedeep "^4.5.0" + lodash.endswith "^4.2.1" + lodash.flatten "^4.4.0" + lodash.flattendeep "^4.4.0" + lodash.get "^4.4.2" + lodash.groupby "^4.6.0" + lodash.isempty "^4.4.0" + lodash.isobject "^3.0.2" + lodash.map "^4.6.0" + lodash.omit "^4.5.0" + lodash.orderby "^4.6.0" + lodash.sortby "^4.7.0" + lodash.uniq "^4.5.0" + lodash.upperfirst "^4.3.1" + lodash.values "^4.3.0" + micromatch "4.0.2" + needle "2.6.0" + open "^7.0.3" + ora "5.3.0" os-name "^3.0.0" - proxy-agent "^3.1.1" + promise-queue "^2.2.5" proxy-from-env "^1.0.0" + rimraf "^2.6.3" semver "^6.0.0" - snyk-config "^2.2.1" - snyk-docker-plugin "1.38.0" - snyk-go-plugin "1.11.1" - snyk-gradle-plugin "3.2.4" - snyk-module "1.9.1" - snyk-mvn-plugin "2.8.0" - snyk-nodejs-lockfile-parser "1.17.0" - snyk-nuget-plugin "1.16.0" - snyk-php-plugin "1.7.0" - snyk-policy "1.13.5" - snyk-python-plugin "1.17.0" - snyk-resolve "1.0.1" - snyk-resolve-deps "4.4.0" + snyk-config "4.0.0" + snyk-cpp-plugin "2.2.1" + snyk-docker-plugin "4.19.3" + snyk-go-plugin "1.17.0" + snyk-gradle-plugin "3.14.2" + snyk-module "3.1.0" + snyk-mvn-plugin "2.25.3" + snyk-nodejs-lockfile-parser "1.32.0" + snyk-nuget-plugin "1.21.1" + snyk-php-plugin "1.9.2" + snyk-policy "1.19.0" + snyk-python-plugin "1.19.8" + snyk-resolve "1.1.0" + snyk-resolve-deps "4.7.2" snyk-sbt-plugin "2.11.0" snyk-tree "^1.0.0" snyk-try-require "1.3.1" source-map-support "^0.5.11" strip-ansi "^5.2.0" + tar "^6.1.0" tempfile "^2.0.0" - then-fs "^2.0.0" + update-notifier "^4.1.0" uuid "^3.3.2" wrap-ansi "^5.1.0" -socks-proxy-agent@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-4.0.2.tgz#3c8991f3145b2799e70e11bd5fbc8b1963116386" - integrity sha512-NT6syHhI9LmuEMSK6Kd2V7gNv5KFZoLE7V5udWmn0de+3Mkj3UMA/AJPLyeNUVmElCurSHtUdM3ETpR3z770Wg== - dependencies: - agent-base "~4.2.1" - socks "~2.3.2" - -socks@~2.3.2: - version "2.3.3" - resolved "https://registry.yarnpkg.com/socks/-/socks-2.3.3.tgz#01129f0a5d534d2b897712ed8aceab7ee65d78e3" - integrity sha512-o5t52PCNtVdiOvzMry7wU4aOqYWL0PeCXRWBEiJow4/i/wr+wpsJQ9awEu1EonLIqsfGd5qSgDdxEOvCdmBEpA== - dependencies: - ip "1.1.5" - smart-buffer "^4.1.0" - source-map-support@^0.5.11, source-map-support@^0.5.7: version "0.5.16" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.16.tgz#0ae069e7fe3ba7538c64c98515e35339eac5a042" @@ -1855,37 +3070,72 @@ source-map-support@^0.5.11, source-map-support@^0.5.7: buffer-from "^1.0.0" source-map "^0.6.0" -source-map@^0.6.0, source-map@~0.6.1: +source-map@^0.6.0: version "0.6.1" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== +split-ca@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/split-ca/-/split-ca-1.0.1.tgz#6c83aff3692fa61256e0cd197e05e9de157691a6" + integrity sha1-bIOv82kvphJW4M0ZfgXp3hV2kaY= + +sprintf-js@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.2.tgz#da1765262bf8c0f571749f2ad6c26300207ae673" + integrity sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug== + sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= -"statuses@>= 1.5.0 < 2": - version "1.5.0" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" - integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= - -string-width@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" - integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M= +ssh2-streams@~0.4.10: + version "0.4.10" + resolved "https://registry.yarnpkg.com/ssh2-streams/-/ssh2-streams-0.4.10.tgz#48ef7e8a0e39d8f2921c30521d56dacb31d23a34" + integrity sha512-8pnlMjvnIZJvmTzUIIA5nT4jr2ZWNNVHwyXfMGdRJbug9TpI3kd99ffglgfSWqujVv/0gxwMsDn9j9RVst8yhQ== dependencies: - code-point-at "^1.0.0" - is-fullwidth-code-point "^1.0.0" - strip-ansi "^3.0.0" + asn1 "~0.2.0" + bcrypt-pbkdf "^1.0.2" + streamsearch "~0.1.2" -string-width@^2.0.0, string-width@^2.1.0, string-width@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" - integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== +ssh2@^0.8.7: + version "0.8.9" + resolved "https://registry.yarnpkg.com/ssh2/-/ssh2-0.8.9.tgz#54da3a6c4ba3daf0d8477a538a481326091815f3" + integrity sha512-GmoNPxWDMkVpMFa9LVVzQZHF6EW3WKmBwL+4/GeILf2hFmix5Isxm7Amamo8o7bHiU0tC+wXsGcUXOxp8ChPaw== dependencies: - is-fullwidth-code-point "^2.0.0" - strip-ansi "^4.0.0" + ssh2-streams "~0.4.10" + +stream-buffers@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/stream-buffers/-/stream-buffers-3.0.2.tgz#5249005a8d5c2d00b3a32e6e0a6ea209dc4f3521" + integrity sha512-DQi1h8VEBA/lURbSwFtEHnSTb9s2/pwLEaFuNhXwy1Dx3Sa0lOuYT2yNUr4/j2fs8oCAMANtrZ5OrPZtyVs3MQ== + +stream-shift@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d" + integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ== + +stream-to-array@~2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/stream-to-array/-/stream-to-array-2.3.0.tgz#bbf6b39f5f43ec30bc71babcb37557acecf34353" + integrity sha1-u/azn19D7DC8cbq8s3VXrOzzQ1M= + dependencies: + any-promise "^1.1.0" + +stream-to-promise@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/stream-to-promise/-/stream-to-promise-2.2.0.tgz#b1edb2e1c8cb11289d1b503c08d3f2aef51e650f" + integrity sha1-se2y4cjLESidG1A8CNPyrvUeZQ8= + dependencies: + any-promise "~1.3.0" + end-of-stream "~1.1.0" + stream-to-array "~2.3.0" + +streamsearch@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a" + integrity sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo= string-width@^3.0.0: version "3.1.0" @@ -1896,6 +3146,15 @@ string-width@^3.0.0: is-fullwidth-code-point "^2.0.0" strip-ansi "^5.1.0" +string-width@^4.0.0, string-width@^4.1.0: + version "4.2.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.2.tgz#dafd4f9559a7585cfba529c6a0a4f73488ebd4c5" + integrity sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.0" + string_decoder@^1.1.1: version "1.3.0" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" @@ -1903,11 +3162,6 @@ string_decoder@^1.1.1: dependencies: safe-buffer "~5.2.0" -string_decoder@~0.10.x: - version "0.10.31" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" - integrity sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ= - string_decoder@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" @@ -1915,19 +3169,12 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -strip-ansi@^3.0.0, strip-ansi@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" - integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= +strip-ansi@6.0.0, strip-ansi@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532" + integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w== dependencies: - ansi-regex "^2.0.0" - -strip-ansi@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" - integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= - dependencies: - ansi-regex "^3.0.0" + ansi-regex "^5.0.0" strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: version "5.2.0" @@ -1953,6 +3200,24 @@ supports-color@^5.3.0: dependencies: has-flag "^3.0.0" +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +tar-stream@^2.0.1, tar-stream@^2.1.2: + version "2.2.0" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" + integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== + dependencies: + bl "^4.0.3" + end-of-stream "^1.4.1" + fs-constants "^1.0.0" + inherits "^2.0.3" + readable-stream "^3.1.1" + tar-stream@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.1.0.tgz#d1aaa3661f05b38b5acc9b7020efdca5179a2cc3" @@ -1964,11 +3229,28 @@ tar-stream@^2.1.0: inherits "^2.0.3" readable-stream "^3.1.1" +tar@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.0.tgz#d1724e9bcc04b977b18d5c573b333a2207229a83" + integrity sha512-DUCttfhsnLCjwoDoFcI+B2iJgYa93vBnDUATYEeRx6sntCTdN01VnqsIuTlALXla/LWooNg0yEGeB+Y8WdFxGA== + dependencies: + chownr "^2.0.0" + fs-minipass "^2.0.0" + minipass "^3.0.0" + minizlib "^2.1.1" + mkdirp "^1.0.3" + yallist "^4.0.0" + temp-dir@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-1.0.0.tgz#0a7c0ea26d3a39afa7e0ebea9c1fc0bc4daa011d" integrity sha1-CnwOom06Oa+n4OvqnB/AvE2qAR0= +temp-dir@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-2.0.0.tgz#bde92b05bdfeb1516e804c9c00ad45177f31321e" + integrity sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg== + tempfile@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/tempfile/-/tempfile-2.0.0.tgz#6b0446856a9b1114d1856ffcbe509cccb0977265" @@ -1977,12 +3259,10 @@ tempfile@^2.0.0: temp-dir "^1.0.0" uuid "^3.0.1" -term-size@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/term-size/-/term-size-1.2.0.tgz#458b83887f288fc56d6fffbfad262e26638efa69" - integrity sha1-RYuDiH8oj8Vtb/+/rSYuJmOO+mk= - dependencies: - execa "^0.7.0" +term-size@^2.1.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/term-size/-/term-size-2.2.1.tgz#2a6a54840432c2fb6320fea0f415531e90189f54" + integrity sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg== then-fs@^2.0.0: version "2.0.0" @@ -1991,21 +3271,19 @@ then-fs@^2.0.0: dependencies: promise ">=3.2 <8" +through2@^2.0.3: + version "2.0.5" + resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" + integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ== + dependencies: + readable-stream "~2.3.6" + xtend "~4.0.1" + through@^2.3.6: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= -thunkify@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/thunkify/-/thunkify-2.1.2.tgz#faa0e9d230c51acc95ca13a361ac05ca7e04553d" - integrity sha1-+qDp0jDFGsyVyhOjYawFyn4EVT0= - -timed-out@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f" - integrity sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8= - tmp@0.0.33, tmp@^0.0.33: version "0.0.33" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" @@ -2013,6 +3291,13 @@ tmp@0.0.33, tmp@^0.0.33: dependencies: os-tmpdir "~1.0.2" +tmp@0.2.1, tmp@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14" + integrity sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ== + dependencies: + rimraf "^3.0.0" + tmp@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.1.0.tgz#ee434a4e22543082e294ba6201dcc6eafefa2877" @@ -2020,10 +3305,17 @@ tmp@^0.1.0: dependencies: rimraf "^2.6.3" -toidentifier@1.0.0: +to-readable-stream@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" - integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== + resolved "https://registry.yarnpkg.com/to-readable-stream/-/to-readable-stream-1.0.0.tgz#ce0aa0c2f3df6adf852efb404a783e77c0475771" + integrity sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q== + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" toml@^3.0.0: version "3.0.0" @@ -2035,46 +3327,105 @@ tree-kill@^1.2.2: resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc" integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A== -tslib@1.9.3: - version "1.9.3" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286" - integrity sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ== +treeify@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/treeify/-/treeify-1.1.0.tgz#4e31c6a463accd0943879f30667c4fdaff411bb8" + integrity sha512-1m4RA7xVAJrSGrrXGs0L3YTwyvBs2S8PbRHaLZAkFw7JR8oIFwYtysxlBZhYIa7xSyiYJKZ3iGrrk55cGA3i9A== + +tslib@1.11.1: + version "1.11.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.11.1.tgz#eb15d128827fbee2841549e171f45ed338ac7e35" + integrity sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA== tslib@^1, tslib@^1.10.0, tslib@^1.9.0, tslib@^1.9.3: version "1.10.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a" integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ== -type-check@~0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" - integrity sha1-WITKtRLPHTVeP7eE8wgEsrUg23I= +tslib@^1.11.2, tslib@^1.13.0: + version "1.14.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" + integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== + +tslib@^2.0.0, tslib@^2.1.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.2.0.tgz#fb2c475977e35e241311ede2693cee1ec6698f5c" + integrity sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w== + +tunnel@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.6.tgz#72f1314b34a5b192db012324df2cc587ca47f92c" + integrity sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg== + +tweetnacl@^0.14.3: + version "0.14.5" + resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" + integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= + +type-fest@^0.13.1: + version "0.13.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.13.1.tgz#0172cb5bce80b0bd542ea348db50c7e21834d934" + integrity sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg== + +type-fest@^0.21.3: + version "0.21.3" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" + integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== + +type-fest@^0.8.1: + version "0.8.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" + integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== + +typedarray-to-buffer@^3.1.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" + integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== dependencies: - prelude-ls "~1.1.2" + is-typedarray "^1.0.0" -unique-string@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-1.0.0.tgz#9e1057cca851abb93398f8b33ae187b99caec11a" - integrity sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo= +unique-string@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-2.0.0.tgz#39c6451f81afb2749de2b233e3f7c5e8843bd89d" + integrity sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg== dependencies: - crypto-random-string "^1.0.0" + crypto-random-string "^2.0.0" -unpipe@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" - integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= - -unzip-response@^2.0.1: +upath@2.0.1: version "2.0.1" - resolved "https://registry.yarnpkg.com/unzip-response/-/unzip-response-2.0.1.tgz#d2f0f737d16b0615e72a6935ed04214572d56f97" - integrity sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c= + resolved "https://registry.yarnpkg.com/upath/-/upath-2.0.1.tgz#50c73dea68d6f6b990f51d279ce6081665d61a8b" + integrity sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w== -url-parse-lax@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-1.0.0.tgz#7af8f303645e9bd79a272e7a14ac68bc0609da73" - integrity sha1-evjzA2Rem9eaJy56FKxovAYJ2nM= +update-notifier@^4.1.0: + version "4.1.3" + resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-4.1.3.tgz#be86ee13e8ce48fb50043ff72057b5bd598e1ea3" + integrity sha512-Yld6Z0RyCYGB6ckIjffGOSOmHXj1gMeE7aROz4MG+XMkmixBX4jUngrGXNYz7wPKBmtoD4MnBa2Anu7RSKht/A== dependencies: - prepend-http "^1.0.1" + boxen "^4.2.0" + chalk "^3.0.0" + configstore "^5.0.1" + has-yarn "^2.1.0" + import-lazy "^2.1.0" + is-ci "^2.0.0" + is-installed-globally "^0.3.1" + is-npm "^4.0.0" + is-yarn-global "^0.3.0" + latest-version "^5.0.0" + pupa "^2.0.1" + semver-diff "^3.1.1" + xdg-basedir "^4.0.0" + +url-parse-lax@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-3.0.0.tgz#16b5cafc07dbe3676c1b1999177823d6503acb0c" + integrity sha1-FrXK/Afb42dsGxmZF3gj1lA6yww= + dependencies: + prepend-http "^2.0.0" + +utf8@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/utf8/-/utf8-3.0.0.tgz#f052eed1364d696e769ef058b183df88c87f69d1" + integrity sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ== util-deprecate@^1.0.1, util-deprecate@~1.0.1: version "1.0.2" @@ -2086,10 +3437,22 @@ uuid@^3.0.1, uuid@^3.3.2: resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== -vscode-languageserver-types@^3.5.0: - version "3.15.1" - resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.15.1.tgz#17be71d78d2f6236d414f0001ce1ef4d23e6b6de" - integrity sha512-+a9MPUQrNGRrGU630OGbYVQ+11iOIovjCkqxajPa9w57Sd5ruK8WQNsslzpa0x/QJqC8kRc2DUxWjIFwoNm4ZQ== +uuid@^8.2.0, uuid@^8.3.0, uuid@^8.3.2: + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== + +vscode-languageserver-types@^3.16.0: + version "3.16.0" + resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0.tgz#ecf393fc121ec6974b2da3efb3155644c514e247" + integrity sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA== + +wcwidth@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8" + integrity sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g= + dependencies: + defaults "^1.0.3" which@^1.2.9: version "1.3.1" @@ -2098,17 +3461,19 @@ which@^1.2.9: dependencies: isexe "^2.0.0" -widest-line@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-2.0.1.tgz#7438764730ec7ef4381ce4df82fb98a53142a3fc" - integrity sha512-Ba5m9/Fa4Xt9eb2ELXt77JxVDV8w7qQrH0zS/TWSJdLyAwQjWoOzpzj5lwVftDz6n/EOu3tNACS84v509qwnJA== +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== dependencies: - string-width "^2.1.1" + isexe "^2.0.0" -window-size@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.4.tgz#f8e1aa1ee5a53ec5bf151ffa09742a6ad7697876" - integrity sha1-+OGqHuWlPsW/FR/6CXQqatdpeHY= +widest-line@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-3.1.0.tgz#8292333bbf66cb45ff0de1603b136b7ae1496eca" + integrity sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg== + dependencies: + string-width "^4.0.0" windows-release@^3.1.0: version "3.2.0" @@ -2117,19 +3482,6 @@ windows-release@^3.1.0: dependencies: execa "^1.0.0" -word-wrap@~1.2.3: - version "1.2.3" - resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" - integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== - -wrap-ansi@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85" - integrity sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU= - dependencies: - string-width "^1.0.1" - strip-ansi "^3.0.1" - wrap-ansi@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09" @@ -2144,29 +3496,29 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= -write-file-atomic@^2.0.0: - version "2.4.3" - resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-2.4.3.tgz#1fd2e9ae1df3e75b8d8c367443c692d4ca81f481" - integrity sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ== +write-file-atomic@^3.0.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8" + integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q== dependencies: - graceful-fs "^4.1.11" imurmurhash "^0.1.4" + is-typedarray "^1.0.0" signal-exit "^3.0.2" + typedarray-to-buffer "^3.1.5" -xdg-basedir@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4" - integrity sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ= +xdg-basedir@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13" + integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q== -xml2js@0.4.19: - version "0.4.19" - resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.19.tgz#686c20f213209e94abf0d1bcf1efaa291c7827a7" - integrity sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q== +xml-js@^1.6.11: + version "1.6.11" + resolved "https://registry.yarnpkg.com/xml-js/-/xml-js-1.6.11.tgz#927d2f6947f7f1c19a316dd8eea3614e8b18f8e9" + integrity sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g== dependencies: - sax ">=0.6.0" - xmlbuilder "~9.0.1" + sax "^1.2.4" -xml2js@^0.4.17: +xml2js@0.4.23, xml2js@^0.4.17: version "0.4.23" resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.23.tgz#a0c69516752421eb2ac758ee4d4ccf58843eac66" integrity sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug== @@ -2179,20 +3531,10 @@ xmlbuilder@~11.0.0: resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3" integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA== -xmlbuilder@~9.0.1: - version "9.0.7" - resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d" - integrity sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0= - -xregexp@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-2.0.0.tgz#52a63e56ca0b84a7f3a5f3d61872f126ad7a5943" - integrity sha1-UqY+VsoLhKfzpfPWGHLxJq16WUM= - -y18n@^3.2.0: - version "3.2.1" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41" - integrity sha1-bRX7qITAhnnA136I53WegR4H+kE= +xtend@~4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" + integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== yallist@^2.1.2: version "2.1.2" @@ -2204,15 +3546,17 @@ yallist@^3.0.2: resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== -yargs@^3.19.0: - version "3.32.0" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.32.0.tgz#03088e9ebf9e756b69751611d2a5ef591482c995" - integrity sha1-AwiOnr+edWtpdRYR0qXvWRSCyZU= - dependencies: - camelcase "^2.0.1" - cliui "^3.0.3" - decamelize "^1.1.1" - os-locale "^1.4.0" - string-width "^1.0.1" - window-size "^0.1.4" - y18n "^3.2.0" +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + +yaml-js@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/yaml-js/-/yaml-js-0.3.0.tgz#ad0893d9de881a93fd6bf936e8d89cdde309e848" + integrity sha512-JbTUdsPiCkOyz+JOSqAVc19omTnUBnBQglhuclYov5HpWbEOz8y+ftqWjiMa9Pe/eF/dmCUeNgVs/VWg53GlgQ== + +yaml@^1.9.2: + version "1.10.2" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" + integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== From 71342320ce33fb09fa7f0f26f8f1ce070c2bb7c3 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Mon, 26 Apr 2021 15:32:28 +0530 Subject: [PATCH 133/326] fix(Italy): multiple fixes in import supplier invoice (#25466) * fix(Italy): remove incorrect method, use defined variable * fix: show attach section after save; use db_set --- .../import_supplier_invoice.json | 5 ++- .../import_supplier_invoice.py | 33 +++++-------------- 2 files changed, 12 insertions(+), 26 deletions(-) diff --git a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.json b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.json index c1680c4b49..afdd54b418 100644 --- a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.json +++ b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.json @@ -1,4 +1,5 @@ { + "actions": [], "creation": "2019-10-15 12:33:21.845329", "doctype": "DocType", "editable_grid": 1, @@ -86,12 +87,14 @@ "reqd": 1 }, { + "depends_on": "eval:!doc.__islocal", "fieldname": "upload_xml_invoices_section", "fieldtype": "Section Break", "label": "Upload XML Invoices" } ], - "modified": "2020-05-25 21:32:49.064579", + "links": [], + "modified": "2021-04-24 10:33:12.250687", "modified_by": "Administrator", "module": "Regional", "name": "Import Supplier Invoice", diff --git a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py index cc6b907bc1..00300539e9 100644 --- a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py +++ b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py @@ -28,14 +28,19 @@ class ImportSupplierInvoice(Document): self.name = "Import Invoice on " + format_datetime(self.creation) def import_xml_data(self): - import_file = frappe.get_doc("File", {"file_url": self.zip_file}) + zip_file = frappe.get_doc("File", { + "file_url": self.zip_file, + "attached_to_doctype": self.doctype, + "attached_to_name": self.name + }) + self.publish("File Import", _("Processing XML Files"), 1, 3) self.file_count = 0 self.purchase_invoices_count = 0 self.default_uom = frappe.db.get_value("Stock Settings", fieldname="stock_uom") - with zipfile.ZipFile(get_full_path(self.zip_file)) as zf: + with zipfile.ZipFile(zip_file.get_full_path()) as zf: for file_name in zf.namelist(): content = get_file_content(file_name, zf) file_content = bs(content, "xml") @@ -126,8 +131,7 @@ class ImportSupplierInvoice(Document): @frappe.whitelist() def process_file_data(self): - self.status = "Processing File Data" - self.save() + self.db_set("status", "Processing File Data", notify=True, commit=True) frappe.enqueue_doc(self.doctype, self.name, "import_xml_data", queue="long", timeout=3600) def publish(self, title, message, count, total): @@ -381,24 +385,3 @@ def create_uom(uom): new_uom.uom_name = uom new_uom.save() return new_uom.uom_name - -def get_full_path(file_name): - """Returns file path from given file name""" - file_path = file_name - - if "/" not in file_path: - file_path = "/files/" + file_path - - if file_path.startswith("/private/files/"): - file_path = get_files_path(*file_path.split("/private/files/", 1)[1].split("/"), is_private=1) - - elif file_path.startswith("/files/"): - file_path = get_files_path(*file_path.split("/files/", 1)[1].split("/")) - - elif file_path.startswith("http"): - pass - - elif not self.file_url: - frappe.throw(_("There is some problem with the file url: {0}").format(file_path)) - - return file_path From 616f1d36242e15fab5429dc57592a7c801e6ba09 Mon Sep 17 00:00:00 2001 From: Jannat Patel <31363128+pateljannat@users.noreply.github.com> Date: Mon, 26 Apr 2021 15:35:49 +0530 Subject: [PATCH 134/326] fix: issue in project custom status (#25452) --- erpnext/projects/doctype/project/project.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py index f9e1359b45..55c5149a9c 100644 --- a/erpnext/projects/doctype/project/project.py +++ b/erpnext/projects/doctype/project/project.py @@ -179,9 +179,6 @@ class Project(Document): if self.percent_complete == 100: self.status = "Completed" - else: - self.status = "Open" - def update_costing(self): from_time_sheet = frappe.db.sql("""select sum(costing_amount) as costing_amount, From 9178d95cca43c7b8124190572e7ae0131d9d7d45 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 26 Apr 2021 15:37:17 +0530 Subject: [PATCH 135/326] fix: can not create item variants (#25433) * fix: item_code assigned to function returning None * fix: ignore has_variants and attributes in variant * fix: patch to remove has_variants and attributes * chore: fix whitespace in translation --- erpnext/controllers/item_variant.py | 3 ++- erpnext/patches.txt | 1 + .../remove_attribute_field_from_item_variant_setting.py | 8 ++++++++ erpnext/stock/doctype/item/item.py | 6 +++--- .../item_variant_settings/item_variant_settings.py | 5 +++-- 5 files changed, 17 insertions(+), 6 deletions(-) create mode 100644 erpnext/patches/v13_0/remove_attribute_field_from_item_variant_setting.py diff --git a/erpnext/controllers/item_variant.py b/erpnext/controllers/item_variant.py index 1f95e00424..051481ff60 100644 --- a/erpnext/controllers/item_variant.py +++ b/erpnext/controllers/item_variant.py @@ -262,7 +262,8 @@ def copy_attributes_to_variant(item, variant): # copy non no-copy fields exclude_fields = ["naming_series", "item_code", "item_name", "show_in_website", - "show_variant_in_website", "opening_stock", "variant_of", "valuation_rate"] + "show_variant_in_website", "opening_stock", "variant_of", "valuation_rate", + "has_variants", "attributes"] if item.variant_based_on=='Manufacturer': # don't copy manufacturer values if based on part no diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 620cc5be62..de9f6e31f8 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -773,3 +773,4 @@ erpnext.patches.v13_0.fix_non_unique_represents_company erpnext.patches.v12_0.add_document_type_field_for_italy_einvoicing erpnext.patches.v13_0.make_non_standard_user_type #13-04-2021 erpnext.patches.v13_0.update_shipment_status +erpnext.patches.v13_0.remove_attribute_field_from_item_variant_setting diff --git a/erpnext/patches/v13_0/remove_attribute_field_from_item_variant_setting.py b/erpnext/patches/v13_0/remove_attribute_field_from_item_variant_setting.py new file mode 100644 index 0000000000..53da7006b9 --- /dev/null +++ b/erpnext/patches/v13_0/remove_attribute_field_from_item_variant_setting.py @@ -0,0 +1,8 @@ +import frappe + +def execute(): + """Remove has_variants and attribute fields from item variant settings.""" + frappe.reload_doc("stock", "doctype", "Item Variant Settings") + + frappe.db.sql("""delete from `tabVariant Field` + where field_name in ('attributes', 'has_variants')""") diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index 7cb84a69f0..dbac79465e 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -63,7 +63,7 @@ class Item(WebsiteGenerator): if self.variant_of: if not self.item_code: template_item_name = frappe.db.get_value("Item", self.variant_of, "item_name") - self.item_code = make_variant_item_code(self.variant_of, template_item_name, self) + make_variant_item_code(self.variant_of, template_item_name, self) else: from frappe.model.naming import set_name_by_naming_series set_name_by_naming_series(self) @@ -674,10 +674,10 @@ class Item(WebsiteGenerator): if not records: return document = _("Stock Reconciliation") if len(records) == 1 else _("Stock Reconciliations") - msg = _("The items {0} and {1} are present in the following {2} : ").format( + msg = _("The items {0} and {1} are present in the following {2} :").format( frappe.bold(old_name), frappe.bold(new_name), document) - msg += '
' + msg += '
' msg += ', '.join([get_link_to_form("Stock Reconciliation", d.parent) for d in records]) + "

" msg += _("Note: To merge the items, create a separate Stock Reconciliation for the old item {0}").format( diff --git a/erpnext/stock/doctype/item_variant_settings/item_variant_settings.py b/erpnext/stock/doctype/item_variant_settings/item_variant_settings.py index 04224424a5..78f1131b76 100644 --- a/erpnext/stock/doctype/item_variant_settings/item_variant_settings.py +++ b/erpnext/stock/doctype/item_variant_settings/item_variant_settings.py @@ -13,10 +13,11 @@ class ItemVariantSettings(Document): def set_default_fields(self): self.fields = [] fields = frappe.get_meta('Item').fields - exclude_fields = ["naming_series", "item_code", "item_name", "show_in_website", + exclude_fields = {"naming_series", "item_code", "item_name", "show_in_website", "show_variant_in_website", "standard_rate", "opening_stock", "image", "description", "variant_of", "valuation_rate", "description", "barcodes", - "website_image", "thumbnail", "website_specifiations", "web_long_description"] + "website_image", "thumbnail", "website_specifiations", "web_long_description", + "has_variants", "attributes"} for d in fields: if not d.no_copy and d.fieldname not in exclude_fields and \ From 7a84d76489c9b621c1ddf755112990088595707f Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 26 Apr 2021 16:03:10 +0530 Subject: [PATCH 136/326] fix: tests --- ...ee_hours_utilization_based_on_timesheet.py | 4 - .../test_employee_util.py | 78 ++++++++++--------- .../test_project_profitability.py | 6 +- 3 files changed, 44 insertions(+), 44 deletions(-) diff --git a/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py index 842fd4d314..87bac02f9a 100644 --- a/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py +++ b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py @@ -167,15 +167,11 @@ class EmployeeHoursReport: def set_employee_department_and_name(self): for emp in self.stats_by_employee: - emp_name = frappe.db.get_value( - 'Employee', emp, 'employee_name' - ) emp_dept = frappe.db.get_value( 'Employee', emp, 'department' ) self.stats_by_employee[emp]['department'] = emp_dept - self.stats_by_employee[emp]['employee_name'] = emp_name def calculate_utilizations(self): TOTAL_HOURS = flt(self.standard_working_hours * self.day_span, 2) diff --git a/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/test_employee_util.py b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/test_employee_util.py index 977a10d1d9..6d955450ec 100644 --- a/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/test_employee_util.py +++ b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/test_employee_util.py @@ -20,6 +20,8 @@ class TestEmployeeUtilization(unittest.TestCase): # Create test timesheets cls.create_test_timesheets() + frappe.db.set_value("HR Settings", "HR Settings", "standard_working_hours", 9) + @classmethod def create_test_timesheets(cls): timesheet1 = frappe.new_doc("Timesheet") @@ -51,7 +53,7 @@ class TestEmployeeUtilization(unittest.TestCase): }) timesheet2.save() - timesheet2.submit() + timesheet2.submit() @classmethod def tearDownClass(cls): @@ -59,8 +61,8 @@ class TestEmployeeUtilization(unittest.TestCase): frappe.db.sql(""" DELETE FROM `tabTimesheet Detail` WHERE parent IN ( - SELECT name - FROM `tabTimesheet` + SELECT name + FROM `tabTimesheet` WHERE company = '_Test Company' ) """) @@ -72,14 +74,14 @@ class TestEmployeeUtilization(unittest.TestCase): filters = { "company": "_Test Company", "from_date": "2021-04-01", - "to_date": "2021-04-03" + "to_date": "2021-04-03" } report = execute(filters) expected_data = self.get_expected_data_for_test_employees() self.assertEqual(report[1], expected_data) - + def test_utilization_report_for_single_employee(self): filters = { "company": "_Test Company", @@ -93,14 +95,14 @@ class TestEmployeeUtilization(unittest.TestCase): emp1_data = frappe.get_doc('Employee', self.test_emp1) expected_data = [ { - 'employee': self.test_emp1, - 'employee_name': emp1_data.employee_name, + 'employee': self.test_emp1, + 'billed_hours': 5.0, + 'non_billed_hours': 0.0, 'department': emp1_data.department, - 'billed_hours': 5.0, - 'non_billed_hours': 0.0, - 'total_hours': 18.0, - 'untracked_hours': 13.0, - 'per_util': 27.78 + 'total_hours': 18.0, + 'untracked_hours': 13.0, + 'per_util': 27.78, + 'per_util_billed_only': 27.78 } ] @@ -115,18 +117,18 @@ class TestEmployeeUtilization(unittest.TestCase): } report = execute(filters) - + emp2_data = frappe.get_doc('Employee', self.test_emp2) expected_data = [ { 'employee': self.test_emp2, - 'employee_name': emp2_data.employee_name, + 'billed_hours': 0.0, + 'non_billed_hours': 10.0, 'department': emp2_data.department, - 'billed_hours': 0.0, - 'non_billed_hours': 10.0, - 'total_hours': 18.0, - 'untracked_hours': 8.0, - 'per_util': 55.56 + 'total_hours': 18.0, + 'untracked_hours': 8.0, + 'per_util': 55.56, + 'per_util_billed_only': 0.0 } ] @@ -155,7 +157,7 @@ class TestEmployeeUtilization(unittest.TestCase): report = execute(filters) summary = report[4] - expected_summary_values = ['41.67%', 5.0, 10.0, 21.0] + expected_summary_values = ['41.67%', '13.89%', 5.0, 10.0] self.assertEqual(len(summary), 4) @@ -167,26 +169,26 @@ class TestEmployeeUtilization(unittest.TestCase): def get_expected_data_for_test_employees(self): emp1_data = frappe.get_doc('Employee', self.test_emp1) emp2_data = frappe.get_doc('Employee', self.test_emp2) - + return [ { - 'employee': self.test_emp2, - 'employee_name': emp2_data.employee_name, - 'department': emp2_data.department, - 'billed_hours': 0.0, - 'non_billed_hours': 10.0, - 'total_hours': 18.0, - 'untracked_hours': 8.0, - 'per_util': 55.56 - }, + 'employee': self.test_emp2, + 'billed_hours': 0.0, + 'non_billed_hours': 10.0, + 'department': emp2_data.department, + 'total_hours': 18.0, + 'untracked_hours': 8.0, + 'per_util': 55.56, + 'per_util_billed_only': 0.0 + }, { - 'employee': self.test_emp1, - 'employee_name': emp1_data.employee_name, - 'department': emp1_data.department, - 'billed_hours': 5.0, - 'non_billed_hours': 0.0, - 'total_hours': 18.0, - 'untracked_hours': 13.0, - 'per_util': 27.78 + 'employee': self.test_emp1, + 'billed_hours': 5.0, + 'non_billed_hours': 0.0, + 'department': emp1_data.department, + 'total_hours': 18.0, + 'untracked_hours': 13.0, + 'per_util': 27.78, + 'per_util_billed_only': 27.78 } ] \ No newline at end of file diff --git a/erpnext/projects/report/project_profitability/test_project_profitability.py b/erpnext/projects/report/project_profitability/test_project_profitability.py index 251b71da59..7fe28b1074 100644 --- a/erpnext/projects/report/project_profitability/test_project_profitability.py +++ b/erpnext/projects/report/project_profitability/test_project_profitability.py @@ -3,7 +3,7 @@ import unittest import frappe from frappe.utils import getdate, nowdate from erpnext.hr.doctype.employee.test_employee import make_employee -from erpnext.projects.doctype.timesheet.test_timesheet import make_salary_structure_for_timesheet, make_timesheet +from erpnext.projects.doctype.timesheet.test_timesheet import make_salary_structure_for_timesheet, make_timesheet from erpnext.projects.doctype.timesheet.timesheet import make_salary_slip, make_sales_invoice from erpnext.projects.report.project_profitability.project_profitability import execute @@ -21,6 +21,8 @@ class TestProjectProfitability(unittest.TestCase): self.sales_invoice.due_date = nowdate() self.sales_invoice.submit() + frappe.db.set_value("HR Settings", "HR Settings", "standard_working_hours", 8) + def test_project_profitability(self): filters = { 'company': '_Test Company', @@ -43,7 +45,7 @@ class TestProjectProfitability(unittest.TestCase): standard_working_hours = frappe.db.get_single_value("HR Settings", "standard_working_hours") utilization = timesheet.total_billed_hours/(self.salary_slip.total_working_days * standard_working_hours) self.assertEqual(utilization, row.utilization) - + profit = self.sales_invoice.base_grand_total - self.salary_slip.base_gross_pay * utilization self.assertEqual(profit, row.profit) From d5ca9474ce792f26e327745cd798fe7006e65262 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 26 Apr 2021 16:10:06 +0530 Subject: [PATCH 137/326] fix: add employee name in report data --- .../employee_hours_utilization_based_on_timesheet.py | 4 ++++ .../test_employee_util.py | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py index 87bac02f9a..842fd4d314 100644 --- a/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py +++ b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py @@ -167,11 +167,15 @@ class EmployeeHoursReport: def set_employee_department_and_name(self): for emp in self.stats_by_employee: + emp_name = frappe.db.get_value( + 'Employee', emp, 'employee_name' + ) emp_dept = frappe.db.get_value( 'Employee', emp, 'department' ) self.stats_by_employee[emp]['department'] = emp_dept + self.stats_by_employee[emp]['employee_name'] = emp_name def calculate_utilizations(self): TOTAL_HOURS = flt(self.standard_working_hours * self.day_span, 2) diff --git a/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/test_employee_util.py b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/test_employee_util.py index 6d955450ec..fa8782733f 100644 --- a/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/test_employee_util.py +++ b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/test_employee_util.py @@ -96,6 +96,7 @@ class TestEmployeeUtilization(unittest.TestCase): expected_data = [ { 'employee': self.test_emp1, + 'employee_name': 'test1@employeeutil.com', 'billed_hours': 5.0, 'non_billed_hours': 0.0, 'department': emp1_data.department, @@ -122,6 +123,7 @@ class TestEmployeeUtilization(unittest.TestCase): expected_data = [ { 'employee': self.test_emp2, + 'employee_name': 'test2@employeeutil.com', 'billed_hours': 0.0, 'non_billed_hours': 10.0, 'department': emp2_data.department, @@ -173,6 +175,7 @@ class TestEmployeeUtilization(unittest.TestCase): return [ { 'employee': self.test_emp2, + 'employee_name': 'test2@employeeutil.com', 'billed_hours': 0.0, 'non_billed_hours': 10.0, 'department': emp2_data.department, @@ -183,6 +186,7 @@ class TestEmployeeUtilization(unittest.TestCase): }, { 'employee': self.test_emp1, + 'employee_name': 'test1@employeeutil.com', 'billed_hours': 5.0, 'non_billed_hours': 0.0, 'department': emp1_data.department, From 34ce4edf2953a781e3aacbf072d8016a796dfd2e Mon Sep 17 00:00:00 2001 From: Saqib Date: Mon, 26 Apr 2021 16:38:57 +0530 Subject: [PATCH 138/326] fix: cannot scan spacebar character in pos (#25479) --- erpnext/selling/page/point_of_sale/pos_item_selector.js | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/selling/page/point_of_sale/pos_item_selector.js b/erpnext/selling/page/point_of_sale/pos_item_selector.js index 9fb3943b53..709fe57747 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_selector.js +++ b/erpnext/selling/page/point_of_sale/pos_item_selector.js @@ -168,6 +168,7 @@ erpnext.PointOfSale.ItemSelector = class { case (iCode >= 160 && iCode <= 164) || iCode == 170: // ^ ! # $ * case iCode >= 186 && iCode <= 194: // (; = , - . / `) case iCode >= 219 && iCode <= 222: // ([ \ ] ') + case iCode == 32: // spacebar if (oEvent.key !== undefined && oEvent.key !== '') { return oEvent.key; } From b643776c53742b7c190dfa38512f192bb4151b1e Mon Sep 17 00:00:00 2001 From: Saqib Date: Mon, 26 Apr 2021 17:12:20 +0530 Subject: [PATCH 139/326] feat(e-invoicing): auto calculate distance for e-way bill generations (#25480) --- erpnext/regional/india/e_invoice/einvoice.js | 18 ++++++++++-------- erpnext/regional/india/e_invoice/utils.py | 10 ++++------ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/erpnext/regional/india/e_invoice/einvoice.js b/erpnext/regional/india/e_invoice/einvoice.js index 8d682beec3..23d4fe9030 100644 --- a/erpnext/regional/india/e_invoice/einvoice.js +++ b/erpnext/regional/india/e_invoice/einvoice.js @@ -115,17 +115,19 @@ erpnext.setup_einvoice_actions = (doctype) => { message += '

'; message += __('You must first use the portal to cancel the e-way bill and then update the cancelled status in the ERPNext system.'); - frappe.msgprint({ + const dialog = frappe.msgprint({ title: __('Update E-Way Bill Cancelled Status?'), message: message, indicator: 'orange', - primary_action: function() { - frappe.call({ - method: 'erpnext.regional.india.e_invoice.utils.cancel_eway_bill', - args: { doctype, docname: name }, - freeze: true, - callback: () => frm.reload_doc() - }); + primary_action: { + action: function() { + frappe.call({ + method: 'erpnext.regional.india.e_invoice.utils.cancel_eway_bill', + args: { doctype, docname: name }, + freeze: true, + callback: () => frm.reload_doc() || dialog.hide() + }); + } }, primary_action_label: __('Yes') }); diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 1d3cb661dd..699441be7e 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -339,9 +339,7 @@ def get_eway_bill_details(invoice): if invoice.is_return: frappe.throw(_('E-Way Bill cannot be generated for Credit Notes & Debit Notes. Please clear fields in the Transporter Section of the invoice.'), title=_('Invalid Fields')) - - if not invoice.distance: - frappe.throw(_('Distance is mandatory for generating e-way bill for an e-invoice.'), title=_('Missing Field')) + mode_of_transport = { '': '', 'Road': '1', 'Air': '2', 'Rail': '3', 'Ship': '4' } vehicle_type = { 'Regular': 'R', 'Over Dimensional Cargo (ODC)': 'O' } @@ -450,7 +448,7 @@ def make_einvoice(invoice): if invoice.is_return: prev_doc_details = get_return_doc_reference(invoice) - if invoice.transporter and flt(invoice.distance) and not invoice.is_return: + if invoice.transporter and not invoice.is_return: eway_bill_details = get_eway_bill_details(invoice) # not yet implemented @@ -1027,12 +1025,12 @@ def generate_eway_bill(doctype, docname, **kwargs): gsp_connector.generate_eway_bill(**kwargs) @frappe.whitelist() -def cancel_eway_bill(doctype, docname, eway_bill, reason, remark): +def cancel_eway_bill(doctype, docname): # TODO: uncomment when eway_bill api from Adequare is enabled # gsp_connector = GSPConnector(doctype, docname) # gsp_connector.cancel_eway_bill(eway_bill, reason, remark) - # update cancelled status only, to be able to cancel irn next + frappe.db.set_value(doctype, docname, 'ewaybill', '') frappe.db.set_value(doctype, docname, 'eway_bill_cancelled', 1) @frappe.whitelist() From 7f1b2de74d989cbaa49d05eb1e1548271bd24092 Mon Sep 17 00:00:00 2001 From: Anurag Mishra <32095923+Anurag810@users.noreply.github.com> Date: Mon, 26 Apr 2021 22:23:48 +0530 Subject: [PATCH 140/326] feat: Employee Referral (#24997) * feat: Employee referal design and status sync * feat: create job Applicant and linked Document * feat: Added list view indicator * chore: formatted file * feat: Addedd Employee Referral form Dashboard * feat: pay compensation via additional salary * test: Employee Rreferral * Update erpnext/hr/doctype/employee_advance/employee_advance.js Co-authored-by: Rucha Mahabal * fix: changes requested * fix: changes requested * fix: sider * fix: translation * fix: test * feat: added to Dashboard * fix: changes * chore: clean-up Employee Referral form - fix labels - fix full name field - fix failing test - set title for Employee Referral form - set fields in List View and Standard Filter * feat: option to add a resume link - copy resume and resume link from Employee Referral to Job Applicant * fix: multiple fixes - confirm before rejecting employee referral - set primary actions for custom buttons - Validations for additional salary against employee referrals with status as accepted only - fix list view indicators - code formatting style * feat: Add field to track Referral Bonus Payment Status - fix visibility of additional salary button Co-authored-by: Rucha Mahabal --- .../employee_advance/employee_advance.js | 53 ++-- .../employee_advance_dashboard.py | 8 +- .../hr/doctype/employee_referral/__init__.py | 0 .../employee_referral/employee_referral.js | 68 ++++ .../employee_referral/employee_referral.json | 294 ++++++++++++++++++ .../employee_referral/employee_referral.py | 71 +++++ .../employee_referral_dashboard.py | 15 + .../employee_referral_list.js | 14 + .../test_employee_referral.py | 60 ++++ .../doctype/job_applicant/job_applicant.json | 10 +- .../hr/doctype/job_applicant/job_applicant.py | 11 + erpnext/hr/workspace/hr/hr.json | 11 +- .../additional_salary/additional_salary.py | 33 +- 13 files changed, 613 insertions(+), 35 deletions(-) create mode 100644 erpnext/hr/doctype/employee_referral/__init__.py create mode 100644 erpnext/hr/doctype/employee_referral/employee_referral.js create mode 100644 erpnext/hr/doctype/employee_referral/employee_referral.json create mode 100644 erpnext/hr/doctype/employee_referral/employee_referral.py create mode 100644 erpnext/hr/doctype/employee_referral/employee_referral_dashboard.py create mode 100644 erpnext/hr/doctype/employee_referral/employee_referral_list.js create mode 100644 erpnext/hr/doctype/employee_referral/test_employee_referral.py diff --git a/erpnext/hr/doctype/employee_advance/employee_advance.js b/erpnext/hr/doctype/employee_advance/employee_advance.js index 5037ceb489..fa4b06aad3 100644 --- a/erpnext/hr/doctype/employee_advance/employee_advance.js +++ b/erpnext/hr/doctype/employee_advance/employee_advance.js @@ -34,7 +34,7 @@ frappe.ui.form.on('Employee Advance', { }; }); - frm.set_query('salary_component', function(doc) { + frm.set_query('salary_component', function() { return { filters: { "type": "Deduction" @@ -44,48 +44,49 @@ frappe.ui.form.on('Employee Advance', { }, refresh: function(frm) { - if (frm.doc.docstatus===1 - && (flt(frm.doc.paid_amount) < flt(frm.doc.advance_amount)) - && frappe.model.can_create("Payment Entry")) { + if (frm.doc.docstatus === 1 && + (flt(frm.doc.paid_amount) < flt(frm.doc.advance_amount)) && + frappe.model.can_create("Payment Entry")) { frm.add_custom_button(__('Payment'), - function() { frm.events.make_payment_entry(frm); }, __('Create')); - } - else if ( - frm.doc.docstatus === 1 - && flt(frm.doc.claimed_amount) < flt(frm.doc.paid_amount) - flt(frm.doc.return_amount) - && frappe.model.can_create("Expense Claim") + function () { + frm.events.make_payment_entry(frm); + }, __('Create')); + } else if ( + frm.doc.docstatus === 1 && + flt(frm.doc.claimed_amount) < flt(frm.doc.paid_amount) - flt(frm.doc.return_amount) && + frappe.model.can_create("Expense Claim") ) { frm.add_custom_button( __("Expense Claim"), - function() { + function () { frm.events.make_expense_claim(frm); }, __('Create') ); } - if (frm.doc.docstatus === 1 - && (flt(frm.doc.claimed_amount) < flt(frm.doc.paid_amount) && flt(frm.doc.paid_amount) != flt(frm.doc.return_amount))) { + if (frm.doc.docstatus === 1 && + (flt(frm.doc.claimed_amount) < flt(frm.doc.paid_amount) && flt(frm.doc.paid_amount) != flt(frm.doc.return_amount))) { - if (frm.doc.repay_unclaimed_amount_from_salary == 0 && frappe.model.can_create("Journal Entry")){ - frm.add_custom_button(__("Return"), function() { + if (frm.doc.repay_unclaimed_amount_from_salary == 0 && frappe.model.can_create("Journal Entry")) { + frm.add_custom_button(__("Return"), function() { frm.trigger('make_return_entry'); }, __('Create')); - }else if (frm.doc.repay_unclaimed_amount_from_salary == 1 && frappe.model.can_create("Additional Salary")){ - frm.add_custom_button(__("Deduction from salary"), function() { + } else if (frm.doc.repay_unclaimed_amount_from_salary == 1 && frappe.model.can_create("Additional Salary")) { + frm.add_custom_button(__("Deduction from salary"), function() { frm.events.make_deduction_via_additional_salary(frm); }, __('Create')); } } }, - make_deduction_via_additional_salary: function(frm){ + make_deduction_via_additional_salary: function(frm) { frappe.call({ method: "erpnext.hr.doctype.employee_advance.employee_advance.create_return_through_additional_salary", args: { doc: frm.doc }, - callback: function (r){ + callback: function(r) { var doclist = frappe.model.sync(r.message); frappe.set_route("Form", doclist[0].doctype, doclist[0].name); } @@ -94,7 +95,7 @@ frappe.ui.form.on('Employee Advance', { make_payment_entry: function(frm) { var method = "erpnext.accounts.doctype.payment_entry.payment_entry.get_payment_entry"; - if(frm.doc.__onload && frm.doc.__onload.make_payment_via_journal_entry) { + if (frm.doc.__onload && frm.doc.__onload.make_payment_via_journal_entry) { method = "erpnext.hr.doctype.employee_advance.employee_advance.make_bank_entry"; } return frappe.call({ @@ -148,11 +149,11 @@ frappe.ui.form.on('Employee Advance', { }); }, - employee: function (frm) { + employee: function(frm) { if (frm.doc.employee) { frappe.run_serially([ - () => frm.trigger('get_employee_currency'), - () => frm.trigger('get_pending_amount') + () => frm.trigger('get_employee_currency'), + () => frm.trigger('get_pending_amount') ]); } }, @@ -199,7 +200,7 @@ frappe.ui.form.on('Employee Advance', { } else { frm.set_value("exchange_rate", 1.0); frm.set_df_property('exchange_rate', 'hidden', 1); - frm.set_df_property("exchange_rate", "description", "" ); + frm.set_df_property("exchange_rate", "description", ""); } frm.refresh_fields(); } @@ -215,8 +216,8 @@ frappe.ui.form.on('Employee Advance', { callback: function(r) { frm.set_value("exchange_rate", flt(r.message)); frm.set_df_property('exchange_rate', 'hidden', 0); - frm.set_df_property("exchange_rate", "description", "1 " + frm.doc.currency - + " = [?] " + company_currency); + frm.set_df_property("exchange_rate", "description", "1 " + frm.doc.currency + + " = [?] " + company_currency); } }); } diff --git a/erpnext/hr/doctype/employee_advance/employee_advance_dashboard.py b/erpnext/hr/doctype/employee_advance/employee_advance_dashboard.py index c3b4a3a889..2f493e2d4e 100644 --- a/erpnext/hr/doctype/employee_advance/employee_advance_dashboard.py +++ b/erpnext/hr/doctype/employee_advance/employee_advance_dashboard.py @@ -4,10 +4,10 @@ from frappe import _ def get_data(): return { 'fieldname': 'employee_advance', - 'non_standard_fieldnames': { - 'Payment Entry': 'reference_name', - 'Journal Entry': 'reference_name' - }, + 'non_standard_fieldnames': { + 'Payment Entry': 'reference_name', + 'Journal Entry': 'reference_name' + }, 'transactions': [ { 'items': ['Expense Claim'] diff --git a/erpnext/hr/doctype/employee_referral/__init__.py b/erpnext/hr/doctype/employee_referral/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/hr/doctype/employee_referral/employee_referral.js b/erpnext/hr/doctype/employee_referral/employee_referral.js new file mode 100644 index 0000000000..9c99bbbefa --- /dev/null +++ b/erpnext/hr/doctype/employee_referral/employee_referral.js @@ -0,0 +1,68 @@ +// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on("Employee Referral", { + refresh: function(frm) { + if (frm.doc.docstatus === 1 && frm.doc.status === "Pending") { + frm.add_custom_button(__("Reject Employee Referral"), function() { + frappe.confirm( + __("Are you sure you want to reject the Employee Referral?"), + function() { + frm.doc.status = "Rejected"; + frm.dirty(); + frm.save_or_update(); + }, + function() { + window.close(); + } + ); + }); + + frm.add_custom_button(__("Create Job Applicant"), function() { + frm.events.create_job_applicant(frm); + }).addClass("btn-primary"); + } + + // To check whether Payment is done or not + if (frm.doc.docstatus === 1 && frm.doc.status === "Accepted") { + frappe.db.get_list("Additional Salary", { + filters: { + ref_docname: cur_frm.doc.name, + docstatus: 1 + }, + fields: ["count(name) as additional_salary_count"] + }).then((data) => { + + let additional_salary_count = data[0].additional_salary_count; + + if (frm.doc.is_applicable_for_referral_bonus && !additional_salary_count) { + frm.add_custom_button(__("Create Additional Salary"), function() { + frm.events.create_additional_salary(frm); + }).addClass("btn-primary"); + } + }); + } + + + + }, + create_job_applicant: function(frm) { + frappe.model.open_mapped_doc({ + method: "erpnext.hr.doctype.employee_referral.employee_referral.create_job_applicant", + frm: frm + }); + }, + + create_additional_salary: function(frm) { + frappe.call({ + method: "erpnext.hr.doctype.employee_referral.employee_referral.create_additional_salary", + args: { + doc: frm.doc + }, + callback: function (r) { + var doclist = frappe.model.sync(r.message); + frappe.set_route("Form", doclist[0].doctype, doclist[0].name); + } + }); + }, +}); diff --git a/erpnext/hr/doctype/employee_referral/employee_referral.json b/erpnext/hr/doctype/employee_referral/employee_referral.json new file mode 100644 index 0000000000..bfd404b435 --- /dev/null +++ b/erpnext/hr/doctype/employee_referral/employee_referral.json @@ -0,0 +1,294 @@ +{ + "actions": [], + "autoname": "format:HR-REF-{####}", + "creation": "2021-03-23 14:54:45.047051", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "first_name", + "last_name", + "full_name", + "email", + "contact_no", + "resume", + "resume_link", + "column_break_6", + "date", + "status", + "for_designation", + "current_employer", + "current_job_title", + "referrer_details_section", + "referrer", + "referrer_name", + "column_break_14", + "is_applicable_for_referral_bonus", + "referral_payment_status", + "department", + "additional_information_section", + "qualification_reason", + "work_references", + "amended_from" + ], + "fields": [ + { + "fieldname": "first_name", + "fieldtype": "Data", + "label": "First Name ", + "reqd": 1 + }, + { + "fieldname": "last_name", + "fieldtype": "Data", + "label": "Last Name", + "reqd": 1 + }, + { + "fieldname": "full_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Full Name", + "read_only": 1 + }, + { + "fieldname": "contact_no", + "fieldtype": "Data", + "in_standard_filter": 1, + "label": "Contact No.", + "options": "Phone" + }, + { + "fieldname": "current_employer", + "fieldtype": "Data", + "label": "Current Employer " + }, + { + "fieldname": "column_break_6", + "fieldtype": "Column Break" + }, + { + "fieldname": "date", + "fieldtype": "Date", + "in_standard_filter": 1, + "label": "Date", + "reqd": 1 + }, + { + "allow_on_submit": 1, + "fieldname": "status", + "fieldtype": "Select", + "in_standard_filter": 1, + "label": "Status", + "no_copy": 1, + "options": "Pending\nIn Process\nAccepted\nRejected", + "permlevel": 1, + "read_only": 1, + "reqd": 1 + }, + { + "fieldname": "current_job_title", + "fieldtype": "Data", + "label": "Current Job Title" + }, + { + "fieldname": "resume", + "fieldtype": "Attach", + "label": "Resume" + }, + { + "fieldname": "referrer_details_section", + "fieldtype": "Section Break", + "label": "Referrer Details" + }, + { + "fetch_from": "employee.department", + "fieldname": "department", + "fieldtype": "Link", + "label": "Department", + "options": "Department", + "read_only": 1 + }, + { + "fieldname": "additional_information_section", + "fieldtype": "Section Break", + "label": "Additional Information " + }, + { + "fieldname": "work_references", + "fieldtype": "Text Editor", + "label": "Work References" + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Employee Referral", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "column_break_14", + "fieldtype": "Column Break" + }, + { + "fieldname": "for_designation", + "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "For Designation ", + "options": "Designation", + "reqd": 1 + }, + { + "fieldname": "email", + "fieldtype": "Data", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Email", + "options": "Email", + "reqd": 1, + "unique": 1 + }, + { + "default": "1", + "fieldname": "is_applicable_for_referral_bonus", + "fieldtype": "Check", + "label": "Is Applicable for Referral Bonus" + }, + { + "fieldname": "qualification_reason", + "fieldtype": "Text Editor", + "label": "Why is this Candidate Qualified for this Position?" + }, + { + "fieldname": "referrer", + "fieldtype": "Link", + "in_standard_filter": 1, + "label": "Referrer", + "options": "Employee", + "reqd": 1 + }, + { + "fetch_from": "referrer.employee_name", + "fieldname": "referrer_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Referrer Name", + "read_only": 1 + }, + { + "fieldname": "resume_link", + "fieldtype": "Data", + "label": "Resume Link" + }, + { + "fieldname": "referral_payment_status", + "fieldtype": "Select", + "label": "Referral Bonus Payment Status", + "options": "\nUnpaid\nPaid", + "read_only": 1 + } + ], + "index_web_pages_for_search": 1, + "is_submittable": 1, + "links": [], + "modified": "2021-04-26 21:21:38.094086", + "modified_by": "Administrator", + "module": "HR", + "name": "Employee Referral", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + }, + { + "amend": 1, + "create": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Employee", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "amend": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "HR Manager", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "amend": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "HR User", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "delete": 1, + "email": 1, + "export": 1, + "permlevel": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "HR Manager", + "share": 1, + "write": 1 + }, + { + "delete": 1, + "email": 1, + "export": 1, + "permlevel": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "HR User", + "share": 1, + "write": 1 + }, + { + "email": 1, + "export": 1, + "permlevel": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Employee", + "share": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "full_name" +} \ No newline at end of file diff --git a/erpnext/hr/doctype/employee_referral/employee_referral.py b/erpnext/hr/doctype/employee_referral/employee_referral.py new file mode 100644 index 0000000000..45d68729ce --- /dev/null +++ b/erpnext/hr/doctype/employee_referral/employee_referral.py @@ -0,0 +1,71 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe import _ +from frappe.utils import get_link_to_form +from frappe.model.document import Document + +class EmployeeReferral(Document): + def validate(self): + self.set_full_name() + self.set_referral_bonus_payment_status() + + def set_full_name(self): + self.full_name = " ".join(filter(None, [self.first_name, self.last_name])) + + def set_referral_bonus_payment_status(self): + if not self.is_applicable_for_referral_bonus: + self.referral_payment_status = "" + else: + if not self.referral_payment_status: + self.referral_payment_status = "Unpaid" + + +@frappe.whitelist() +def create_job_applicant(source_name, target_doc=None): + emp_ref = frappe.get_doc("Employee Referral", source_name) + #just for Api call if some set status apart from default Status + status = emp_ref.status + if emp_ref.status in ["Pending", "In process"]: + status = "Open" + + job_applicant = frappe.new_doc("Job Applicant") + job_applicant.employee_referral = emp_ref.name + job_applicant.status = status + job_applicant.applicant_name = emp_ref.full_name + job_applicant.email_id = emp_ref.email + job_applicant.phone_number = emp_ref.contact_no + job_applicant.resume_attachment = emp_ref.resume + job_applicant.resume_link = emp_ref.resume_link + job_applicant.save() + + frappe.msgprint(_("Job Applicant {0} created successfully.").format( + get_link_to_form("Job Applicant", job_applicant.name)), + title=_("Success"), indicator="green") + + emp_ref.db_set("status", "In Process") + + return job_applicant + + +@frappe.whitelist() +def create_additional_salary(doc): + import json + from six import string_types + + if isinstance(doc, string_types): + doc = frappe._dict(json.loads(doc)) + + if not frappe.db.exists("Additional Salary", {"ref_docname": doc.name}): + additional_salary = frappe.new_doc("Additional Salary") + additional_salary.employee = doc.referrer + additional_salary.company = frappe.db.get_value("Employee", doc.referrer, "company") + additional_salary.overwrite_salary_structure_amount = 0 + additional_salary.ref_doctype = doc.doctype + additional_salary.ref_docname = doc.name + + return additional_salary + diff --git a/erpnext/hr/doctype/employee_referral/employee_referral_dashboard.py b/erpnext/hr/doctype/employee_referral/employee_referral_dashboard.py new file mode 100644 index 0000000000..afa2a1ff1f --- /dev/null +++ b/erpnext/hr/doctype/employee_referral/employee_referral_dashboard.py @@ -0,0 +1,15 @@ +from __future__ import unicode_literals + +def get_data(): + return { + 'fieldname': 'employee_referral', + 'non_standard_fieldnames': { + 'Additional Salary': 'ref_docname' + }, + 'transactions': [ + { + 'items': ['Job Applicant', 'Additional Salary'] + }, + + ] + } \ No newline at end of file diff --git a/erpnext/hr/doctype/employee_referral/employee_referral_list.js b/erpnext/hr/doctype/employee_referral/employee_referral_list.js new file mode 100644 index 0000000000..7533ab635f --- /dev/null +++ b/erpnext/hr/doctype/employee_referral/employee_referral_list.js @@ -0,0 +1,14 @@ +frappe.listview_settings['Employee Referral'] = { + add_fields: ["status"], + get_indicator: function (doc) { + if (doc.status == "Pending") { + return [__(doc.status), "grey", "status,=," + doc.status]; + } else if (doc.status == "In Process") { + return [__(doc.status), "orange", "status,=," + doc.status]; + } else if (doc.status == "Accepted") { + return [__(doc.status), "green", "status,=," + doc.status]; + } else if (doc.status == "Rejected") { + return [__(doc.status), "red", "status,=," + doc.status]; + } + }, +}; \ No newline at end of file diff --git a/erpnext/hr/doctype/employee_referral/test_employee_referral.py b/erpnext/hr/doctype/employee_referral/test_employee_referral.py new file mode 100644 index 0000000000..a674f39026 --- /dev/null +++ b/erpnext/hr/doctype/employee_referral/test_employee_referral.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +import frappe +from frappe.utils import today +from erpnext.hr.doctype.designation.test_designation import create_designation +from erpnext.hr.doctype.employee_referral.employee_referral import create_job_applicant, create_additional_salary +from erpnext.hr.doctype.employee.test_employee import make_employee +import unittest + +class TestEmployeeReferral(unittest.TestCase): + def test_workflow_and_status_sync(self): + emp_ref = create_employee_referral() + + #Check Initial status + self.assertTrue(emp_ref.status, "Pending") + + job_applicant = create_job_applicant(emp_ref.name) + + + #Check status sync + emp_ref.reload() + self.assertTrue(emp_ref.status, "In Process") + + job_applicant.reload() + job_applicant.status = "Rejected" + job_applicant.save() + + emp_ref.reload() + self.assertTrue(emp_ref.status, "Rejected") + + job_applicant.reload() + job_applicant.status = "Accepted" + job_applicant.save() + + emp_ref.reload() + self.assertTrue(emp_ref.status, "Accepted") + + + # Check for Referral reference in additional salary + + add_sal = create_additional_salary(emp_ref) + self.assertTrue(add_sal.ref_docname, emp_ref.name) + + +def create_employee_referral(): + emp_ref = frappe.new_doc("Employee Referral") + emp_ref.first_name = "Mahesh" + emp_ref.last_name = "Singh" + emp_ref.email = "a@b.c" + emp_ref.date = today() + emp_ref.for_designation = create_designation().name + emp_ref.referrer = make_employee("testassetmovemp@example.com", company="_Test Company") + emp_ref.is_applicable_for_employee_referral_compensation = 1 + emp_ref.save() + emp_ref.submit() + + return emp_ref \ No newline at end of file diff --git a/erpnext/hr/doctype/job_applicant/job_applicant.json b/erpnext/hr/doctype/job_applicant/job_applicant.json index 1360fd1890..bcea5f50d9 100644 --- a/erpnext/hr/doctype/job_applicant/job_applicant.json +++ b/erpnext/hr/doctype/job_applicant/job_applicant.json @@ -18,6 +18,7 @@ "job_title", "source", "source_name", + "employee_referral", "applicant_rating", "section_break_6", "notes", @@ -152,13 +153,20 @@ "fieldtype": "Link", "label": "Currency", "options": "Currency" + }, + { + "fieldname": "employee_referral", + "fieldtype": "Link", + "label": "Employee Referral", + "options": "Employee Referral", + "read_only": 1 } ], "icon": "fa fa-user", "idx": 1, "index_web_pages_for_search": 1, "links": [], - "modified": "2020-09-18 12:39:02.557563", + "modified": "2021-03-24 15:51:11.117517", "modified_by": "Administrator", "module": "HR", "name": "Job Applicant", diff --git a/erpnext/hr/doctype/job_applicant/job_applicant.py b/erpnext/hr/doctype/job_applicant/job_applicant.py index a6aef04919..0594ba395b 100644 --- a/erpnext/hr/doctype/job_applicant/job_applicant.py +++ b/erpnext/hr/doctype/job_applicant/job_applicant.py @@ -28,10 +28,21 @@ class JobApplicant(Document): if self.email_id: validate_email_address(self.email_id, True) + if self.employee_referral: + self.set_status_for_employee_referral() + if not self.applicant_name and self.email_id: guess = self.email_id.split('@')[0] self.applicant_name = ' '.join([p.capitalize() for p in guess.split('.')]) + def set_status_for_employee_referral(self): + emp_ref = frappe.get_doc("Employee Referral", self.employee_referral) + if self.status in ["Open", "Replied", "Hold"]: + emp_ref.db_set("status", "In Process") + elif self.status in ["Accepted", "Rejected"]: + emp_ref.db_set("status", self.status) + + def check_email_id_is_unique(self): if self.email_id: names = frappe.db.sql_list("""select name from `tabJob Applicant` diff --git a/erpnext/hr/workspace/hr/hr.json b/erpnext/hr/workspace/hr/hr.json index f4b56a0e17..c5201c22c9 100644 --- a/erpnext/hr/workspace/hr/hr.json +++ b/erpnext/hr/workspace/hr/hr.json @@ -520,6 +520,15 @@ "onboard": 1, "type": "Link" }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Employee Referral", + "link_to": "Employee Referral", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, { "dependencies": "", "hidden": 0, @@ -814,7 +823,7 @@ "type": "Link" } ], - "modified": "2021-03-24 17:35:21.483297", + "modified": "2021-04-26 13:36:15.413819", "modified_by": "Administrator", "module": "HR", "name": "HR", diff --git a/erpnext/payroll/doctype/additional_salary/additional_salary.py b/erpnext/payroll/doctype/additional_salary/additional_salary.py index 13b6c05e22..ebeddf97f9 100644 --- a/erpnext/payroll/doctype/additional_salary/additional_salary.py +++ b/erpnext/payroll/doctype/additional_salary/additional_salary.py @@ -13,12 +13,19 @@ class AdditionalSalary(Document): if self.ref_doctype == "Employee Advance" and self.ref_docname: frappe.db.set_value("Employee Advance", self.ref_docname, "return_amount", self.amount) + self.update_employee_referral() + + def on_cancel(self): + self.update_employee_referral(cancel=True) + def validate(self): self.validate_dates() self.validate_salary_structure() self.validate_recurring_additional_salary_overlap() + self.validate_employee_referral() + if self.amount < 0: - frappe.throw(_("Amount should not be less than zero.")) + frappe.throw(_("Amount should not be less than zero")) def validate_salary_structure(self): if not frappe.db.exists('Salary Structure Assignment', {'employee': self.employee}): @@ -70,6 +77,27 @@ class AdditionalSalary(Document): if self.payroll_date and getdate(self.payroll_date) > getdate(relieving_date): frappe.throw(_("Payroll date can not be greater than employee's relieving date.")) + def validate_employee_referral(self): + if self.ref_doctype == "Employee Referral": + referral_details = frappe.db.get_value("Employee Referral", self.ref_docname, + ["is_applicable_for_referral_bonus", "status"], as_dict=1) + + if not referral_details.is_applicable_for_referral_bonus: + frappe.throw(_("Employee Referral {0} is not applicable for referral bonus.").format( + self.ref_docname)) + + if self.type == "Deduction": + frappe.throw(_("Earning Salary Component is required for Employee Referral Bonus.")) + + if referral_details.status != "Accepted": + frappe.throw(_("Additional Salary for referral bonus can only be created against Employee Referral with status {0}").format( + frappe.bold("Accepted"))) + + def update_employee_referral(self, cancel=False): + if self.ref_doctype == "Employee Referral": + status = "Unpaid" if cancel else "Paid" + frappe.db.set_value("Employee Referral", self.ref_docname, "referral_payment_status", status) + def get_amount(self, sal_start_date, sal_end_date): start_date = getdate(sal_start_date) end_date = getdate(sal_end_date) @@ -110,8 +138,7 @@ def get_additional_salaries(employee, start_date, end_date, component_type): for d in additional_salary_list: if d.overwrite: if d.component in components_to_overwrite: - frappe.throw(_("Multiple Additional Salaries with overwrite " - "property exist for Salary Component {0} between {1} and {2}.").format( + frappe.throw(_("Multiple Additional Salaries with overwrite property exist for Salary Component {0} between {1} and {2}.").format( frappe.bold(d.component), start_date, end_date), title=_("Error")) components_to_overwrite.append(d.component) From df06e49e0c821e2ea2989588cafbbeeb0a21ec83 Mon Sep 17 00:00:00 2001 From: Afshan <33727827+AfshanKhan@users.noreply.github.com> Date: Mon, 26 Apr 2021 22:48:13 +0530 Subject: [PATCH 141/326] feat: enable custom field search on POS (#25421) * feat: multi lingual item search in POS * fix: limit the options for selection and broken down funtion for geting condition --- .../doctype/pos_search_fields/__init__.py | 0 .../pos_search_fields/pos_search_fields.json | 37 +++++++++++++++++ .../pos_search_fields/pos_search_fields.py | 10 +++++ .../doctype/pos_settings/pos_settings.js | 40 +++++++++++++++++++ .../doctype/pos_settings/pos_settings.json | 11 ++++- .../page/point_of_sale/point_of_sale.py | 26 +++++++++--- 6 files changed, 117 insertions(+), 7 deletions(-) create mode 100644 erpnext/accounts/doctype/pos_search_fields/__init__.py create mode 100644 erpnext/accounts/doctype/pos_search_fields/pos_search_fields.json create mode 100644 erpnext/accounts/doctype/pos_search_fields/pos_search_fields.py diff --git a/erpnext/accounts/doctype/pos_search_fields/__init__.py b/erpnext/accounts/doctype/pos_search_fields/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/accounts/doctype/pos_search_fields/pos_search_fields.json b/erpnext/accounts/doctype/pos_search_fields/pos_search_fields.json new file mode 100644 index 0000000000..a627f5b5be --- /dev/null +++ b/erpnext/accounts/doctype/pos_search_fields/pos_search_fields.json @@ -0,0 +1,37 @@ +{ + "actions": [], + "creation": "2021-04-19 14:56:06.652327", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "field", + "fieldname" + ], + "fields": [ + { + "fieldname": "fieldname", + "fieldtype": "Data", + "hidden": 1, + "label": "Fieldname" + }, + { + "fieldname": "field", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Field" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-04-21 11:12:54.632093", + "modified_by": "Administrator", + "module": "Accounts", + "name": "POS Search Fields", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/pos_search_fields/pos_search_fields.py b/erpnext/accounts/doctype/pos_search_fields/pos_search_fields.py new file mode 100644 index 0000000000..720ea77745 --- /dev/null +++ b/erpnext/accounts/doctype/pos_search_fields/pos_search_fields.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, 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 POSSearchFields(Document): + pass diff --git a/erpnext/accounts/doctype/pos_settings/pos_settings.js b/erpnext/accounts/doctype/pos_settings/pos_settings.js index 3625393a80..9003af56a5 100644 --- a/erpnext/accounts/doctype/pos_settings/pos_settings.js +++ b/erpnext/accounts/doctype/pos_settings/pos_settings.js @@ -1,9 +1,17 @@ // Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt +let search_fields_datatypes = ['Data', 'Link', 'Dynamic Link', 'Long Text', 'Select', 'Small Text', 'Text', 'Text Editor']; +let do_not_include_fields = ["naming_series", "item_code", "item_name", "stock_uom", "hub_sync_id", "asset_naming_series", + "default_material_request_type", "valuation_method", "warranty_period", "weight_uom", "batch_number_series", + "serial_no_series", "purchase_uom", "customs_tariff_number", "sales_uom", "deferred_revenue_account", + "deferred_expense_account", "quality_inspection_template", "route", "slideshow", "website_image_alt", "thumbnail", + "web_long_description", "hub_sync_id"] + frappe.ui.form.on('POS Settings', { onload: function(frm) { frm.trigger("get_invoice_fields"); + frm.trigger("add_search_options"); }, get_invoice_fields: function(frm) { @@ -21,6 +29,38 @@ frappe.ui.form.on('POS Settings', { ); }); + }, + + add_search_options: function(frm) { + frappe.model.with_doctype("Item", () => { + var fields = $.map(frappe.get_doc("DocType", "Item").fields, function(d) { + if (search_fields_datatypes.includes(d.fieldtype) && !(do_not_include_fields.includes(d.fieldname))) { + return [d.label]; + } else { + return null; + } + }); + + fields.unshift(''); + frm.fields_dict.pos_search_fields.grid.update_docfield_property('field', 'options', fields); + }); + + } +}); + +frappe.ui.form.on("POS Search Fields", { + field: function(frm, doctype, name) { + var doc = frappe.get_doc(doctype, name); + var df = $.map(frappe.get_doc("DocType", "Item").fields, function(d) { + if (doc.field == d.label && search_fields_datatypes.includes(d.fieldtype)) { + return d; + } else { + return null; + } + })[0]; + + doc.fieldname = df.fieldname; + frm.refresh_field("fields"); } }); diff --git a/erpnext/accounts/doctype/pos_settings/pos_settings.json b/erpnext/accounts/doctype/pos_settings/pos_settings.json index 35395889a6..962eb94a29 100644 --- a/erpnext/accounts/doctype/pos_settings/pos_settings.json +++ b/erpnext/accounts/doctype/pos_settings/pos_settings.json @@ -5,7 +5,8 @@ "editable_grid": 1, "engine": "InnoDB", "field_order": [ - "invoice_fields" + "invoice_fields", + "pos_search_fields" ], "fields": [ { @@ -13,11 +14,17 @@ "fieldtype": "Table", "label": "POS Field", "options": "POS Field" + }, + { + "fieldname": "pos_search_fields", + "fieldtype": "Table", + "label": "POS Search Fields", + "options": "POS Search Fields" } ], "issingle": 1, "links": [], - "modified": "2020-06-01 15:46:41.478928", + "modified": "2021-04-19 14:56:24.465218", "modified_by": "Administrator", "module": "Accounts", "name": "POS Settings", diff --git a/erpnext/selling/page/point_of_sale/point_of_sale.py b/erpnext/selling/page/point_of_sale/point_of_sale.py index 062cba19e6..750a1a6071 100644 --- a/erpnext/selling/page/point_of_sale/point_of_sale.py +++ b/erpnext/selling/page/point_of_sale/point_of_sale.py @@ -23,7 +23,7 @@ def get_items(start, page_length, price_list, item_group, pos_profile, search_va if search_value: data = search_serial_or_batch_or_barcode_number(search_value) - + item_code = data.get("item_code") if data.get("item_code") else search_value serial_no = data.get("serial_no") if data.get("serial_no") else "" batch_no = data.get("batch_no") if data.get("batch_no") else "" @@ -31,7 +31,7 @@ def get_items(start, page_length, price_list, item_group, pos_profile, search_va if data: item_info = frappe.db.get_value( - "Item", data.get("item_code"), + "Item", data.get("item_code"), ["name as item_code", "item_name", "description", "stock_uom", "image as item_image", "is_stock_item"] , as_dict=1) item_info.setdefault('serial_no', serial_no) @@ -139,8 +139,24 @@ def get_conditions(item_code, serial_no, batch_no, barcode): if serial_no or batch_no or barcode: return "item.name = {0}".format(frappe.db.escape(item_code)) - return """(item.name like {item_code} - or item.item_name like {item_code})""".format(item_code = frappe.db.escape('%' + item_code + '%')) + return make_condition(item_code) + +def make_condition(item_code): + condition = "(" + condition += """item.name like {item_code} + or item.item_name like {item_code}""".format(item_code = frappe.db.escape('%' + item_code + '%')) + condition += add_search_fields_condition(item_code) + condition += ")" + + return condition + +def add_search_fields_condition(item_code): + condition = '' + search_fields = frappe.get_all('POS Search Fields', fields = ['fieldname']) + if search_fields: + for field in search_fields: + condition += " or item.{0} like {1}".format(field['fieldname'], frappe.db.escape('%' + item_code + '%')) + return condition def get_item_group_condition(pos_profile): cond = "and 1=1" @@ -257,4 +273,4 @@ def set_customer_info(fieldname, customer, value=""): elif fieldname == 'mobile_no': contact_doc.set('phone_nos', [{ 'phone': value, 'is_primary_mobile_no': 1}]) frappe.db.set_value('Customer', customer, 'mobile_no', value) - contact_doc.save() \ No newline at end of file + contact_doc.save() From 5b9d3f15a243511a4fedcd12eb25fbd248399aac Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Tue, 27 Apr 2021 12:34:10 +0200 Subject: [PATCH 142/326] docs: replace whitespace indent in docstring with tabs --- erpnext/regional/report/datev/datev.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/regional/report/datev/datev.py b/erpnext/regional/report/datev/datev.py index 1a215031c1..a5ca7eee5d 100644 --- a/erpnext/regional/report/datev/datev.py +++ b/erpnext/regional/report/datev/datev.py @@ -3,9 +3,9 @@ Provide a report and downloadable CSV according to the German DATEV format. - Query report showing only the columns that contain data, formatted nicely for - dispay to the user. + dispay to the user. - CSV download functionality `download_datev_csv` that provides a CSV file with - all required columns. Used to import the data into the DATEV Software. + all required columns. Used to import the data into the DATEV Software. """ from __future__ import unicode_literals From 8a1e5e189ca0705ed85db27f9665ac34e066cea3 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 27 Apr 2021 16:57:34 +0530 Subject: [PATCH 143/326] fix: Ageing errors in PSOA --- .../process_statement_of_accounts.html | 12 ++++++------ .../process_statement_of_accounts.py | 13 +++++++++---- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html index b623898829..f61aacbce2 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html @@ -60,8 +60,8 @@

-{% if aging %} -

{{ _("Ageing Report Based On ") }} {{ aging.ageing_based_on }}

+{% if ageing %} +

{{ _("Ageing Report Based On ") }} {{ ageing.ageing_based_on }}

{{ _("Up to " ) }} {{ frappe.format(filters.to_date, 'Date')}}
@@ -78,10 +78,10 @@ - {{ frappe.utils.fmt_money(aging.range1, currency=filters.presentation_currency) }} - {{ frappe.utils.fmt_money(aging.range2, currency=filters.presentation_currency) }} - {{ frappe.utils.fmt_money(aging.range3, currency=filters.presentation_currency) }} - {{ frappe.utils.fmt_money(aging.range4, currency=filters.presentation_currency) }} + {{ frappe.utils.fmt_money(ageing.range1, currency=filters.presentation_currency) }} + {{ frappe.utils.fmt_money(ageing.range2, currency=filters.presentation_currency) }} + {{ frappe.utils.fmt_money(ageing.range3, currency=filters.presentation_currency) }} + {{ frappe.utils.fmt_money(ageing.range4, currency=filters.presentation_currency) }} diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py index b6149e89f5..62090c0255 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py @@ -40,7 +40,7 @@ class ProcessStatementOfAccounts(Document): def get_report_pdf(doc, consolidated=True): statement_dict = {} - aging = '' + ageing = '' base_template_path = "frappe/www/printview.html" template_path = "erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html" @@ -56,8 +56,10 @@ def get_report_pdf(doc, consolidated=True): 'range4': 120, 'customer': entry.customer }) - col1, aging = get_ageing(ageing_filters) - aging[0]['ageing_based_on'] = doc.ageing_based_on + col1, ageing = get_ageing(ageing_filters) + + if ageing: + ageing[0]['ageing_based_on'] = doc.ageing_based_on tax_id = frappe.get_doc('Customer', entry.customer).tax_id presentation_currency = get_party_account_currency('Customer', entry.customer, doc.company) \ @@ -87,11 +89,14 @@ def get_report_pdf(doc, consolidated=True): if len(res) == 3: continue + html = frappe.render_template(template_path, \ - {"filters": filters, "data": res, "aging": aging[0] if doc.include_ageing else None}) + {"filters": filters, "data": res, "ageing": ageing[0] if (doc.include_ageing and ageing) else None}) + html = frappe.render_template(base_template_path, {"body": html, \ "css": get_print_style(), "title": "Statement For " + entry.customer}) statement_dict[entry.customer] = html + if not bool(statement_dict): return False elif consolidated: From ad4365eb0eb4583333243c5c7a18995e736ec0d0 Mon Sep 17 00:00:00 2001 From: Saqib Date: Tue, 27 Apr 2021 20:00:21 +0530 Subject: [PATCH 144/326] fix: unexpected keyword argument 'merge_logs' (#25489) * fix: unexpected keyword arguement 'merge_logs' * fix: reference error * fix: test --- .../pos_invoice_merge_log.py | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py index 6d2cffcf68..4d5472df4b 100644 --- a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py +++ b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py @@ -235,11 +235,11 @@ def get_invoice_customer_map(pos_invoices): return pos_invoice_customer_map -def consolidate_pos_invoices(pos_invoices=[], closing_entry={}): - invoices = pos_invoices or closing_entry.get('pos_transactions') or get_all_unconsolidated_invoices() +def consolidate_pos_invoices(pos_invoices=None, closing_entry=None): + invoices = pos_invoices or (closing_entry and closing_entry.get('pos_transactions')) or get_all_unconsolidated_invoices() invoice_by_customer = get_invoice_customer_map(invoices) - if len(invoices) >= 5 and closing_entry: + if len(invoices) >= 1 and closing_entry: closing_entry.set_status(update=True, status='Queued') enqueue_job(create_merge_logs, invoice_by_customer=invoice_by_customer, closing_entry=closing_entry) else: @@ -252,18 +252,18 @@ def unconsolidate_pos_invoices(closing_entry): pluck='name' ) - if len(merge_logs) >= 5: + if len(merge_logs) >= 1: closing_entry.set_status(update=True, status='Queued') enqueue_job(cancel_merge_logs, merge_logs=merge_logs, closing_entry=closing_entry) else: cancel_merge_logs(merge_logs, closing_entry) -def create_merge_logs(invoice_by_customer, closing_entry={}): +def create_merge_logs(invoice_by_customer, closing_entry=None): for customer, invoices in iteritems(invoice_by_customer): merge_log = frappe.new_doc('POS Invoice Merge Log') - merge_log.posting_date = getdate(closing_entry.get('posting_date')) + merge_log.posting_date = getdate(closing_entry.get('posting_date')) if closing_entry else nowdate() merge_log.customer = customer - merge_log.pos_closing_entry = closing_entry.get('name', None) + merge_log.pos_closing_entry = closing_entry.get('name') if closing_entry else None merge_log.set('pos_invoices', invoices) merge_log.save(ignore_permissions=True) @@ -273,7 +273,7 @@ def create_merge_logs(invoice_by_customer, closing_entry={}): closing_entry.set_status(update=True, status='Submitted') closing_entry.update_opening_entry() -def cancel_merge_logs(merge_logs, closing_entry={}): +def cancel_merge_logs(merge_logs, closing_entry=None): for log in merge_logs: merge_log = frappe.get_doc('POS Invoice Merge Log', log) merge_log.flags.ignore_permissions = True @@ -283,20 +283,20 @@ def cancel_merge_logs(merge_logs, closing_entry={}): closing_entry.set_status(update=True, status='Cancelled') closing_entry.update_opening_entry(for_cancel=True) -def enqueue_job(job, merge_logs=None, invoice_by_customer=None, closing_entry=None): +def enqueue_job(job, **kwargs): check_scheduler_status() + closing_entry = kwargs.get('closing_entry') or {} + job_name = closing_entry.get("name") if not job_already_enqueued(job_name): enqueue( job, + **kwargs, queue="long", timeout=10000, event="processing_merge_logs", job_name=job_name, - closing_entry=closing_entry, - invoice_by_customer=invoice_by_customer, - merge_logs=merge_logs, now=frappe.conf.developer_mode or frappe.flags.in_test ) From 7b3dd3585b5f2371254a02fe2ae713a23bdc9871 Mon Sep 17 00:00:00 2001 From: Saqib Date: Tue, 27 Apr 2021 20:00:21 +0530 Subject: [PATCH 145/326] fix: unexpected keyword argument 'merge_logs' (#25489) * fix: unexpected keyword arguement 'merge_logs' * fix: reference error * fix: test --- .../pos_invoice_merge_log.py | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py index 6d2cffcf68..4d5472df4b 100644 --- a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py +++ b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py @@ -235,11 +235,11 @@ def get_invoice_customer_map(pos_invoices): return pos_invoice_customer_map -def consolidate_pos_invoices(pos_invoices=[], closing_entry={}): - invoices = pos_invoices or closing_entry.get('pos_transactions') or get_all_unconsolidated_invoices() +def consolidate_pos_invoices(pos_invoices=None, closing_entry=None): + invoices = pos_invoices or (closing_entry and closing_entry.get('pos_transactions')) or get_all_unconsolidated_invoices() invoice_by_customer = get_invoice_customer_map(invoices) - if len(invoices) >= 5 and closing_entry: + if len(invoices) >= 1 and closing_entry: closing_entry.set_status(update=True, status='Queued') enqueue_job(create_merge_logs, invoice_by_customer=invoice_by_customer, closing_entry=closing_entry) else: @@ -252,18 +252,18 @@ def unconsolidate_pos_invoices(closing_entry): pluck='name' ) - if len(merge_logs) >= 5: + if len(merge_logs) >= 1: closing_entry.set_status(update=True, status='Queued') enqueue_job(cancel_merge_logs, merge_logs=merge_logs, closing_entry=closing_entry) else: cancel_merge_logs(merge_logs, closing_entry) -def create_merge_logs(invoice_by_customer, closing_entry={}): +def create_merge_logs(invoice_by_customer, closing_entry=None): for customer, invoices in iteritems(invoice_by_customer): merge_log = frappe.new_doc('POS Invoice Merge Log') - merge_log.posting_date = getdate(closing_entry.get('posting_date')) + merge_log.posting_date = getdate(closing_entry.get('posting_date')) if closing_entry else nowdate() merge_log.customer = customer - merge_log.pos_closing_entry = closing_entry.get('name', None) + merge_log.pos_closing_entry = closing_entry.get('name') if closing_entry else None merge_log.set('pos_invoices', invoices) merge_log.save(ignore_permissions=True) @@ -273,7 +273,7 @@ def create_merge_logs(invoice_by_customer, closing_entry={}): closing_entry.set_status(update=True, status='Submitted') closing_entry.update_opening_entry() -def cancel_merge_logs(merge_logs, closing_entry={}): +def cancel_merge_logs(merge_logs, closing_entry=None): for log in merge_logs: merge_log = frappe.get_doc('POS Invoice Merge Log', log) merge_log.flags.ignore_permissions = True @@ -283,20 +283,20 @@ def cancel_merge_logs(merge_logs, closing_entry={}): closing_entry.set_status(update=True, status='Cancelled') closing_entry.update_opening_entry(for_cancel=True) -def enqueue_job(job, merge_logs=None, invoice_by_customer=None, closing_entry=None): +def enqueue_job(job, **kwargs): check_scheduler_status() + closing_entry = kwargs.get('closing_entry') or {} + job_name = closing_entry.get("name") if not job_already_enqueued(job_name): enqueue( job, + **kwargs, queue="long", timeout=10000, event="processing_merge_logs", job_name=job_name, - closing_entry=closing_entry, - invoice_by_customer=invoice_by_customer, - merge_logs=merge_logs, now=frappe.conf.developer_mode or frappe.flags.in_test ) From a86a51f0207cdd9480d84cbebea3abd93a60f435 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Fri, 23 Apr 2021 20:03:50 +0530 Subject: [PATCH 146/326] fix(HR): Permission error while adding weekly holidays (#25450) --- erpnext/hr/doctype/holiday_list/holiday_list.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/hr/doctype/holiday_list/holiday_list.py b/erpnext/hr/doctype/holiday_list/holiday_list.py index 6df7bc88c0..67630a0abe 100644 --- a/erpnext/hr/doctype/holiday_list/holiday_list.py +++ b/erpnext/hr/doctype/holiday_list/holiday_list.py @@ -16,6 +16,7 @@ class HolidayList(Document): self.validate_days() self.total_holidays = len(self.holidays) + @frappe.whitelist() def get_weekly_off_dates(self): self.validate_values() date_list = self.get_weekly_off_date_list(self.from_date, self.to_date) From 3e5c6424ed4f15c16be0f909f0cf110879fa50d5 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Tue, 27 Apr 2021 20:28:01 +0550 Subject: [PATCH 147/326] bumped to version 13.1.1 --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 4da0605370..ea83d08bf2 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -5,7 +5,7 @@ import frappe from erpnext.hooks import regional_overrides from frappe.utils import getdate -__version__ = '13.1.0' +__version__ = '13.1.1' def get_default_company(user=None): '''Get default company for user''' From c9187b00af2d13ec3f5c1a696e66c74346778fa0 Mon Sep 17 00:00:00 2001 From: Mohammad Hasnain Mohsin Rajan Date: Wed, 28 Apr 2021 13:32:27 +0530 Subject: [PATCH 148/326] fix: change coveralls version (#25499) --- .github/workflows/ci-tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml index 824b74e013..4103bc68a9 100644 --- a/.github/workflows/ci-tests.yml +++ b/.github/workflows/ci-tests.yml @@ -85,8 +85,8 @@ jobs: run: | cp ~/frappe-bench/sites/.coverage ${GITHUB_WORKSPACE} cd ${GITHUB_WORKSPACE} - pip install coveralls==3.0.1 - pip install coverage==5.5 + pip install coveralls==2.2.0 + pip install coverage==4.5.4 coveralls --service=github env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From aa4f750d8c9eae6cdf3d4b853ed3de3e08a5f38e Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 28 Apr 2021 14:42:49 +0530 Subject: [PATCH 149/326] fix: test --- erpnext/accounts/general_ledger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index 4c285ff088..f1717c50d8 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -175,7 +175,7 @@ def round_off_debit_credit(gl_map): frappe.throw(_("Debit and Credit not equal for {0} #{1}. Difference is {2}.") .format(gl_map[0].voucher_type, gl_map[0].voucher_no, debit_credit_diff)) - elif abs(debit_credit_diff) > (1.0 / (10**precision)): + elif abs(debit_credit_diff) >= (1.0 / (10**precision)): make_round_off_gle(gl_map, debit_credit_diff, precision) def make_round_off_gle(gl_map, debit_credit_diff, precision): From da5b55c4f5fa89770ace9a400f7523d0e83550d0 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 28 Apr 2021 14:55:33 +0530 Subject: [PATCH 150/326] fix: test --- erpnext/controllers/accounts_controller.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index d7f89b6270..a61292491c 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -944,7 +944,7 @@ class AccountsController(TransactionBase): for item in data: self.append("payment_schedule", item) else: - data = dict(due_date=due_date, invoice_portion=100, payment_amount=grand_total) + data = dict(due_date=due_date, invoice_portion=100, payment_amount=grand_total, base_payment_amount=base_grand_total) self.append("payment_schedule", data) else: for d in self.get("payment_schedule"): @@ -1007,7 +1007,8 @@ class AccountsController(TransactionBase): else: grand_total -= self.get("total_advance") base_grand_total = flt(grand_total * self.get("conversion_rate"), self.precision("base_grand_total")) - + print(total, base_total) + print(grand_total, base_grand_total) if total != flt(grand_total, self.precision("grand_total")) or \ base_total != flt(base_grand_total, self.precision("base_grand_total")): frappe.throw(_("Total Payment Amount in Payment Schedule must be equal to Grand / Rounded Total")) From 44b07e4ef590d5f8a41b14c75291b86e251faeec Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 28 Apr 2021 15:11:57 +0530 Subject: [PATCH 151/326] refactor: remove extra fields --- .../payment_schedule/payment_schedule.json | 22 ++----------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/erpnext/accounts/doctype/payment_schedule/payment_schedule.json b/erpnext/accounts/doctype/payment_schedule/payment_schedule.json index e9f8ba29dc..6ed7a3154e 100644 --- a/erpnext/accounts/doctype/payment_schedule/payment_schedule.json +++ b/erpnext/accounts/doctype/payment_schedule/payment_schedule.json @@ -24,9 +24,7 @@ "paid_amount", "discounted_amount", "column_break_3", - "base_payment_amount", - "base_outstanding", - "base_paid_amount" + "base_payment_amount" ], "fields": [ { @@ -156,28 +154,12 @@ "fieldtype": "Currency", "label": "Payment Amount (Company Currency)", "options": "Company:company:default_currency" - }, - { - "default": "0", - "fieldname": "base_outstanding", - "fieldtype": "Currency", - "label": "Outstanding (Company Currency)", - "options": "Company:company:default_currency", - "read_only": 1 - }, - { - "default": "0", - "fieldname": "base_paid_amount", - "fieldtype": "Currency", - "label": "Paid Amount (Company Currency)", - "options": "Company:company:default_currency", - "read_only": 1 } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-04-23 13:55:59.548043", + "modified": "2021-04-28 05:41:35.084233", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Schedule", From a949480acf0e124e013dd0a936412b5593df7fe6 Mon Sep 17 00:00:00 2001 From: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Date: Wed, 28 Apr 2021 12:38:23 +0200 Subject: [PATCH 152/326] fix: add translation to make semgrep pass --- erpnext/accounts/doctype/subscription/subscription.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/subscription/subscription.py b/erpnext/accounts/doctype/subscription/subscription.py index 175710c9f8..7c4ff73d90 100644 --- a/erpnext/accounts/doctype/subscription/subscription.py +++ b/erpnext/accounts/doctype/subscription/subscription.py @@ -276,7 +276,7 @@ class Subscription(Document): frappe.throw(_('Subscription End Date is mandatory to follow calendar months')) if billing_info[0]['billing_interval'] != 'Month': - frappe.throw('Billing Interval in Subscription Plan must be Month to follow calendar months') + frappe.throw(_('Billing Interval in Subscription Plan must be Month to follow calendar months')) def after_insert(self): # todo: deal with users who collect prepayments. Maybe a new Subscription Invoice doctype? From 64a38f52cf72a56568876bcc376e69aec3680007 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 28 Apr 2021 13:46:47 +0530 Subject: [PATCH 153/326] chore: remove frappe from requirements.txt Due to recent changes in pip dependency resolver, in some random cases pip thinks frappe is not installed and tries to fetch it from pypi, which results in errors like #25496 Until a better solution is available, frappe should not be part of requirements.txt --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 377fd7df6c..f1ffeb8f48 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -frappe +# frappe # https://github.com/frappe/frappe is installed during bench-init gocardless-pro~=1.22.0 googlemaps # used in ERPNext, but dependency is defined in Frappe pandas~=1.1.5 From ab052599c055789a5519c10cdd1567c4a86c78c4 Mon Sep 17 00:00:00 2001 From: Afshan Date: Wed, 28 Apr 2021 20:21:04 +0530 Subject: [PATCH 154/326] fix: allow to cancel loan with cancelled replayment entry --- erpnext/loan_management/doctype/loan/loan.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/loan_management/doctype/loan/loan.py b/erpnext/loan_management/doctype/loan/loan.py index 83a813f947..20b44a15e3 100644 --- a/erpnext/loan_management/doctype/loan/loan.py +++ b/erpnext/loan_management/doctype/loan/loan.py @@ -44,6 +44,7 @@ class Loan(AccountsController): def on_cancel(self): self.unlink_loan_security_pledge() + self.ignore_linked_doctypes = ['GL Entry'] def set_missing_fields(self): if not self.company: @@ -359,4 +360,4 @@ def get_shortfall_applicants(): return { "value": len(applicants), "fieldtype": "Int" - } \ No newline at end of file + } From dc086dd52ffa6a06afbc965587f2a9c0f34f7edf Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Thu, 29 Apr 2021 11:03:27 +0530 Subject: [PATCH 155/326] fix: item stock levels displaying inconsistently (#25506) * fix: fixed stock levels dashboard not displaying after any interactions * fix: minor translation fix --- erpnext/stock/doctype/item/item.js | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js index 2079cf88dd..8aec89381a 100644 --- a/erpnext/stock/doctype/item/item.js +++ b/erpnext/stock/doctype/item/item.js @@ -46,9 +46,6 @@ frappe.ui.form.on("Item", { }, __("View")); } - if (!frm.doc.is_fixed_asset) { - erpnext.item.make_dashboard(frm); - } if (frm.doc.is_fixed_asset) { frm.trigger('is_fixed_asset'); @@ -96,6 +93,10 @@ frappe.ui.form.on("Item", { erpnext.item.edit_prices_button(frm); erpnext.item.toggle_attributes(frm); + + if (!frm.doc.is_fixed_asset) { + erpnext.item.make_dashboard(frm); + } frm.add_custom_button(__('Duplicate'), function() { var new_item = frappe.model.copy_doc(frm.doc); @@ -473,11 +474,15 @@ $.extend(erpnext.item, { me.multiple_variant_dialog.get_primary_btn().html(__('Create Variants')); me.multiple_variant_dialog.disable_primary_action(); } else { + let no_of_combinations = lengths.reduce((a, b) => a * b, 1); - me.multiple_variant_dialog.get_primary_btn() - .html(__( - `Make ${no_of_combinations} Variant${no_of_combinations === 1 ? '' : 's'}` - )); + let msg; + if (no_of_combinations === 1) { + msg = __("Make {0} Variant", [no_of_combinations]); + } else { + msg = __("Make {0} Variants", [no_of_combinations]); + } + me.multiple_variant_dialog.get_primary_btn().html(msg); me.multiple_variant_dialog.enable_primary_action(); } } From 83e3820575a2706f7b6ca8b150e0455df716a9cb Mon Sep 17 00:00:00 2001 From: Saqib Date: Thu, 29 Apr 2021 12:21:56 +0530 Subject: [PATCH 156/326] fix: remove print statement --- erpnext/controllers/accounts_controller.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index a61292491c..c409850734 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -952,7 +952,6 @@ class AccountsController(TransactionBase): d.payment_amount = flt(grand_total * flt(d.invoice_portion / 100), d.precision('payment_amount')) d.base_payment_amount = flt(base_grand_total * flt(d.invoice_portion / 100), d.precision('payment_amount')) d.outstanding = d.payment_amount - d.base_outstanding = d.base_payment_amount def set_due_date(self): due_dates = [d.due_date for d in self.get("payment_schedule") if d.due_date] @@ -1007,7 +1006,6 @@ class AccountsController(TransactionBase): else: grand_total -= self.get("total_advance") base_grand_total = flt(grand_total * self.get("conversion_rate"), self.precision("base_grand_total")) - print(total, base_total) print(grand_total, base_grand_total) if total != flt(grand_total, self.precision("grand_total")) or \ base_total != flt(base_grand_total, self.precision("base_grand_total")): @@ -1278,7 +1276,6 @@ def get_payment_term_details(term, posting_date=None, grand_total=None, base_gra term_details.discount_type = term.discount_type term_details.discount = term.discount term_details.outstanding = term_details.payment_amount - term_details.base_outstanding = term_details.base_payment_amount term_details.mode_of_payment = term.mode_of_payment if bill_date: From 6fb417590fd4ed431aae585b5a30612ab5e900a0 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Thu, 29 Apr 2021 14:05:20 +0530 Subject: [PATCH 157/326] fix: sider issues --- erpnext/public/js/controllers/transaction.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index dc731ca213..a2b95cb757 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1363,9 +1363,6 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ } this.update_payment_schedule_grid_labels(company_currency); - - // set labels - var $wrapper = $(this.frm.wrapper); }, update_item_grid_labels: function(company_currency) { @@ -1382,7 +1379,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ update_payment_schedule_grid_labels: function(company_currency) { const me = this; - if(this.frm.fields_dict["payment_schedule"]) { + if (this.frm.fields_dict["payment_schedule"]) { this.frm.set_currency_labels(["base_payment_amount", "base_outstanding", "base_paid_amount"], company_currency, "payment_schedule"); this.frm.set_currency_labels(["payment_amount", "outstanding", "paid_amount"], @@ -1390,7 +1387,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ var schedule_grid = this.frm.fields_dict["payment_schedule"].grid; $.each(["base_payment_amount", "base_outstanding", "base_paid_amount"], function(i, fname) { - if(frappe.meta.get_docfield(schedule_grid.doctype, fname)) + if (frappe.meta.get_docfield(schedule_grid.doctype, fname)) schedule_grid.set_column_disp(fname, me.frm.doc.currency != company_currency); }); } @@ -2045,6 +2042,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ }, payment_term: function(doc, cdt, cdn) { + const me = this; var row = locals[cdt][cdn]; if(row.payment_term) { frappe.call({ @@ -2061,7 +2059,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ for (var d in r.message) { frappe.model.set_value(cdt, cdn, d, r.message[d]); const company_currency = me.get_company_currency(); - this.update_payment_schedule_grid_labels(company_currency); + me.update_payment_schedule_grid_labels(company_currency); } } } From 0dc00219e8e0dbc32f0b6b00c3b268c2f2fb908a Mon Sep 17 00:00:00 2001 From: Mohammad Hasnain Mohsin Rajan Date: Thu, 29 Apr 2021 15:21:16 +0530 Subject: [PATCH 158/326] fix: coveralls different services for different events (#25513) --- .github/workflows/ci-tests.yml | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml index 4103bc68a9..84ecfb1457 100644 --- a/.github/workflows/ci-tests.yml +++ b/.github/workflows/ci-tests.yml @@ -80,8 +80,8 @@ jobs: env: TYPE: ${{ matrix.TYPE }} - - name: Coverage - if: matrix.TYPE == 'server' + - name: Coverage - Pull Request + if: matrix.TYPE == 'server' && github.event_name == 'pull_request' run: | cp ~/frappe-bench/sites/.coverage ${GITHUB_WORKSPACE} cd ${GITHUB_WORKSPACE} @@ -91,3 +91,18 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_TOKEN }} + COVERALLS_SERVICE_NAME: github + + - name: Coverage - Push + if: matrix.TYPE == 'server' && github.event_name == 'push' + run: | + cp ~/frappe-bench/sites/.coverage ${GITHUB_WORKSPACE} + cd ${GITHUB_WORKSPACE} + pip install coveralls==2.2.0 + pip install coverage==4.5.4 + coveralls --service=github-actions + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_TOKEN }} + COVERALLS_SERVICE_NAME: github-actions + From dd1e7624bf62aaa6b7a4fa797bdde4d61ea6f215 Mon Sep 17 00:00:00 2001 From: Anuja P Date: Thu, 29 Apr 2021 17:00:59 +0530 Subject: [PATCH 159/326] fix: changing paid amount should change allocated amount --- erpnext/accounts/doctype/payment_entry/payment_entry.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index 830a7f25c0..b80e8ada38 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -561,7 +561,7 @@ frappe.ui.form.on('Payment Entry', { flt(frm.doc.received_amount) * flt(frm.doc.target_exchange_rate)); if(frm.doc.payment_type == "Pay") - frm.events.allocate_party_amount_against_ref_docs(frm, frm.doc.received_amount); + frm.events.allocate_party_amount_against_ref_docs(frm, frm.doc.received_amount, 1); else frm.events.set_unallocated_amount(frm); From e70d9c925c5f56ca35d0da84f59e305d38481e35 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Mon, 12 Apr 2021 15:40:27 +0530 Subject: [PATCH 160/326] fix: payment amount showing in foreign currency --- .../accounts/doctype/payment_schedule/payment_schedule.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/payment_schedule/payment_schedule.json b/erpnext/accounts/doctype/payment_schedule/payment_schedule.json index e362566af0..39eda331db 100644 --- a/erpnext/accounts/doctype/payment_schedule/payment_schedule.json +++ b/erpnext/accounts/doctype/payment_schedule/payment_schedule.json @@ -65,7 +65,6 @@ "fieldtype": "Currency", "in_list_view": 1, "label": "Payment Amount", - "options": "currency", "reqd": 1 }, { @@ -150,7 +149,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-02-15 21:03:12.540546", + "modified": "2021-04-12 15:39:47.363609", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Schedule", From a91eebf6aa64851bb7223f39771019225f21fe7c Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Fri, 23 Apr 2021 14:46:52 +0530 Subject: [PATCH 161/326] feat: base payment amount in payment schedule --- .../payment_schedule/payment_schedule.json | 36 +++++++++++-- .../purchase_invoice/test_purchase_invoice.py | 2 +- erpnext/controllers/accounts_controller.py | 53 ++++++++++++------- erpnext/public/js/controllers/transaction.js | 5 +- 4 files changed, 70 insertions(+), 26 deletions(-) diff --git a/erpnext/accounts/doctype/payment_schedule/payment_schedule.json b/erpnext/accounts/doctype/payment_schedule/payment_schedule.json index 39eda331db..e9f8ba29dc 100644 --- a/erpnext/accounts/doctype/payment_schedule/payment_schedule.json +++ b/erpnext/accounts/doctype/payment_schedule/payment_schedule.json @@ -20,10 +20,13 @@ "discount", "section_break_9", "payment_amount", + "outstanding", + "paid_amount", "discounted_amount", "column_break_3", - "outstanding", - "paid_amount" + "base_payment_amount", + "base_outstanding", + "base_paid_amount" ], "fields": [ { @@ -65,6 +68,7 @@ "fieldtype": "Currency", "in_list_view": 1, "label": "Payment Amount", + "options": "currency", "reqd": 1 }, { @@ -77,7 +81,8 @@ "depends_on": "paid_amount", "fieldname": "paid_amount", "fieldtype": "Currency", - "label": "Paid Amount" + "label": "Paid Amount", + "options": "currency" }, { "fieldname": "column_break_3", @@ -96,6 +101,7 @@ "fieldname": "outstanding", "fieldtype": "Currency", "label": "Outstanding", + "options": "currency", "read_only": 1 }, { @@ -144,12 +150,34 @@ { "fieldname": "section_break_4", "fieldtype": "Section Break" + }, + { + "fieldname": "base_payment_amount", + "fieldtype": "Currency", + "label": "Payment Amount (Company Currency)", + "options": "Company:company:default_currency" + }, + { + "default": "0", + "fieldname": "base_outstanding", + "fieldtype": "Currency", + "label": "Outstanding (Company Currency)", + "options": "Company:company:default_currency", + "read_only": 1 + }, + { + "default": "0", + "fieldname": "base_paid_amount", + "fieldtype": "Currency", + "label": "Paid Amount (Company Currency)", + "options": "Company:company:default_currency", + "read_only": 1 } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-04-12 15:39:47.363609", + "modified": "2021-04-23 13:55:59.548043", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Schedule", diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 50492f50b5..66be11ff23 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -397,7 +397,7 @@ class TestPurchaseInvoice(unittest.TestCase): pi.update({ "payment_schedule": get_payment_terms("_Test Payment Term Template", - pi.posting_date, pi.grand_total) + pi.posting_date, pi.grand_total, pi.base_grand_total) }) pi.save() diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 97bcb00210..d7f89b6270 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -923,20 +923,24 @@ class AccountsController(TransactionBase): date = self.get("due_date") due_date = date or posting_date - if party_account_currency == self.company_currency: - grand_total = self.get("base_rounded_total") or self.base_grand_total - else: - grand_total = self.get("rounded_total") or self.grand_total + base_grand_total = self.get("base_rounded_total") or self.base_grand_total + grand_total = self.get("rounded_total") or self.grand_total if self.doctype in ("Sales Invoice", "Purchase Invoice"): + base_grand_total = base_grand_total - flt(self.base_write_off_amount) grand_total = grand_total - flt(self.write_off_amount) if self.get("total_advance"): - grand_total -= self.get("total_advance") + if party_account_currency == self.company_currency: + base_grand_total -= self.get("total_advance") + grand_total = flt(base_grand_total / self.get("conversion_rate"), self.precision("grand_total")) + else: + grand_total -= self.get("total_advance") + base_grand_total = flt(grand_total * self.get("conversion_rate"), self.precision("base_grand_total")) if not self.get("payment_schedule"): if self.get("payment_terms_template"): - data = get_payment_terms(self.payment_terms_template, posting_date, grand_total) + data = get_payment_terms(self.payment_terms_template, posting_date, grand_total, base_grand_total) for item in data: self.append("payment_schedule", item) else: @@ -946,7 +950,9 @@ class AccountsController(TransactionBase): for d in self.get("payment_schedule"): if d.invoice_portion: d.payment_amount = flt(grand_total * flt(d.invoice_portion / 100), d.precision('payment_amount')) + d.base_payment_amount = flt(base_grand_total * flt(d.invoice_portion / 100), d.precision('payment_amount')) d.outstanding = d.payment_amount + d.base_outstanding = d.base_payment_amount def set_due_date(self): due_dates = [d.due_date for d in self.get("payment_schedule") if d.due_date] @@ -982,22 +988,28 @@ class AccountsController(TransactionBase): if self.get("payment_schedule"): total = 0 + base_total = 0 for d in self.get("payment_schedule"): total += flt(d.payment_amount) + base_total += flt(d.base_payment_amount) - if party_account_currency == self.company_currency: - total = flt(total, self.precision("base_grand_total")) - grand_total = flt(self.get("base_rounded_total") or self.base_grand_total, self.precision('base_grand_total')) - else: - total = flt(total, self.precision("grand_total")) - grand_total = flt(self.get("rounded_total") or self.grand_total, self.precision('grand_total')) - - if self.get("total_advance"): - grand_total -= self.get("total_advance") + base_grand_total = self.get("base_rounded_total") or self.base_grand_total + grand_total = self.get("rounded_total") or self.grand_total if self.doctype in ("Sales Invoice", "Purchase Invoice"): + base_grand_total = base_grand_total - flt(self.base_write_off_amount) grand_total = grand_total - flt(self.write_off_amount) - if total != flt(grand_total, self.precision("grand_total")): + + if self.get("total_advance"): + if party_account_currency == self.company_currency: + base_grand_total -= self.get("total_advance") + grand_total = flt(base_grand_total / self.get("conversion_rate"), self.precision("grand_total")) + else: + grand_total -= self.get("total_advance") + base_grand_total = flt(grand_total * self.get("conversion_rate"), self.precision("base_grand_total")) + + if total != flt(grand_total, self.precision("grand_total")) or \ + base_total != flt(base_grand_total, self.precision("base_grand_total")): frappe.throw(_("Total Payment Amount in Payment Schedule must be equal to Grand / Rounded Total")) def is_rounded_total_disabled(self): @@ -1237,7 +1249,7 @@ def update_invoice_status(): @frappe.whitelist() -def get_payment_terms(terms_template, posting_date=None, grand_total=None, bill_date=None): +def get_payment_terms(terms_template, posting_date=None, grand_total=None, base_grand_total=None, bill_date=None): if not terms_template: return @@ -1245,14 +1257,14 @@ def get_payment_terms(terms_template, posting_date=None, grand_total=None, bill_ schedule = [] for d in terms_doc.get("terms"): - term_details = get_payment_term_details(d, posting_date, grand_total, bill_date) + term_details = get_payment_term_details(d, posting_date, grand_total, base_grand_total, bill_date) schedule.append(term_details) return schedule @frappe.whitelist() -def get_payment_term_details(term, posting_date=None, grand_total=None, bill_date=None): +def get_payment_term_details(term, posting_date=None, grand_total=None, base_grand_total=None, bill_date=None): term_details = frappe._dict() if isinstance(term, text_type): term = frappe.get_doc("Payment Term", term) @@ -1261,10 +1273,11 @@ def get_payment_term_details(term, posting_date=None, grand_total=None, bill_dat term_details.description = term.description term_details.invoice_portion = term.invoice_portion term_details.payment_amount = flt(term.invoice_portion) * flt(grand_total) / 100 + term_details.base_payment_amount = flt(term.invoice_portion) * flt(base_grand_total) / 100 term_details.discount_type = term.discount_type term_details.discount = term.discount - # term_details.discounted_amount = flt(grand_total) * (term.discount / 100) if term.discount_type == 'Percentage' else discount term_details.outstanding = term_details.payment_amount + term_details.base_outstanding = term_details.base_payment_amount term_details.mode_of_payment = term.mode_of_payment if bill_date: diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index a0398e718f..258262aed3 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1995,6 +1995,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ terms_template: doc.payment_terms_template, posting_date: posting_date, grand_total: doc.rounded_total || doc.grand_total, + base_grand_total: doc.base_rounded_total || doc.base_grand_total, bill_date: doc.bill_date }, callback: function(r) { @@ -2009,13 +2010,15 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ payment_term: function(doc, cdt, cdn) { var row = locals[cdt][cdn]; if(row.payment_term) { + debugger; frappe.call({ method: "erpnext.controllers.accounts_controller.get_payment_term_details", args: { term: row.payment_term, bill_date: this.frm.doc.bill_date, posting_date: this.frm.doc.posting_date || this.frm.doc.transaction_date, - grand_total: this.frm.doc.rounded_total || this.frm.doc.grand_total + grand_total: this.frm.doc.rounded_total || this.frm.doc.grand_total, + base_grand_total: this.frm.doc.base_rounded_total || this.frm.doc.base_grand_total }, callback: function(r) { if(r.message && !r.exc) { From 973afeac3cb7176fe8324ac2df007e9e089ac01c Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Fri, 23 Apr 2021 15:34:58 +0530 Subject: [PATCH 162/326] feat: set dynamic labels for payment schedule fields --- erpnext/public/js/controllers/transaction.js | 54 +++++++++++++++++--- 1 file changed, 46 insertions(+), 8 deletions(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 258262aed3..dc731ca213 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -640,6 +640,10 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ let key = item.name; me.apply_rule_on_other_items({key: item}); } + }, + () => { + var company_currency = me.get_company_currency(); + me.update_item_grid_labels(company_currency); } ]); } @@ -1321,11 +1325,9 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ change_grid_labels: function(company_currency) { var me = this; - this.frm.set_currency_labels(["base_rate", "base_net_rate", "base_price_list_rate", "base_amount", "base_net_amount", "base_rate_with_margin"], - company_currency, "items"); + this.update_item_grid_labels(company_currency); - this.frm.set_currency_labels(["rate", "net_rate", "price_list_rate", "amount", "net_amount", "stock_uom_rate", "rate_with_margin"], - this.frm.doc.currency, "items"); + this.toggle_item_grid_columns(company_currency); if(this.frm.fields_dict["operations"]) { this.frm.set_currency_labels(["operating_cost", "hour_rate"], this.frm.doc.currency, "operations"); @@ -1360,6 +1362,42 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ this.frm.doc.party_account_currency, "advances"); } + this.update_payment_schedule_grid_labels(company_currency); + + // set labels + var $wrapper = $(this.frm.wrapper); + }, + + update_item_grid_labels: function(company_currency) { + this.frm.set_currency_labels([ + "base_rate", "base_net_rate", "base_price_list_rate", + "base_amount", "base_net_amount", "base_rate_with_margin" + ], company_currency, "items"); + + this.frm.set_currency_labels([ + "rate", "net_rate", "price_list_rate", "amount", + "net_amount", "stock_uom_rate", "rate_with_margin" + ], this.frm.doc.currency, "items"); + }, + + update_payment_schedule_grid_labels: function(company_currency) { + const me = this; + if(this.frm.fields_dict["payment_schedule"]) { + this.frm.set_currency_labels(["base_payment_amount", "base_outstanding", "base_paid_amount"], + company_currency, "payment_schedule"); + this.frm.set_currency_labels(["payment_amount", "outstanding", "paid_amount"], + this.frm.doc.currency, "payment_schedule"); + + var schedule_grid = this.frm.fields_dict["payment_schedule"].grid; + $.each(["base_payment_amount", "base_outstanding", "base_paid_amount"], function(i, fname) { + if(frappe.meta.get_docfield(schedule_grid.doctype, fname)) + schedule_grid.set_column_disp(fname, me.frm.doc.currency != company_currency); + }); + } + }, + + toggle_item_grid_columns: function(company_currency) { + const me = this; // toggle columns var item_grid = this.frm.fields_dict["items"].grid; $.each(["base_rate", "base_price_list_rate", "base_amount", "base_rate_with_margin"], function(i, fname) { @@ -1379,9 +1417,6 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ if(frappe.meta.get_docfield(item_grid.doctype, fname)) item_grid.set_column_disp(fname, (show && (me.frm.doc.currency != company_currency))); }); - - // set labels - var $wrapper = $(this.frm.wrapper); }, recalculate: function() { @@ -2001,6 +2036,8 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ callback: function(r) { if(r.message && !r.exc) { me.frm.set_value("payment_schedule", r.message); + const company_currency = me.get_company_currency(); + this.update_payment_schedule_grid_labels(company_currency); } } }) @@ -2010,7 +2047,6 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ payment_term: function(doc, cdt, cdn) { var row = locals[cdt][cdn]; if(row.payment_term) { - debugger; frappe.call({ method: "erpnext.controllers.accounts_controller.get_payment_term_details", args: { @@ -2024,6 +2060,8 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ if(r.message && !r.exc) { for (var d in r.message) { frappe.model.set_value(cdt, cdn, d, r.message[d]); + const company_currency = me.get_company_currency(); + this.update_payment_schedule_grid_labels(company_currency); } } } From 3a5ecb75873e8e9be1fea6e2c134fec31f6b895c Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 28 Apr 2021 14:55:33 +0530 Subject: [PATCH 163/326] fix: test --- erpnext/controllers/accounts_controller.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index d7f89b6270..a61292491c 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -944,7 +944,7 @@ class AccountsController(TransactionBase): for item in data: self.append("payment_schedule", item) else: - data = dict(due_date=due_date, invoice_portion=100, payment_amount=grand_total) + data = dict(due_date=due_date, invoice_portion=100, payment_amount=grand_total, base_payment_amount=base_grand_total) self.append("payment_schedule", data) else: for d in self.get("payment_schedule"): @@ -1007,7 +1007,8 @@ class AccountsController(TransactionBase): else: grand_total -= self.get("total_advance") base_grand_total = flt(grand_total * self.get("conversion_rate"), self.precision("base_grand_total")) - + print(total, base_total) + print(grand_total, base_grand_total) if total != flt(grand_total, self.precision("grand_total")) or \ base_total != flt(base_grand_total, self.precision("base_grand_total")): frappe.throw(_("Total Payment Amount in Payment Schedule must be equal to Grand / Rounded Total")) From 6dd1c5cf922c5a73f87ca26a780b5885d755e180 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 28 Apr 2021 15:11:57 +0530 Subject: [PATCH 164/326] refactor: remove extra fields --- .../payment_schedule/payment_schedule.json | 22 ++----------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/erpnext/accounts/doctype/payment_schedule/payment_schedule.json b/erpnext/accounts/doctype/payment_schedule/payment_schedule.json index e9f8ba29dc..6ed7a3154e 100644 --- a/erpnext/accounts/doctype/payment_schedule/payment_schedule.json +++ b/erpnext/accounts/doctype/payment_schedule/payment_schedule.json @@ -24,9 +24,7 @@ "paid_amount", "discounted_amount", "column_break_3", - "base_payment_amount", - "base_outstanding", - "base_paid_amount" + "base_payment_amount" ], "fields": [ { @@ -156,28 +154,12 @@ "fieldtype": "Currency", "label": "Payment Amount (Company Currency)", "options": "Company:company:default_currency" - }, - { - "default": "0", - "fieldname": "base_outstanding", - "fieldtype": "Currency", - "label": "Outstanding (Company Currency)", - "options": "Company:company:default_currency", - "read_only": 1 - }, - { - "default": "0", - "fieldname": "base_paid_amount", - "fieldtype": "Currency", - "label": "Paid Amount (Company Currency)", - "options": "Company:company:default_currency", - "read_only": 1 } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-04-23 13:55:59.548043", + "modified": "2021-04-28 05:41:35.084233", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Schedule", From 2bde1baa38078477d8c3ffad98d2d9c5475ec1f1 Mon Sep 17 00:00:00 2001 From: Saqib Date: Thu, 29 Apr 2021 12:21:56 +0530 Subject: [PATCH 165/326] fix: remove print statement --- erpnext/controllers/accounts_controller.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index a61292491c..c409850734 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -952,7 +952,6 @@ class AccountsController(TransactionBase): d.payment_amount = flt(grand_total * flt(d.invoice_portion / 100), d.precision('payment_amount')) d.base_payment_amount = flt(base_grand_total * flt(d.invoice_portion / 100), d.precision('payment_amount')) d.outstanding = d.payment_amount - d.base_outstanding = d.base_payment_amount def set_due_date(self): due_dates = [d.due_date for d in self.get("payment_schedule") if d.due_date] @@ -1007,7 +1006,6 @@ class AccountsController(TransactionBase): else: grand_total -= self.get("total_advance") base_grand_total = flt(grand_total * self.get("conversion_rate"), self.precision("base_grand_total")) - print(total, base_total) print(grand_total, base_grand_total) if total != flt(grand_total, self.precision("grand_total")) or \ base_total != flt(base_grand_total, self.precision("base_grand_total")): @@ -1278,7 +1276,6 @@ def get_payment_term_details(term, posting_date=None, grand_total=None, base_gra term_details.discount_type = term.discount_type term_details.discount = term.discount term_details.outstanding = term_details.payment_amount - term_details.base_outstanding = term_details.base_payment_amount term_details.mode_of_payment = term.mode_of_payment if bill_date: From 3e83542360975d79e9bff6d5fed1ac8efc90632e Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Thu, 29 Apr 2021 14:05:20 +0530 Subject: [PATCH 166/326] fix: sider issues --- erpnext/public/js/controllers/transaction.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index dc731ca213..a2b95cb757 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1363,9 +1363,6 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ } this.update_payment_schedule_grid_labels(company_currency); - - // set labels - var $wrapper = $(this.frm.wrapper); }, update_item_grid_labels: function(company_currency) { @@ -1382,7 +1379,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ update_payment_schedule_grid_labels: function(company_currency) { const me = this; - if(this.frm.fields_dict["payment_schedule"]) { + if (this.frm.fields_dict["payment_schedule"]) { this.frm.set_currency_labels(["base_payment_amount", "base_outstanding", "base_paid_amount"], company_currency, "payment_schedule"); this.frm.set_currency_labels(["payment_amount", "outstanding", "paid_amount"], @@ -1390,7 +1387,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ var schedule_grid = this.frm.fields_dict["payment_schedule"].grid; $.each(["base_payment_amount", "base_outstanding", "base_paid_amount"], function(i, fname) { - if(frappe.meta.get_docfield(schedule_grid.doctype, fname)) + if (frappe.meta.get_docfield(schedule_grid.doctype, fname)) schedule_grid.set_column_disp(fname, me.frm.doc.currency != company_currency); }); } @@ -2045,6 +2042,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ }, payment_term: function(doc, cdt, cdn) { + const me = this; var row = locals[cdt][cdn]; if(row.payment_term) { frappe.call({ @@ -2061,7 +2059,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ for (var d in r.message) { frappe.model.set_value(cdt, cdn, d, r.message[d]); const company_currency = me.get_company_currency(); - this.update_payment_schedule_grid_labels(company_currency); + me.update_payment_schedule_grid_labels(company_currency); } } } From 76e1e68cf4d3bcbc29a8061c53e31c1960230071 Mon Sep 17 00:00:00 2001 From: Ernesto Ruiz Date: Thu, 29 Apr 2021 08:24:56 -0600 Subject: [PATCH 167/326] fix: Make strings translatable Make strings translatable --- erpnext/payroll/doctype/salary_structure/salary_structure.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/payroll/doctype/salary_structure/salary_structure.js b/erpnext/payroll/doctype/salary_structure/salary_structure.js index 44368a69fb..d5c20dce6b 100755 --- a/erpnext/payroll/doctype/salary_structure/salary_structure.js +++ b/erpnext/payroll/doctype/salary_structure/salary_structure.js @@ -16,7 +16,7 @@ frappe.ui.form.on('Salary Structure', { onload: function(frm) { let help_button = $(` - ${__(Condition and Formula Help)} + ${__("Condition and Formula Help")} `).click(()=>{ let d = new frappe.ui.Dialog({ From 7783a56c08d343baab2e583d7f90f6e25e69a8ac Mon Sep 17 00:00:00 2001 From: Vignesh S Date: Thu, 29 Apr 2021 22:39:42 +0530 Subject: [PATCH 168/326] fix: List invoices in Payment Reconciliation Payment Invoices are not listed in the Payment Reconciliation Payment table due to a typo in the code --- .../doctype/payment_reconciliation/payment_reconciliation.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js index 08103184d5..d1523cd7ac 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js @@ -234,7 +234,7 @@ erpnext.accounts.PaymentReconciliationController = frappe.ui.form.Controller.ext }); if (invoices) { - this.frm.fields_dict.payment.grid.update_docfield_property( + this.frm.fields_dict.payments.grid.update_docfield_property( 'invoice_number', 'options', "\n" + invoices.join("\n") ); From 435e93bda9efe882031bbd5ed4b4994b74888db1 Mon Sep 17 00:00:00 2001 From: Anuja P Date: Fri, 30 Apr 2021 13:52:48 +0530 Subject: [PATCH 169/326] fix: allocated amount change wrt paid amount change --- erpnext/accounts/doctype/payment_entry/payment_entry.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index 830a7f25c0..b80e8ada38 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -561,7 +561,7 @@ frappe.ui.form.on('Payment Entry', { flt(frm.doc.received_amount) * flt(frm.doc.target_exchange_rate)); if(frm.doc.payment_type == "Pay") - frm.events.allocate_party_amount_against_ref_docs(frm, frm.doc.received_amount); + frm.events.allocate_party_amount_against_ref_docs(frm, frm.doc.received_amount, 1); else frm.events.set_unallocated_amount(frm); From 178938b2f0e3f18b0652bfc320a499699888ef70 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 27 Apr 2021 16:57:34 +0530 Subject: [PATCH 170/326] fix: Ageing errors in PSOA --- .../process_statement_of_accounts.html | 12 ++++++------ .../process_statement_of_accounts.py | 13 +++++++++---- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html index 94ae79a0c6..16d4ebe6cb 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html @@ -60,8 +60,8 @@

-{% if aging %} -

{{ _("Ageing Report Based On ") }} {{ aging.ageing_based_on }}

+{% if ageing %} +

{{ _("Ageing Report Based On ") }} {{ ageing.ageing_based_on }}

{{ _("Up to " ) }} {{ frappe.format(filters.to_date, 'Date')}}
@@ -78,10 +78,10 @@ - {{ aging.range1 }} - {{ aging.range2 }} - {{ aging.range3 }} - {{ aging.range4 }} + {{ frappe.utils.fmt_money(ageing.range1, currency=filters.presentation_currency) }} + {{ frappe.utils.fmt_money(ageing.range2, currency=filters.presentation_currency) }} + {{ frappe.utils.fmt_money(ageing.range3, currency=filters.presentation_currency) }} + {{ frappe.utils.fmt_money(ageing.range4, currency=filters.presentation_currency) }} diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py index 43fbb0600a..fd2102b87f 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py @@ -38,7 +38,7 @@ class ProcessStatementOfAccounts(Document): def get_report_pdf(doc, consolidated=True): statement_dict = {} - aging = '' + ageing = '' base_template_path = "frappe/www/printview.html" template_path = "erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html" @@ -54,8 +54,10 @@ def get_report_pdf(doc, consolidated=True): 'range4': 120, 'customer': entry.customer }) - col1, aging = get_ageing(ageing_filters) - aging[0]['ageing_based_on'] = doc.ageing_based_on + col1, ageing = get_ageing(ageing_filters) + + if ageing: + ageing[0]['ageing_based_on'] = doc.ageing_based_on tax_id = frappe.get_doc('Customer', entry.customer).tax_id @@ -83,11 +85,14 @@ def get_report_pdf(doc, consolidated=True): if len(res) == 3: continue + html = frappe.render_template(template_path, \ - {"filters": filters, "data": res, "aging": aging[0] if doc.include_ageing else None}) + {"filters": filters, "data": res, "ageing": ageing[0] if (doc.include_ageing and ageing) else None}) + html = frappe.render_template(base_template_path, {"body": html, \ "css": get_print_style(), "title": "Statement For " + entry.customer}) statement_dict[entry.customer] = html + if not bool(statement_dict): return False elif consolidated: From 9cbca06f4e533466978f63e4c3fbb78261e38628 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 19 Apr 2021 12:19:31 +0530 Subject: [PATCH 171/326] fix: Updated filters for process statement of accounts --- .../process_statement_of_accounts.html | 2 +- .../process_statement_of_accounts.py | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html index 16d4ebe6cb..f61aacbce2 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html @@ -19,7 +19,7 @@ - {% for row in data %} + {% for row in data %} {% if(row.posting_date) %} {{ frappe.format(row.posting_date, 'Date') }} diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py index fd2102b87f..948431bf5b 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py @@ -7,8 +7,9 @@ import frappe from frappe.model.document import Document from erpnext.accounts.report.general_ledger.general_ledger import execute as get_soa from erpnext.accounts.report.accounts_receivable_summary.accounts_receivable_summary import execute as get_ageing -from frappe.core.doctype.communication.email import make +from erpnext import get_company_currency +from frappe.core.doctype.communication.email import make from frappe.utils.print_format import report_to_pdf from frappe.utils.pdf import get_pdf from frappe.utils import today, add_days, add_months, getdate, format_date @@ -66,16 +67,16 @@ def get_report_pdf(doc, consolidated=True): 'to_date': doc.to_date, 'company': doc.company, 'finance_book': doc.finance_book if doc.finance_book else None, - "account": doc.account if doc.account else None, + 'account': doc.account if doc.account else None, 'party_type': 'Customer', 'party': [entry.customer], + 'presentation_currency': doc.currency or get_company_currency(doc.company), 'group_by': doc.group_by, 'currency': doc.currency, 'cost_center': [cc.cost_center_name for cc in doc.cost_center], 'project': [p.project_name for p in doc.project], 'show_opening_entries': 0, 'include_default_book_entries': 0, - 'show_cancelled_entries': 1, 'tax_id': tax_id if tax_id else None }) col, res = get_soa(filters) From bd5ee7d725c8583526fff0e93334e81812a36147 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 19 Apr 2021 17:27:26 +0530 Subject: [PATCH 172/326] fix: Use party account currency --- .../process_statement_of_accounts.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py index 948431bf5b..f6724449b8 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py @@ -8,6 +8,7 @@ from frappe.model.document import Document from erpnext.accounts.report.general_ledger.general_ledger import execute as get_soa from erpnext.accounts.report.accounts_receivable_summary.accounts_receivable_summary import execute as get_ageing from erpnext import get_company_currency +from erpnext.accounts.party import get_party_account_currency from frappe.core.doctype.communication.email import make from frappe.utils.print_format import report_to_pdf @@ -70,7 +71,8 @@ def get_report_pdf(doc, consolidated=True): 'account': doc.account if doc.account else None, 'party_type': 'Customer', 'party': [entry.customer], - 'presentation_currency': doc.currency or get_company_currency(doc.company), + 'presentation_currency': get_party_account_currency('Customer', entry.customer, doc.company) \ + or doc.currency or get_company_currency(doc.company), 'group_by': doc.group_by, 'currency': doc.currency, 'cost_center': [cc.cost_center_name for cc in doc.cost_center], From 1deef5229e36f5fc758c9980a8638ae222ca273d Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 26 Apr 2021 14:51:00 +0530 Subject: [PATCH 173/326] fix: Linting and translation issues --- .../process_statement_of_accounts.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py index f6724449b8..62090c0255 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py @@ -4,13 +4,13 @@ from __future__ import unicode_literals import frappe +from frappe import _ from frappe.model.document import Document from erpnext.accounts.report.general_ledger.general_ledger import execute as get_soa from erpnext.accounts.report.accounts_receivable_summary.accounts_receivable_summary import execute as get_ageing from erpnext import get_company_currency from erpnext.accounts.party import get_party_account_currency -from frappe.core.doctype.communication.email import make from frappe.utils.print_format import report_to_pdf from frappe.utils.pdf import get_pdf from frappe.utils import today, add_days, add_months, getdate, format_date @@ -31,7 +31,7 @@ class ProcessStatementOfAccounts(Document): validate_template(self.body) if not self.customers: - frappe.throw(frappe._('Customers not selected.')) + frappe.throw(_('Customers not selected.')) if self.enable_auto_email: self.to_date = self.start_date @@ -62,6 +62,8 @@ def get_report_pdf(doc, consolidated=True): ageing[0]['ageing_based_on'] = doc.ageing_based_on tax_id = frappe.get_doc('Customer', entry.customer).tax_id + presentation_currency = get_party_account_currency('Customer', entry.customer, doc.company) \ + or doc.currency or get_company_currency(doc.company) filters= frappe._dict({ 'from_date': doc.from_date, @@ -71,8 +73,7 @@ def get_report_pdf(doc, consolidated=True): 'account': doc.account if doc.account else None, 'party_type': 'Customer', 'party': [entry.customer], - 'presentation_currency': get_party_account_currency('Customer', entry.customer, doc.company) \ - or doc.currency or get_company_currency(doc.company), + 'presentation_currency': presentation_currency, 'group_by': doc.group_by, 'currency': doc.currency, 'cost_center': [cc.cost_center_name for cc in doc.cost_center], @@ -175,7 +176,7 @@ def fetch_customers(customer_collection, collection_name, primary_mandatory): if customer_collection == 'Sales Person': customers = get_customers_based_on_sales_person(collection_name) if not bool(customers): - frappe.throw('No Customers found with selected options.') + frappe.throw(_('No Customers found with selected options.')) else: if customer_collection == 'Sales Partner': customers = frappe.get_list('Customer', fields=['name', 'email_id'], \ @@ -207,14 +208,14 @@ def get_customer_emails(customer_name, primary_mandatory, billing_and_primary=Tr if len(billing_email) == 0 or (billing_email[0][0] is None): if billing_and_primary: - frappe.throw('No billing email found for customer: '+ customer_name) + frappe.throw(_("No billing email found for customer: {0}").format(customer_name)) else: return '' if billing_and_primary: primary_email = frappe.get_value('Customer', customer_name, 'email_id') if primary_email is None and int(primary_mandatory): - frappe.throw('No primary email found for customer: '+ customer_name) + frappe.throw(_("No primary email found for customer: {0}").format(customer_name)) return [primary_email or '', billing_email[0][0]] else: return billing_email[0][0] or '' From 88d40e8dc477361ce257bf22d8f8cf5088e1f6a8 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 30 Apr 2021 15:33:33 +0530 Subject: [PATCH 174/326] fix: Remove extra space --- .../process_statement_of_accounts.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py index 62090c0255..a0dbff3db4 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py @@ -62,7 +62,7 @@ def get_report_pdf(doc, consolidated=True): ageing[0]['ageing_based_on'] = doc.ageing_based_on tax_id = frappe.get_doc('Customer', entry.customer).tax_id - presentation_currency = get_party_account_currency('Customer', entry.customer, doc.company) \ + presentation_currency = get_party_account_currency('Customer', entry.customer, doc.company) \ or doc.currency or get_company_currency(doc.company) filters= frappe._dict({ From a90c81626f662890a0ee7bcbf87878c1ffa0d8d6 Mon Sep 17 00:00:00 2001 From: noahjacob Date: Fri, 30 Apr 2021 15:49:09 +0530 Subject: [PATCH 175/326] fix: updated item filters for material request --- .../doctype/material_request/material_request.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/material_request/material_request.js b/erpnext/stock/doctype/material_request/material_request.js index 7dfc5da50d..92c8d21387 100644 --- a/erpnext/stock/doctype/material_request/material_request.js +++ b/erpnext/stock/doctype/material_request/material_request.js @@ -433,13 +433,21 @@ erpnext.buying.MaterialRequestController = erpnext.buying.BuyingController.exten if (doc.material_request_type == "Customer Provided") { return{ query: "erpnext.controllers.queries.item_query", - filters:{ 'customer': me.frm.doc.customer } + filters:{ + 'customer': me.frm.doc.customer, + 'is_stock_item':1 + } } - } else if (doc.material_request_type != "Manufacture") { + } else if (doc.material_request_type == "Purchase") { return{ query: "erpnext.controllers.queries.item_query", filters: {'is_purchase_item': 1} } + } else { + return{ + query: "erpnext.controllers.queries.item_query", + filters: {'is_stock_item': 1} + } } }); }, From da0ba15cbf1d5c5b1e96d97f6b915c2ad508df1c Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Fri, 30 Apr 2021 18:38:41 +0530 Subject: [PATCH 176/326] fix: Fetch total stock at company in PO (#25532) --- erpnext/stock/get_item_details.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 1a61f30b9a..3fc1df76bc 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -86,7 +86,7 @@ def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=Tru out.update(get_bin_details(args.item_code, args.get("from_warehouse"))) elif out.get("warehouse"): - out.update(get_bin_details(args.item_code, out.warehouse)) + out.update(get_bin_details(args.item_code, out.warehouse, args.company)) # update args with out, if key or value not exists for key, value in iteritems(out): From a19c6a9278a13cf1b67306eb51627d8d53bc5ce2 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Fri, 30 Apr 2021 18:38:49 +0530 Subject: [PATCH 177/326] fix: Fetch total stock at company in PO (#25533) --- erpnext/stock/get_item_details.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 1a61f30b9a..3fc1df76bc 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -86,7 +86,7 @@ def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=Tru out.update(get_bin_details(args.item_code, args.get("from_warehouse"))) elif out.get("warehouse"): - out.update(get_bin_details(args.item_code, out.warehouse)) + out.update(get_bin_details(args.item_code, out.warehouse, args.company)) # update args with out, if key or value not exists for key, value in iteritems(out): From 5a4615fd4e74e258f32c9c2e9addb4915f800660 Mon Sep 17 00:00:00 2001 From: Afshan <33727827+AfshanKhan@users.noreply.github.com> Date: Fri, 30 Apr 2021 18:40:43 +0530 Subject: [PATCH 178/326] fix: allow to cancel loan with cancelled replayment entry (#25508) --- erpnext/loan_management/doctype/loan/loan.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/loan_management/doctype/loan/loan.py b/erpnext/loan_management/doctype/loan/loan.py index 83a813f947..20b44a15e3 100644 --- a/erpnext/loan_management/doctype/loan/loan.py +++ b/erpnext/loan_management/doctype/loan/loan.py @@ -44,6 +44,7 @@ class Loan(AccountsController): def on_cancel(self): self.unlink_loan_security_pledge() + self.ignore_linked_doctypes = ['GL Entry'] def set_missing_fields(self): if not self.company: @@ -359,4 +360,4 @@ def get_shortfall_applicants(): return { "value": len(applicants), "fieldtype": "Int" - } \ No newline at end of file + } From e91b0021ac362ae46e894c1f954da743cc476e4b Mon Sep 17 00:00:00 2001 From: Afshan <33727827+AfshanKhan@users.noreply.github.com> Date: Fri, 30 Apr 2021 18:41:19 +0530 Subject: [PATCH 179/326] fix: fieldname when updating docfield property (#25516) From 1fd1e2bb60f6c6fd118f2843ba27c85b5ccd5ed9 Mon Sep 17 00:00:00 2001 From: Afshan <33727827+AfshanKhan@users.noreply.github.com> Date: Fri, 30 Apr 2021 18:41:26 +0530 Subject: [PATCH 180/326] fix: fieldname when updating docfield property (#25517) --- .../doctype/payment_reconciliation/payment_reconciliation.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js index 08103184d5..d1523cd7ac 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js @@ -234,7 +234,7 @@ erpnext.accounts.PaymentReconciliationController = frappe.ui.form.Controller.ext }); if (invoices) { - this.frm.fields_dict.payment.grid.update_docfield_property( + this.frm.fields_dict.payments.grid.update_docfield_property( 'invoice_number', 'options', "\n" + invoices.join("\n") ); From 00ea336b5241b2695f41c87378c2c34c89cb2143 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Sat, 1 May 2021 13:53:39 +0530 Subject: [PATCH 181/326] fix: stock ledger entry created against draft stock entry (#25540) --- erpnext/stock/stock_ledger.py | 2 +- erpnext/stock/utils.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 985901fc44..bbfcb7ad7d 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -416,7 +416,7 @@ class update_entries_after(object): frappe.db.set_value("Stock Entry Detail", sle.voucher_detail_no, "basic_rate", outgoing_rate) # Update outgoing item's rate, recalculate FG Item's rate and total incoming/outgoing amount - stock_entry = frappe.get_doc("Stock Entry", sle.voucher_no) + stock_entry = frappe.get_doc("Stock Entry", sle.voucher_no, for_update=True) stock_entry.calculate_rate_and_amount(reset_outgoing_rate=False, raise_error_if_no_rate=False) stock_entry.db_update() for d in stock_entry.items: diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py index 0af3d90822..034d3ebbb5 100644 --- a/erpnext/stock/utils.py +++ b/erpnext/stock/utils.py @@ -172,7 +172,7 @@ def get_bin(item_code, warehouse): bin_obj.flags.ignore_permissions = 1 bin_obj.insert() else: - bin_obj = frappe.get_cached_doc('Bin', bin) + bin_obj = frappe.get_doc('Bin', bin, for_update=True) bin_obj.flags.ignore_permissions = True return bin_obj From 2e38f1514fd67dd4ab80fe301668e0ee88d05e1c Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Sat, 1 May 2021 13:53:46 +0530 Subject: [PATCH 182/326] fix: stock ledger entry created against draft stock entry (#25539) --- erpnext/stock/stock_ledger.py | 2 +- erpnext/stock/utils.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 985901fc44..bbfcb7ad7d 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -416,7 +416,7 @@ class update_entries_after(object): frappe.db.set_value("Stock Entry Detail", sle.voucher_detail_no, "basic_rate", outgoing_rate) # Update outgoing item's rate, recalculate FG Item's rate and total incoming/outgoing amount - stock_entry = frappe.get_doc("Stock Entry", sle.voucher_no) + stock_entry = frappe.get_doc("Stock Entry", sle.voucher_no, for_update=True) stock_entry.calculate_rate_and_amount(reset_outgoing_rate=False, raise_error_if_no_rate=False) stock_entry.db_update() for d in stock_entry.items: diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py index 0af3d90822..034d3ebbb5 100644 --- a/erpnext/stock/utils.py +++ b/erpnext/stock/utils.py @@ -172,7 +172,7 @@ def get_bin(item_code, warehouse): bin_obj.flags.ignore_permissions = 1 bin_obj.insert() else: - bin_obj = frappe.get_cached_doc('Bin', bin) + bin_obj = frappe.get_doc('Bin', bin, for_update=True) bin_obj.flags.ignore_permissions = True return bin_obj From 3c788f032558e4e709d9975311801478fb2dea54 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Sat, 1 May 2021 14:42:30 +0530 Subject: [PATCH 183/326] chore: Add release note for v13.2.0 --- erpnext/change_log/v13/v13_2_0.md | 56 +++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 erpnext/change_log/v13/v13_2_0.md diff --git a/erpnext/change_log/v13/v13_2_0.md b/erpnext/change_log/v13/v13_2_0.md new file mode 100644 index 0000000000..eb9499d115 --- /dev/null +++ b/erpnext/change_log/v13/v13_2_0.md @@ -0,0 +1,56 @@ +# Version 13.2.0 Release Notes + +### Features & Enhancements + +- Employee Hours Utilization Report ([#25209](https://github.com/frappe/erpnext/pull/25209)) +- Delayed Tasks Summary Report ([#25024](https://github.com/frappe/erpnext/pull/25024)) +- Project Profitability Report ([#24944](https://github.com/frappe/erpnext/pull/24944)) +- Timer in LMS Quiz ([#24246](https://github.com/frappe/erpnext/pull/24246)) +- Role to allow over billing, delivery, receipt ([#24854](https://github.com/frappe/erpnext/pull/24854)) +- Auto calculate distance for e-way bill generations ([#25480](https://github.com/frappe/erpnext/pull/25480)) +- Add total available stock field in PO ([#24878](https://github.com/frappe/erpnext/pull/24878)) +- Refactored Setup Taxes and Charges ([#24805](https://github.com/frappe/erpnext/pull/24805)) +- Inpatient Occupancy Table Editable for Healthcare Admin ([#24989](https://github.com/frappe/erpnext/pull/24989)) +- Added Disable Rounded Total in sales transactions ([#25362](https://github.com/frappe/erpnext/pull/25362)) + + +### Fixes + +- Incorrect GL Entry validation ([#25474](https://github.com/frappe/erpnext/pull/25474)) +- Cannot create item variants ([#25433](https://github.com/frappe/erpnext/pull/25433)) +- Leave policy in leave allocation ([#25334](https://github.com/frappe/erpnext/pull/25334)) +- Let Administrator delete company transactions ([#25300](https://github.com/frappe/erpnext/pull/25300)) +- Display reconcile tool when closing balance 0 ([#25417](https://github.com/frappe/erpnext/pull/25417)) +- Bulk Salary Structure Assignment ([#25389](https://github.com/frappe/erpnext/pull/25389)) +- Payment amount showing in foreign currency ([#25518](https://github.com/frappe/erpnext/pull/25518)) +- Commit changes to shipment status in database ([#25374](https://github.com/frappe/erpnext/pull/25374)) +- Add amend perm for loan and system manager for loan doctypes ([#25393](https://github.com/frappe/erpnext/pull/25393)) +- Cashier query in POS Opening/Closing Entry ([#25398](https://github.com/frappe/erpnext/pull/25398)) +- Apply single transaction threshold on net_total instead of supplier credit amount ([#25243](https://github.com/frappe/erpnext/pull/25243)) +- Update allocated amount after paid amount is changed in PE ([#25528](https://github.com/frappe/erpnext/pull/25528)) +- Remove non-standard module cards from Home Workspace ([#25391](https://github.com/frappe/erpnext/pull/25391)) +- Cannot scan spacebar character in pos ([#25479](https://github.com/frappe/erpnext/pull/25479)) +- Permission error after submitting exchange rate revaluation ([#25432](https://github.com/frappe/erpnext/pull/25432)) +- Equality check instead of assignment in cart ([#25372](https://github.com/frappe/erpnext/pull/25372)) +- Disable auto naming of customer during import ([#25152](https://github.com/frappe/erpnext/pull/25152)) +- Additional Salary component amount not getting set ([#25355](https://github.com/frappe/erpnext/pull/25355)) +- Round off values near to zero ([#25304](https://github.com/frappe/erpnext/pull/25304)) +- Allow to cancel loan with cancelled repayment entry ([#25508](https://github.com/frappe/erpnext/pull/25508)) +- Currency symbol in bank transaction list view ([#25336](https://github.com/frappe/erpnext/pull/25336)) +- Incorrect batch picked in subcontracted purchase receipt ([#25186](https://github.com/frappe/erpnext/pull/25186)) +- Issue in project custom status ([#25452](https://github.com/frappe/erpnext/pull/25452)) +- Shipment pickup_to, pickup_from functionality. ([#25359](https://github.com/frappe/erpnext/pull/25359)) +- Stock ledger entry created against draft stock entry ([#25539](https://github.com/frappe/erpnext/pull/25539)) +- Ageing errors in PSOA ([#25529](https://github.com/frappe/erpnext/pull/25529)) +- Permission error while adding weekly holidays ([#25450](https://github.com/frappe/erpnext/pull/25450)) +- Filter for employees in salary slip ([#25360](https://github.com/frappe/erpnext/pull/25360)) +- Backward compatibility for GSTR-1 report ([#25444](https://github.com/frappe/erpnext/pull/25444)) +- Incorrect incoming rate for the sales return ([#25145](https://github.com/frappe/erpnext/pull/25145)) +- POS print receipt ([#25328](https://github.com/frappe/erpnext/pull/25328)) +- Laboratory Module patch ([#25431](https://github.com/frappe/erpnext/pull/25431)) +- Performance: fetching exchange rate on every line item slows down PO ([#25345](https://github.com/frappe/erpnext/pull/25345)) +- Presentation currency in statement of accounts ([#25367](https://github.com/frappe/erpnext/pull/25367)) +- Serial No not updated correctly via Inter Company Stock Transfer ([#25006](https://github.com/frappe/erpnext/pull/25006)) +- Ignore Customer Group Perm on All Products page ([#25396](https://github.com/frappe/erpnext/pull/25396)) +- Change subcontracted item display ([#25425](https://github.com/frappe/erpnext/pull/25425)) +- Add company validation for e-invoicing ([#25348](https://github.com/frappe/erpnext/pull/25348)) From d8cd5a95cb86e2589d6a6724bfc3c05d4e9db450 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Sat, 1 May 2021 15:12:44 +0550 Subject: [PATCH 184/326] bumped to version 13.2.0 --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 4da0605370..a988d7217d 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -5,7 +5,7 @@ import frappe from erpnext.hooks import regional_overrides from frappe.utils import getdate -__version__ = '13.1.0' +__version__ = '13.2.0' def get_default_company(user=None): '''Get default company for user''' From 2fb573781df1bbbe0d45dc7a2a13585c836b620e Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Sat, 1 May 2021 17:56:40 +0530 Subject: [PATCH 185/326] fix: rename field has not updated value of deposit and withdrawal fields --- .../patches/v13_0/delete_old_bank_reconciliation_doctypes.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/patches/v13_0/delete_old_bank_reconciliation_doctypes.py b/erpnext/patches/v13_0/delete_old_bank_reconciliation_doctypes.py index af1f6e7ec1..77a23cfc3f 100644 --- a/erpnext/patches/v13_0/delete_old_bank_reconciliation_doctypes.py +++ b/erpnext/patches/v13_0/delete_old_bank_reconciliation_doctypes.py @@ -22,5 +22,7 @@ def execute(): frappe.delete_doc("Page", "bank-reconciliation", force=1) + frappe.reload_doc('accounts', 'doctype', 'bank_transaction') + rename_field("Bank Transaction", "debit", "deposit") rename_field("Bank Transaction", "credit", "withdrawal") From 1efafb3ce841948116c57f2203a880d9df796c6a Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sat, 1 May 2021 21:52:43 +0530 Subject: [PATCH 186/326] fix: Do not roundoff balance amount --- erpnext/loan_management/doctype/loan/loan.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/erpnext/loan_management/doctype/loan/loan.py b/erpnext/loan_management/doctype/loan/loan.py index 83a813f947..f480402c35 100644 --- a/erpnext/loan_management/doctype/loan/loan.py +++ b/erpnext/loan_management/doctype/loan/loan.py @@ -70,7 +70,6 @@ class Loan(AccountsController): frappe.throw(_("Repay From Salary can be selected only for term loans")) def make_repayment_schedule(self): - if not self.repayment_start_date: frappe.throw(_("Repayment Start Date is mandatory for term loans")) @@ -78,10 +77,9 @@ class Loan(AccountsController): payment_date = self.repayment_start_date balance_amount = self.loan_amount while(balance_amount > 0): - interest_amount = rounded(balance_amount * flt(self.rate_of_interest) / (12*100)) + interest_amount = flt(balance_amount * flt(self.rate_of_interest) / (12*100)) principal_amount = self.monthly_repayment_amount - interest_amount - balance_amount = rounded(balance_amount + interest_amount - self.monthly_repayment_amount) - + balance_amount = flt(balance_amount + interest_amount - self.monthly_repayment_amount) if balance_amount < 0: principal_amount += balance_amount balance_amount = 0.0 From 824f08956951de31839a79f27d35d055dc39ddee Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sat, 1 May 2021 22:07:13 +0530 Subject: [PATCH 187/326] fix: Auto write off on loan closure --- erpnext/loan_management/doctype/loan/loan.py | 3 ++- .../loan_management/doctype/loan_repayment/loan_repayment.py | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/loan_management/doctype/loan/loan.py b/erpnext/loan_management/doctype/loan/loan.py index f480402c35..c570e5efda 100644 --- a/erpnext/loan_management/doctype/loan/loan.py +++ b/erpnext/loan_management/doctype/loan/loan.py @@ -193,7 +193,8 @@ def request_loan_closure(loan, posting_date=None): posting_date = getdate() amounts = calculate_amounts(loan, posting_date) - pending_amount = amounts['payable_amount'] + amounts['unaccrued_interest'] + pending_amount = amounts['pending_principal_amount'] + amounts['unaccrued_interest'] + \ + amounts['interest_amount'] + amounts['penalty_amount'] loan_type = frappe.get_value('Loan', loan, 'loan_type') write_off_limit = frappe.get_value('Loan Type', loan_type, 'write_off_amount') diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py index 728eadf22a..3d99b1f304 100644 --- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py +++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py @@ -435,7 +435,6 @@ def get_amounts(amounts, against_loan, posting_date): @frappe.whitelist() def calculate_amounts(against_loan, posting_date, payment_type=''): - amounts = { 'penalty_amount': 0.0, 'interest_amount': 0.0, From c571141c1f4b0cac7a0e556e9aa7b47ee89ff5b4 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 2 May 2021 15:31:04 +0530 Subject: [PATCH 188/326] fix: Test cases --- .../loan_management/doctype/loan/test_loan.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/erpnext/loan_management/doctype/loan/test_loan.py b/erpnext/loan_management/doctype/loan/test_loan.py index 6f8da3166f..fae6f860b6 100644 --- a/erpnext/loan_management/doctype/loan/test_loan.py +++ b/erpnext/loan_management/doctype/loan/test_loan.py @@ -56,25 +56,25 @@ class TestLoan(unittest.TestCase): def test_loan(self): loan = frappe.get_doc("Loan", {"applicant":self.applicant1}) self.assertEquals(loan.monthly_repayment_amount, 15052) - self.assertEquals(loan.total_interest_payable, 21034) - self.assertEquals(loan.total_payment, 301034) + self.assertEquals(flt(loan.total_interest_payable, 0), 21034) + self.assertEquals(flt(loan.total_payment, 0), 301034) schedule = loan.repayment_schedule self.assertEqual(len(schedule), 20) - for idx, principal_amount, interest_amount, balance_loan_amount in [[3, 13369, 1683, 227079], [19, 14941, 105, 0], [17, 14740, 312, 29785]]: - self.assertEqual(schedule[idx].principal_amount, principal_amount) - self.assertEqual(schedule[idx].interest_amount, interest_amount) - self.assertEqual(schedule[idx].balance_loan_amount, balance_loan_amount) + for idx, principal_amount, interest_amount, balance_loan_amount in [[3, 13369, 1683, 227080], [19, 14941, 105, 0], [17, 14740, 312, 29785]]: + self.assertEqual(flt(schedule[idx].principal_amount, 0), principal_amount) + self.assertEqual(flt(schedule[idx].interest_amount, 0), interest_amount) + self.assertEqual(flt(schedule[idx].balance_loan_amount, 0), balance_loan_amount) loan.repayment_method = "Repay Fixed Amount per Period" loan.monthly_repayment_amount = 14000 loan.save() self.assertEquals(len(loan.repayment_schedule), 22) - self.assertEquals(loan.total_interest_payable, 22712) - self.assertEquals(loan.total_payment, 302712) + self.assertEquals(flt(loan.total_interest_payable, 0), 22712) + self.assertEquals(flt(loan.total_payment, 0), 302712) def test_loan_with_security(self): From 35d4829383e278809bc64784b5d8a558db2ac61f Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Sat, 1 May 2021 13:07:26 +0530 Subject: [PATCH 189/326] fix: serial no changed after saving stock reconciliation --- .../report/project_profitability/project_profitability.py | 5 +++-- .../doctype/stock_reconciliation/stock_reconciliation.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/erpnext/projects/report/project_profitability/project_profitability.py b/erpnext/projects/report/project_profitability/project_profitability.py index 5ad2d85232..9139d84fac 100644 --- a/erpnext/projects/report/project_profitability/project_profitability.py +++ b/erpnext/projects/report/project_profitability/project_profitability.py @@ -4,6 +4,7 @@ from __future__ import unicode_literals import frappe from frappe import _ +from frappe.utils import flt def execute(filters=None): columns, data = [], [] @@ -52,8 +53,8 @@ def get_rows(filters): def calculate_cost_and_profit(data): for row in data: - row.fractional_cost = row.base_gross_pay * row.utilization - row.profit = row.base_grand_total - row.base_gross_pay * row.utilization + row.fractional_cost = flt(row.base_gross_pay) * flt(row.utilization) + row.profit = flt(row.base_grand_total) - flt(row.base_gross_pay) * flt(row.utilization) return data def get_conditions(filters): diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index 1396f19d3f..e4cdcb4116 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -72,7 +72,7 @@ class StockReconciliation(StockController): if item_dict.get("serial_nos"): item.current_serial_no = item_dict.get("serial_nos") - if self.purpose == "Stock Reconciliation": + if self.purpose == "Stock Reconciliation" and not item.serial_no:: item.serial_no = item.current_serial_no item.current_qty = item_dict.get("qty") From 6a5a380c07893f45f72f57ff3ac135b48f207ed0 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Sun, 2 May 2021 18:02:28 +0530 Subject: [PATCH 190/326] fix: total stock summary report not working --- erpnext/stock/report/total_stock_summary/total_stock_summary.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/report/total_stock_summary/total_stock_summary.py b/erpnext/stock/report/total_stock_summary/total_stock_summary.py index ed52393923..59c253c425 100644 --- a/erpnext/stock/report/total_stock_summary/total_stock_summary.py +++ b/erpnext/stock/report/total_stock_summary/total_stock_summary.py @@ -51,7 +51,7 @@ def get_total_stock(filters): INNER JOIN `tabWarehouse` warehouse ON warehouse.name = ledger.warehouse WHERE - actual_qty != 0 %s""" % (columns, conditions)) + ledger.actual_qty != 0 %s""" % (columns, conditions)) def validate_filters(filters): if filters.get("group_by") == 'Company' and \ From 9cc7c294e78b2580351b7e033bb6506143d3c263 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Mon, 3 May 2021 10:25:53 +0530 Subject: [PATCH 191/326] Update stock_reconciliation.py --- .../stock/doctype/stock_reconciliation/stock_reconciliation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index e4cdcb4116..2029b0708a 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -72,7 +72,7 @@ class StockReconciliation(StockController): if item_dict.get("serial_nos"): item.current_serial_no = item_dict.get("serial_nos") - if self.purpose == "Stock Reconciliation" and not item.serial_no:: + if self.purpose == "Stock Reconciliation" and not item.serial_no: item.serial_no = item.current_serial_no item.current_qty = item_dict.get("qty") From cdc99cdd49c152b00ecfce464917390feb323890 Mon Sep 17 00:00:00 2001 From: Saqib Date: Mon, 3 May 2021 11:54:55 +0530 Subject: [PATCH 192/326] fix(pos): incorrect expense account set in pos invoice (#25543) --- erpnext/public/js/controllers/transaction.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index a2b95cb757..f91b432a39 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -562,7 +562,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ weight_uom: item.weight_uom, manufacturer: item.manufacturer, stock_uom: item.stock_uom, - pos_profile: me.frm.doc.doctype == 'Sales Invoice' ? me.frm.doc.pos_profile : '', + pos_profile: cint(me.frm.doc.is_pos) ? me.frm.doc.pos_profile : '', cost_center: item.cost_center, tax_category: me.frm.doc.tax_category, item_tax_template: item.item_tax_template, From 8f34ca4ac6133c38f2d789e9b8bfd67c21555894 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 3 May 2021 15:32:13 +0530 Subject: [PATCH 193/326] fix: stock reconciliation getting time out error during submission --- .../stock/doctype/stock_reconciliation/stock_reconciliation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index 1396f19d3f..0ee6dc7877 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -469,7 +469,7 @@ class StockReconciliation(StockController): def submit(self): if len(self.items) > 100: msgprint(_("The task has been enqueued as a background job. In case there is any issue on processing in background, the system will add a comment about the error on this Stock Reconciliation and revert to the Draft stage")) - self.queue_action('submit') + self.queue_action('submit', timeout=2000) else: self._submit() From f1bdfac7a8855be07d101d99b6ca863d1f42ed3c Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 3 May 2021 18:37:00 +0530 Subject: [PATCH 194/326] fix: Employee Separation (#25503) * fix: Employee Separation - add ignore_mandatory flag for project creation - form clean-up * fix: Employee Separation test --- .../employee_separation.json | 761 ++++-------------- .../test_employee_separation.py | 2 +- erpnext/hr/utils.py | 4 +- 3 files changed, 160 insertions(+), 607 deletions(-) diff --git a/erpnext/hr/doctype/employee_separation/employee_separation.json b/erpnext/hr/doctype/employee_separation/employee_separation.json index f44d83060a..7af209887f 100644 --- a/erpnext/hr/doctype/employee_separation/employee_separation.json +++ b/erpnext/hr/doctype/employee_separation/employee_separation.json @@ -1,626 +1,177 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "HR-EMP-SEP-.YYYY.-.#####", - "beta": 0, - "creation": "2018-05-10 02:29:16.740490", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", + "actions": [], + "autoname": "HR-EMP-SEP-.YYYY.-.#####", + "creation": "2018-05-10 02:29:16.740490", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "employee", + "employee_name", + "department", + "designation", + "employee_grade", + "column_break_7", + "company", + "boarding_status", + "resignation_letter_date", + "project", + "table_for_activity", + "employee_separation_template", + "activities", + "notify_users_by_email", + "section_break_14", + "exit_interview", + "amended_from" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "employee", - "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": "Employee", - "length": 0, - "no_copy": 0, - "options": "Employee", - "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 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "employee.employee_name", - "fieldname": "employee_name", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Employee Name", - "length": 0, - "no_copy": 0, - "options": "", - "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 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "employee.resignation_letter_date", - "fieldname": "resignation_letter_date", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Resignation Letter 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 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "boarding_status", - "fieldtype": "Select", - "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": "Status", - "length": 0, - "no_copy": 0, - "options": "\nPending\nIn Process\nCompleted", - "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": "employee", + "fieldtype": "Link", + "label": "Employee", + "options": "Employee", + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_bulk_edit": 0, - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_in_quick_entry": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "notify_users_by_email", - "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": "Notify users by email", - "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 - }, - { - "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 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "employee_separation_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": "Employee Separation Template", - "length": 0, - "no_copy": 0, - "options": "Employee Separation 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 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "employee.company", - "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": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fetch_from": "employee.employee_name", + "fieldname": "employee_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Employee Name", + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "project", - "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": "Project", - "length": 0, - "no_copy": 0, - "options": "Project", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fetch_from": "employee.resignation_letter_date", + "fieldname": "resignation_letter_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Resignation Letter Date", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "employee.department", - "fieldname": "department", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Department", - "length": 0, - "no_copy": 0, - "options": "Department", - "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 - }, + "allow_on_submit": 1, + "fieldname": "boarding_status", + "fieldtype": "Select", + "label": "Status", + "options": "\nPending\nIn Process\nCompleted", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "employee.designation", - "fieldname": "designation", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Designation", - "length": 0, - "no_copy": 0, - "options": "Designation", - "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 - }, + "allow_on_submit": 1, + "default": "0", + "fieldname": "notify_users_by_email", + "fieldtype": "Check", + "label": "Notify users by email" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "employee.grade", - "fieldname": "employee_grade", - "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": "Employee Grade", - "length": 0, - "no_copy": 0, - "options": "Employee Grade", - "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": "table_for_activity", - "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": "", - "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": "employee_separation_template", + "fieldtype": "Link", + "label": "Employee Separation Template", + "options": "Employee Separation Template" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "activities", - "fieldtype": "Table", - "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": "Activities", - "length": 0, - "no_copy": 0, - "options": "Employee Boarding Activity", - "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 - }, + "fetch_from": "employee.company", + "fieldname": "company", + "fieldtype": "Link", + "label": "Company", + "options": "Company", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_14", - "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": "project", + "fieldtype": "Link", + "label": "Project", + "options": "Project", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "exit_interview", - "fieldtype": "Text Editor", - "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": "Exit Interview Summary", - "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 - }, + "fetch_from": "employee.department", + "fieldname": "department", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Department", + "options": "Department", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "amended_from", - "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": "Amended From", - "length": 0, - "no_copy": 1, - "options": "Employee Separation", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fetch_from": "employee.designation", + "fieldname": "designation", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Designation", + "options": "Designation", + "read_only": 1 + }, + { + "fetch_from": "employee.grade", + "fieldname": "employee_grade", + "fieldtype": "Link", + "label": "Employee Grade", + "options": "Employee Grade", + "read_only": 1 + }, + { + "fieldname": "table_for_activity", + "fieldtype": "Section Break", + "label": "Separation Activities" + }, + { + "allow_on_submit": 1, + "fieldname": "activities", + "fieldtype": "Table", + "label": "Activities", + "options": "Employee Boarding Activity" + }, + { + "fieldname": "section_break_14", + "fieldtype": "Section Break" + }, + { + "fieldname": "exit_interview", + "fieldtype": "Text Editor", + "label": "Exit Interview Summary" + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Employee Separation", + "print_hide": 1, + "read_only": 1 } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 1, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2019-08-03 16:15:39.025898", - "modified_by": "Administrator", - "module": "HR", - "name": "Employee Separation", - "name_case": "", - "owner": "Administrator", + ], + "is_submittable": 1, + "links": [], + "modified": "2021-04-28 15:58:36.020196", + "modified_by": "Administrator", + "module": "HR", + "name": "Employee Separation", + "owner": "Administrator", "permissions": [ { - "amend": 1, - "cancel": 1, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 1, + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "submit": 1, "write": 1 } - ], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "title_field": "employee_name", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "employee_name", + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/hr/doctype/employee_separation/test_employee_separation.py b/erpnext/hr/doctype/employee_separation/test_employee_separation.py index 2fa114d345..713fcf526b 100644 --- a/erpnext/hr/doctype/employee_separation/test_employee_separation.py +++ b/erpnext/hr/doctype/employee_separation/test_employee_separation.py @@ -18,7 +18,7 @@ class TestEmployeeSeparation(unittest.TestCase): 'activity_name': 'Deactivate Employee', 'role': 'HR User' }) - separation.status = 'Pending' + separation.boarding_status = 'Pending' separation.insert() separation.submit() self.assertEqual(separation.docstatus, 1) diff --git a/erpnext/hr/utils.py b/erpnext/hr/utils.py index 190eb4f10a..2540b3db63 100644 --- a/erpnext/hr/utils.py +++ b/erpnext/hr/utils.py @@ -32,13 +32,15 @@ class EmployeeBoardingController(Document): project_name += self.job_applicant else: project_name += self.employee + project = frappe.get_doc({ "doctype": "Project", "project_name": project_name, "expected_start_date": self.date_of_joining if self.doctype == "Employee Onboarding" else self.resignation_letter_date, "department": self.department, "company": self.company - }).insert(ignore_permissions=True) + }).insert(ignore_permissions=True, ignore_mandatory=True) + self.db_set("project", project.name) self.db_set("boarding_status", "Pending") self.reload() From e36f3030422babc8aed6207c9534716bfb5ae921 Mon Sep 17 00:00:00 2001 From: 18alantom <2.alan.tom@gmail.com> Date: Mon, 3 May 2021 19:49:22 +0530 Subject: [PATCH 195/326] fix: use percent string templates for db.sql calls --- erpnext/stock/get_item_details.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 3fc1df76bc..98d08c0a18 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -935,8 +935,8 @@ def get_bin_details(item_code, warehouse, company=None): def get_company_total_stock(item_code, company): return frappe.db.sql("""SELECT sum(actual_qty) from (`tabBin` INNER JOIN `tabWarehouse` ON `tabBin`.warehouse = `tabWarehouse`.name) - WHERE `tabWarehouse`.company = '{0}' and `tabBin`.item_code = '{1}'""" - .format(company, item_code))[0][0] + WHERE `tabWarehouse`.company = %s and `tabBin`.item_code = %s""", + (company, item_code))[0][0] @frappe.whitelist() def get_serial_no_details(item_code, warehouse, stock_qty, serial_no): From 308905b1bee572c0161c26e0f89bf8a2a86c0c1f Mon Sep 17 00:00:00 2001 From: 18alantom <2.alan.tom@gmail.com> Date: Mon, 3 May 2021 23:34:34 +0530 Subject: [PATCH 196/326] fix: semgrep, refactor default mutable dict --- erpnext/stock/get_item_details.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 98d08c0a18..3832415db6 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -470,7 +470,9 @@ def get_item_tax_template(args, item, out): item_tax_template = _get_item_tax_template(args, item_group_doc.taxes, out) item_group = item_group_doc.parent_item_group -def _get_item_tax_template(args, taxes, out={}, for_validate=False): +def _get_item_tax_template(args, taxes, out=None, for_validate=False): + if out is None: + out = {} taxes_with_validity = [] taxes_with_no_validity = [] From 076020643d55252c881cdc20ff1e169af97fb897 Mon Sep 17 00:00:00 2001 From: Saqib Date: Tue, 4 May 2021 12:26:49 +0530 Subject: [PATCH 197/326] fix: empty payment term column in accounts receivable report (#25556) --- .../report/accounts_receivable/accounts_receivable.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index 444b40ed79..db605f7285 100755 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -364,7 +364,7 @@ class ReceivablePayableReport(object): payment_terms_details = frappe.db.sql(""" select si.name, si.party_account_currency, si.currency, si.conversion_rate, - ps.due_date, ps.payment_amount, ps.description, ps.paid_amount, ps.discounted_amount + ps.due_date, ps.payment_term, ps.payment_amount, ps.description, ps.paid_amount, ps.discounted_amount from `tab{0}` si, `tabPayment Schedule` ps where si.name = ps.parent and @@ -394,7 +394,7 @@ class ReceivablePayableReport(object): "due_date": d.due_date, "invoiced": invoiced, "invoice_grand_total": row.invoiced, - "payment_term": d.description, + "payment_term": d.description or d.payment_term, "paid": d.paid_amount + d.discounted_amount, "credit_note": 0.0, "outstanding": invoiced - d.paid_amount - d.discounted_amount From 1e554378c7b79b03fc669056fc2653d993b3466a Mon Sep 17 00:00:00 2001 From: Saqib Date: Tue, 4 May 2021 12:28:24 +0530 Subject: [PATCH 198/326] fix(pos): incorrect expense account set in pos invoice (#25571) From 384f4b5b7e6d23436046d0f84888c5687cdca7f7 Mon Sep 17 00:00:00 2001 From: Saqib Date: Tue, 4 May 2021 12:33:49 +0530 Subject: [PATCH 199/326] fix: can't open general ledger from consolidated financial report (#25542) --- .../consolidated_financial_statement.js | 224 +++++++++--------- .../consolidated_financial_statement.py | 5 +- 2 files changed, 120 insertions(+), 109 deletions(-) diff --git a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.js b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.js index 09479221fb..1363b53746 100644 --- a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.js +++ b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.js @@ -2,118 +2,128 @@ // For license information, please see license.txt /* eslint-disable */ -frappe.query_reports["Consolidated Financial Statement"] = { - "filters": [ - { - "fieldname":"company", - "label": __("Company"), - "fieldtype": "Link", - "options": "Company", - "default": frappe.defaults.get_user_default("Company"), - "reqd": 1 - }, - { - "fieldname":"filter_based_on", - "label": __("Filter Based On"), - "fieldtype": "Select", - "options": ["Fiscal Year", "Date Range"], - "default": ["Fiscal Year"], - "reqd": 1, - on_change: function() { - let filter_based_on = frappe.query_report.get_filter_value('filter_based_on'); - frappe.query_report.toggle_filter_display('from_fiscal_year', filter_based_on === 'Date Range'); - frappe.query_report.toggle_filter_display('to_fiscal_year', filter_based_on === 'Date Range'); - frappe.query_report.toggle_filter_display('period_start_date', filter_based_on === 'Fiscal Year'); - frappe.query_report.toggle_filter_display('period_end_date', filter_based_on === 'Fiscal Year'); +frappe.require("assets/erpnext/js/financial_statements.js", function() { + frappe.query_reports["Consolidated Financial Statement"] = { + "filters": [ + { + "fieldname":"company", + "label": __("Company"), + "fieldtype": "Link", + "options": "Company", + "default": frappe.defaults.get_user_default("Company"), + "reqd": 1 + }, + { + "fieldname":"filter_based_on", + "label": __("Filter Based On"), + "fieldtype": "Select", + "options": ["Fiscal Year", "Date Range"], + "default": ["Fiscal Year"], + "reqd": 1, + on_change: function() { + let filter_based_on = frappe.query_report.get_filter_value('filter_based_on'); + frappe.query_report.toggle_filter_display('from_fiscal_year', filter_based_on === 'Date Range'); + frappe.query_report.toggle_filter_display('to_fiscal_year', filter_based_on === 'Date Range'); + frappe.query_report.toggle_filter_display('period_start_date', filter_based_on === 'Fiscal Year'); + frappe.query_report.toggle_filter_display('period_end_date', filter_based_on === 'Fiscal Year'); - frappe.query_report.refresh(); + frappe.query_report.refresh(); + } + }, + { + "fieldname":"period_start_date", + "label": __("Start Date"), + "fieldtype": "Date", + "hidden": 1, + "reqd": 1 + }, + { + "fieldname":"period_end_date", + "label": __("End Date"), + "fieldtype": "Date", + "hidden": 1, + "reqd": 1 + }, + { + "fieldname":"from_fiscal_year", + "label": __("Start Year"), + "fieldtype": "Link", + "options": "Fiscal Year", + "default": frappe.defaults.get_user_default("fiscal_year"), + "reqd": 1 + }, + { + "fieldname":"to_fiscal_year", + "label": __("End Year"), + "fieldtype": "Link", + "options": "Fiscal Year", + "default": frappe.defaults.get_user_default("fiscal_year"), + "reqd": 1 + }, + { + "fieldname":"finance_book", + "label": __("Finance Book"), + "fieldtype": "Link", + "options": "Finance Book" + }, + { + "fieldname":"report", + "label": __("Report"), + "fieldtype": "Select", + "options": ["Profit and Loss Statement", "Balance Sheet", "Cash Flow"], + "default": "Balance Sheet", + "reqd": 1 + }, + { + "fieldname": "presentation_currency", + "label": __("Currency"), + "fieldtype": "Select", + "options": erpnext.get_presentation_currency_list(), + "default": frappe.defaults.get_user_default("Currency") + }, + { + "fieldname":"accumulated_in_group_company", + "label": __("Accumulated Values in Group Company"), + "fieldtype": "Check", + "default": 0 + }, + { + "fieldname": "include_default_book_entries", + "label": __("Include Default Book Entries"), + "fieldtype": "Check", + "default": 1 + } + ], + "formatter": function(value, row, column, data, default_formatter) { + if (data && column.fieldname=="account") { + value = data.account_name || value; + + column.link_onclick = + "erpnext.financial_statements.open_general_ledger(" + JSON.stringify(data) + ")"; + column.is_tree = true; } - }, - { - "fieldname":"period_start_date", - "label": __("Start Date"), - "fieldtype": "Date", - "hidden": 1, - "reqd": 1 - }, - { - "fieldname":"period_end_date", - "label": __("End Date"), - "fieldtype": "Date", - "hidden": 1, - "reqd": 1 - }, - { - "fieldname":"from_fiscal_year", - "label": __("Start Year"), - "fieldtype": "Link", - "options": "Fiscal Year", - "default": frappe.defaults.get_user_default("fiscal_year"), - "reqd": 1 - }, - { - "fieldname":"to_fiscal_year", - "label": __("End Year"), - "fieldtype": "Link", - "options": "Fiscal Year", - "default": frappe.defaults.get_user_default("fiscal_year"), - "reqd": 1 - }, - { - "fieldname":"finance_book", - "label": __("Finance Book"), - "fieldtype": "Link", - "options": "Finance Book" - }, - { - "fieldname":"report", - "label": __("Report"), - "fieldtype": "Select", - "options": ["Profit and Loss Statement", "Balance Sheet", "Cash Flow"], - "default": "Balance Sheet", - "reqd": 1 - }, - { - "fieldname": "presentation_currency", - "label": __("Currency"), - "fieldtype": "Select", - "options": erpnext.get_presentation_currency_list(), - "default": frappe.defaults.get_user_default("Currency") - }, - { - "fieldname":"accumulated_in_group_company", - "label": __("Accumulated Values in Group Company"), - "fieldtype": "Check", - "default": 0 - }, - { - "fieldname": "include_default_book_entries", - "label": __("Include Default Book Entries"), - "fieldtype": "Check", - "default": 1 - } - ], - "formatter": function(value, row, column, data, default_formatter) { - value = default_formatter(value, row, column, data); - if (!data.parent_account) { - value = $(`${value}`); + value = default_formatter(value, row, column, data); - var $value = $(value).css("font-weight", "bold"); + if (!data.parent_account) { + value = $(`${value}`); - value = $value.wrap("

").parent().html(); - } - return value; - }, - onload: function() { - let fiscal_year = frappe.defaults.get_user_default("fiscal_year") + var $value = $(value).css("font-weight", "bold"); - frappe.model.with_doc("Fiscal Year", fiscal_year, function(r) { - var fy = frappe.model.get_doc("Fiscal Year", fiscal_year); - frappe.query_report.set_filter_value({ - period_start_date: fy.year_start_date, - period_end_date: fy.year_end_date + value = $value.wrap("

").parent().html(); + } + return value; + }, + onload: function() { + let fiscal_year = frappe.defaults.get_user_default("fiscal_year") + + frappe.model.with_doc("Fiscal Year", fiscal_year, function(r) { + var fy = frappe.model.get_doc("Fiscal Year", fiscal_year); + frappe.query_report.set_filter_value({ + period_start_date: fy.year_start_date, + period_end_date: fy.year_end_date + }); }); - }); + } } -} +}); \ No newline at end of file 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 0c4a422440..094f5db89b 100644 --- a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py +++ b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py @@ -329,8 +329,9 @@ def prepare_data(accounts, start_date, end_date, balance_must_be, companies, com has_value = False total = 0 row = frappe._dict({ - "account_name": _(d.account_name), - "account": _(d.account_name), + "account_name": ('%s - %s' %(_(d.account_number), _(d.account_name)) + if d.account_number else _(d.account_name)), + "account": _(d.name), "parent_account": _(d.parent_account), "indent": flt(d.indent), "year_start_date": start_date, From ba8dc1ffbd8d02cb6d01e3b10c0c21a9956bcca3 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 4 May 2021 15:03:10 +0530 Subject: [PATCH 200/326] fix: stock balance and batchwise balance history report showing different closing stock --- .../batch_wise_balance_history/batch_wise_balance_history.py | 2 +- erpnext/stock/report/stock_balance/stock_balance.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py b/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py index 087c12ed2d..01927c2d10 100644 --- a/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py +++ b/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py @@ -70,7 +70,7 @@ def get_stock_ledger_entries(filters): return frappe.db.sql(""" select item_code, batch_no, warehouse, posting_date, sum(actual_qty) as actual_qty from `tabStock Ledger Entry` - where docstatus < 2 and ifnull(batch_no, '') != '' %s + where is_cancelled = 0 and docstatus < 2 and ifnull(batch_no, '') != '' %s group by voucher_no, batch_no, item_code, warehouse order by item_code, warehouse""" % conditions, as_dict=1) diff --git a/erpnext/stock/report/stock_balance/stock_balance.py b/erpnext/stock/report/stock_balance/stock_balance.py index 6dfede4590..bbd73e9112 100644 --- a/erpnext/stock/report/stock_balance/stock_balance.py +++ b/erpnext/stock/report/stock_balance/stock_balance.py @@ -165,7 +165,7 @@ def get_stock_ledger_entries(filters, items): select sle.item_code, warehouse, sle.posting_date, sle.actual_qty, sle.valuation_rate, sle.company, sle.voucher_type, sle.qty_after_transaction, sle.stock_value_difference, - sle.item_code as name, sle.voucher_no, sle.stock_value + sle.item_code as name, sle.voucher_no, sle.stock_value, sle.batch_no from `tabStock Ledger Entry` sle force index (posting_sort_index) where sle.docstatus < 2 %s %s @@ -193,7 +193,7 @@ def get_item_warehouse_map(filters, sle): qty_dict = iwb_map[(d.company, d.item_code, d.warehouse)] - if d.voucher_type == "Stock Reconciliation": + if d.voucher_type == "Stock Reconciliation" and not d.batch_no: qty_diff = flt(d.qty_after_transaction) - flt(qty_dict.bal_qty) else: qty_diff = flt(d.actual_qty) From eebc6e9277b3d461ae4da4a92fa9cc45c27eea55 Mon Sep 17 00:00:00 2001 From: Afshan <33727827+AfshanKhan@users.noreply.github.com> Date: Tue, 4 May 2021 17:05:12 +0530 Subject: [PATCH 201/326] refactor: Show item's full name on hover over item in POS (#25554) Co-authored-by: Saqib --- .../page/point_of_sale/pos_item_selector.js | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/erpnext/selling/page/point_of_sale/pos_item_selector.js b/erpnext/selling/page/point_of_sale/pos_item_selector.js index 709fe57747..9384ae5542 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_selector.js +++ b/erpnext/selling/page/point_of_sale/pos_item_selector.js @@ -81,13 +81,24 @@ erpnext.PointOfSale.ItemSelector = class { const { item_image, serial_no, batch_no, barcode, actual_qty, stock_uom } = item; const indicator_color = actual_qty > 10 ? "green" : actual_qty <= 0 ? "red" : "orange"; + let qty_to_display = actual_qty; + + if (Math.round(qty_to_display) > 999) { + qty_to_display = Math.round(qty_to_display)/1000; + qty_to_display = qty_to_display.toFixed(1) + 'K'; + } + function get_item_image_html() { if (!me.hide_images && item_image) { - return `
+ return `
+ ${qty_to_display}
+
${frappe.get_abbr(item.item_name)}
`; } else { - return `
${frappe.get_abbr(item.item_name)}
`; + return `
+ ${qty_to_display}
+
${frappe.get_abbr(item.item_name)}
`; } } @@ -95,13 +106,12 @@ erpnext.PointOfSale.ItemSelector = class { `
+ title="${item.item_name}"> ${get_item_image_html()}
- ${frappe.ellipsis(item.item_name, 18)}
${format_currency(item.price_list_rate, item.currency, 0) || 0}
@@ -316,4 +326,4 @@ erpnext.PointOfSale.ItemSelector = class { toggle_component(show) { show ? this.$component.css('display', 'flex') : this.$component.css('display', 'none'); } -}; \ No newline at end of file +}; From 18ad15ed160ce209cea7b6da7cc6fc07fb3ee152 Mon Sep 17 00:00:00 2001 From: Asharam Seervi Date: Tue, 4 May 2021 19:28:07 +0530 Subject: [PATCH 202/326] fix: designation insufficient permission on lead doctype. (#25331) --- erpnext/hr/doctype/designation/designation.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/erpnext/hr/doctype/designation/designation.json b/erpnext/hr/doctype/designation/designation.json index 4c3888be4a..bab6b90d1a 100644 --- a/erpnext/hr/doctype/designation/designation.json +++ b/erpnext/hr/doctype/designation/designation.json @@ -182,6 +182,10 @@ "share": 1, "submit": 0, "write": 1 + }, + { + "read": 1, + "role": "Sales User" } ], "quick_entry": 1, @@ -191,4 +195,4 @@ "track_changes": 0, "track_seen": 0, "track_views": 0 -} \ No newline at end of file +} From 04923d6a65637e7e6c50db826b46e92bbb491c2e Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 4 May 2021 21:01:12 +0530 Subject: [PATCH 203/326] fix: function call to update payment schedule labels --- erpnext/public/js/controllers/transaction.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index f91b432a39..d218a5ee5f 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1384,7 +1384,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ company_currency, "payment_schedule"); this.frm.set_currency_labels(["payment_amount", "outstanding", "paid_amount"], this.frm.doc.currency, "payment_schedule"); - + var schedule_grid = this.frm.fields_dict["payment_schedule"].grid; $.each(["base_payment_amount", "base_outstanding", "base_paid_amount"], function(i, fname) { if (frappe.meta.get_docfield(schedule_grid.doctype, fname)) @@ -2034,7 +2034,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ if(r.message && !r.exc) { me.frm.set_value("payment_schedule", r.message); const company_currency = me.get_company_currency(); - this.update_payment_schedule_grid_labels(company_currency); + me.update_payment_schedule_grid_labels(company_currency); } } }) From 2a20a03c28c32944b5a5726241906010d42456af Mon Sep 17 00:00:00 2001 From: 18alantom <2.alan.tom@gmail.com> Date: Wed, 5 May 2021 11:59:15 +0530 Subject: [PATCH 204/326] fix: check for None in item.schedule_date before setting --- erpnext/controllers/buying_controller.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index b686dc026c..3f2d3390c0 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -838,9 +838,10 @@ class BuyingController(StockController): if not self.get("items"): return - earliest_schedule_date = min([d.schedule_date for d in self.get("items")]) - if earliest_schedule_date: - self.schedule_date = earliest_schedule_date + if any(d.schedule_date for d in self.get("items")): + # Select earliest schedule_date. + self.schedule_date = min(d.schedule_date for d in self.get("items") + if d.schedule_date is not None) if self.schedule_date: for d in self.get('items'): From 1bb7bb74adb4b07bd2efc6e2fb641ed84a262fae Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 5 May 2021 12:19:57 +0530 Subject: [PATCH 205/326] fix: Check if payment schedule exits before updating label --- erpnext/public/js/controllers/transaction.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index d218a5ee5f..10c802c6f0 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1379,7 +1379,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ update_payment_schedule_grid_labels: function(company_currency) { const me = this; - if (this.frm.fields_dict["payment_schedule"]) { + if (this.frm.doc.payment_schedule.length > 0) { this.frm.set_currency_labels(["base_payment_amount", "base_outstanding", "base_paid_amount"], company_currency, "payment_schedule"); this.frm.set_currency_labels(["payment_amount", "outstanding", "paid_amount"], From 136eb30081b7b74d3b500acf6731696b4692da8e Mon Sep 17 00:00:00 2001 From: 18alantom <2.alan.tom@gmail.com> Date: Wed, 5 May 2021 12:26:29 +0530 Subject: [PATCH 206/326] fix: use get_serial_nos for splitting --- erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 4461f29fe3..4de877353a 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -1111,7 +1111,7 @@ class SalesInvoice(SellingController): if not item.serial_no: continue - for serial_no in item.serial_no.split("\n"): + for serial_no in get_serial_nos(item.serial_no): if serial_no and frappe.db.get_value('Serial No', serial_no, 'item_code') == item.item_code: frappe.db.set_value('Serial No', serial_no, 'sales_invoice', invoice) From ffea9d4126c9ab544ac7a659874b9f41254404ec Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 5 May 2021 12:28:40 +0530 Subject: [PATCH 207/326] fix: Check if payment schedule exists --- erpnext/public/js/controllers/transaction.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 10c802c6f0..0af8da77a0 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1379,7 +1379,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ update_payment_schedule_grid_labels: function(company_currency) { const me = this; - if (this.frm.doc.payment_schedule.length > 0) { + if (this.frm.doc.payment_schedule && this.frm.doc.payment_schedule.length > 0) { this.frm.set_currency_labels(["base_payment_amount", "base_outstanding", "base_paid_amount"], company_currency, "payment_schedule"); this.frm.set_currency_labels(["payment_amount", "outstanding", "paid_amount"], From 85b675a554cfcdf78cfa5255e0a12e06e7e02e44 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 5 May 2021 20:57:31 +0530 Subject: [PATCH 208/326] fix: Invoices not fetch during payment reconciliation --- .../doctype/payment_reconciliation/payment_reconciliation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index cf6ec18f3b..6635128f9e 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -114,7 +114,7 @@ class PaymentReconciliation(Document): 'party_type': self.party_type, 'voucher_type': voucher_type, 'account': self.receivable_payable_account - }, as_dict=1, debug=1) + }, as_dict=1) def add_payment_entries(self, entries): self.set('payments', []) From bb3e5d00f44869132d869fbead5046ba1d3ddfc6 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Sat, 24 Apr 2021 17:28:33 +0530 Subject: [PATCH 209/326] fix: allow to receive same serial numbers multiple times --- .../purchase_receipt/test_purchase_receipt.py | 83 +++++++++++++------ erpnext/stock/doctype/serial_no/serial_no.py | 18 +--- .../stock/report/serial_no_ledger/__init__.py | 0 .../serial_no_ledger/serial_no_ledger.js | 52 ++++++++++++ .../serial_no_ledger/serial_no_ledger.json | 33 ++++++++ .../serial_no_ledger/serial_no_ledger.py | 53 ++++++++++++ erpnext/stock/stock_ledger.py | 53 ++++++++++-- 7 files changed, 247 insertions(+), 45 deletions(-) create mode 100644 erpnext/stock/report/serial_no_ledger/__init__.py create mode 100644 erpnext/stock/report/serial_no_ledger/serial_no_ledger.js create mode 100644 erpnext/stock/report/serial_no_ledger/serial_no_ledger.json create mode 100644 erpnext/stock/report/serial_no_ledger/serial_no_ledger.py diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 16eea24f84..f9b2c1d787 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -13,8 +13,9 @@ from erpnext.stock.doctype.serial_no.serial_no import SerialNoDuplicateError from erpnext.accounts.doctype.account.test_account import get_inventory_account from erpnext.stock.doctype.item.test_item import make_item from six import iteritems +from erpnext.stock.stock_ledger import SerialNoExistsInFutureTransaction from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse - +from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos class TestPurchaseReceipt(unittest.TestCase): def setUp(self): @@ -144,6 +145,62 @@ class TestPurchaseReceipt(unittest.TestCase): self.assertFalse(frappe.db.get_value('Batch', {'item': item.name, 'reference_name': pr.name})) self.assertFalse(frappe.db.get_all('Serial No', {'batch_no': batch_no})) + def test_duplicate_serial_nos(self): + from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note + + item = frappe.db.exists("Item", {'item_name': 'Test Serialized Item 123'}) + if not item: + item = create_item("Test Serialized Item 123") + item.has_serial_no = 1 + item.serial_no_series = "TSI123-.####" + item.save() + else: + item = frappe.get_doc("Item", {'item_name': 'Test Serialized Item 123'}) + + # First make purchase receipt + pr = make_purchase_receipt(item_code=item.name, qty=2, rate=500) + pr.load_from_db() + + serial_nos = frappe.db.get_value('Stock Ledger Entry', + {'voucher_type': 'Purchase Receipt', 'voucher_no': pr.name, 'item_code': item.name}, 'serial_no') + + serial_nos = get_serial_nos(serial_nos) + + self.assertEquals(get_serial_nos(pr.items[0].serial_no), serial_nos) + + # Then tried to receive same serial nos in difference company + pr_different_company = make_purchase_receipt(item_code=item.name, qty=2, rate=500, + serial_no='\n'.join(serial_nos), company='_Test Company 1', do_not_submit=True, + warehouse = 'Stores - _TC1') + + self.assertRaises(SerialNoDuplicateError, pr_different_company.submit) + + # Then made delivery note to remove the serial nos from stock + dn = create_delivery_note(item_code=item.name, qty=2, rate = 1500, serial_no='\n'.join(serial_nos)) + dn.load_from_db() + self.assertEquals(get_serial_nos(dn.items[0].serial_no), serial_nos) + + posting_date = add_days(today(), -3) + + # Try to receive same serial nos again in the same company with backdated. + pr1 = make_purchase_receipt(item_code=item.name, qty=2, rate=500, + posting_date=posting_date, serial_no='\n'.join(serial_nos), do_not_submit=True) + + self.assertRaises(SerialNoExistsInFutureTransaction, pr1.submit) + + # Try to receive same serial nos with different company with backdated. + pr2 = make_purchase_receipt(item_code=item.name, qty=2, rate=500, + posting_date=posting_date, serial_no='\n'.join(serial_nos), company='_Test Company 1', do_not_submit=True, + warehouse = 'Stores - _TC1') + + self.assertRaises(SerialNoExistsInFutureTransaction, pr2.submit) + + # Receive the same serial nos after the delivery note posting date and time + make_purchase_receipt(item_code=item.name, qty=2, rate=500, serial_no='\n'.join(serial_nos)) + + # Raise the error for backdated deliver note entry cancel + self.assertRaises(SerialNoExistsInFutureTransaction, dn.cancel) + def test_purchase_receipt_gl_entry(self): pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1", @@ -562,30 +619,6 @@ class TestPurchaseReceipt(unittest.TestCase): new_pr_doc.cancel() - def test_not_accept_duplicate_serial_no(self): - from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry - from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note - - item_code = frappe.db.get_value('Item', {'has_serial_no': 1, 'is_fixed_asset': 0, "has_batch_no": 0}) - if not item_code: - item = make_item("Test Serial Item 1", dict(has_serial_no=1, has_batch_no=0)) - item_code = item.name - - serial_no = random_string(5) - pr1 = make_purchase_receipt(item_code=item_code, qty=1, serial_no=serial_no) - dn = create_delivery_note(item_code=item_code, qty=1, serial_no=serial_no) - - pr2 = make_purchase_receipt(item_code=item_code, qty=1, serial_no=serial_no, do_not_submit=True) - self.assertRaises(SerialNoDuplicateError, pr2.submit) - - se = make_stock_entry(item_code=item_code, target="_Test Warehouse - _TC", qty=1, - serial_no=serial_no, basic_rate=100, do_not_submit=True) - se.submit() - - se.cancel() - dn.cancel() - pr1.cancel() - def test_auto_asset_creation(self): asset_item = "Test Asset Item" diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py index c02dd2e518..5ecc9f8140 100644 --- a/erpnext/stock/doctype/serial_no/serial_no.py +++ b/erpnext/stock/doctype/serial_no/serial_no.py @@ -243,7 +243,7 @@ def validate_serial_no(sle, item_det): if frappe.db.exists("Serial No", serial_no): sr = frappe.db.get_value("Serial No", serial_no, ["name", "item_code", "batch_no", "sales_order", "delivery_document_no", "delivery_document_type", "warehouse", "purchase_document_type", - "purchase_document_no", "company"], as_dict=1) + "purchase_document_no", "company", "status"], as_dict=1) if sr.item_code!=sle.item_code: if not allow_serial_nos_with_different_item(serial_no, sle): @@ -266,6 +266,9 @@ def validate_serial_no(sle, item_det): frappe.throw(_("Serial No {0} does not belong to Warehouse {1}").format(serial_no, sle.warehouse), SerialNoWarehouseError) + if not sr.purchase_document_no: + frappe.throw(_("Serial No {0} not in stock").format(serial_no), SerialNoNotExistsError) + if sle.voucher_type in ("Delivery Note", "Sales Invoice"): if sr.batch_no and sr.batch_no != sle.batch_no: @@ -382,19 +385,6 @@ def has_serial_no_exists(sn, sle): if sn.company != sle.company: return False - status = False - if sn.purchase_document_no: - if (sle.voucher_type in ['Purchase Receipt', 'Stock Entry', "Purchase Invoice"] and - sn.delivery_document_type not in ['Purchase Receipt', 'Stock Entry', "Purchase Invoice"]): - status = True - - # If status is receipt then system will allow to in-ward the delivered serial no - if (status and sle.voucher_type == "Stock Entry" and frappe.db.get_value("Stock Entry", - sle.voucher_no, "purpose") in ("Material Receipt", "Material Transfer")): - status = False - - return status - def allow_serial_nos_with_different_item(sle_serial_no, sle): """ Allows same serial nos for raw materials and finished goods diff --git a/erpnext/stock/report/serial_no_ledger/__init__.py b/erpnext/stock/report/serial_no_ledger/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/stock/report/serial_no_ledger/serial_no_ledger.js b/erpnext/stock/report/serial_no_ledger/serial_no_ledger.js new file mode 100644 index 0000000000..616312e311 --- /dev/null +++ b/erpnext/stock/report/serial_no_ledger/serial_no_ledger.js @@ -0,0 +1,52 @@ +// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports["Serial No Ledger"] = { + "filters": [ + { + 'label': __('Item Code'), + 'fieldtype': 'Link', + 'fieldname': 'item_code', + 'reqd': 1, + 'options': 'Item', + get_query: function() { + return { + filters: { + 'has_serial_no': 1 + } + } + } + }, + { + 'label': __('Serial No'), + 'fieldtype': 'Link', + 'fieldname': 'serial_no', + 'options': 'Serial No', + 'reqd': 1 + }, + { + 'label': __('Warehouse'), + 'fieldtype': 'Link', + 'fieldname': 'warehouse', + 'options': 'Warehouse', + get_query: function() { + let company = frappe.query_report.get_filter_value('company'); + + if (company) { + return { + filters: { + 'company': company + } + } + } + } + }, + { + 'label': __('As On Date'), + 'fieldtype': 'Date', + 'fieldname': 'posting_date', + 'default': frappe.datetime.get_today() + }, + ] +}; diff --git a/erpnext/stock/report/serial_no_ledger/serial_no_ledger.json b/erpnext/stock/report/serial_no_ledger/serial_no_ledger.json new file mode 100644 index 0000000000..e20e74c78b --- /dev/null +++ b/erpnext/stock/report/serial_no_ledger/serial_no_ledger.json @@ -0,0 +1,33 @@ +{ + "add_total_row": 0, + "columns": [], + "creation": "2021-04-20 13:32:41.523219", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 0, + "is_standard": "Yes", + "json": "{}", + "modified": "2021-04-20 13:33:19.015829", + "modified_by": "Administrator", + "module": "Stock", + "name": "Serial No Ledger", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Stock Ledger Entry", + "report_name": "Serial No Ledger", + "report_type": "Script Report", + "roles": [ + { + "role": "Stock User" + }, + { + "role": "Purchase User" + }, + { + "role": "Sales User" + } + ] +} \ No newline at end of file diff --git a/erpnext/stock/report/serial_no_ledger/serial_no_ledger.py b/erpnext/stock/report/serial_no_ledger/serial_no_ledger.py new file mode 100644 index 0000000000..c3339fd341 --- /dev/null +++ b/erpnext/stock/report/serial_no_ledger/serial_no_ledger.py @@ -0,0 +1,53 @@ +# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +from frappe import _ +from erpnext.stock.stock_ledger import get_stock_ledger_entries +from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos + +def execute(filters=None): + columns = get_columns(filters) + data = get_data(filters) + return columns, data + +def get_columns(filters): + columns = [{ + 'label': _('Posting Date'), + 'fieldtype': 'Date', + 'fieldname': 'posting_date' + }, { + 'label': _('Posting Time'), + 'fieldtype': 'Time', + 'fieldname': 'posting_time' + }, { + 'label': _('Voucher Type'), + 'fieldtype': 'Link', + 'fieldname': 'voucher_type', + 'options': 'DocType', + 'width': 220 + }, { + 'label': _('Voucher No'), + 'fieldtype': 'Dynamic Link', + 'fieldname': 'voucher_no', + 'options': 'voucher_type', + 'width': 220 + }, { + 'label': _('Company'), + 'fieldtype': 'Link', + 'fieldname': 'company', + 'options': 'Company', + 'width': 220 + }, { + 'label': _('Warehouse'), + 'fieldtype': 'Link', + 'fieldname': 'warehouse', + 'options': 'Warehouse', + 'width': 220 + }] + + return columns + +def get_data(filters): + return get_stock_ledger_entries(filters, '<=', order="asc") or [] + diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index bbfcb7ad7d..9729987d2d 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -2,9 +2,11 @@ # License: GNU General Public License v3. See license.txt from __future__ import unicode_literals -import frappe, erpnext +import frappe +import erpnext +import copy from frappe import _ -from frappe.utils import cint, flt, cstr, now, now_datetime +from frappe.utils import cint, flt, cstr, now, get_link_to_form from frappe.model.meta import get_field_precision from erpnext.stock.utils import get_valuation_method, get_incoming_outgoing_rate_for_cancel from erpnext.stock.utils import get_bin @@ -13,6 +15,8 @@ from six import iteritems # future reposting class NegativeStockError(frappe.ValidationError): pass +class SerialNoExistsInFutureTransaction(frappe.ValidationError): + pass _exceptions = frappe.local('stockledger_exceptions') # _exceptions = [] @@ -27,6 +31,9 @@ def make_sl_entries(sl_entries, allow_negative_stock=False, via_landed_cost_vouc set_as_cancel(sl_entries[0].get('voucher_type'), sl_entries[0].get('voucher_no')) for sle in sl_entries: + if sle.serial_no: + validate_serial_no(sle) + if cancel: sle['actual_qty'] = -flt(sle.get('actual_qty')) @@ -46,6 +53,30 @@ def make_sl_entries(sl_entries, allow_negative_stock=False, via_landed_cost_vouc args = sle_doc.as_dict() update_bin(args, allow_negative_stock, via_landed_cost_voucher) +def validate_serial_no(sle): + from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos + for sn in get_serial_nos(sle.serial_no): + args = copy.deepcopy(sle) + args.serial_no = sn + args.warehouse = '' + + vouchers = [] + for row in get_stock_ledger_entries(args, '>'): + voucher_type = frappe.bold(row.voucher_type) + voucher_no = frappe.bold(get_link_to_form(row.voucher_type, row.voucher_no)) + vouchers.append(f'{voucher_type} {voucher_no}') + + if vouchers: + serial_no = frappe.bold(sn) + msg = (f'''The serial no {serial_no} has been used in the future transactions so you need to cancel them first. + The list of the transactions are as below.''' + '

  • ') + + msg += '
  • '.join(vouchers) + msg += '
' + + title = 'Cannot Submit' if not sle.get('is_cancelled') else 'Cannot Cancel' + frappe.throw(_(msg), title=_(title), exc=SerialNoExistsInFutureTransaction) + def validate_cancellation(args): if args[0].get("is_cancelled"): repost_entry = frappe.db.get_value("Repost Item Valuation", { @@ -718,7 +749,17 @@ def get_stock_ledger_entries(previous_sle, operator=None, conditions += " and " + previous_sle.get("warehouse_condition") if check_serial_no and previous_sle.get("serial_no"): - conditions += " and serial_no like {}".format(frappe.db.escape('%{0}%'.format(previous_sle.get("serial_no")))) + # conditions += " and serial_no like {}".format(frappe.db.escape('%{0}%'.format(previous_sle.get("serial_no")))) + serial_no = previous_sle.get("serial_no") + conditions += (""" and + ( + serial_no = {0} + or serial_no like {1} + or serial_no like {2} + or serial_no like {3} + ) + """).format(frappe.db.escape(serial_no), frappe.db.escape('{}\n%'.format(serial_no)), + frappe.db.escape('%\n{}'.format(serial_no)), frappe.db.escape('%\n{}\n%'.format(serial_no))) if not previous_sle.get("posting_date"): previous_sle["posting_date"] = "1900-01-01" @@ -793,12 +834,12 @@ def get_valuation_rate(item_code, warehouse, voucher_type, voucher_no, if not allow_zero_rate and not valuation_rate and raise_error_if_no_rate \ and cint(erpnext.is_perpetual_inventory_enabled(company)): frappe.local.message_log = [] - form_link = frappe.utils.get_link_to_form("Item", item_code) + form_link = get_link_to_form("Item", item_code) message = _("Valuation Rate for the Item {0}, is required to do accounting entries for {1} {2}.").format(form_link, voucher_type, voucher_no) - message += "

" + _(" Here are the options to proceed:") + message += "

" + _("Here are the options to proceed:") solutions = "
  • " + _("If the item is transacting as a Zero Valuation Rate item in this entry, please enable 'Allow Zero Valuation Rate' in the {0} Item table.").format(voucher_type) + "
  • " - solutions += "
  • " + _("If not, you can Cancel / Submit this entry ") + _("{0}").format(frappe.bold("after")) + _(" performing either one below:") + "
  • " + solutions += "
  • " + _("If not, you can Cancel / Submit this entry") + " {0} ".format(frappe.bold("after")) + _("performing either one below:") + "
  • " sub_solutions = "
    • " + _("Create an incoming stock transaction for the Item.") + "
    • " sub_solutions += "
    • " + _("Mention Valuation Rate in the Item master.") + "
    " msg = message + solutions + sub_solutions + "" From d502f763197b9d3891c4c2e498c132a87d35ee19 Mon Sep 17 00:00:00 2001 From: Saqib Date: Thu, 6 May 2021 16:10:55 +0530 Subject: [PATCH 210/326] feat(e-invoicing): e-way bill validity field (#25555) --- erpnext/patches.txt | 1 + .../patches/v12_0/add_ewaybill_validity_field.py | 16 ++++++++++++++++ erpnext/regional/india/e_invoice/utils.py | 12 ++++++++---- erpnext/regional/india/setup.py | 3 +++ 4 files changed, 28 insertions(+), 4 deletions(-) create mode 100644 erpnext/patches/v12_0/add_ewaybill_validity_field.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 23f9fd8ecb..7faaf26158 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -774,5 +774,6 @@ erpnext.patches.v12_0.add_document_type_field_for_italy_einvoicing erpnext.patches.v13_0.make_non_standard_user_type #13-04-2021 erpnext.patches.v13_0.update_shipment_status erpnext.patches.v13_0.remove_attribute_field_from_item_variant_setting +erpnext.patches.v12_0.add_ewaybill_validity_field erpnext.patches.v13_0.germany_make_custom_fields erpnext.patches.v13_0.germany_fill_debtor_creditor_number diff --git a/erpnext/patches/v12_0/add_ewaybill_validity_field.py b/erpnext/patches/v12_0/add_ewaybill_validity_field.py new file mode 100644 index 0000000000..87d98f1a56 --- /dev/null +++ b/erpnext/patches/v12_0/add_ewaybill_validity_field.py @@ -0,0 +1,16 @@ +from __future__ import unicode_literals +import frappe +from frappe.custom.doctype.custom_field.custom_field import create_custom_fields + +def execute(): + company = frappe.get_all('Company', filters = {'country': 'India'}) + if not company: + return + + custom_fields = { + 'Sales Invoice': [ + dict(fieldname='eway_bill_validity', label='E-Way Bill Validity', fieldtype='Data', no_copy=1, print_hide=1, + depends_on='ewaybill', read_only=1, allow_on_submit=1, insert_after='ewaybill') + ] + } + create_custom_fields(custom_fields, update=True) \ No newline at end of file diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 699441be7e..b4e7a8889e 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -71,13 +71,14 @@ def validate_einvoice_fields(doc): def raise_document_name_too_long_error(): title = _('Document ID Too Long') - msg = _('As you have E-Invoicing enabled, to be able to generate IRN for this invoice, ') - msg += _('document id {} exceed 16 letters. ').format(bold(_('should not'))) + msg = _('As you have E-Invoicing enabled, to be able to generate IRN for this invoice') + msg += ', ' + msg += _('document id {} exceed 16 letters.').format(bold(_('should not'))) msg += '

    ' - msg += _('You must {} your {} in order to have document id of {} length 16. ').format( + msg += _('You must {} your {} in order to have document id of {} length 16.').format( bold(_('modify')), bold(_('naming series')), bold(_('maximum')) ) - msg += _('Please account for ammended documents too. ') + msg += _('Please account for ammended documents too.') frappe.throw(msg, title=title) def read_json(name): @@ -847,6 +848,7 @@ class GSPConnector(): res = self.make_request('post', self.generate_ewaybill_url, headers, data) if res.get('success'): self.invoice.ewaybill = res.get('result').get('EwbNo') + self.invoice.eway_bill_validity = res.get('result').get('EwbValidTill') self.invoice.eway_bill_cancelled = 0 self.invoice.update(args) self.invoice.flags.updater_reference = { @@ -944,6 +946,7 @@ class GSPConnector(): self.invoice.irn = res.get('Irn') self.invoice.ewaybill = res.get('EwbNo') + self.invoice.eway_bill_validity = res.get('EwbValidTill') self.invoice.ack_no = res.get('AckNo') self.invoice.ack_date = res.get('AckDt') self.invoice.signed_einvoice = dec_signed_invoice @@ -960,6 +963,7 @@ class GSPConnector(): 'label': _('IRN Generated') } self.update_invoice() + def attach_qrcode_image(self): qrcode = self.invoice.signed_qr_code doctype = self.invoice.doctype diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index 9ded8dab5b..b12e152b14 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -422,6 +422,9 @@ def make_custom_fields(update=True): dict(fieldname='irn_cancelled', label='IRN Cancelled', fieldtype='Check', no_copy=1, print_hide=1, depends_on='eval:(doc.irn_cancelled === 1)', read_only=1, allow_on_submit=1, insert_after='customer'), + dict(fieldname='eway_bill_validity', label='E-Way Bill Validity', fieldtype='Data', no_copy=1, print_hide=1, + depends_on='ewaybill', read_only=1, allow_on_submit=1, insert_after='ewaybill'), + dict(fieldname='eway_bill_cancelled', label='E-Way Bill Cancelled', fieldtype='Check', no_copy=1, print_hide=1, depends_on='eval:(doc.eway_bill_cancelled === 1)', read_only=1, allow_on_submit=1, insert_after='customer'), From f43a86d90ffb4029dcaee6768c6974f4e90e3026 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Thu, 6 May 2021 16:40:06 +0530 Subject: [PATCH 211/326] perf: significant reduction in time taken to save a delivery note (#25475) --- erpnext/selling/doctype/customer/customer.py | 32 +++++++++++++++----- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index 49ca9423e8..51d86ff0bf 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -490,7 +490,7 @@ def get_customer_outstanding(customer, company, ignore_outstanding_sales_order=F outstanding_based_on_gle = flt(outstanding_based_on_gle[0][0]) if outstanding_based_on_gle else 0 # Outstanding based on Sales Order - outstanding_based_on_so = 0.0 + outstanding_based_on_so = 0 # if credit limit check is bypassed at sales order level, # we should not consider outstanding Sales Orders, when customer credit balance report is run @@ -501,9 +501,11 @@ def get_customer_outstanding(customer, company, ignore_outstanding_sales_order=F where customer=%s and docstatus = 1 and company=%s and per_billed < 100 and status != 'Closed'""", (customer, company)) - outstanding_based_on_so = flt(outstanding_based_on_so[0][0]) if outstanding_based_on_so else 0.0 + outstanding_based_on_so = flt(outstanding_based_on_so[0][0]) if outstanding_based_on_so else 0 # Outstanding based on Delivery Note, which are not created against Sales Order + outstanding_based_on_dn = 0 + unmarked_delivery_note_items = frappe.db.sql("""select dn_item.name, dn_item.amount, dn.base_net_total, dn.base_grand_total from `tabDelivery Note` dn, `tabDelivery Note Item` dn_item @@ -515,15 +517,29 @@ def get_customer_outstanding(customer, company, ignore_outstanding_sales_order=F and ifnull(dn_item.against_sales_invoice, '') = '' """, (customer, company), as_dict=True) - outstanding_based_on_dn = 0.0 + if not unmarked_delivery_note_items: + return outstanding_based_on_gle + outstanding_based_on_so + + si_amounts = frappe.db.sql(""" + SELECT + dn_detail, sum(amount) from `tabSales Invoice Item` + WHERE + docstatus = 1 + and dn_detail in ({}) + GROUP BY dn_detail""".format(", ".join( + frappe.db.escape(dn_item.name) + for dn_item in unmarked_delivery_note_items + )) + ) + + si_amounts = {si_item[0]: si_item[1] for si_item in si_amounts} for dn_item in unmarked_delivery_note_items: - si_amount = frappe.db.sql("""select sum(amount) - from `tabSales Invoice Item` - where dn_detail = %s and docstatus = 1""", dn_item.name)[0][0] + dn_amount = flt(dn_item.amount) + si_amount = flt(si_amounts.get(dn_item.name)) - if flt(dn_item.amount) > flt(si_amount) and dn_item.base_net_total: - outstanding_based_on_dn += ((flt(dn_item.amount) - flt(si_amount)) \ + if dn_amount > si_amount and dn_item.base_net_total: + outstanding_based_on_dn += ((dn_amount - si_amount) / dn_item.base_net_total) * dn_item.base_grand_total return outstanding_based_on_gle + outstanding_based_on_so + outstanding_based_on_dn From 900a8fb21a9a22d4f683912035777c33341730d6 Mon Sep 17 00:00:00 2001 From: Saqib Date: Thu, 6 May 2021 17:02:47 +0530 Subject: [PATCH 212/326] feat(pos): ability to retry on pos closing failure (#25595) * feat(pos): ability to retry on pos closing failure * fix: sider issues * fix: sider issues * fix: mark all queued closing entry as failed * feat: add headline message --- .../pos_closing_entry/pos_closing_entry.js | 104 ++++++++++-------- .../pos_closing_entry/pos_closing_entry.json | 21 +++- .../pos_closing_entry/pos_closing_entry.py | 4 + .../pos_closing_entry_list.js | 1 + .../pos_invoice_merge_log.py | 87 +++++++++++---- erpnext/controllers/status_updater.py | 1 + erpnext/patches.txt | 1 + .../v13_0/set_pos_closing_as_failed.py | 7 ++ 8 files changed, 158 insertions(+), 68 deletions(-) create mode 100644 erpnext/patches/v13_0/set_pos_closing_as_failed.py diff --git a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js index 9ea616f8e7..aa0c53e228 100644 --- a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js +++ b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js @@ -22,7 +22,43 @@ frappe.ui.form.on('POS Closing Entry', { }); if (frm.doc.docstatus === 0 && !frm.doc.amended_from) frm.set_value("period_end_date", frappe.datetime.now_datetime()); - if (frm.doc.docstatus === 1) set_html_data(frm); + + frappe.realtime.on('closing_process_complete', async function(data) { + await frm.reload_doc(); + if (frm.doc.status == 'Failed' && frm.doc.error_message && data.user == frappe.session.user) { + frappe.msgprint({ + title: __('POS Closing Failed'), + message: frm.doc.error_message, + indicator: 'orange', + clear: true + }); + } + }); + + set_html_data(frm); + }, + + refresh: function(frm) { + if (frm.doc.docstatus == 1 && frm.doc.status == 'Failed') { + const issue = 'issue'; + frm.dashboard.set_headline( + __('POS Closing failed while running in a background process. You can resolve the {0} and retry the process again.', [issue])); + + $('#jump_to_error').on('click', (e) => { + e.preventDefault(); + frappe.utils.scroll_to( + cur_frm.get_field("error_message").$wrapper, + true, + 30 + ); + }); + + frm.add_custom_button(__('Retry'), function () { + frm.call('retry', {}, () => { + frm.reload_doc(); + }); + }); + } }, pos_opening_entry(frm) { @@ -61,44 +97,24 @@ frappe.ui.form.on('POS Closing Entry', { refresh_fields(frm); set_html_data(frm); } - }) + }); + }, + + before_save: function(frm) { + for (let row of frm.doc.pos_transactions) { + frappe.db.get_doc("POS Invoice", row.pos_invoice).then(doc => { + cur_frm.doc.grand_total -= flt(doc.grand_total); + cur_frm.doc.net_total -= flt(doc.net_total); + cur_frm.doc.total_quantity -= flt(doc.total_qty); + refresh_payments(doc, cur_frm, 1); + refresh_taxes(doc, cur_frm, 1); + refresh_fields(cur_frm); + set_html_data(cur_frm); + }); + } } }); -cur_frm.cscript.before_pos_transactions_remove = function(doc, cdt, cdn) { - const removed_row = locals[cdt][cdn]; - - if (!removed_row.pos_invoice) return; - - frappe.db.get_doc("POS Invoice", removed_row.pos_invoice).then(doc => { - cur_frm.doc.grand_total -= flt(doc.grand_total); - cur_frm.doc.net_total -= flt(doc.net_total); - cur_frm.doc.total_quantity -= flt(doc.total_qty); - refresh_payments(doc, cur_frm, 1); - refresh_taxes(doc, cur_frm, 1); - refresh_fields(cur_frm); - set_html_data(cur_frm); - }); -} - -frappe.ui.form.on('POS Invoice Reference', { - pos_invoice(frm, cdt, cdn) { - const added_row = locals[cdt][cdn]; - - if (!added_row.pos_invoice) return; - - frappe.db.get_doc("POS Invoice", added_row.pos_invoice).then(doc => { - frm.doc.grand_total += flt(doc.grand_total); - frm.doc.net_total += flt(doc.net_total); - frm.doc.total_quantity += flt(doc.total_qty); - refresh_payments(doc, frm); - refresh_taxes(doc, frm); - refresh_fields(frm); - set_html_data(frm); - }); - } -}) - frappe.ui.form.on('POS Closing Entry Detail', { closing_amount: (frm, cdt, cdn) => { const row = locals[cdt][cdn]; @@ -177,11 +193,13 @@ function refresh_fields(frm) { } function set_html_data(frm) { - frappe.call({ - method: "get_payment_reconciliation_details", - doc: frm.doc, - callback: (r) => { - frm.get_field("payment_reconciliation_details").$wrapper.html(r.message); - } - }) + if (frm.doc.docstatus === 1 && frm.doc.status == 'Submitted') { + frappe.call({ + method: "get_payment_reconciliation_details", + doc: frm.doc, + callback: (r) => { + frm.get_field("payment_reconciliation_details").$wrapper.html(r.message); + } + }); + } } diff --git a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.json b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.json index a9b91e02a9..4d6e4a2ba0 100644 --- a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.json +++ b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.json @@ -30,6 +30,8 @@ "total_quantity", "column_break_16", "taxes", + "failure_description_section", + "error_message", "section_break_14", "amended_from" ], @@ -195,7 +197,7 @@ "fieldtype": "Select", "hidden": 1, "label": "Status", - "options": "Draft\nSubmitted\nQueued\nCancelled", + "options": "Draft\nSubmitted\nQueued\nFailed\nCancelled", "print_hide": 1, "read_only": 1 }, @@ -203,6 +205,21 @@ "fieldname": "period_details_section", "fieldtype": "Section Break", "label": "Period Details" + }, + { + "collapsible": 1, + "collapsible_depends_on": "error_message", + "depends_on": "error_message", + "fieldname": "failure_description_section", + "fieldtype": "Section Break", + "label": "Failure Description" + }, + { + "depends_on": "error_message", + "fieldname": "error_message", + "fieldtype": "Small Text", + "label": "Error", + "read_only": 1 } ], "is_submittable": 1, @@ -212,7 +229,7 @@ "link_fieldname": "pos_closing_entry" } ], - "modified": "2021-02-01 13:47:20.722104", + "modified": "2021-05-05 16:59:49.723261", "modified_by": "Administrator", "module": "Accounts", "name": "POS Closing Entry", diff --git a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py index 1065168a50..82528728dd 100644 --- a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py +++ b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py @@ -60,6 +60,10 @@ class POSClosingEntry(StatusUpdater): def on_cancel(self): unconsolidate_pos_invoices(closing_entry=self) + @frappe.whitelist() + def retry(self): + consolidate_pos_invoices(closing_entry=self) + def update_opening_entry(self, for_cancel=False): opening_entry = frappe.get_doc("POS Opening Entry", self.pos_opening_entry) opening_entry.pos_closing_entry = self.name if not for_cancel else None diff --git a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry_list.js b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry_list.js index 20fd610899..cffeb4d535 100644 --- a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry_list.js +++ b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry_list.js @@ -8,6 +8,7 @@ frappe.listview_settings['POS Closing Entry'] = { "Draft": "red", "Submitted": "blue", "Queued": "orange", + "Failed": "red", "Cancelled": "red" }; diff --git a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py index 4d5472df4b..bc7874305c 100644 --- a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py +++ b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py @@ -13,8 +13,7 @@ from frappe.model.mapper import map_doc, map_child_doc from frappe.utils.scheduler import is_scheduler_inactive from frappe.core.page.background_jobs.background_jobs import get_info import json - -from six import iteritems +import six class POSInvoiceMergeLog(Document): def validate(self): @@ -239,7 +238,7 @@ def consolidate_pos_invoices(pos_invoices=None, closing_entry=None): invoices = pos_invoices or (closing_entry and closing_entry.get('pos_transactions')) or get_all_unconsolidated_invoices() invoice_by_customer = get_invoice_customer_map(invoices) - if len(invoices) >= 1 and closing_entry: + if len(invoices) >= 10 and closing_entry: closing_entry.set_status(update=True, status='Queued') enqueue_job(create_merge_logs, invoice_by_customer=invoice_by_customer, closing_entry=closing_entry) else: @@ -252,36 +251,68 @@ def unconsolidate_pos_invoices(closing_entry): pluck='name' ) - if len(merge_logs) >= 1: + if len(merge_logs) >= 10: closing_entry.set_status(update=True, status='Queued') enqueue_job(cancel_merge_logs, merge_logs=merge_logs, closing_entry=closing_entry) else: cancel_merge_logs(merge_logs, closing_entry) def create_merge_logs(invoice_by_customer, closing_entry=None): - for customer, invoices in iteritems(invoice_by_customer): - merge_log = frappe.new_doc('POS Invoice Merge Log') - merge_log.posting_date = getdate(closing_entry.get('posting_date')) if closing_entry else nowdate() - merge_log.customer = customer - merge_log.pos_closing_entry = closing_entry.get('name') if closing_entry else None + try: + for customer, invoices in six.iteritems(invoice_by_customer): + merge_log = frappe.new_doc('POS Invoice Merge Log') + merge_log.posting_date = getdate(closing_entry.get('posting_date')) if closing_entry else nowdate() + merge_log.customer = customer + merge_log.pos_closing_entry = closing_entry.get('name') if closing_entry else None - merge_log.set('pos_invoices', invoices) - merge_log.save(ignore_permissions=True) - merge_log.submit() + merge_log.set('pos_invoices', invoices) + merge_log.save(ignore_permissions=True) + merge_log.submit() - if closing_entry: - closing_entry.set_status(update=True, status='Submitted') - closing_entry.update_opening_entry() + if closing_entry: + closing_entry.set_status(update=True, status='Submitted') + closing_entry.db_set('error_message', '') + closing_entry.update_opening_entry() + + except Exception: + frappe.db.rollback() + message_log = frappe.message_log.pop() + error_message = safe_load_json(message_log) + + if closing_entry: + closing_entry.set_status(update=True, status='Failed') + closing_entry.db_set('error_message', error_message) + raise + + finally: + frappe.db.commit() + frappe.publish_realtime('closing_process_complete', {'user': frappe.session.user}) def cancel_merge_logs(merge_logs, closing_entry=None): - for log in merge_logs: - merge_log = frappe.get_doc('POS Invoice Merge Log', log) - merge_log.flags.ignore_permissions = True - merge_log.cancel() + try: + for log in merge_logs: + merge_log = frappe.get_doc('POS Invoice Merge Log', log) + merge_log.flags.ignore_permissions = True + merge_log.cancel() - if closing_entry: - closing_entry.set_status(update=True, status='Cancelled') - closing_entry.update_opening_entry(for_cancel=True) + if closing_entry: + closing_entry.set_status(update=True, status='Cancelled') + closing_entry.db_set('error_message', '') + closing_entry.update_opening_entry(for_cancel=True) + + except Exception: + frappe.db.rollback() + message_log = frappe.message_log.pop() + error_message = safe_load_json(message_log) + + if closing_entry: + closing_entry.set_status(update=True, status='Submitted') + closing_entry.db_set('error_message', error_message) + raise + + finally: + frappe.db.commit() + frappe.publish_realtime('closing_process_complete', {'user': frappe.session.user}) def enqueue_job(job, **kwargs): check_scheduler_status() @@ -314,4 +345,14 @@ def check_scheduler_status(): def job_already_enqueued(job_name): enqueued_jobs = [d.get("job_name") for d in get_info()] if job_name in enqueued_jobs: - return True \ No newline at end of file + return True + +def safe_load_json(message): + JSONDecodeError = ValueError if six.PY2 else json.JSONDecodeError + + try: + json_message = json.loads(message).get('message') + except JSONDecodeError: + json_message = message + + return json_message \ No newline at end of file diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py index 5276da9720..4bb6138e5d 100644 --- a/erpnext/controllers/status_updater.py +++ b/erpnext/controllers/status_updater.py @@ -98,6 +98,7 @@ status_map = { ["Draft", None], ["Submitted", "eval:self.docstatus == 1"], ["Queued", "eval:self.status == 'Queued'"], + ["Failed", "eval:self.status == 'Failed'"], ["Cancelled", "eval:self.docstatus == 2"], ] } diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 7faaf26158..82d223cada 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -777,3 +777,4 @@ erpnext.patches.v13_0.remove_attribute_field_from_item_variant_setting erpnext.patches.v12_0.add_ewaybill_validity_field erpnext.patches.v13_0.germany_make_custom_fields erpnext.patches.v13_0.germany_fill_debtor_creditor_number +erpnext.patches.v13_0.set_pos_closing_as_failed diff --git a/erpnext/patches/v13_0/set_pos_closing_as_failed.py b/erpnext/patches/v13_0/set_pos_closing_as_failed.py new file mode 100644 index 0000000000..1c576db1c7 --- /dev/null +++ b/erpnext/patches/v13_0/set_pos_closing_as_failed.py @@ -0,0 +1,7 @@ +from __future__ import unicode_literals +import frappe + +def execute(): + frappe.reload_doc('accounts', 'doctype', 'pos_closing_entry') + + frappe.db.sql("update `tabPOS Closing Entry` set `status` = 'Failed' where `status` = 'Queued'") \ No newline at end of file From 695becdd0569aac2bb57e0c87b9d4234b3ca9647 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 6 May 2021 18:03:32 +0530 Subject: [PATCH 213/326] fix: added validation in stock entry to check duplicate serial nos --- .../stock/doctype/stock_entry/stock_entry.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 48cfa51041..2f76bc7d56 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -76,6 +76,7 @@ class StockEntry(StockController): self.validate_difference_account() self.set_job_card_data() self.set_purpose_for_stock_entry() + self.validate_duplicate_serial_no() if not self.from_bom: self.fg_completed_qty = 0.0 @@ -587,6 +588,22 @@ class StockEntry(StockController): self.purpose = frappe.get_cached_value('Stock Entry Type', self.stock_entry_type, 'purpose') + def validate_duplicate_serial_no(self): + warehouse_wise_serial_nos = {} + + # In case of repack the source and target serial nos could be same + for warehouse in ['s_warehouse', 't_warehouse']: + serial_nos = [] + for row in self.items: + if not (row.serial_no and row.get(warehouse)): continue + + for sn in get_serial_nos(row.serial_no): + if sn in serial_nos: + frappe.throw(_('The serial no {0} has added multiple times in the stock entry {1}') + .format(frappe.bold(sn), self.name)) + + serial_nos.append(sn) + def validate_purchase_order(self): """Throw exception if more raw material is transferred against Purchase Order than in the raw materials supplied table""" From 134eaa5786745fc9930a795b74f0461e353363ac Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Thu, 6 May 2021 19:13:54 +0530 Subject: [PATCH 214/326] perf: Performance enhancement on setup wizard (#25605) * perf: Performance enhancement on setup wizard * fix: create departments without updating nsm --- erpnext/accounts/doctype/account/account.py | 2 +- .../chart_of_accounts/chart_of_accounts.py | 4 +- .../accounts_settings/accounts_settings.py | 4 +- .../education_settings/education_settings.py | 4 +- erpnext/hr/doctype/department/department.py | 3 +- .../payroll_settings/payroll_settings.py | 4 +- .../selling_settings/selling_settings.py | 4 +- .../global_defaults/global_defaults.py | 12 +- .../doctype/naming_series/naming_series.py | 16 +-- .../operations/install_fixtures.py | 134 ++++++++++-------- erpnext/setup/setup_wizard/setup_wizard.py | 9 -- .../doctype/stock_settings/stock_settings.py | 10 +- 12 files changed, 105 insertions(+), 101 deletions(-) diff --git a/erpnext/accounts/doctype/account/account.py b/erpnext/accounts/doctype/account/account.py index 0606823821..1be2fbf5c8 100644 --- a/erpnext/accounts/doctype/account/account.py +++ b/erpnext/accounts/doctype/account/account.py @@ -13,7 +13,7 @@ class BalanceMismatchError(frappe.ValidationError): pass class Account(NestedSet): nsm_parent_field = 'parent_account' def on_update(self): - if frappe.local.flags.ignore_on_update: + if frappe.local.flags.ignore_update_nsm: return else: super(Account, self).on_update() 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 0e3b24cda3..927adc7086 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 @@ -57,10 +57,10 @@ def create_charts(company, chart_template=None, existing_company=None, custom_ch # Rebuild NestedSet HSM tree for Account Doctype # after all accounts are already inserted. - frappe.local.flags.ignore_on_update = True + frappe.local.flags.ignore_update_nsm = True _import_accounts(chart, None, None, root_account=True) rebuild_tree("Account", "parent_account") - frappe.local.flags.ignore_on_update = False + frappe.local.flags.ignore_update_nsm = False def add_suffix_if_duplicate(account_name, account_number, accounts): if account_number: diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py index 5593466fc2..4d3388090d 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py @@ -30,5 +30,5 @@ class AccountsSettings(Document): def enable_payment_schedule_in_print(self): show_in_print = cint(self.show_payment_schedule_in_print) for doctype in ("Sales Order", "Sales Invoice", "Purchase Order", "Purchase Invoice"): - make_property_setter(doctype, "due_date", "print_hide", show_in_print, "Check") - make_property_setter(doctype, "payment_schedule", "print_hide", 0 if show_in_print else 1, "Check") + make_property_setter(doctype, "due_date", "print_hide", show_in_print, "Check", validate_fields_for_doctype=False) + make_property_setter(doctype, "payment_schedule", "print_hide", 0 if show_in_print else 1, "Check", validate_fields_for_doctype=False) diff --git a/erpnext/education/doctype/education_settings/education_settings.py b/erpnext/education/doctype/education_settings/education_settings.py index a85d3e70f3..658380ea42 100644 --- a/erpnext/education/doctype/education_settings/education_settings.py +++ b/erpnext/education/doctype/education_settings/education_settings.py @@ -31,9 +31,9 @@ class EducationSettings(Document): def validate(self): from frappe.custom.doctype.property_setter.property_setter import make_property_setter if self.get('instructor_created_by')=='Naming Series': - make_property_setter('Instructor', "naming_series", "hidden", 0, "Check") + make_property_setter('Instructor', "naming_series", "hidden", 0, "Check", validate_fields_for_doctype=False) else: - make_property_setter('Instructor', "naming_series", "hidden", 1, "Check") + make_property_setter('Instructor', "naming_series", "hidden", 1, "Check", validate_fields_for_doctype=False) def update_website_context(context): context["lms_enabled"] = frappe.get_doc("Education Settings").enable_lms \ No newline at end of file diff --git a/erpnext/hr/doctype/department/department.py b/erpnext/hr/doctype/department/department.py index 2cef509276..539a360269 100644 --- a/erpnext/hr/doctype/department/department.py +++ b/erpnext/hr/doctype/department/department.py @@ -31,7 +31,8 @@ class Department(NestedSet): return new def on_update(self): - NestedSet.on_update(self) + if not frappe.local.flags.ignore_update_nsm: + super(Department, self).on_update() def on_trash(self): super(Department, self).on_trash() diff --git a/erpnext/payroll/doctype/payroll_settings/payroll_settings.py b/erpnext/payroll/doctype/payroll_settings/payroll_settings.py index 5efa41db1f..459b7eacb4 100644 --- a/erpnext/payroll/doctype/payroll_settings/payroll_settings.py +++ b/erpnext/payroll/doctype/payroll_settings/payroll_settings.py @@ -28,5 +28,5 @@ class PayrollSettings(Document): def toggle_rounded_total(self): self.disable_rounded_total = cint(self.disable_rounded_total) - make_property_setter("Salary Slip", "rounded_total", "hidden", self.disable_rounded_total, "Check") - make_property_setter("Salary Slip", "rounded_total", "print_hide", self.disable_rounded_total, "Check") + make_property_setter("Salary Slip", "rounded_total", "hidden", self.disable_rounded_total, "Check", validate_fields_for_doctype=False) + make_property_setter("Salary Slip", "rounded_total", "print_hide", self.disable_rounded_total, "Check", validate_fields_for_doctype=False) diff --git a/erpnext/selling/doctype/selling_settings/selling_settings.py b/erpnext/selling/doctype/selling_settings/selling_settings.py index d297883876..b219e7ecce 100644 --- a/erpnext/selling/doctype/selling_settings/selling_settings.py +++ b/erpnext/selling/doctype/selling_settings/selling_settings.py @@ -30,8 +30,8 @@ class SellingSettings(Document): # Make property setters to hide tax_id fields for doctype in ("Sales Order", "Sales Invoice", "Delivery Note"): - make_property_setter(doctype, "tax_id", "hidden", self.hide_tax_id, "Check") - make_property_setter(doctype, "tax_id", "print_hide", self.hide_tax_id, "Check") + make_property_setter(doctype, "tax_id", "hidden", self.hide_tax_id, "Check", validate_fields_for_doctype=False) + make_property_setter(doctype, "tax_id", "print_hide", self.hide_tax_id, "Check", validate_fields_for_doctype=False) def set_default_customer_group_and_territory(self): if not self.customer_group: diff --git a/erpnext/setup/doctype/global_defaults/global_defaults.py b/erpnext/setup/doctype/global_defaults/global_defaults.py index 76a8450829..e587217181 100644 --- a/erpnext/setup/doctype/global_defaults/global_defaults.py +++ b/erpnext/setup/doctype/global_defaults/global_defaults.py @@ -60,11 +60,11 @@ class GlobalDefaults(Document): # Make property setters to hide rounded total fields for doctype in ("Quotation", "Sales Order", "Sales Invoice", "Delivery Note", "Supplier Quotation", "Purchase Order", "Purchase Invoice"): - make_property_setter(doctype, "base_rounded_total", "hidden", self.disable_rounded_total, "Check") - make_property_setter(doctype, "base_rounded_total", "print_hide", 1, "Check") + make_property_setter(doctype, "base_rounded_total", "hidden", self.disable_rounded_total, "Check", validate_fields_for_doctype=False) + make_property_setter(doctype, "base_rounded_total", "print_hide", 1, "Check", validate_fields_for_doctype=False) - make_property_setter(doctype, "rounded_total", "hidden", self.disable_rounded_total, "Check") - make_property_setter(doctype, "rounded_total", "print_hide", self.disable_rounded_total, "Check") + make_property_setter(doctype, "rounded_total", "hidden", self.disable_rounded_total, "Check", validate_fields_for_doctype=False) + make_property_setter(doctype, "rounded_total", "print_hide", self.disable_rounded_total, "Check", validate_fields_for_doctype=False) def toggle_in_words(self): self.disable_in_words = cint(self.disable_in_words) @@ -72,5 +72,5 @@ class GlobalDefaults(Document): # Make property setters to hide in words fields for doctype in ("Quotation", "Sales Order", "Sales Invoice", "Delivery Note", "Supplier Quotation", "Purchase Order", "Purchase Invoice", "Purchase Receipt"): - make_property_setter(doctype, "in_words", "hidden", self.disable_in_words, "Check") - make_property_setter(doctype, "in_words", "print_hide", self.disable_in_words, "Check") + make_property_setter(doctype, "in_words", "hidden", self.disable_in_words, "Check", validate_fields_for_doctype=False) + make_property_setter(doctype, "in_words", "print_hide", self.disable_in_words, "Check", validate_fields_for_doctype=False) diff --git a/erpnext/setup/doctype/naming_series/naming_series.py b/erpnext/setup/doctype/naming_series/naming_series.py index 373b0a58c9..c1f9433b41 100644 --- a/erpnext/setup/doctype/naming_series/naming_series.py +++ b/erpnext/setup/doctype/naming_series/naming_series.py @@ -183,8 +183,8 @@ class NamingSeries(Document): def set_by_naming_series(doctype, fieldname, naming_series, hide_name_field=True): from frappe.custom.doctype.property_setter.property_setter import make_property_setter if naming_series: - make_property_setter(doctype, "naming_series", "hidden", 0, "Check") - make_property_setter(doctype, "naming_series", "reqd", 1, "Check") + make_property_setter(doctype, "naming_series", "hidden", 0, "Check", validate_fields_for_doctype=False) + make_property_setter(doctype, "naming_series", "reqd", 1, "Check", validate_fields_for_doctype=False) # set values for mandatory try: @@ -195,15 +195,15 @@ def set_by_naming_series(doctype, fieldname, naming_series, hide_name_field=True pass if hide_name_field: - make_property_setter(doctype, fieldname, "reqd", 0, "Check") - make_property_setter(doctype, fieldname, "hidden", 1, "Check") + make_property_setter(doctype, fieldname, "reqd", 0, "Check", validate_fields_for_doctype=False) + make_property_setter(doctype, fieldname, "hidden", 1, "Check", validate_fields_for_doctype=False) else: - make_property_setter(doctype, "naming_series", "reqd", 0, "Check") - make_property_setter(doctype, "naming_series", "hidden", 1, "Check") + make_property_setter(doctype, "naming_series", "reqd", 0, "Check", validate_fields_for_doctype=False) + make_property_setter(doctype, "naming_series", "hidden", 1, "Check", validate_fields_for_doctype=False) if hide_name_field: - make_property_setter(doctype, fieldname, "hidden", 0, "Check") - make_property_setter(doctype, fieldname, "reqd", 1, "Check") + make_property_setter(doctype, fieldname, "hidden", 0, "Check", validate_fields_for_doctype=False) + make_property_setter(doctype, fieldname, "reqd", 1, "Check", validate_fields_for_doctype=False) # set values for mandatory frappe.db.sql("""update `tab{doctype}` set `{fieldname}`=`name` where diff --git a/erpnext/setup/setup_wizard/operations/install_fixtures.py b/erpnext/setup/setup_wizard/operations/install_fixtures.py index 5053c6a512..5c725d332d 100644 --- a/erpnext/setup/setup_wizard/operations/install_fixtures.py +++ b/erpnext/setup/setup_wizard/operations/install_fixtures.py @@ -12,6 +12,7 @@ from frappe.desk.doctype.global_search_settings.global_search_settings import up from erpnext.accounts.doctype.account.account import RootNotEditable from erpnext.regional.address_template.setup import set_up_address_templates +from frappe.utils.nestedset import rebuild_tree default_lead_sources = ["Existing Customer", "Reference", "Advertisement", "Cold Calling", "Exhibition", "Supplier Reference", "Mass Mailing", @@ -280,13 +281,15 @@ def install(country=None): set_more_defaults() update_global_search_doctypes() - # path = frappe.get_app_path('erpnext', 'regional', frappe.scrub(country)) - # if os.path.exists(path.encode("utf-8")): - # frappe.get_attr("erpnext.regional.{0}.setup.setup_company_independent_fixtures".format(frappe.scrub(country)))() - - def set_more_defaults(): # Do more setup stuff that can be done here with no dependencies + update_selling_defaults() + update_buying_defaults() + update_hr_defaults() + add_uom_data() + update_item_variant_settings() + +def update_selling_defaults(): selling_settings = frappe.get_doc("Selling Settings") selling_settings.set_default_customer_group_and_territory() selling_settings.cust_master_name = "Customer Name" @@ -296,13 +299,7 @@ def set_more_defaults(): selling_settings.sales_update_frequency = "Each Transaction" selling_settings.save() - add_uom_data() - - # set no copy fields of an item doctype to item variant settings - doc = frappe.get_doc('Item Variant Settings') - doc.set_default_fields() - doc.save() - +def update_buying_defaults(): buying_settings = frappe.get_doc("Buying Settings") buying_settings.supp_master_name = "Supplier Name" buying_settings.po_required = "No" @@ -311,12 +308,19 @@ def set_more_defaults(): buying_settings.allow_multiple_items = 1 buying_settings.save() +def update_hr_defaults(): hr_settings = frappe.get_doc("HR Settings") hr_settings.emp_created_by = "Naming Series" hr_settings.leave_approval_notification_template = _("Leave Approval Notification") hr_settings.leave_status_notification_template = _("Leave Status Notification") hr_settings.save() +def update_item_variant_settings(): + # set no copy fields of an item doctype to item variant settings + doc = frappe.get_doc('Item Variant Settings') + doc.set_default_fields() + doc.save() + def add_uom_data(): # add UOMs uoms = json.loads(open(frappe.get_app_path("erpnext", "setup", "setup_wizard", "data", "uom_data.json")).read()) @@ -327,7 +331,7 @@ def add_uom_data(): "uom_name": _(d.get("uom_name")), "name": _(d.get("uom_name")), "must_be_whole_number": d.get("must_be_whole_number") - }).insert(ignore_permissions=True) + }).db_insert() # bootstrap uom conversion factors uom_conversions = json.loads(open(frappe.get_app_path("erpnext", "setup", "setup_wizard", "data", "uom_conversion_data.json")).read()) @@ -336,7 +340,7 @@ def add_uom_data(): frappe.get_doc({ "doctype": "UOM Category", "category_name": _(d.get("category")) - }).insert(ignore_permissions=True) + }).db_insert() if not frappe.db.exists("UOM Conversion Factor", {"from_uom": _(d.get("from_uom")), "to_uom": _(d.get("to_uom"))}): uom_conversion = frappe.get_doc({ @@ -369,8 +373,8 @@ def add_sale_stages(): {"doctype": "Sales Stage", "stage_name": _("Proposal/Price Quote")}, {"doctype": "Sales Stage", "stage_name": _("Negotiation/Review")} ] - - make_records(records) + for sales_stage in records: + frappe.get_doc(sales_stage).db_insert() def install_company(args): records = [ @@ -418,7 +422,14 @@ def install_post_company_fixtures(args=None): {'doctype': 'Department', 'department_name': _('Legal'), 'parent_department': _('All Departments'), 'company': args.company_name}, ] - make_records(records) + # Make root department with NSM updation + make_records(records[:1]) + + frappe.local.flags.ignore_update_nsm = True + make_records(records[1:]) + frappe.local.flags.ignore_update_nsm = False + + rebuild_tree("Department", "parent_department") def install_defaults(args=None): @@ -432,7 +443,15 @@ def install_defaults(args=None): # enable default currency frappe.db.set_value("Currency", args.get("currency"), "enabled", 1) + frappe.db.set_value("Stock Settings", None, "email_footer_address", args.get("company_name")) + set_global_defaults(args) + set_active_domains(args) + update_stock_settings() + update_shopping_cart_settings(args) + create_bank_account(args) + +def set_global_defaults(args): global_defaults = frappe.get_doc("Global Defaults", "Global Defaults") current_fiscal_year = frappe.get_all("Fiscal Year")[0] @@ -445,13 +464,10 @@ def install_defaults(args=None): global_defaults.save() - system_settings = frappe.get_doc("System Settings") - system_settings.email_footer_address = args.get("company_name") - system_settings.save() - - domain_settings = frappe.get_single('Domain Settings') - domain_settings.set_active_domains(args.get('domains')) +def set_active_domains(args): + frappe.get_single('Domain Settings').set_active_domains(args.get('domains')) +def update_stock_settings(): stock_settings = frappe.get_doc("Stock Settings") stock_settings.item_naming_by = "Item Code" stock_settings.valuation_method = "FIFO" @@ -463,48 +479,44 @@ def install_defaults(args=None): stock_settings.set_qty_in_transactions_based_on_serial_no_input = 1 stock_settings.save() - if args.bank_account: - company_name = args.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, - 'parent_account': bank_account_group, - 'is_group':0, - 'company': company_name, - "account_type": "Bank", - }) - try: - doc = bank_account.insert() +def create_bank_account(args): + if not args.bank_account: + return - frappe.db.set_value("Company", args.company_name, "default_bank_account", bank_account.name, update_modified=False) + company_name = args.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, + 'parent_account': bank_account_group, + 'is_group':0, + 'company': company_name, + "account_type": "Bank", + }) + try: + doc = bank_account.insert() - except RootNotEditable: - frappe.throw(_("Bank account cannot be named as {0}").format(args.bank_account)) - except frappe.DuplicateEntryError: - # bank account same as a CoA entry - pass + frappe.db.set_value("Company", args.company_name, "default_bank_account", bank_account.name, update_modified=False) - # Now, with fixtures out of the way, onto concrete stuff - records = [ - - # Shopping cart: needs price lists - { - "doctype": "Shopping Cart Settings", - "enabled": 1, - 'company': args.company_name, - # uh oh - 'price_list': frappe.db.get_value("Price List", {"selling": 1}), - 'default_customer_group': _("Individual"), - 'quotation_series': "QTN-", - }, - ] - - make_records(records) + except RootNotEditable: + frappe.throw(_("Bank account cannot be named as {0}").format(args.bank_account)) + except frappe.DuplicateEntryError: + # bank account same as a CoA entry + pass +def update_shopping_cart_settings(args): + shopping_cart = frappe.get_doc("Shopping Cart Settings") + shopping_cart.update({ + "enabled": 1, + 'company': args.company_name, + 'price_list': frappe.db.get_value("Price List", {"selling": 1}), + 'default_customer_group': _("Individual"), + 'quotation_series': "QTN-", + }) + shopping_cart.update_single(shopping_cart.get_valid_dict()) def get_fy_details(fy_start_date, fy_end_date): start_year = getdate(fy_start_date).year diff --git a/erpnext/setup/setup_wizard/setup_wizard.py b/erpnext/setup/setup_wizard/setup_wizard.py index e74d837ef5..f63d2695aa 100644 --- a/erpnext/setup/setup_wizard/setup_wizard.py +++ b/erpnext/setup/setup_wizard/setup_wizard.py @@ -51,11 +51,6 @@ def get_setup_stages(args=None): 'status': _('Setting defaults'), 'fail_msg': 'Failed to set defaults', 'tasks': [ - { - 'fn': setup_post_company_fixtures, - 'args': args, - 'fail_msg': _("Failed to setup post company fixtures") - }, { 'fn': setup_defaults, 'args': args, @@ -94,9 +89,6 @@ def stage_fixtures(args): def setup_company(args): fixtures.install_company(args) -def setup_post_company_fixtures(args): - fixtures.install_post_company_fixtures(args) - def setup_defaults(args): fixtures.install_defaults(frappe._dict(args)) @@ -129,7 +121,6 @@ def login_as_first_user(args): def setup_complete(args=None): stage_fixtures(args) setup_company(args) - setup_post_company_fixtures(args) setup_defaults(args) stage_four(args) fin(args) diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.py b/erpnext/stock/doctype/stock_settings/stock_settings.py index 3b9608b805..2dd7c6f35b 100644 --- a/erpnext/stock/doctype/stock_settings/stock_settings.py +++ b/erpnext/stock/doctype/stock_settings/stock_settings.py @@ -30,7 +30,7 @@ class StockSettings(Document): # show/hide barcode field for name in ["barcode", "barcodes", "scan_barcode"]: frappe.make_property_setter({'fieldname': name, 'property': 'hidden', - 'value': 0 if self.show_barcode_field else 1}) + 'value': 0 if self.show_barcode_field else 1}, validate_fields_for_doctype=False) self.validate_warehouses() self.cant_change_valuation_method() @@ -67,10 +67,10 @@ class StockSettings(Document): self.toggle_warehouse_field_for_inter_warehouse_transfer() def toggle_warehouse_field_for_inter_warehouse_transfer(self): - make_property_setter("Sales Invoice Item", "target_warehouse", "hidden", 1 - cint(self.allow_from_dn), "Check") - make_property_setter("Delivery Note Item", "target_warehouse", "hidden", 1 - cint(self.allow_from_dn), "Check") - make_property_setter("Purchase Invoice Item", "from_warehouse", "hidden", 1 - cint(self.allow_from_pr), "Check") - make_property_setter("Purchase Receipt Item", "from_warehouse", "hidden", 1 - cint(self.allow_from_pr), "Check") + make_property_setter("Sales Invoice Item", "target_warehouse", "hidden", 1 - cint(self.allow_from_dn), "Check", validate_fields_for_doctype=False) + make_property_setter("Delivery Note Item", "target_warehouse", "hidden", 1 - cint(self.allow_from_dn), "Check", validate_fields_for_doctype=False) + make_property_setter("Purchase Invoice Item", "from_warehouse", "hidden", 1 - cint(self.allow_from_pr), "Check", validate_fields_for_doctype=False) + make_property_setter("Purchase Receipt Item", "from_warehouse", "hidden", 1 - cint(self.allow_from_pr), "Check", validate_fields_for_doctype=False) def clean_all_descriptions(): From 4ecae62194d00f455dd757ce45bb80148d750977 Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Thu, 6 May 2021 19:42:01 +0530 Subject: [PATCH 215/326] fix: added is_stock_item filter (#25530) --- erpnext/manufacturing/doctype/bom/bom.js | 5 ++++- erpnext/manufacturing/doctype/bom/bom.py | 3 +++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js index fbfd801a11..a09a5e3430 100644 --- a/erpnext/manufacturing/doctype/bom/bom.js +++ b/erpnext/manufacturing/doctype/bom/bom.js @@ -29,7 +29,10 @@ frappe.ui.form.on("BOM", { frm.set_query("item", function() { return { - query: "erpnext.manufacturing.doctype.bom.bom.item_query" + query: "erpnext.manufacturing.doctype.bom.bom.item_query", + filters: { + "is_stock_item": 1 + } }; }); diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 979f7ca312..d1f63854c7 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -973,6 +973,9 @@ def item_query(doctype, txt, searchfield, start, page_len, filters): if not has_variants: query_filters["has_variants"] = 0 + if filters and filters.get("is_stock_item"): + query_filters["is_stock_item"] = 1 + return frappe.get_all("Item", fields = fields, filters=query_filters, or_filters = or_cond_filters, order_by=order_by, From 0e0de6baa14683b10b18e43d1b2dab03c9a94f37 Mon Sep 17 00:00:00 2001 From: Alan <2.alan.tom@gmail.com> Date: Fri, 7 May 2021 11:40:02 +0530 Subject: [PATCH 216/326] fix: prevent spurious defaults for items when making prec from dnote (#25559) * fix: prevent spurious defaults for items when making prec from dnote * refactor: make concise, use dict comp --- erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 4de877353a..bb74a02606 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -1755,15 +1755,10 @@ def update_pr_items(doc, sales_item_map, purchase_item_map, parent_child_map, wa item.purchase_order = parent_child_map.get(sales_item_map.get(item.delivery_note_item)) def get_delivery_note_details(internal_reference): - so_item_map = {} - si_item_details = frappe.get_all('Delivery Note Item', fields=['name', 'so_detail'], filters={'parent': internal_reference}) - for d in si_item_details: - so_item_map.setdefault(d.name, d.so_detail) - - return so_item_map + return {d.name: d.so_detail for d in si_item_details if d.so_detail} def get_sales_invoice_details(internal_reference): dn_item_map = {} From f132ed4335996a7848a386d017ed6cfbc93c8197 Mon Sep 17 00:00:00 2001 From: Anuja Pawar <60467153+Anuja-pawar@users.noreply.github.com> Date: Fri, 7 May 2021 12:11:09 +0530 Subject: [PATCH 217/326] fix: update item level cost center from POS (#25609) --- erpnext/stock/get_item_details.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 3832415db6..d1dcdc21c8 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -79,7 +79,7 @@ def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=Tru get_price_list_rate(args, item, out) if args.customer and cint(args.is_pos): - out.update(get_pos_profile_item_details(args.company, args)) + out.update(get_pos_profile_item_details(args.company, args, update_data=True)) if (args.get("doctype") == "Material Request" and args.get("material_request_type") == "Material Transfer"): @@ -935,8 +935,8 @@ def get_bin_details(item_code, warehouse, company=None): return bin_details def get_company_total_stock(item_code, company): - return frappe.db.sql("""SELECT sum(actual_qty) from - (`tabBin` INNER JOIN `tabWarehouse` ON `tabBin`.warehouse = `tabWarehouse`.name) + return frappe.db.sql("""SELECT sum(actual_qty) from + (`tabBin` INNER JOIN `tabWarehouse` ON `tabBin`.warehouse = `tabWarehouse`.name) WHERE `tabWarehouse`.company = %s and `tabBin`.item_code = %s""", (company, item_code))[0][0] From 996f7e53a19e2ab741f9709f09f774d04498cc81 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 7 May 2021 12:14:14 +0530 Subject: [PATCH 218/326] fix: update shopify api version (#25600) --- .../erpnext_integrations/connectors/shopify_connection.py | 6 +++--- .../doctype/shopify_settings/shopify_settings.py | 4 ++-- .../doctype/shopify_settings/sync_product.py | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/erpnext/erpnext_integrations/connectors/shopify_connection.py b/erpnext/erpnext_integrations/connectors/shopify_connection.py index f0a05ed192..5d5b2e19ce 100644 --- a/erpnext/erpnext_integrations/connectors/shopify_connection.py +++ b/erpnext/erpnext_integrations/connectors/shopify_connection.py @@ -335,13 +335,13 @@ def get_url(shopify_settings): if not last_order_id: if shopify_settings.sync_based_on == 'Date': - url = get_shopify_url("admin/api/2020-10/orders.json?limit=250&created_at_min={0}&since_id=0".format( + url = get_shopify_url("admin/api/2021-04/orders.json?limit=250&created_at_min={0}&since_id=0".format( get_datetime(shopify_settings.from_date)), shopify_settings) else: - url = get_shopify_url("admin/api/2020-10/orders.json?limit=250&since_id={0}".format( + url = get_shopify_url("admin/api/2021-04/orders.json?limit=250&since_id={0}".format( shopify_settings.from_order_id), shopify_settings) else: - url = get_shopify_url("admin/api/2020-10/orders.json?limit=250&since_id={0}".format(last_order_id), shopify_settings) + url = get_shopify_url("admin/api/2021-04/orders.json?limit=250&since_id={0}".format(last_order_id), shopify_settings) return url diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py b/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py index cbdf90681d..7634fd0caf 100644 --- a/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py +++ b/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py @@ -30,7 +30,7 @@ class ShopifySettings(Document): webhooks = ["orders/create", "orders/paid", "orders/fulfilled"] # url = get_shopify_url('admin/webhooks.json', self) created_webhooks = [d.method for d in self.webhooks] - url = get_shopify_url('admin/api/2020-04/webhooks.json', self) + url = get_shopify_url('admin/api/2021-04/webhooks.json', self) for method in webhooks: session = get_request_session() try: @@ -56,7 +56,7 @@ class ShopifySettings(Document): deleted_webhooks = [] for d in self.webhooks: - url = get_shopify_url('admin/api/2020-04/webhooks/{0}.json'.format(d.webhook_id), self) + url = get_shopify_url('admin/api/2021-04/webhooks/{0}.json'.format(d.webhook_id), self) try: res = session.delete(url, headers=get_header(self)) res.raise_for_status() diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/sync_product.py b/erpnext/erpnext_integrations/doctype/shopify_settings/sync_product.py index f9f0bb3cec..16efb6caee 100644 --- a/erpnext/erpnext_integrations/doctype/shopify_settings/sync_product.py +++ b/erpnext/erpnext_integrations/doctype/shopify_settings/sync_product.py @@ -8,7 +8,7 @@ from erpnext.erpnext_integrations.doctype.shopify_settings.shopify_settings impo shopify_variants_attr_list = ["option1", "option2", "option3"] def sync_item_from_shopify(shopify_settings, item): - url = get_shopify_url("admin/api/2020-04/products/{0}.json".format(item.get("product_id")), shopify_settings) + url = get_shopify_url("admin/api/2021-04/products/{0}.json".format(item.get("product_id")), shopify_settings) session = get_request_session() try: From f648d2d7c4a23061f4b0a03afa060d0d061eea95 Mon Sep 17 00:00:00 2001 From: Shadrak Gurupnor <30501401+shadrak98@users.noreply.github.com> Date: Fri, 7 May 2021 12:15:19 +0530 Subject: [PATCH 219/326] fix: added tax_types list (#25587) --- .../v12_0/move_item_tax_to_item_tax_template.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/erpnext/patches/v12_0/move_item_tax_to_item_tax_template.py b/erpnext/patches/v12_0/move_item_tax_to_item_tax_template.py index 06331d7ff7..a6471eb53c 100644 --- a/erpnext/patches/v12_0/move_item_tax_to_item_tax_template.py +++ b/erpnext/patches/v12_0/move_item_tax_to_item_tax_template.py @@ -44,9 +44,11 @@ def execute(): # make current item's tax map item_tax_map = {} for d in old_item_taxes[item_code]: - item_tax_map[d.tax_type] = d.tax_rate + if d.tax_type not in item_tax_map: + item_tax_map[d.tax_type] = d.tax_rate - item_tax_template_name = get_item_tax_template(item_tax_templates, item_tax_map, item_code) + tax_types = [] + item_tax_template_name = get_item_tax_template(item_tax_templates, item_tax_map, item_code, tax_types=tax_types) # update the item tax table frappe.db.sql("delete from `tabItem Tax` where parent=%s and parenttype='Item'", item_code) @@ -68,7 +70,7 @@ def execute(): and item_tax_template is NULL""".format(dt), as_dict=1): item_tax_map = json.loads(d.item_tax_rate) item_tax_template_name = get_item_tax_template(item_tax_templates, - item_tax_map, d.item_code, d.parenttype, d.parent) + item_tax_map, d.item_code, d.parenttype, d.parent, tax_types=tax_types) frappe.db.set_value(dt + " Item", d.name, "item_tax_template", item_tax_template_name) frappe.db.auto_commit_on_many_writes = False @@ -78,7 +80,7 @@ def execute(): settings.determine_address_tax_category_from = "Billing Address" settings.save() -def get_item_tax_template(item_tax_templates, item_tax_map, item_code, parenttype=None, parent=None): +def get_item_tax_template(item_tax_templates, item_tax_map, item_code, parenttype=None, parent=None, tax_types=None): # search for previously created item tax template by comparing tax maps for template, item_tax_template_map in iteritems(item_tax_templates): if item_tax_map == item_tax_template_map: @@ -126,7 +128,9 @@ def get_item_tax_template(item_tax_templates, item_tax_map, item_code, parenttyp account_type = frappe.get_cached_value("Account", tax_type, "account_type") if tax_type and account_type in ('Tax', 'Chargeable', 'Income Account', 'Expense Account', 'Expenses Included In Valuation'): - item_tax_template.append("taxes", {"tax_type": tax_type, "tax_rate": tax_rate}) + if tax_type not in tax_types: + item_tax_template.append("taxes", {"tax_type": tax_type, "tax_rate": tax_rate}) + tax_types.append(tax_type) item_tax_templates.setdefault(item_tax_template.title, {}) item_tax_templates[item_tax_template.title][tax_type] = tax_rate if item_tax_template.get("taxes"): From 00e00e4e903ac9f6e37520903b0e7ca97e1c388b Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Fri, 7 May 2021 12:16:44 +0530 Subject: [PATCH 220/326] fix: Report summary showing inflated values when values are accumulated in Group Company (#25577) * fix: Report summary showing inflated values when values are accumullated in Group Company * fix: Remove extra space * fix: Translate strings * fix: Remove unintended changes --- .../report/balance_sheet/balance_sheet.py | 7 ++++++- erpnext/accounts/report/cash_flow/cash_flow.py | 17 ++++++++++++----- .../consolidated_financial_statement.py | 6 +++--- erpnext/accounts/report/financial_statements.py | 14 +++++++++++--- .../profit_and_loss_statement.py | 11 ++++++++--- 5 files changed, 40 insertions(+), 15 deletions(-) diff --git a/erpnext/accounts/report/balance_sheet/balance_sheet.py b/erpnext/accounts/report/balance_sheet/balance_sheet.py index 1729abce9e..287b8a7484 100644 --- a/erpnext/accounts/report/balance_sheet/balance_sheet.py +++ b/erpnext/accounts/report/balance_sheet/balance_sheet.py @@ -5,7 +5,8 @@ from __future__ import unicode_literals import frappe from frappe import _ from frappe.utils import flt, cint -from erpnext.accounts.report.financial_statements import (get_period_list, get_columns, get_data) +from erpnext.accounts.report.financial_statements import (get_period_list, get_columns, get_data, + get_filtered_list_for_consolidated_report) def execute(filters=None): period_list = get_period_list(filters.from_fiscal_year, filters.to_fiscal_year, @@ -132,6 +133,10 @@ def get_report_summary(period_list, asset, liability, equity, provisional_profit if filters.get('accumulated_values'): period_list = [period_list[-1]] + # from consolidated financial statement + if filters.get('accumulated_in_group_company'): + period_list = get_filtered_list_for_consolidated_report(period_list) + for period in period_list: key = period if consolidated else period.key if asset: diff --git a/erpnext/accounts/report/cash_flow/cash_flow.py b/erpnext/accounts/report/cash_flow/cash_flow.py index cf0946beab..3577457c98 100644 --- a/erpnext/accounts/report/cash_flow/cash_flow.py +++ b/erpnext/accounts/report/cash_flow/cash_flow.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import frappe from frappe import _ from frappe.utils import cint, cstr -from erpnext.accounts.report.financial_statements import (get_period_list, get_columns, get_data) +from erpnext.accounts.report.financial_statements import (get_period_list, get_columns, get_data, get_filtered_list_for_consolidated_report) from erpnext.accounts.report.profit_and_loss_statement.profit_and_loss_statement import get_net_profit_loss from erpnext.accounts.utils import get_fiscal_year from six import iteritems @@ -67,9 +67,9 @@ def execute(filters=None): section_data.append(account_data) add_total_row_account(data, section_data, cash_flow_account['section_footer'], - period_list, company_currency, summary_data) + period_list, company_currency, summary_data, filters) - add_total_row_account(data, data, _("Net Change in Cash"), period_list, company_currency, summary_data) + add_total_row_account(data, data, _("Net Change in Cash"), period_list, company_currency, summary_data, filters) columns = get_columns(filters.periodicity, period_list, filters.accumulated_values, filters.company) chart = get_chart_data(columns, data) @@ -162,18 +162,26 @@ def get_start_date(period, accumulated_values, company): return start_date -def add_total_row_account(out, data, label, period_list, currency, summary_data, consolidated = False): +def add_total_row_account(out, data, label, period_list, currency, summary_data, filters, consolidated=False): total_row = { "account_name": "'" + _("{0}").format(label) + "'", "account": "'" + _("{0}").format(label) + "'", "currency": currency } + + summary_data[label] = 0 + + # from consolidated financial statement + if filters.get('accumulated_in_group_company'): + period_list = get_filtered_list_for_consolidated_report(filters, period_list) + for row in data: if row.get("parent_account"): for period in period_list: key = period if consolidated else period['key'] total_row.setdefault(key, 0.0) total_row[key] += row.get(key, 0.0) + summary_data[label] += row.get(key) total_row.setdefault("total", 0.0) total_row["total"] += row["total"] @@ -181,7 +189,6 @@ def add_total_row_account(out, data, label, period_list, currency, summary_data, out.append(total_row) out.append({}) - summary_data[label] = total_row["total"] def get_report_summary(summary_data, currency): report_summary = [] 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 094f5db89b..7793af737f 100644 --- a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py +++ b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py @@ -94,7 +94,7 @@ def get_profit_loss_data(fiscal_year, companies, columns, filters): chart = get_pl_chart_data(filters, columns, income, expense, net_profit_loss) - report_summary = get_pl_summary(companies, '', income, expense, net_profit_loss, company_currency, True) + report_summary = get_pl_summary(companies, '', income, expense, net_profit_loss, company_currency, filters, True) return data, None, chart, report_summary @@ -149,9 +149,9 @@ def get_cash_flow_data(fiscal_year, companies, filters): section_data.append(account_data) add_total_row_account(data, section_data, cash_flow_account['section_footer'], - companies, company_currency, summary_data, True) + companies, company_currency, summary_data, filters, True) - add_total_row_account(data, data, _("Net Change in Cash"), companies, company_currency, summary_data, True) + add_total_row_account(data, data, _("Net Change in Cash"), companies, company_currency, summary_data, filters, True) report_summary = get_cash_flow_summary(summary_data, company_currency) diff --git a/erpnext/accounts/report/financial_statements.py b/erpnext/accounts/report/financial_statements.py index 14efa1f8fc..d20ddbde5c 100644 --- a/erpnext/accounts/report/financial_statements.py +++ b/erpnext/accounts/report/financial_statements.py @@ -119,10 +119,10 @@ def validate_fiscal_year(fiscal_year, from_fiscal_year, to_fiscal_year): def validate_dates(from_date, to_date): if not from_date or not to_date: - frappe.throw("From Date and To Date are mandatory") + frappe.throw(_("From Date and To Date are mandatory")) if to_date < from_date: - frappe.throw("To Date cannot be less than From Date") + frappe.throw(_("To Date cannot be less than From Date")) def get_months(start_date, end_date): diff = (12 * end_date.year + end_date.month) - (12 * start_date.year + start_date.month) @@ -522,4 +522,12 @@ def get_columns(periodicity, period_list, accumulated_values=1, company=None): "width": 150 }) - return columns \ No newline at end of file + return columns + +def get_filtered_list_for_consolidated_report(filters, period_list): + filtered_summary_list = [] + for period in period_list: + if period == filters.get('company'): + filtered_summary_list.append(period) + + return filtered_summary_list diff --git a/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.py b/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.py index fe261b30b4..5d04824b57 100644 --- a/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.py +++ b/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.py @@ -5,7 +5,8 @@ from __future__ import unicode_literals import frappe from frappe import _ from frappe.utils import flt -from erpnext.accounts.report.financial_statements import (get_period_list, get_columns, get_data) +from erpnext.accounts.report.financial_statements import (get_period_list, get_columns, get_data, + get_filtered_list_for_consolidated_report) def execute(filters=None): period_list = get_period_list(filters.from_fiscal_year, filters.to_fiscal_year, @@ -33,13 +34,17 @@ def execute(filters=None): chart = get_chart_data(filters, columns, income, expense, net_profit_loss) currency = filters.presentation_currency or frappe.get_cached_value('Company', filters.company, "default_currency") - report_summary = get_report_summary(period_list, filters.periodicity, income, expense, net_profit_loss, currency) + report_summary = get_report_summary(period_list, filters.periodicity, income, expense, net_profit_loss, currency, filters) return columns, data, None, chart, report_summary -def get_report_summary(period_list, periodicity, income, expense, net_profit_loss, currency, consolidated=False): +def get_report_summary(period_list, periodicity, income, expense, net_profit_loss, currency, filters, consolidated=False): net_income, net_expense, net_profit = 0.0, 0.0, 0.0 + # from consolidated financial statement + if filters.get('accumulated_in_group_company'): + period_list = get_filtered_list_for_consolidated_report(filters, period_list) + for period in period_list: key = period if consolidated else period.key if income: From 735fbdc350d0449720db36e684c85ca56161442f Mon Sep 17 00:00:00 2001 From: Shariq Ansari <30859809+shariquerik@users.noreply.github.com> Date: Fri, 7 May 2021 12:26:32 +0530 Subject: [PATCH 221/326] fix: Updating Standard Notification's channel field (#25564) --- .../notification_for_new_fiscal_year.json | 1 + erpnext/hr/notification/training_feedback/training_feedback.json | 1 + .../payroll/notification/retention_bonus/retention_bonus.json | 1 + 3 files changed, 3 insertions(+) diff --git a/erpnext/accounts/notification/notification_for_new_fiscal_year/notification_for_new_fiscal_year.json b/erpnext/accounts/notification/notification_for_new_fiscal_year/notification_for_new_fiscal_year.json index bd7a126517..4c7faf4f65 100644 --- a/erpnext/accounts/notification/notification_for_new_fiscal_year/notification_for_new_fiscal_year.json +++ b/erpnext/accounts/notification/notification_for_new_fiscal_year/notification_for_new_fiscal_year.json @@ -1,5 +1,6 @@ { "attach_print": 0, + "channel": "Email", "condition": "doc.auto_created", "creation": "2018-04-25 14:19:05.440361", "days_in_advance": 0, diff --git a/erpnext/hr/notification/training_feedback/training_feedback.json b/erpnext/hr/notification/training_feedback/training_feedback.json index 2cc064f34a..92b68a98a9 100644 --- a/erpnext/hr/notification/training_feedback/training_feedback.json +++ b/erpnext/hr/notification/training_feedback/training_feedback.json @@ -1,5 +1,6 @@ { "attach_print": 0, + "channel": "Email", "creation": "2017-08-11 03:17:11.769210", "days_in_advance": 0, "docstatus": 0, diff --git a/erpnext/payroll/notification/retention_bonus/retention_bonus.json b/erpnext/payroll/notification/retention_bonus/retention_bonus.json index 50db0338c4..37381fa942 100644 --- a/erpnext/payroll/notification/retention_bonus/retention_bonus.json +++ b/erpnext/payroll/notification/retention_bonus/retention_bonus.json @@ -1,5 +1,6 @@ { "attach_print": 0, + "channel": "Email", "condition": "doc.docstatus==1", "creation": "2018-05-15 18:52:36.362838", "date_changed": "bonus_payment_date", From 7f79d463f69237189f9393c820e1bae58054493a Mon Sep 17 00:00:00 2001 From: Umair Sayed Date: Fri, 7 May 2021 12:28:57 +0530 Subject: [PATCH 222/326] fix: Stock and Accounts Settings form refactor (#25534) * stock and accounts settings page * fix: Stock and accounts settings page cleanup Co-authored-by: Umair Sayed --- .../accounts_settings/accounts_settings.json | 61 ++++++++++------ .../stock_settings/stock_settings.json | 70 ++++++++++++------- 2 files changed, 86 insertions(+), 45 deletions(-) diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json index e1276e7da3..781f94e203 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json @@ -7,26 +7,30 @@ "editable_grid": 1, "engine": "InnoDB", "field_order": [ - "auto_accounting_for_stock", - "acc_frozen_upto", - "frozen_accounts_modifier", - "determine_address_tax_category_from", + "accounts_transactions_settings_section", "over_billing_allowance", "role_allowed_to_over_bill", - "column_break_4", - "credit_controller", - "check_supplier_invoice_uniqueness", "make_payment_via_journal_entry", + "column_break_11", + "check_supplier_invoice_uniqueness", "unlink_payment_on_cancellation_of_invoice", - "unlink_advance_payment_on_cancelation_of_order", - "book_asset_depreciation_entry_automatically", - "add_taxes_from_item_tax_template", "automatically_fetch_payment_terms", "delete_linked_ledger_entries", + "book_asset_depreciation_entry_automatically", + "unlink_advance_payment_on_cancelation_of_order", + "tax_settings_section", + "determine_address_tax_category_from", + "column_break_19", + "add_taxes_from_item_tax_template", + "period_closing_settings_section", + "acc_frozen_upto", + "frozen_accounts_modifier", + "column_break_4", + "credit_controller", "deferred_accounting_settings_section", - "automatically_process_deferred_accounting_entry", "book_deferred_entries_based_on", "column_break_18", + "automatically_process_deferred_accounting_entry", "book_deferred_entries_via_journal_entry", "submit_journal_entries", "print_settings", @@ -40,15 +44,6 @@ "use_custom_cash_flow" ], "fields": [ - { - "default": "1", - "description": "If enabled, the system will post accounting entries for inventory automatically", - "fieldname": "auto_accounting_for_stock", - "fieldtype": "Check", - "hidden": 1, - "in_list_view": 1, - "label": "Make Accounting Entry For Every Stock Movement" - }, { "description": "Accounting entries are frozen up to this date. Nobody can create or modify entries except users with the role specified below", "fieldname": "acc_frozen_upto", @@ -94,6 +89,7 @@ "default": "0", "fieldname": "make_payment_via_journal_entry", "fieldtype": "Check", + "hidden": 1, "label": "Make Payment via Journal Entry" }, { @@ -234,6 +230,29 @@ "fieldtype": "Link", "label": "Role Allowed to Over Bill ", "options": "Role" + }, + { + "fieldname": "period_closing_settings_section", + "fieldtype": "Section Break", + "label": "Period Closing Settings" + }, + { + "fieldname": "accounts_transactions_settings_section", + "fieldtype": "Section Break", + "label": "Transactions Settings" + }, + { + "fieldname": "column_break_11", + "fieldtype": "Column Break" + }, + { + "fieldname": "tax_settings_section", + "fieldtype": "Section Break", + "label": "Tax Settings" + }, + { + "fieldname": "column_break_19", + "fieldtype": "Column Break" } ], "icon": "icon-cog", @@ -241,7 +260,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2021-03-11 18:52:05.601996", + "modified": "2021-04-30 15:25:10.381008", "modified_by": "Administrator", "module": "Accounts", "name": "Accounts Settings", diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.json b/erpnext/stock/doctype/stock_settings/stock_settings.json index f18eabc84b..cf5d98d092 100644 --- a/erpnext/stock/doctype/stock_settings/stock_settings.json +++ b/erpnext/stock/doctype/stock_settings/stock_settings.json @@ -5,40 +5,44 @@ "doctype": "DocType", "engine": "InnoDB", "field_order": [ + "item_defaults_section", "item_naming_by", "item_group", "stock_uom", "default_warehouse", - "sample_retention_warehouse", "column_break_4", "valuation_method", + "sample_retention_warehouse", + "use_naming_series", + "naming_series_prefix", + "section_break_9", "over_delivery_receipt_allowance", "role_allowed_to_over_deliver_receive", - "action_if_quality_inspection_is_not_submitted", - "show_barcode_field", - "clean_description_html", - "disable_serial_no_and_batch_selector", - "section_break_7", + "column_break_12", "auto_insert_price_list_rate_if_missing", "allow_negative_stock", - "column_break_10", + "show_barcode_field", + "clean_description_html", + "action_if_quality_inspection_is_not_submitted", + "section_break_7", "automatically_set_serial_nos_based_on_fifo", "set_qty_in_transactions_based_on_serial_no_input", + "column_break_10", + "disable_serial_no_and_batch_selector", "auto_material_request", "auto_indent", + "column_break_27", "reorder_email_notify", "inter_warehouse_transfer_settings_section", "allow_from_dn", + "column_break_31", "allow_from_pr", "control_historical_stock_transactions_section", - "role_allowed_to_create_edit_back_dated_transactions", - "column_break_26", "stock_frozen_upto", "stock_frozen_upto_days", - "stock_auth_role", - "batch_id_sb", - "use_naming_series", - "naming_series_prefix" + "column_break_26", + "role_allowed_to_create_edit_back_dated_transactions", + "stock_auth_role" ], "fields": [ { @@ -102,23 +106,24 @@ "default": "1", "fieldname": "show_barcode_field", "fieldtype": "Check", - "label": "Show Barcode Field" + "label": "Show Barcode Field in Stock Transactions" }, { "default": "1", "fieldname": "clean_description_html", "fieldtype": "Check", - "label": "Convert Item Description to Clean HTML" + "label": "Convert Item Description to Clean HTML in Transactions" }, { "fieldname": "section_break_7", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "label": "Serialised and Batch Setting" }, { "default": "0", "fieldname": "auto_insert_price_list_rate_if_missing", "fieldtype": "Check", - "label": "Auto Insert Price List Rate If Missing" + "label": "Auto Insert Item Price If Missing" }, { "default": "0", @@ -179,16 +184,11 @@ "label": "Role Allowed to Edit Frozen Stock", "options": "Role" }, - { - "fieldname": "batch_id_sb", - "fieldtype": "Section Break", - "label": "Batch Identification" - }, { "default": "0", "fieldname": "use_naming_series", "fieldtype": "Check", - "label": "Use Naming Series" + "label": "Have Default Naming Series for Batch ID?" }, { "default": "BATCH-", @@ -242,6 +242,28 @@ "fieldtype": "Link", "label": "Role Allowed to Over Deliver/Receive", "options": "Role" + }, + { + "fieldname": "item_defaults_section", + "fieldtype": "Section Break", + "label": "Item Defaults" + }, + { + "fieldname": "section_break_9", + "fieldtype": "Section Break", + "label": "Stock Transactions Settings" + }, + { + "fieldname": "column_break_12", + "fieldtype": "Column Break" + }, + { + "fieldname": "column_break_27", + "fieldtype": "Column Break" + }, + { + "fieldname": "column_break_31", + "fieldtype": "Column Break" } ], "icon": "icon-cog", @@ -249,7 +271,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2021-03-11 18:48:14.513055", + "modified": "2021-04-30 17:27:42.709231", "modified_by": "Administrator", "module": "Stock", "name": "Stock Settings", From 062d30146f967a28672a76f9f8d286c0d28470ca Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Fri, 7 May 2021 13:31:14 +0530 Subject: [PATCH 223/326] fix: Include search fields in Project Link field query (#25505) * fix: Include search fields in Project Link field query * fix: add project_name to Project search fields --- erpnext/controllers/queries.py | 10 +++++++--- erpnext/projects/doctype/project/project.json | 6 +++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index bc1ac5ea06..b31724fa48 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -292,11 +292,14 @@ def get_project_name(doctype, txt, searchfield, start, page_len, filters): cond = """(`tabProject`.customer = %s or ifnull(`tabProject`.customer,"")="") and""" %(frappe.db.escape(filters.get("customer"))) - fields = get_fields("Project", ["name"]) + fields = get_fields("Project", ["name", "project_name"]) + searchfields = frappe.get_meta("Project").get_search_fields() + searchfields = " or ".join([field + " like %(txt)s" for field in searchfields]) return frappe.db.sql("""select {fields} from `tabProject` - where `tabProject`.status not in ("Completed", "Cancelled") - and {cond} `tabProject`.name like %(txt)s {match_cond} + where + `tabProject`.status not in ("Completed", "Cancelled") + and {cond} {match_cond} {scond} order by if(locate(%(_txt)s, name), locate(%(_txt)s, name), 99999), idx desc, @@ -304,6 +307,7 @@ def get_project_name(doctype, txt, searchfield, start, page_len, filters): limit {start}, {page_len}""".format( fields=", ".join(['`tabProject`.{0}'.format(f) for f in fields]), cond=cond, + scond=searchfields, match_cond=get_match_cond(doctype), start=start, page_len=page_len), { diff --git a/erpnext/projects/doctype/project/project.json b/erpnext/projects/doctype/project/project.json index 3cdfcb212f..2570df7026 100644 --- a/erpnext/projects/doctype/project/project.json +++ b/erpnext/projects/doctype/project/project.json @@ -458,7 +458,7 @@ "index_web_pages_for_search": 1, "links": [], "max_attachments": 4, - "modified": "2020-09-02 11:54:01.223620", + "modified": "2021-04-28 16:36:11.654632", "modified_by": "Administrator", "module": "Projects", "name": "Project", @@ -495,11 +495,11 @@ } ], "quick_entry": 1, - "search_fields": "customer, status, priority, is_active", + "search_fields": "project_name,customer, status, priority, is_active", "show_name_in_global_search": 1, "sort_field": "modified", "sort_order": "DESC", "timeline_field": "customer", "title_field": "project_name", "track_seen": 1 -} +} \ No newline at end of file From 5618ce3852e0dbad1460d31dae834cf19d61a197 Mon Sep 17 00:00:00 2001 From: Ganga Manoj Date: Fri, 7 May 2021 13:35:09 +0530 Subject: [PATCH 224/326] fix(Material Request): Add 'Partially Received' to Status drop-down list (#24857) Co-authored-by: Ganga Manoj Co-authored-by: Nabin Hait --- erpnext/stock/doctype/material_request/material_request.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/material_request/material_request.json b/erpnext/stock/doctype/material_request/material_request.json index 8d7b238c17..4e2d9e6170 100644 --- a/erpnext/stock/doctype/material_request/material_request.json +++ b/erpnext/stock/doctype/material_request/material_request.json @@ -181,7 +181,7 @@ "no_copy": 1, "oldfieldname": "status", "oldfieldtype": "Select", - "options": "\nDraft\nSubmitted\nStopped\nCancelled\nPending\nPartially Ordered\nOrdered\nIssued\nTransferred\nReceived", + "options": "\nDraft\nSubmitted\nStopped\nCancelled\nPending\nPartially Ordered\nPartially Received\nOrdered\nIssued\nTransferred\nReceived", "print_hide": 1, "print_width": "100px", "read_only": 1, From 27cf19a19f09169a0f5fc08d537555471cc466e2 Mon Sep 17 00:00:00 2001 From: Saqib Date: Fri, 7 May 2021 13:37:42 +0530 Subject: [PATCH 225/326] feat(pos): show POS reserved stock in stock projected qty report (#25593) * feat(pos): consider POS reserved stock in stock projected qty report * chore: remove unwanted string formats --- .../doctype/pos_invoice/pos_invoice.py | 22 +++++++++++-------- .../report/pos_register/pos_register.py | 13 +++++------ .../stock_projected_qty.js | 9 +++++++- .../stock_projected_qty.py | 13 ++++++++--- 4 files changed, 36 insertions(+), 21 deletions(-) diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py index 1e6a3d1b3b..473db565fa 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py @@ -461,7 +461,17 @@ def get_stock_availability(item_code, warehouse): order by posting_date desc, posting_time desc limit 1""", (item_code, warehouse), as_dict=1) - pos_sales_qty = frappe.db.sql("""select sum(p_item.qty) as qty + pos_sales_qty = get_pos_reserved_qty(item_code, warehouse) + + sle_qty = latest_sle[0].qty_after_transaction or 0 if latest_sle else 0 + + if sle_qty and pos_sales_qty: + return sle_qty - pos_sales_qty + else: + return sle_qty + +def get_pos_reserved_qty(item_code, warehouse): + reserved_qty = frappe.db.sql("""select sum(p_item.qty) as qty from `tabPOS Invoice` p, `tabPOS Invoice Item` p_item where p.name = p_item.parent and p.consolidated_invoice is NULL @@ -470,14 +480,8 @@ def get_stock_availability(item_code, warehouse): and p_item.item_code = %s and p_item.warehouse = %s """, (item_code, warehouse), as_dict=1) - - sle_qty = latest_sle[0].qty_after_transaction or 0 if latest_sle else 0 - pos_sales_qty = pos_sales_qty[0].qty or 0 if pos_sales_qty else 0 - - if sle_qty and pos_sales_qty: - return sle_qty - pos_sales_qty - else: - return sle_qty + + return reserved_qty[0].qty or 0 if reserved_qty else 0 @frappe.whitelist() def make_sales_return(source_name, target_doc=None): diff --git a/erpnext/accounts/report/pos_register/pos_register.py b/erpnext/accounts/report/pos_register/pos_register.py index 52f7fe238e..cfbd7fd0c8 100644 --- a/erpnext/accounts/report/pos_register/pos_register.py +++ b/erpnext/accounts/report/pos_register/pos_register.py @@ -116,22 +116,19 @@ def validate_filters(filters): frappe.throw(_("Can not filter based on Payment Method, if grouped by Payment Method")) def get_conditions(filters): - conditions = "company = %(company)s AND posting_date >= %(from_date)s AND posting_date <= %(to_date)s".format( - company=filters.get("company"), - from_date=filters.get("from_date"), - to_date=filters.get("to_date")) + conditions = "company = %(company)s AND posting_date >= %(from_date)s AND posting_date <= %(to_date)s" if filters.get("pos_profile"): - conditions += " AND pos_profile = %(pos_profile)s".format(pos_profile=filters.get("pos_profile")) + conditions += " AND pos_profile = %(pos_profile)s" if filters.get("owner"): - conditions += " AND owner = %(owner)s".format(owner=filters.get("owner")) + conditions += " AND owner = %(owner)s" if filters.get("customer"): - conditions += " AND customer = %(customer)s".format(customer=filters.get("customer")) + conditions += " AND customer = %(customer)s" if filters.get("is_return"): - conditions += " AND is_return = %(is_return)s".format(is_return=filters.get("is_return")) + conditions += " AND is_return = %(is_return)s" if filters.get("mode_of_payment"): conditions += """ diff --git a/erpnext/stock/report/stock_projected_qty/stock_projected_qty.js b/erpnext/stock/report/stock_projected_qty/stock_projected_qty.js index babc6dc960..cb109f8050 100644 --- a/erpnext/stock/report/stock_projected_qty/stock_projected_qty.js +++ b/erpnext/stock/report/stock_projected_qty/stock_projected_qty.js @@ -14,7 +14,14 @@ frappe.query_reports["Stock Projected Qty"] = { "fieldname":"warehouse", "label": __("Warehouse"), "fieldtype": "Link", - "options": "Warehouse" + "options": "Warehouse", + "get_query": () => { + return { + filters: { + company: frappe.query_report.get_filter_value('company') + } + } + } }, { "fieldname":"item_code", diff --git a/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py b/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py index 1183e41d04..808d279170 100644 --- a/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py +++ b/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py @@ -6,6 +6,7 @@ import frappe from frappe import _ from frappe.utils import flt, today from erpnext.stock.utils import update_included_uom_in_report, is_reposting_item_valuation_in_progress +from erpnext.accounts.doctype.pos_invoice.pos_invoice import get_pos_reserved_qty def execute(filters=None): is_reposting_item_valuation_in_progress() @@ -49,9 +50,13 @@ def execute(filters=None): if (re_order_level or re_order_qty) and re_order_level > bin.projected_qty: shortage_qty = re_order_level - flt(bin.projected_qty) + reserved_qty_for_pos = get_pos_reserved_qty(bin.item_code, bin.warehouse) + if reserved_qty_for_pos: + bin.projected_qty -= reserved_qty_for_pos + data.append([item.name, item.item_name, item.description, item.item_group, item.brand, bin.warehouse, item.stock_uom, bin.actual_qty, bin.planned_qty, bin.indented_qty, bin.ordered_qty, - bin.reserved_qty, bin.reserved_qty_for_production, bin.reserved_qty_for_sub_contract, + bin.reserved_qty, bin.reserved_qty_for_production, bin.reserved_qty_for_sub_contract, reserved_qty_for_pos, bin.projected_qty, re_order_level, re_order_qty, shortage_qty]) if include_uom: @@ -74,9 +79,11 @@ def get_columns(): {"label": _("Requested Qty"), "fieldname": "indented_qty", "fieldtype": "Float", "width": 110, "convertible": "qty"}, {"label": _("Ordered Qty"), "fieldname": "ordered_qty", "fieldtype": "Float", "width": 100, "convertible": "qty"}, {"label": _("Reserved Qty"), "fieldname": "reserved_qty", "fieldtype": "Float", "width": 100, "convertible": "qty"}, - {"label": _("Reserved Qty for Production"), "fieldname": "reserved_qty_for_production", "fieldtype": "Float", + {"label": _("Reserved for Production"), "fieldname": "reserved_qty_for_production", "fieldtype": "Float", "width": 100, "convertible": "qty"}, - {"label": _("Reserved for sub contracting"), "fieldname": "reserved_qty_for_sub_contract", "fieldtype": "Float", + {"label": _("Reserved for Sub Contracting"), "fieldname": "reserved_qty_for_sub_contract", "fieldtype": "Float", + "width": 100, "convertible": "qty"}, + {"label": _("Reserved for POS Transactions"), "fieldname": "reserved_qty_for_pos", "fieldtype": "Float", "width": 100, "convertible": "qty"}, {"label": _("Projected Qty"), "fieldname": "projected_qty", "fieldtype": "Float", "width": 100, "convertible": "qty"}, {"label": _("Reorder Level"), "fieldname": "re_order_level", "fieldtype": "Float", "width": 100, "convertible": "qty"}, From da7fefe29d82886a90263064b309b1fa0a9b02d9 Mon Sep 17 00:00:00 2001 From: Anupam Kumar Date: Fri, 7 May 2021 20:26:50 +0530 Subject: [PATCH 226/326] fix: timesheet filter date exclusive issue (#25626) --- erpnext/projects/doctype/timesheet/timesheet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/projects/doctype/timesheet/timesheet.py b/erpnext/projects/doctype/timesheet/timesheet.py index ed02f79c2d..8d99b48b59 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.py +++ b/erpnext/projects/doctype/timesheet/timesheet.py @@ -209,7 +209,7 @@ def get_projectwise_timesheet_data(project, parent=None, from_time=None, to_time if parent: condition = "AND parent = %(parent)s" if from_time and to_time: - condition += "AND from_time BETWEEN %(from_time)s AND %(to_time)s" + condition += "AND CAST(from_time as DATE) BETWEEN %(from_time)s AND %(to_time)s" return frappe.db.sql("""select name, parent, billing_hours, billing_amount as billing_amt from `tabTimesheet Detail` where parenttype = 'Timesheet' and docstatus=1 and project = %(project)s {0} and billable = 1 From e28165ea871720a68ebdc00cfb7b97d6bf775d73 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 7 May 2021 20:27:51 +0530 Subject: [PATCH 227/326] fix: force https for shopify webhook registration (#25630) --- .../doctype/shopify_settings/shopify_settings.py | 2 +- erpnext/erpnext_integrations/utils.py | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py b/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py index 7634fd0caf..381c5e5dec 100644 --- a/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py +++ b/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py @@ -37,7 +37,7 @@ class ShopifySettings(Document): res = session.post(url, data=json.dumps({ "webhook": { "topic": method, - "address": get_webhook_address(connector_name='shopify_connection', method='store_request_data'), + "address": get_webhook_address(connector_name='shopify_connection', method='store_request_data', force_https=True), "format": "json" } }), headers=get_header(self)) diff --git a/erpnext/erpnext_integrations/utils.py b/erpnext/erpnext_integrations/utils.py index 362f6cf88e..3840e781b4 100644 --- a/erpnext/erpnext_integrations/utils.py +++ b/erpnext/erpnext_integrations/utils.py @@ -28,7 +28,7 @@ def validate_webhooks_request(doctype, hmac_key, secret_key='secret'): return innerfn -def get_webhook_address(connector_name, method, exclude_uri=False): +def get_webhook_address(connector_name, method, exclude_uri=False, force_https=False): endpoint = "erpnext.erpnext_integrations.connectors.{0}.{1}".format(connector_name, method) if exclude_uri: @@ -39,7 +39,11 @@ def get_webhook_address(connector_name, method, exclude_uri=False): except RuntimeError: url = "http://localhost:8000" - server_url = '{uri.scheme}://{uri.netloc}/api/method/{endpoint}'.format(uri=urlparse(url), endpoint=endpoint) + url_data = urlparse(url) + scheme = "https" if force_https else url_data.scheme + netloc = url_data.netloc + + server_url = f"{scheme}://{netloc}/api/method/{endpoint}" return server_url From 90e671905a9a4e8b496a84a8315ceba25e10d9ed Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 7 May 2021 20:28:51 +0530 Subject: [PATCH 228/326] chore: replace assertEquals with alias assertEqual (#25613) * chore: replace assertEquals with alias assertEqual assertEquals has been deprecated. ref: https://docs.python.org/3/library/unittest.html#deprecated-aliases * chore: sider fixes --- .../accounts/doctype/dunning/test_dunning.py | 6 +- .../doctype/gl_entry/test_gl_entry.py | 2 +- .../payment_order/test_payment_order.py | 8 +- .../doctype/pricing_rule/test_pricing_rule.py | 36 +++---- erpnext/assets/doctype/asset/test_asset.py | 4 +- .../purchase_order/test_purchase_order.py | 42 ++++---- .../mpesa_settings/test_mpesa_settings.py | 26 ++--- .../test_clinical_procedure.py | 2 +- .../doctype/lab_test/test_lab_test.py | 4 +- .../test_patient_appointment.py | 8 +- .../doctype/therapy_plan/test_therapy_plan.py | 12 +-- .../doctype/therapy_type/test_therapy_type.py | 2 +- .../test_compensatory_leave_request.py | 16 ++-- .../expense_claim/test_expense_claim.py | 12 +-- .../hr/doctype/job_offer/test_job_offer.py | 4 +- .../leave_allocation/test_leave_allocation.py | 14 +-- .../test_leave_application.py | 78 +++++++-------- .../leave_encashment/test_leave_encashment.py | 8 +- .../loan_management/doctype/loan/test_loan.py | 96 +++++++++---------- .../test_loan_disbursement.py | 6 +- .../test_loan_interest_accrual.py | 6 +- erpnext/manufacturing/doctype/bom/test_bom.py | 2 +- .../bom_update_tool/test_bom_update_tool.py | 6 +- .../doctype/work_order/test_work_order.py | 10 +- .../doctype/donation/test_donation.py | 2 +- .../portal/doctype/homepage/test_homepage.py | 2 +- .../homepage_section/test_homepage_section.py | 4 +- .../test_tax_exemption_80g_certificate.py | 12 +-- .../doctype/quotation/test_quotation.py | 2 +- .../doctype/sales_order/test_sales_order.py | 4 +- .../delivery_note/test_delivery_note.py | 6 +- .../purchase_receipt/test_purchase_receipt.py | 10 +- erpnext/support/doctype/issue/test_issue.py | 28 +++--- 33 files changed, 240 insertions(+), 240 deletions(-) diff --git a/erpnext/accounts/doctype/dunning/test_dunning.py b/erpnext/accounts/doctype/dunning/test_dunning.py index cb18309e3c..c5ce514cdd 100644 --- a/erpnext/accounts/doctype/dunning/test_dunning.py +++ b/erpnext/accounts/doctype/dunning/test_dunning.py @@ -42,9 +42,9 @@ class TestDunning(unittest.TestCase): ['Sales - _TC', 0.0, 20.44] ]) for gle in gl_entries: - self.assertEquals(expected_values[gle.account][0], gle.account) - self.assertEquals(expected_values[gle.account][1], gle.debit) - self.assertEquals(expected_values[gle.account][2], gle.credit) + self.assertEqual(expected_values[gle.account][0], gle.account) + self.assertEqual(expected_values[gle.account][1], gle.debit) + self.assertEqual(expected_values[gle.account][2], gle.credit) def test_payment_entry(self): dunning = create_dunning() diff --git a/erpnext/accounts/doctype/gl_entry/test_gl_entry.py b/erpnext/accounts/doctype/gl_entry/test_gl_entry.py index b4a547b21b..4167ca70df 100644 --- a/erpnext/accounts/doctype/gl_entry/test_gl_entry.py +++ b/erpnext/accounts/doctype/gl_entry/test_gl_entry.py @@ -54,4 +54,4 @@ class TestGLEntry(unittest.TestCase): self.assertTrue(all(new.name != old.name for new, old in zip(gl_entries, new_gl_entries))) new_naming_series_current_value = frappe.db.sql("SELECT current from tabSeries where name = %s", naming_series)[0][0] - self.assertEquals(old_naming_series_current_value + 2, new_naming_series_current_value) + self.assertEqual(old_naming_series_current_value + 2, new_naming_series_current_value) diff --git a/erpnext/accounts/doctype/payment_order/test_payment_order.py b/erpnext/accounts/doctype/payment_order/test_payment_order.py index 1c23e2a0ec..5fdde07faa 100644 --- a/erpnext/accounts/doctype/payment_order/test_payment_order.py +++ b/erpnext/accounts/doctype/payment_order/test_payment_order.py @@ -31,10 +31,10 @@ class TestPaymentOrder(unittest.TestCase): doc = create_payment_order_against_payment_entry(payment_entry, "Payment Entry") reference_doc = doc.get("references")[0] - self.assertEquals(reference_doc.reference_name, payment_entry.name) - self.assertEquals(reference_doc.reference_doctype, "Payment Entry") - self.assertEquals(reference_doc.supplier, "_Test Supplier") - self.assertEquals(reference_doc.amount, 250) + self.assertEqual(reference_doc.reference_name, payment_entry.name) + self.assertEqual(reference_doc.reference_doctype, "Payment Entry") + self.assertEqual(reference_doc.supplier, "_Test Supplier") + self.assertEqual(reference_doc.amount, 250) def create_payment_order_against_payment_entry(ref_doc, order_type): payment_order = frappe.get_doc(dict( diff --git a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py index ef9aad562d..ffe8be1162 100644 --- a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py @@ -99,7 +99,7 @@ class TestPricingRule(unittest.TestCase): args.item_code = "_Test Item 2" details = get_item_details(args) - self.assertEquals(details.get("discount_percentage"), 15) + self.assertEqual(details.get("discount_percentage"), 15) def test_pricing_rule_for_margin(self): from erpnext.stock.get_item_details import get_item_details @@ -145,8 +145,8 @@ class TestPricingRule(unittest.TestCase): "name": None }) details = get_item_details(args) - self.assertEquals(details.get("margin_type"), "Percentage") - self.assertEquals(details.get("margin_rate_or_amount"), 10) + self.assertEqual(details.get("margin_type"), "Percentage") + self.assertEqual(details.get("margin_rate_or_amount"), 10) def test_mixed_conditions_for_item_group(self): for item in ["Mixed Cond Item 1", "Mixed Cond Item 2"]: @@ -192,7 +192,7 @@ class TestPricingRule(unittest.TestCase): "name": None }) details = get_item_details(args) - self.assertEquals(details.get("discount_percentage"), 10) + self.assertEqual(details.get("discount_percentage"), 10) def test_pricing_rule_for_variants(self): from erpnext.stock.get_item_details import get_item_details @@ -322,11 +322,11 @@ class TestPricingRule(unittest.TestCase): si.insert(ignore_permissions=True) item = si.items[0] - self.assertEquals(item.margin_rate_or_amount, 10) - self.assertEquals(item.rate_with_margin, 1100) + self.assertEqual(item.margin_rate_or_amount, 10) + self.assertEqual(item.rate_with_margin, 1100) self.assertEqual(item.discount_percentage, 10) - self.assertEquals(item.discount_amount, 110) - self.assertEquals(item.rate, 990) + self.assertEqual(item.discount_amount, 110) + self.assertEqual(item.rate, 990) def test_pricing_rule_with_margin_and_discount_amount(self): frappe.delete_doc_if_exists('Pricing Rule', '_Test Pricing Rule') @@ -338,10 +338,10 @@ class TestPricingRule(unittest.TestCase): si.insert(ignore_permissions=True) item = si.items[0] - self.assertEquals(item.margin_rate_or_amount, 10) - self.assertEquals(item.rate_with_margin, 1100) - self.assertEquals(item.discount_amount, 110) - self.assertEquals(item.rate, 990) + self.assertEqual(item.margin_rate_or_amount, 10) + self.assertEqual(item.rate_with_margin, 1100) + self.assertEqual(item.discount_amount, 110) + self.assertEqual(item.rate, 990) def test_pricing_rule_for_product_discount_on_same_item(self): frappe.delete_doc_if_exists('Pricing Rule', '_Test Pricing Rule') @@ -458,21 +458,21 @@ class TestPricingRule(unittest.TestCase): si.items[0].price_list_rate = 1000 si.submit() item = si.items[0] - self.assertEquals(item.rate, 100) + self.assertEqual(item.rate, 100) # Correct Customer and Incorrect is_return value si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 1", is_return=1, qty=-1) si.items[0].price_list_rate = 1000 si.submit() item = si.items[0] - self.assertEquals(item.rate, 100) + self.assertEqual(item.rate, 100) # Correct Customer and correct is_return value si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 1", is_return=0) si.items[0].price_list_rate = 1000 si.submit() item = si.items[0] - self.assertEquals(item.rate, 900) + self.assertEqual(item.rate, 900) def test_multiple_pricing_rules(self): make_pricing_rule(discount_percentage=20, selling=1, priority=1, apply_multiple_pricing_rules=1, @@ -545,11 +545,11 @@ class TestPricingRule(unittest.TestCase): apply_on="Transaction", free_item="Water Flask 1", free_qty=1, free_item_rate=10) si = create_sales_invoice(qty=5, do_not_submit=True) - self.assertEquals(len(si.items), 2) - self.assertEquals(si.items[1].rate, 10) + self.assertEqual(len(si.items), 2) + self.assertEqual(si.items[1].rate, 10) si1 = create_sales_invoice(qty=2, do_not_submit=True) - self.assertEquals(len(si1.items), 1) + self.assertEqual(len(si1.items), 1) for doc in [si, si1]: doc.delete() diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index a0d76031fc..40a8f85d8d 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -78,7 +78,7 @@ class TestAsset(unittest.TestCase): }) doc.set_missing_values() - self.assertEquals(doc.items[0].is_fixed_asset, 1) + self.assertEqual(doc.items[0].is_fixed_asset, 1) def test_schedule_for_straight_line_method(self): pr = make_purchase_receipt(item_code="Macbook Pro", @@ -565,7 +565,7 @@ class TestAsset(unittest.TestCase): doc = make_invoice(pr.name) - self.assertEquals('Asset Received But Not Billed - _TC', doc.items[0].expense_account) + self.assertEqual('Asset Received But Not Billed - _TC', doc.items[0].expense_account) def test_asset_cwip_toggling_cases(self): cwip = frappe.db.get_value("Asset Category", "Computers", "enable_cwip_accounting") diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py index 42f4472f29..aaa98f2f1f 100644 --- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py @@ -187,7 +187,7 @@ class TestPurchaseOrder(unittest.TestCase): update_child_qty_rate('Purchase Order', trans_item, po.name) po.reload() - self.assertEquals(len(po.get('items')), 2) + self.assertEqual(len(po.get('items')), 2) self.assertEqual(po.status, 'To Receive and Bill') # ordered qty should increase on row addition self.assertEqual(get_ordered_qty(), existing_ordered_qty + 7) @@ -234,7 +234,7 @@ class TestPurchaseOrder(unittest.TestCase): update_child_qty_rate('Purchase Order', trans_item, po.name) po.reload() - self.assertEquals(len(po.get('items')), 1) + self.assertEqual(len(po.get('items')), 1) self.assertEqual(po.status, 'To Receive and Bill') # ordered qty should decrease (back to initial) on row deletion @@ -448,13 +448,13 @@ class TestPurchaseOrder(unittest.TestCase): pi.load_from_db() - self.assertEquals(pi.per_received, 100.00) - self.assertEquals(pi.items[0].qty, pi.items[0].received_qty) + self.assertEqual(pi.per_received, 100.00) + self.assertEqual(pi.items[0].qty, pi.items[0].received_qty) po.load_from_db() - self.assertEquals(po.per_received, 100.00) - self.assertEquals(po.per_billed, 100.00) + self.assertEqual(po.per_received, 100.00) + self.assertEqual(po.per_billed, 100.00) pr.cancel() @@ -674,8 +674,8 @@ class TestPurchaseOrder(unittest.TestCase): filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"}, fieldname=["reserved_qty_for_sub_contract", "projected_qty"], as_dict=1) - self.assertEquals(bin2.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract + 10) - self.assertEquals(bin2.projected_qty, bin1.projected_qty - 10) + self.assertEqual(bin2.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract + 10) + self.assertEqual(bin2.projected_qty, bin1.projected_qty - 10) # Create stock transfer rm_item = [{"item_code":"_Test FG Item","rm_item_code":"_Test Item","item_name":"_Test Item", @@ -690,7 +690,7 @@ class TestPurchaseOrder(unittest.TestCase): filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"}, fieldname="reserved_qty_for_sub_contract", as_dict=1) - self.assertEquals(bin3.reserved_qty_for_sub_contract, bin2.reserved_qty_for_sub_contract - 6) + self.assertEqual(bin3.reserved_qty_for_sub_contract, bin2.reserved_qty_for_sub_contract - 6) # close PO po.update_status("Closed") @@ -698,7 +698,7 @@ class TestPurchaseOrder(unittest.TestCase): filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"}, fieldname="reserved_qty_for_sub_contract", as_dict=1) - self.assertEquals(bin4.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract) + self.assertEqual(bin4.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract) # Re-open PO po.update_status("Submitted") @@ -706,7 +706,7 @@ class TestPurchaseOrder(unittest.TestCase): filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"}, fieldname="reserved_qty_for_sub_contract", as_dict=1) - self.assertEquals(bin5.reserved_qty_for_sub_contract, bin2.reserved_qty_for_sub_contract - 6) + self.assertEqual(bin5.reserved_qty_for_sub_contract, bin2.reserved_qty_for_sub_contract - 6) make_stock_entry(target="_Test Warehouse 1 - _TC", item_code="_Test Item", qty=40, basic_rate=100) @@ -723,7 +723,7 @@ class TestPurchaseOrder(unittest.TestCase): filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"}, fieldname="reserved_qty_for_sub_contract", as_dict=1) - self.assertEquals(bin6.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract) + self.assertEqual(bin6.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract) # Cancel PR pr.cancel() @@ -731,7 +731,7 @@ class TestPurchaseOrder(unittest.TestCase): filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"}, fieldname="reserved_qty_for_sub_contract", as_dict=1) - self.assertEquals(bin7.reserved_qty_for_sub_contract, bin2.reserved_qty_for_sub_contract - 6) + self.assertEqual(bin7.reserved_qty_for_sub_contract, bin2.reserved_qty_for_sub_contract - 6) # Make Purchase Invoice pi = make_pi_from_po(po.name) @@ -743,7 +743,7 @@ class TestPurchaseOrder(unittest.TestCase): filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"}, fieldname="reserved_qty_for_sub_contract", as_dict=1) - self.assertEquals(bin8.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract) + self.assertEqual(bin8.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract) # Cancel PR pi.cancel() @@ -751,7 +751,7 @@ class TestPurchaseOrder(unittest.TestCase): filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"}, fieldname="reserved_qty_for_sub_contract", as_dict=1) - self.assertEquals(bin9.reserved_qty_for_sub_contract, bin2.reserved_qty_for_sub_contract - 6) + self.assertEqual(bin9.reserved_qty_for_sub_contract, bin2.reserved_qty_for_sub_contract - 6) # Cancel Stock Entry se.cancel() @@ -759,7 +759,7 @@ class TestPurchaseOrder(unittest.TestCase): filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"}, fieldname="reserved_qty_for_sub_contract", as_dict=1) - self.assertEquals(bin10.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract + 10) + self.assertEqual(bin10.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract + 10) # Cancel PO po.reload() @@ -768,7 +768,7 @@ class TestPurchaseOrder(unittest.TestCase): filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"}, fieldname="reserved_qty_for_sub_contract", as_dict=1) - self.assertEquals(bin11.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract) + self.assertEqual(bin11.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract) def test_exploded_items_in_subcontracted(self): item_code = "_Test Subcontracted FG Item 1" @@ -782,7 +782,7 @@ class TestPurchaseOrder(unittest.TestCase): exploded_items = sorted([d.item_code for d in bom.exploded_items if not d.get('sourced_by_supplier')]) supplied_items = sorted([d.rm_item_code for d in po.supplied_items]) - self.assertEquals(exploded_items, supplied_items) + self.assertEqual(exploded_items, supplied_items) po1 = create_purchase_order(item_code=item_code, qty=1, is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC", include_exploded_items=0) @@ -790,7 +790,7 @@ class TestPurchaseOrder(unittest.TestCase): supplied_items1 = sorted([d.rm_item_code for d in po1.supplied_items]) bom_items = sorted([d.item_code for d in bom.items if not d.get('sourced_by_supplier')]) - self.assertEquals(supplied_items1, bom_items) + self.assertEqual(supplied_items1, bom_items) def test_backflush_based_on_stock_entry(self): item_code = "_Test Subcontracted FG Item 1" @@ -840,8 +840,8 @@ class TestPurchaseOrder(unittest.TestCase): transferred_items = sorted([d.item_code for d in se.get('items') if se.purchase_order == po.name]) issued_items = sorted([d.rm_item_code for d in pr.get('supplied_items')]) - self.assertEquals(transferred_items, issued_items) - self.assertEquals(pr.get('items')[0].rm_supp_cost, 2000) + self.assertEqual(transferred_items, issued_items) + self.assertEqual(pr.get('items')[0].rm_supp_cost, 2000) transferred_rm_map = frappe._dict() 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 29487962f6..d370fbcda7 100644 --- a/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py +++ b/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py @@ -19,7 +19,7 @@ class TestMpesaSettings(unittest.TestCase): 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) - self.assertEquals(mode_of_payment.type, "Phone") + self.assertEqual(mode_of_payment.type, "Phone") def test_processing_of_account_balance(self): mpesa_doc = create_mpesa_settings(payment_gateway_name="_Account Balance") @@ -31,11 +31,11 @@ class TestMpesaSettings(unittest.TestCase): # test integration request creation and successful update of the status on receiving callback response self.assertTrue(integration_request) - self.assertEquals(integration_request.status, "Completed") + self.assertEqual(integration_request.status, "Completed") # test formatting of account balance received as string to json with appropriate currency symbol mpesa_doc.reload() - self.assertEquals(mpesa_doc.account_balance, dumps({ + self.assertEqual(mpesa_doc.account_balance, dumps({ "Working Account": { "current_balance": "Sh 481,000.00", "available_balance": "Sh 481,000.00", @@ -60,7 +60,7 @@ class TestMpesaSettings(unittest.TestCase): pr = pos_invoice.create_payment_request() # test payment request creation - self.assertEquals(pr.payment_gateway, "Mpesa-Payment") + self.assertEqual(pr.payment_gateway, "Mpesa-Payment") # submitting payment request creates integration requests with random id integration_req_ids = frappe.get_all("Integration Request", filters={ @@ -75,12 +75,12 @@ class TestMpesaSettings(unittest.TestCase): # test integration request creation and successful update of the status on receiving callback response self.assertTrue(integration_request) - self.assertEquals(integration_request.status, "Completed") + self.assertEqual(integration_request.status, "Completed") pos_invoice.reload() integration_request.reload() - self.assertEquals(pos_invoice.mpesa_receipt_number, "LGR7OWQX0R") - self.assertEquals(integration_request.status, "Completed") + self.assertEqual(pos_invoice.mpesa_receipt_number, "LGR7OWQX0R") + self.assertEqual(integration_request.status, "Completed") frappe.db.set_value("Customer", "_Test Customer", "default_currency", "") integration_request.delete() @@ -104,7 +104,7 @@ class TestMpesaSettings(unittest.TestCase): pr = pos_invoice.create_payment_request() # test payment request creation - self.assertEquals(pr.payment_gateway, "Mpesa-Payment") + self.assertEqual(pr.payment_gateway, "Mpesa-Payment") # submitting payment request creates integration requests with random id integration_req_ids = frappe.get_all("Integration Request", filters={ @@ -126,12 +126,12 @@ class TestMpesaSettings(unittest.TestCase): verify_transaction(**callback_response) # test completion of integration request integration_request = frappe.get_doc("Integration Request", integration_req_ids[i]) - self.assertEquals(integration_request.status, "Completed") + self.assertEqual(integration_request.status, "Completed") integration_requests.append(integration_request) # check receipt number once all the integration requests are completed pos_invoice.reload() - self.assertEquals(pos_invoice.mpesa_receipt_number, ', '.join(mpesa_receipt_numbers)) + self.assertEqual(pos_invoice.mpesa_receipt_number, ', '.join(mpesa_receipt_numbers)) frappe.db.set_value("Customer", "_Test Customer", "default_currency", "") [d.delete() for d in integration_requests] @@ -155,7 +155,7 @@ class TestMpesaSettings(unittest.TestCase): pr = pos_invoice.create_payment_request() # test payment request creation - self.assertEquals(pr.payment_gateway, "Mpesa-Payment") + self.assertEqual(pr.payment_gateway, "Mpesa-Payment") # submitting payment request creates integration requests with random id integration_req_ids = frappe.get_all("Integration Request", filters={ @@ -175,7 +175,7 @@ class TestMpesaSettings(unittest.TestCase): verify_transaction(**callback_response) # test completion of integration request integration_request = frappe.get_doc("Integration Request", integration_req_ids[0]) - self.assertEquals(integration_request.status, "Completed") + self.assertEqual(integration_request.status, "Completed") # now one request is completed # second integration request fails @@ -187,7 +187,7 @@ class TestMpesaSettings(unittest.TestCase): 'name': ['not in', integration_req_ids] }, pluck="name") - self.assertEquals(len(new_integration_req_ids), 1) + self.assertEqual(len(new_integration_req_ids), 1) frappe.db.set_value("Customer", "_Test Customer", "default_currency", "") frappe.db.sql("delete from `tabIntegration Request` where integration_request_service = 'Mpesa'") diff --git a/erpnext/healthcare/doctype/clinical_procedure/test_clinical_procedure.py b/erpnext/healthcare/doctype/clinical_procedure/test_clinical_procedure.py index fb72073a07..03e96a4b3b 100644 --- a/erpnext/healthcare/doctype/clinical_procedure/test_clinical_procedure.py +++ b/erpnext/healthcare/doctype/clinical_procedure/test_clinical_procedure.py @@ -17,7 +17,7 @@ class TestClinicalProcedure(unittest.TestCase): procedure_template.disabled = 1 procedure_template.save() - self.assertEquals(frappe.db.get_value('Item', procedure_template.item, 'disabled'), 1) + self.assertEqual(frappe.db.get_value('Item', procedure_template.item, 'disabled'), 1) def test_consumables(self): patient, medical_department, practitioner = create_healthcare_docs() diff --git a/erpnext/healthcare/doctype/lab_test/test_lab_test.py b/erpnext/healthcare/doctype/lab_test/test_lab_test.py index 79ab8a4d7f..c9f0029ed8 100644 --- a/erpnext/healthcare/doctype/lab_test/test_lab_test.py +++ b/erpnext/healthcare/doctype/lab_test/test_lab_test.py @@ -18,7 +18,7 @@ class TestLabTest(unittest.TestCase): lab_template.disabled = 1 lab_template.save() - self.assertEquals(frappe.db.get_value('Item', lab_template.item, 'disabled'), 1) + self.assertEqual(frappe.db.get_value('Item', lab_template.item, 'disabled'), 1) lab_template.reload() @@ -57,7 +57,7 @@ class TestLabTest(unittest.TestCase): # sample collection should not be created lab_test.reload() - self.assertEquals(lab_test.sample, None) + self.assertEqual(lab_test.sample, None) def test_create_lab_tests_from_sales_invoice(self): sales_invoice = create_sales_invoice() diff --git a/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py index 2bb8a53c45..5f2dc480a1 100644 --- a/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py +++ b/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py @@ -20,13 +20,13 @@ class TestPatientAppointment(unittest.TestCase): patient, medical_department, practitioner = create_healthcare_docs() frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 0) appointment = create_appointment(patient, practitioner, nowdate()) - self.assertEquals(appointment.status, 'Open') + self.assertEqual(appointment.status, 'Open') appointment = create_appointment(patient, practitioner, add_days(nowdate(), 2)) - self.assertEquals(appointment.status, 'Scheduled') + self.assertEqual(appointment.status, 'Scheduled') encounter = create_encounter(appointment) - self.assertEquals(frappe.db.get_value('Patient Appointment', appointment.name, 'status'), 'Closed') + self.assertEqual(frappe.db.get_value('Patient Appointment', appointment.name, 'status'), 'Closed') encounter.cancel() - self.assertEquals(frappe.db.get_value('Patient Appointment', appointment.name, 'status'), 'Open') + self.assertEqual(frappe.db.get_value('Patient Appointment', appointment.name, 'status'), 'Open') def test_start_encounter(self): patient, medical_department, practitioner = create_healthcare_docs() diff --git a/erpnext/healthcare/doctype/therapy_plan/test_therapy_plan.py b/erpnext/healthcare/doctype/therapy_plan/test_therapy_plan.py index 7fb159d6b5..d079bedb42 100644 --- a/erpnext/healthcare/doctype/therapy_plan/test_therapy_plan.py +++ b/erpnext/healthcare/doctype/therapy_plan/test_therapy_plan.py @@ -18,24 +18,24 @@ class TestTherapyPlan(unittest.TestCase): def test_status(self): plan = create_therapy_plan() - self.assertEquals(plan.status, 'Not Started') + self.assertEqual(plan.status, 'Not Started') session = make_therapy_session(plan.name, plan.patient, 'Basic Rehab', '_Test Company') frappe.get_doc(session).submit() - self.assertEquals(frappe.db.get_value('Therapy Plan', plan.name, 'status'), 'In Progress') + self.assertEqual(frappe.db.get_value('Therapy Plan', plan.name, 'status'), 'In Progress') session = make_therapy_session(plan.name, plan.patient, 'Basic Rehab', '_Test Company') frappe.get_doc(session).submit() - self.assertEquals(frappe.db.get_value('Therapy Plan', plan.name, 'status'), 'Completed') + self.assertEqual(frappe.db.get_value('Therapy Plan', plan.name, 'status'), 'Completed') patient, medical_department, practitioner = create_healthcare_docs() appointment = create_appointment(patient, practitioner, nowdate()) session = make_therapy_session(plan.name, plan.patient, 'Basic Rehab', '_Test Company', appointment.name) session = frappe.get_doc(session) session.submit() - self.assertEquals(frappe.db.get_value('Patient Appointment', appointment.name, 'status'), 'Closed') + self.assertEqual(frappe.db.get_value('Patient Appointment', appointment.name, 'status'), 'Closed') session.cancel() - self.assertEquals(frappe.db.get_value('Patient Appointment', appointment.name, 'status'), 'Open') + self.assertEqual(frappe.db.get_value('Patient Appointment', appointment.name, 'status'), 'Open') def test_therapy_plan_from_template(self): patient = create_patient() @@ -49,7 +49,7 @@ class TestTherapyPlan(unittest.TestCase): si.save() therapy_plan_template_amt = frappe.db.get_value('Therapy Plan Template', template, 'total_amount') - self.assertEquals(si.items[0].amount, therapy_plan_template_amt) + self.assertEqual(si.items[0].amount, therapy_plan_template_amt) def create_therapy_plan(template=None): diff --git a/erpnext/healthcare/doctype/therapy_type/test_therapy_type.py b/erpnext/healthcare/doctype/therapy_type/test_therapy_type.py index 03a1be8a4e..21f6369975 100644 --- a/erpnext/healthcare/doctype/therapy_type/test_therapy_type.py +++ b/erpnext/healthcare/doctype/therapy_type/test_therapy_type.py @@ -13,7 +13,7 @@ class TestTherapyType(unittest.TestCase): therapy_type.disabled = 1 therapy_type.save() - self.assertEquals(frappe.db.get_value('Item', therapy_type.item, 'disabled'), 1) + self.assertEqual(frappe.db.get_value('Item', therapy_type.item, 'disabled'), 1) def create_therapy_type(): exercise = create_exercise_type() diff --git a/erpnext/hr/doctype/compensatory_leave_request/test_compensatory_leave_request.py b/erpnext/hr/doctype/compensatory_leave_request/test_compensatory_leave_request.py index 74ce30108f..3b99c57051 100644 --- a/erpnext/hr/doctype/compensatory_leave_request/test_compensatory_leave_request.py +++ b/erpnext/hr/doctype/compensatory_leave_request/test_compensatory_leave_request.py @@ -68,19 +68,19 @@ class TestCompensatoryLeaveRequest(unittest.TestCase): filters = dict(transaction_name=compensatory_leave_request.leave_allocation) leave_ledger_entry = frappe.get_all('Leave Ledger Entry', fields='*', filters=filters) - self.assertEquals(len(leave_ledger_entry), 1) - self.assertEquals(leave_ledger_entry[0].employee, compensatory_leave_request.employee) - self.assertEquals(leave_ledger_entry[0].leave_type, compensatory_leave_request.leave_type) - self.assertEquals(leave_ledger_entry[0].leaves, 1) + self.assertEqual(len(leave_ledger_entry), 1) + self.assertEqual(leave_ledger_entry[0].employee, compensatory_leave_request.employee) + self.assertEqual(leave_ledger_entry[0].leave_type, compensatory_leave_request.leave_type) + self.assertEqual(leave_ledger_entry[0].leaves, 1) # check reverse leave ledger entry on cancellation compensatory_leave_request.cancel() leave_ledger_entry = frappe.get_all('Leave Ledger Entry', fields='*', filters=filters, order_by = 'creation desc') - self.assertEquals(len(leave_ledger_entry), 2) - self.assertEquals(leave_ledger_entry[0].employee, compensatory_leave_request.employee) - self.assertEquals(leave_ledger_entry[0].leave_type, compensatory_leave_request.leave_type) - self.assertEquals(leave_ledger_entry[0].leaves, -1) + self.assertEqual(len(leave_ledger_entry), 2) + self.assertEqual(leave_ledger_entry[0].employee, compensatory_leave_request.employee) + self.assertEqual(leave_ledger_entry[0].leave_type, compensatory_leave_request.leave_type) + self.assertEqual(leave_ledger_entry[0].leaves, -1) def get_compensatory_leave_request(employee, leave_date=today()): prev_comp_leave_req = frappe.db.get_value('Compensatory Leave Request', diff --git a/erpnext/hr/doctype/expense_claim/test_expense_claim.py b/erpnext/hr/doctype/expense_claim/test_expense_claim.py index 3f22ca2141..578eccf787 100644 --- a/erpnext/hr/doctype/expense_claim/test_expense_claim.py +++ b/erpnext/hr/doctype/expense_claim/test_expense_claim.py @@ -88,9 +88,9 @@ class TestExpenseClaim(unittest.TestCase): ]) for gle in gl_entries: - self.assertEquals(expected_values[gle.account][0], gle.account) - self.assertEquals(expected_values[gle.account][1], gle.debit) - self.assertEquals(expected_values[gle.account][2], gle.credit) + self.assertEqual(expected_values[gle.account][0], gle.account) + self.assertEqual(expected_values[gle.account][1], gle.debit) + self.assertEqual(expected_values[gle.account][2], gle.credit) def test_rejected_expense_claim(self): payable_account = get_payable_account(company_name) @@ -104,11 +104,11 @@ class TestExpenseClaim(unittest.TestCase): }) expense_claim.submit() - self.assertEquals(expense_claim.status, 'Rejected') - self.assertEquals(expense_claim.total_sanctioned_amount, 0.0) + self.assertEqual(expense_claim.status, 'Rejected') + self.assertEqual(expense_claim.total_sanctioned_amount, 0.0) gl_entry = frappe.get_all('GL Entry', {'voucher_type': 'Expense Claim', 'voucher_no': expense_claim.name}) - self.assertEquals(len(gl_entry), 0) + self.assertEqual(len(gl_entry), 0) def test_expense_approver_perms(self): user = "test_approver_perm_emp@example.com" diff --git a/erpnext/hr/doctype/job_offer/test_job_offer.py b/erpnext/hr/doctype/job_offer/test_job_offer.py index 690a692ddc..b3e1dc8d87 100644 --- a/erpnext/hr/doctype/job_offer/test_job_offer.py +++ b/erpnext/hr/doctype/job_offer/test_job_offer.py @@ -35,13 +35,13 @@ class TestJobOffer(unittest.TestCase): job_offer = create_job_offer(job_applicant=job_applicant.name) job_offer.submit() job_applicant.reload() - self.assertEquals(job_applicant.status, "Accepted") + self.assertEqual(job_applicant.status, "Accepted") # status update after rejection job_offer.status = "Rejected" job_offer.submit() job_applicant.reload() - self.assertEquals(job_applicant.status, "Rejected") + self.assertEqual(job_applicant.status, "Rejected") def create_job_offer(**args): args = frappe._dict(args) diff --git a/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py b/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py index 0b71036c86..6e7ae87d08 100644 --- a/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py +++ b/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py @@ -96,7 +96,7 @@ class TestLeaveAllocation(unittest.TestCase): carry_forward=1) leave_allocation_1.submit() - self.assertEquals(leave_allocation_1.unused_leaves, 10) + self.assertEqual(leave_allocation_1.unused_leaves, 10) leave_allocation_1.cancel() @@ -108,7 +108,7 @@ class TestLeaveAllocation(unittest.TestCase): new_leaves_allocated=25) leave_allocation_2.submit() - self.assertEquals(leave_allocation_2.unused_leaves, 5) + self.assertEqual(leave_allocation_2.unused_leaves, 5) def test_carry_forward_leaves_expiry(self): frappe.db.sql("delete from `tabLeave Allocation`") @@ -145,7 +145,7 @@ class TestLeaveAllocation(unittest.TestCase): to_date=add_months(nowdate(), 12)) leave_allocation_1.submit() - self.assertEquals(leave_allocation_1.unused_leaves, leave_allocation.new_leaves_allocated) + self.assertEqual(leave_allocation_1.unused_leaves, leave_allocation.new_leaves_allocated) def test_creation_of_leave_ledger_entry_on_submit(self): frappe.db.sql("delete from `tabLeave Allocation`") @@ -155,10 +155,10 @@ class TestLeaveAllocation(unittest.TestCase): leave_ledger_entry = frappe.get_all('Leave Ledger Entry', fields='*', filters=dict(transaction_name=leave_allocation.name)) - self.assertEquals(len(leave_ledger_entry), 1) - self.assertEquals(leave_ledger_entry[0].employee, leave_allocation.employee) - self.assertEquals(leave_ledger_entry[0].leave_type, leave_allocation.leave_type) - self.assertEquals(leave_ledger_entry[0].leaves, leave_allocation.new_leaves_allocated) + self.assertEqual(len(leave_ledger_entry), 1) + self.assertEqual(leave_ledger_entry[0].employee, leave_allocation.employee) + self.assertEqual(leave_ledger_entry[0].leave_type, leave_allocation.leave_type) + self.assertEqual(leave_ledger_entry[0].leaves, leave_allocation.new_leaves_allocated) # check if leave ledger entry is deleted on cancellation leave_allocation.cancel() diff --git a/erpnext/hr/doctype/leave_application/test_leave_application.py b/erpnext/hr/doctype/leave_application/test_leave_application.py index b54c9712c8..a4a96b813e 100644 --- a/erpnext/hr/doctype/leave_application/test_leave_application.py +++ b/erpnext/hr/doctype/leave_application/test_leave_application.py @@ -16,36 +16,36 @@ from erpnext.hr.doctype.employee.test_employee import make_employee test_dependencies = ["Leave Allocation", "Leave Block List", "Employee"] _test_records = [ - { - "company": "_Test Company", - "doctype": "Leave Application", - "employee": "_T-Employee-00001", - "from_date": "2013-05-01", - "description": "_Test Reason", - "leave_type": "_Test Leave Type", - "posting_date": "2013-01-02", - "to_date": "2013-05-05" - }, - { - "company": "_Test Company", - "doctype": "Leave Application", - "employee": "_T-Employee-00002", - "from_date": "2013-05-01", - "description": "_Test Reason", - "leave_type": "_Test Leave Type", - "posting_date": "2013-01-02", - "to_date": "2013-05-05" - }, - { - "company": "_Test Company", - "doctype": "Leave Application", - "employee": "_T-Employee-00001", - "from_date": "2013-01-15", - "description": "_Test Reason", - "leave_type": "_Test Leave Type LWP", - "posting_date": "2013-01-02", - "to_date": "2013-01-15" - } + { + "company": "_Test Company", + "doctype": "Leave Application", + "employee": "_T-Employee-00001", + "from_date": "2013-05-01", + "description": "_Test Reason", + "leave_type": "_Test Leave Type", + "posting_date": "2013-01-02", + "to_date": "2013-05-05" + }, + { + "company": "_Test Company", + "doctype": "Leave Application", + "employee": "_T-Employee-00002", + "from_date": "2013-05-01", + "description": "_Test Reason", + "leave_type": "_Test Leave Type", + "posting_date": "2013-01-02", + "to_date": "2013-05-05" + }, + { + "company": "_Test Company", + "doctype": "Leave Application", + "employee": "_T-Employee-00001", + "from_date": "2013-01-15", + "description": "_Test Reason", + "leave_type": "_Test Leave Type LWP", + "posting_date": "2013-01-02", + "to_date": "2013-01-15" + } ] @@ -516,9 +516,9 @@ class TestLeaveApplication(unittest.TestCase): leave_application.submit() leave_ledger_entry = frappe.get_all('Leave Ledger Entry', fields='*', filters=dict(transaction_name=leave_application.name)) - self.assertEquals(leave_ledger_entry[0].employee, leave_application.employee) - self.assertEquals(leave_ledger_entry[0].leave_type, leave_application.leave_type) - self.assertEquals(leave_ledger_entry[0].leaves, leave_application.total_leave_days * -1) + self.assertEqual(leave_ledger_entry[0].employee, leave_application.employee) + self.assertEqual(leave_ledger_entry[0].leave_type, leave_application.leave_type) + self.assertEqual(leave_ledger_entry[0].leaves, leave_application.total_leave_days * -1) # check if leave ledger entry is deleted on cancellation leave_application.cancel() @@ -549,11 +549,11 @@ class TestLeaveApplication(unittest.TestCase): leave_ledger_entry = frappe.get_all('Leave Ledger Entry', '*', filters=dict(transaction_name=leave_application.name)) - self.assertEquals(len(leave_ledger_entry), 2) - self.assertEquals(leave_ledger_entry[0].employee, leave_application.employee) - self.assertEquals(leave_ledger_entry[0].leave_type, leave_application.leave_type) - self.assertEquals(leave_ledger_entry[0].leaves, -9) - self.assertEquals(leave_ledger_entry[1].leaves, -2) + self.assertEqual(len(leave_ledger_entry), 2) + self.assertEqual(leave_ledger_entry[0].employee, leave_application.employee) + self.assertEqual(leave_ledger_entry[0].leave_type, leave_application.leave_type) + self.assertEqual(leave_ledger_entry[0].leaves, -9) + self.assertEqual(leave_ledger_entry[1].leaves, -2) def test_leave_application_creation_after_expiry(self): # test leave balance for carry forwarded allocation @@ -566,7 +566,7 @@ class TestLeaveApplication(unittest.TestCase): create_carry_forwarded_allocation(employee, leave_type) - self.assertEquals(get_leave_balance_on(employee.name, leave_type.name, add_days(nowdate(), -85), add_days(nowdate(), -84)), 0) + self.assertEqual(get_leave_balance_on(employee.name, leave_type.name, add_days(nowdate(), -85), add_days(nowdate(), -84)), 0) def test_leave_approver_perms(self): employee = get_employee() diff --git a/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py b/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py index aafc9642d4..e0ffa5dd41 100644 --- a/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py +++ b/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py @@ -88,10 +88,10 @@ class TestLeaveEncashment(unittest.TestCase): leave_ledger_entry = frappe.get_all('Leave Ledger Entry', fields='*', filters=dict(transaction_name=leave_encashment.name)) - self.assertEquals(len(leave_ledger_entry), 1) - self.assertEquals(leave_ledger_entry[0].employee, leave_encashment.employee) - self.assertEquals(leave_ledger_entry[0].leave_type, leave_encashment.leave_type) - self.assertEquals(leave_ledger_entry[0].leaves, leave_encashment.encashable_days * -1) + self.assertEqual(len(leave_ledger_entry), 1) + self.assertEqual(leave_ledger_entry[0].employee, leave_encashment.employee) + self.assertEqual(leave_ledger_entry[0].leave_type, leave_encashment.leave_type) + self.assertEqual(leave_ledger_entry[0].leaves, leave_encashment.encashable_days * -1) # check if leave ledger entry is deleted on cancellation diff --git a/erpnext/loan_management/doctype/loan/test_loan.py b/erpnext/loan_management/doctype/loan/test_loan.py index fae6f860b6..fa4707ce2b 100644 --- a/erpnext/loan_management/doctype/loan/test_loan.py +++ b/erpnext/loan_management/doctype/loan/test_loan.py @@ -55,9 +55,9 @@ class TestLoan(unittest.TestCase): def test_loan(self): loan = frappe.get_doc("Loan", {"applicant":self.applicant1}) - self.assertEquals(loan.monthly_repayment_amount, 15052) - self.assertEquals(flt(loan.total_interest_payable, 0), 21034) - self.assertEquals(flt(loan.total_payment, 0), 301034) + self.assertEqual(loan.monthly_repayment_amount, 15052) + self.assertEqual(flt(loan.total_interest_payable, 0), 21034) + self.assertEqual(flt(loan.total_payment, 0), 301034) schedule = loan.repayment_schedule @@ -72,9 +72,9 @@ class TestLoan(unittest.TestCase): loan.monthly_repayment_amount = 14000 loan.save() - self.assertEquals(len(loan.repayment_schedule), 22) - self.assertEquals(flt(loan.total_interest_payable, 0), 22712) - self.assertEquals(flt(loan.total_payment, 0), 302712) + self.assertEqual(len(loan.repayment_schedule), 22) + self.assertEqual(flt(loan.total_interest_payable, 0), 22712) + self.assertEqual(flt(loan.total_payment, 0), 302712) def test_loan_with_security(self): @@ -89,7 +89,7 @@ class TestLoan(unittest.TestCase): loan = create_loan_with_security(self.applicant2, "Stock Loan", "Repay Over Number of Periods", 12, loan_application) - self.assertEquals(loan.loan_amount, 1000000) + self.assertEqual(loan.loan_amount, 1000000) def test_loan_disbursement(self): pledge = [{ @@ -102,7 +102,7 @@ class TestLoan(unittest.TestCase): create_pledge(loan_application) loan = create_loan_with_security(self.applicant2, "Stock Loan", "Repay Over Number of Periods", 12, loan_application) - self.assertEquals(loan.loan_amount, 1000000) + self.assertEqual(loan.loan_amount, 1000000) loan.submit() @@ -120,8 +120,8 @@ class TestLoan(unittest.TestCase): filters = {'voucher_type': 'Loan Disbursement', 'voucher_no': loan_disbursement_entry2.name} ) - self.assertEquals(loan.status, "Disbursed") - self.assertEquals(loan.disbursed_amount, 1000000) + self.assertEqual(loan.status, "Disbursed") + self.assertEqual(loan.disbursed_amount, 1000000) self.assertTrue(gl_entries1) self.assertTrue(gl_entries2) @@ -137,7 +137,7 @@ class TestLoan(unittest.TestCase): loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01') loan.submit() - self.assertEquals(loan.loan_amount, 1000000) + self.assertEqual(loan.loan_amount, 1000000) first_date = '2019-10-01' last_date = '2019-10-30' @@ -156,15 +156,15 @@ class TestLoan(unittest.TestCase): repayment_entry.submit() penalty_amount = (accrued_interest_amount * 5 * 25) / 100 - self.assertEquals(flt(repayment_entry.penalty_amount,0), flt(penalty_amount, 0)) + self.assertEqual(flt(repayment_entry.penalty_amount,0), flt(penalty_amount, 0)) amounts = frappe.db.get_all('Loan Interest Accrual', {'loan': loan.name}, ['paid_interest_amount']) loan.load_from_db() total_interest_paid = amounts[0]['paid_interest_amount'] + amounts[1]['paid_interest_amount'] - self.assertEquals(amounts[1]['paid_interest_amount'], repayment_entry.interest_payable) - self.assertEquals(flt(loan.total_principal_paid, 0), flt(repayment_entry.amount_paid - + self.assertEqual(amounts[1]['paid_interest_amount'], repayment_entry.interest_payable) + self.assertEqual(flt(loan.total_principal_paid, 0), flt(repayment_entry.amount_paid - penalty_amount - total_interest_paid, 0)) def test_loan_closure(self): @@ -179,7 +179,7 @@ class TestLoan(unittest.TestCase): loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01') loan.submit() - self.assertEquals(loan.loan_amount, 1000000) + self.assertEqual(loan.loan_amount, 1000000) first_date = '2019-10-01' last_date = '2019-10-30' @@ -204,12 +204,12 @@ class TestLoan(unittest.TestCase): amount = frappe.db.get_value('Loan Interest Accrual', {'loan': loan.name}, ['sum(paid_interest_amount)']) - self.assertEquals(flt(amount, 0),flt(accrued_interest_amount, 0)) - self.assertEquals(flt(repayment_entry.penalty_amount, 5), 0) + self.assertEqual(flt(amount, 0),flt(accrued_interest_amount, 0)) + self.assertEqual(flt(repayment_entry.penalty_amount, 5), 0) request_loan_closure(loan.name) loan.load_from_db() - self.assertEquals(loan.status, "Loan Closure Requested") + self.assertEqual(loan.status, "Loan Closure Requested") def test_loan_repayment_for_term_loan(self): pledges = [{ @@ -241,8 +241,8 @@ class TestLoan(unittest.TestCase): amounts = frappe.db.get_value('Loan Interest Accrual', {'loan': loan.name}, ['paid_interest_amount', 'paid_principal_amount']) - self.assertEquals(amounts[0], 11250.00) - self.assertEquals(amounts[1], 78303.00) + self.assertEqual(amounts[0], 11250.00) + self.assertEqual(amounts[1], 78303.00) def test_security_shortfall(self): pledges = [{ @@ -268,17 +268,17 @@ class TestLoan(unittest.TestCase): loan_security_shortfall = frappe.get_doc("Loan Security Shortfall", {"loan": loan.name}) self.assertTrue(loan_security_shortfall) - self.assertEquals(loan_security_shortfall.loan_amount, 1000000.00) - self.assertEquals(loan_security_shortfall.security_value, 800000.00) - self.assertEquals(loan_security_shortfall.shortfall_amount, 600000.00) + self.assertEqual(loan_security_shortfall.loan_amount, 1000000.00) + self.assertEqual(loan_security_shortfall.security_value, 800000.00) + self.assertEqual(loan_security_shortfall.shortfall_amount, 600000.00) frappe.db.sql(""" UPDATE `tabLoan Security Price` SET loan_security_price = 250 where loan_security='Test Security 2'""") create_process_loan_security_shortfall() loan_security_shortfall = frappe.get_doc("Loan Security Shortfall", {"loan": loan.name}) - self.assertEquals(loan_security_shortfall.status, "Completed") - self.assertEquals(loan_security_shortfall.shortfall_amount, 0) + self.assertEqual(loan_security_shortfall.status, "Completed") + self.assertEqual(loan_security_shortfall.shortfall_amount, 0) def test_loan_security_unpledge(self): pledge = [{ @@ -292,7 +292,7 @@ class TestLoan(unittest.TestCase): loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01') loan.submit() - self.assertEquals(loan.loan_amount, 1000000) + self.assertEqual(loan.loan_amount, 1000000) first_date = '2019-10-01' last_date = '2019-10-30' @@ -312,7 +312,7 @@ class TestLoan(unittest.TestCase): request_loan_closure(loan.name) loan.load_from_db() - self.assertEquals(loan.status, "Loan Closure Requested") + self.assertEqual(loan.status, "Loan Closure Requested") unpledge_request = unpledge_security(loan=loan.name, save=1) unpledge_request.submit() @@ -323,11 +323,11 @@ class TestLoan(unittest.TestCase): pledged_qty = get_pledged_security_qty(loan.name) self.assertEqual(loan.status, 'Closed') - self.assertEquals(sum(pledged_qty.values()), 0) + self.assertEqual(sum(pledged_qty.values()), 0) amounts = amounts = calculate_amounts(loan.name, add_days(last_date, 5)) self.assertEqual(amounts['pending_principal_amount'], 0) - self.assertEquals(amounts['payable_principal_amount'], 0.0) + self.assertEqual(amounts['payable_principal_amount'], 0.0) self.assertEqual(amounts['interest_amount'], 0) def test_partial_loan_security_unpledge(self): @@ -346,7 +346,7 @@ class TestLoan(unittest.TestCase): loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01') loan.submit() - self.assertEquals(loan.loan_amount, 1000000) + self.assertEqual(loan.loan_amount, 1000000) first_date = '2019-10-01' last_date = '2019-10-30' @@ -379,7 +379,7 @@ class TestLoan(unittest.TestCase): loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01') loan.submit() - self.assertEquals(loan.loan_amount, 1000000) + self.assertEqual(loan.loan_amount, 1000000) unpledge_map = {'Test Security 1': 4000} unpledge_request = unpledge_security(loan=loan.name, security_map = unpledge_map, save=1) @@ -450,7 +450,7 @@ class TestLoan(unittest.TestCase): loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01') loan.submit() - self.assertEquals(loan.loan_amount, 1000000) + self.assertEqual(loan.loan_amount, 1000000) first_date = '2019-10-01' last_date = '2019-10-30' @@ -475,7 +475,7 @@ class TestLoan(unittest.TestCase): request_loan_closure(loan.name) loan.load_from_db() - self.assertEquals(loan.status, "Loan Closure Requested") + self.assertEqual(loan.status, "Loan Closure Requested") amounts = calculate_amounts(loan.name, add_days(last_date, 5)) self.assertEqual(amounts['pending_principal_amount'], 0.0) @@ -492,7 +492,7 @@ class TestLoan(unittest.TestCase): loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01') loan.submit() - self.assertEquals(loan.loan_amount, 1000000) + self.assertEqual(loan.loan_amount, 1000000) first_date = '2019-10-01' last_date = '2019-10-30' @@ -533,8 +533,8 @@ class TestLoan(unittest.TestCase): calculated_penalty_amount = frappe.db.get_value('Loan Interest Accrual', {'process_loan_interest_accrual': process, 'loan': loan.name}, 'penalty_amount') - self.assertEquals(loan.loan_amount, 1000000) - self.assertEquals(calculated_penalty_amount, penalty_amount) + self.assertEqual(loan.loan_amount, 1000000) + self.assertEqual(calculated_penalty_amount, penalty_amount) def test_penalty_repayment(self): loan, dummy = create_loan_scenario_for_penalty(self) @@ -547,13 +547,13 @@ class TestLoan(unittest.TestCase): repayment_entry.submit() amounts = calculate_amounts(loan.name, '2019-11-30 00:00:01') - self.assertEquals(amounts['penalty_amount'], second_penalty) + self.assertEqual(amounts['penalty_amount'], second_penalty) repayment_entry = create_repayment_entry(loan.name, self.applicant2, '2019-11-30 00:00:01', second_penalty) repayment_entry.submit() amounts = calculate_amounts(loan.name, '2019-11-30 00:00:02') - self.assertEquals(amounts['penalty_amount'], 0) + self.assertEqual(amounts['penalty_amount'], 0) def test_loan_write_off_limit(self): pledge = [{ @@ -567,7 +567,7 @@ class TestLoan(unittest.TestCase): loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01') loan.submit() - self.assertEquals(loan.loan_amount, 1000000) + self.assertEqual(loan.loan_amount, 1000000) first_date = '2019-10-01' last_date = '2019-10-30' @@ -589,15 +589,15 @@ class TestLoan(unittest.TestCase): amount = frappe.db.get_value('Loan Interest Accrual', {'loan': loan.name}, ['sum(paid_interest_amount)']) - self.assertEquals(flt(amount, 0),flt(accrued_interest_amount, 0)) - self.assertEquals(flt(repayment_entry.penalty_amount, 5), 0) + self.assertEqual(flt(amount, 0),flt(accrued_interest_amount, 0)) + self.assertEqual(flt(repayment_entry.penalty_amount, 5), 0) amounts = calculate_amounts(loan.name, add_days(last_date, 5)) - self.assertEquals(flt(amounts['pending_principal_amount'], 0), 50) + self.assertEqual(flt(amounts['pending_principal_amount'], 0), 50) request_loan_closure(loan.name) loan.load_from_db() - self.assertEquals(loan.status, "Loan Closure Requested") + self.assertEqual(loan.status, "Loan Closure Requested") def test_loan_amount_write_off(self): pledge = [{ @@ -611,7 +611,7 @@ class TestLoan(unittest.TestCase): loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01') loan.submit() - self.assertEquals(loan.loan_amount, 1000000) + self.assertEqual(loan.loan_amount, 1000000) first_date = '2019-10-01' last_date = '2019-10-30' @@ -633,17 +633,17 @@ class TestLoan(unittest.TestCase): amount = frappe.db.get_value('Loan Interest Accrual', {'loan': loan.name}, ['sum(paid_interest_amount)']) - self.assertEquals(flt(amount, 0),flt(accrued_interest_amount, 0)) - self.assertEquals(flt(repayment_entry.penalty_amount, 5), 0) + self.assertEqual(flt(amount, 0),flt(accrued_interest_amount, 0)) + self.assertEqual(flt(repayment_entry.penalty_amount, 5), 0) amounts = calculate_amounts(loan.name, add_days(last_date, 5)) - self.assertEquals(flt(amounts['pending_principal_amount'], 0), 100) + self.assertEqual(flt(amounts['pending_principal_amount'], 0), 100) we = make_loan_write_off(loan.name, amount=amounts['pending_principal_amount']) we.submit() amounts = calculate_amounts(loan.name, add_days(last_date, 5)) - self.assertEquals(flt(amounts['pending_principal_amount'], 0), 0) + self.assertEqual(flt(amounts['pending_principal_amount'], 0), 0) def create_loan_scenario_for_penalty(doc): pledge = [{ diff --git a/erpnext/loan_management/doctype/loan_disbursement/test_loan_disbursement.py b/erpnext/loan_management/doctype/loan_disbursement/test_loan_disbursement.py index a8753877a6..da56710c67 100644 --- a/erpnext/loan_management/doctype/loan_disbursement/test_loan_disbursement.py +++ b/erpnext/loan_management/doctype/loan_disbursement/test_loan_disbursement.py @@ -87,7 +87,7 @@ class TestLoanDisbursement(unittest.TestCase): loan = create_demand_loan(self.applicant, "Demand Loan", loan_application, posting_date='2019-10-01') loan.submit() - self.assertEquals(loan.loan_amount, 1000000) + self.assertEqual(loan.loan_amount, 1000000) first_date = '2019-10-01' last_date = '2019-10-30' @@ -114,5 +114,5 @@ class TestLoanDisbursement(unittest.TestCase): per_day_interest = get_per_day_interest(1500000, 13.5, '2019-10-30') interest = per_day_interest * 15 - self.assertEquals(amounts['pending_principal_amount'], 1500000) - self.assertEquals(amounts['interest_amount'], flt(interest + previous_interest, 2)) + self.assertEqual(amounts['pending_principal_amount'], 1500000) + self.assertEqual(amounts['interest_amount'], flt(interest + previous_interest, 2)) diff --git a/erpnext/loan_management/doctype/loan_interest_accrual/test_loan_interest_accrual.py b/erpnext/loan_management/doctype/loan_interest_accrual/test_loan_interest_accrual.py index 85e008ac29..eb626f3eee 100644 --- a/erpnext/loan_management/doctype/loan_interest_accrual/test_loan_interest_accrual.py +++ b/erpnext/loan_management/doctype/loan_interest_accrual/test_loan_interest_accrual.py @@ -52,7 +52,7 @@ class TestLoanInterestAccrual(unittest.TestCase): process_loan_interest_accrual_for_demand_loans(posting_date=last_date) loan_interest_accural = frappe.get_doc("Loan Interest Accrual", {'loan': loan.name}) - self.assertEquals(flt(loan_interest_accural.interest_amount, 0), flt(accrued_interest_amount, 0)) + self.assertEqual(flt(loan_interest_accural.interest_amount, 0), flt(accrued_interest_amount, 0)) def test_accumulated_amounts(self): pledge = [{ @@ -76,7 +76,7 @@ class TestLoanInterestAccrual(unittest.TestCase): process_loan_interest_accrual_for_demand_loans(posting_date=last_date) loan_interest_accrual = frappe.get_doc("Loan Interest Accrual", {'loan': loan.name}) - self.assertEquals(flt(loan_interest_accrual.interest_amount, 0), flt(accrued_interest_amount, 0)) + self.assertEqual(flt(loan_interest_accrual.interest_amount, 0), flt(accrued_interest_amount, 0)) next_start_date = '2019-10-31' next_end_date = '2019-11-29' @@ -90,4 +90,4 @@ class TestLoanInterestAccrual(unittest.TestCase): loan_interest_accrual = frappe.get_doc("Loan Interest Accrual", {'loan': loan.name, 'process_loan_interest_accrual': process}) - self.assertEquals(flt(loan_interest_accrual.total_pending_interest_amount, 0), total_pending_interest_amount) + self.assertEqual(flt(loan_interest_accrual.total_pending_interest_amount, 0), total_pending_interest_amount) diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py index 7108338dab..e1cca9e3ef 100644 --- a/erpnext/manufacturing/doctype/bom/test_bom.py +++ b/erpnext/manufacturing/doctype/bom/test_bom.py @@ -223,7 +223,7 @@ class TestBOM(unittest.TestCase): is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC") bom_items = sorted([d.item_code for d in bom.items if d.sourced_by_supplier != 1]) supplied_items = sorted([d.rm_item_code for d in po.supplied_items]) - self.assertEquals(bom_items, supplied_items) + self.assertEqual(bom_items, supplied_items) def get_default_bom(item_code="_Test FG Item 2"): return frappe.db.get_value("BOM", {"item": item_code, "is_active": 1, "is_default": 1}) diff --git a/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.py b/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.py index ac9a409bcb..80d1cdfc8f 100644 --- a/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.py +++ b/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.py @@ -45,16 +45,16 @@ class TestBOMUpdateTool(unittest.TestCase): else: doc = frappe.get_doc("BOM", bom_no) - self.assertEquals(doc.total_cost, 200) + self.assertEqual(doc.total_cost, 200) frappe.db.set_value("Item", "BOM Cost Test Item 2", "valuation_rate", 200) update_cost() doc.load_from_db() - self.assertEquals(doc.total_cost, 300) + self.assertEqual(doc.total_cost, 300) frappe.db.set_value("Item", "BOM Cost Test Item 2", "valuation_rate", 100) update_cost() doc.load_from_db() - self.assertEquals(doc.total_cost, 200) + self.assertEqual(doc.total_cost, 200) diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index 6b1fafe5f4..cb1ee92196 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -473,7 +473,7 @@ class TestWorkOrder(unittest.TestCase): def test_cost_center_for_manufacture(self): wo_order = make_wo_order_test_record() ste = make_stock_entry(wo_order.name, "Material Transfer for Manufacture", wo_order.qty) - self.assertEquals(ste.get("items")[0].get("cost_center"), "_Test Cost Center - _TC") + self.assertEqual(ste.get("items")[0].get("cost_center"), "_Test Cost Center - _TC") def test_operation_time_with_batch_size(self): fg_item = "Test Batch Size Item For BOM" @@ -539,11 +539,11 @@ class TestWorkOrder(unittest.TestCase): ste_cancel_list.append(ste1) ste3 = frappe.get_doc(make_stock_entry(wo_order.name, "Material Consumption for Manufacture", 2)) - self.assertEquals(ste3.fg_completed_qty, 2) + self.assertEqual(ste3.fg_completed_qty, 2) expected_qty = {"_Test Item": 2, "_Test Item Home Desktop 100": 4} for row in ste3.items: - self.assertEquals(row.qty, expected_qty.get(row.item_code)) + self.assertEqual(row.qty, expected_qty.get(row.item_code)) ste_cancel_list.reverse() for ste_doc in ste_cancel_list: ste_doc.cancel() @@ -577,7 +577,7 @@ class TestWorkOrder(unittest.TestCase): ste3 = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 2)) for ste_row in ste3.items: if itemwise_qty.get(ste_row.item_code) and ste_row.s_warehouse: - self.assertEquals(ste_row.qty, itemwise_qty.get(ste_row.item_code) / 2) + self.assertEqual(ste_row.qty, itemwise_qty.get(ste_row.item_code) / 2) ste3.submit() ste_cancel_list.append(ste3) @@ -585,7 +585,7 @@ class TestWorkOrder(unittest.TestCase): ste2 = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 2)) for ste_row in ste2.items: if itemwise_qty.get(ste_row.item_code) and ste_row.s_warehouse: - self.assertEquals(ste_row.qty, itemwise_qty.get(ste_row.item_code) / 2) + self.assertEqual(ste_row.qty, itemwise_qty.get(ste_row.item_code) / 2) ste_cancel_list.reverse() for ste_doc in ste_cancel_list: ste_doc.cancel() diff --git a/erpnext/non_profit/doctype/donation/test_donation.py b/erpnext/non_profit/doctype/donation/test_donation.py index c6a534dac3..bbe9bf5228 100644 --- a/erpnext/non_profit/doctype/donation/test_donation.py +++ b/erpnext/non_profit/doctype/donation/test_donation.py @@ -39,7 +39,7 @@ class TestDonation(unittest.TestCase): donation.on_payment_authorized() donation.reload() - self.assertEquals(donation.paid, 1) + self.assertEqual(donation.paid, 1) self.assertTrue(frappe.db.exists('Payment Entry', {'reference_no': donation.name})) diff --git a/erpnext/portal/doctype/homepage/test_homepage.py b/erpnext/portal/doctype/homepage/test_homepage.py index bf5c4025a0..b717491a82 100644 --- a/erpnext/portal/doctype/homepage/test_homepage.py +++ b/erpnext/portal/doctype/homepage/test_homepage.py @@ -13,7 +13,7 @@ class TestHomepage(unittest.TestCase): set_request(method='GET', path='home') response = render() - self.assertEquals(response.status_code, 200) + self.assertEqual(response.status_code, 200) html = frappe.safe_decode(response.get_data()) self.assertTrue('
    Date: Fri, 7 May 2021 20:30:04 +0530 Subject: [PATCH 229/326] feat!: add pick batch button (#25413) * feat!: add pick batch button BREAKING CHANGE: replaces setup_serial_no with setup_serial_or_batch_no. * refactor: use setup_serial_or_batch_no instead of setup_serial_no * refactor: use setup_serial_or_batch_no instead of setup_serial_no * refactor: use setup_serial_or_batch_no instead of setup_serial_no * style: add sider review changes * refactor: make consice, extract function * refactor: camel to snake casing --- .../doctype/sales_invoice/sales_invoice.js | 4 +- erpnext/public/js/utils.js | 48 ++++++++++--------- .../doctype/delivery_note/delivery_note.js | 4 +- .../stock/doctype/stock_entry/stock_entry.js | 2 +- 4 files changed, 31 insertions(+), 27 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index 8a42d9e13c..7c73ad6c90 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -356,11 +356,11 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte }, items_on_form_rendered: function() { - erpnext.setup_serial_no(); + erpnext.setup_serial_or_batch_no(); }, packed_items_on_form_rendered: function(doc, grid_row) { - erpnext.setup_serial_no(); + erpnext.setup_serial_or_batch_no(); }, make_sales_return: function() { diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index 19c9073090..472746ab84 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -48,31 +48,24 @@ $.extend(erpnext, { return cint(frappe.boot.sysdefaults.allow_stale); }, - setup_serial_no: function() { - var grid_row = cur_frm.open_grid_row(); - if(!grid_row || !grid_row.grid_form.fields_dict.serial_no || - grid_row.grid_form.fields_dict.serial_no.get_status()!=="Write") return; + setup_serial_or_batch_no: function() { + let grid_row = cur_frm.open_grid_row(); + if (!grid_row || !grid_row.grid_form.fields_dict.serial_no || + grid_row.grid_form.fields_dict.serial_no.get_status() !== "Write") return; - var $btn = $('') - .appendTo($("
    ") - .css({"margin-bottom": "10px", "margin-top": "10px"}) - .appendTo(grid_row.grid_form.fields_dict.serial_no.$wrapper)); + frappe.model.get_value('Item', {'name': grid_row.doc.item_code}, + ['has_serial_no', 'has_batch_no'], ({has_serial_no, has_batch_no}) => { + Object.assign(grid_row.doc, {has_serial_no, has_batch_no}); - var me = this; - $btn.on("click", function() { - let callback = ''; - let on_close = ''; - - frappe.model.get_value('Item', {'name':grid_row.doc.item_code}, 'has_serial_no', - (data) => { - if(data) { - grid_row.doc.has_serial_no = data.has_serial_no; - me.show_serial_batch_selector(grid_row.frm, grid_row.doc, - callback, on_close, true); - } + if (has_serial_no) { + attach_selector_button(__("Add Serial No"), + grid_row.grid_form.fields_dict.serial_no.$wrapper, this, grid_row); + } else if (has_batch_no) { + attach_selector_button(__("Pick Batch No"), + grid_row.grid_form.fields_dict.batch_no.$wrapper, this, grid_row); } - ); - }); + } + ); }, route_to_adjustment_jv: (args) => { @@ -743,3 +736,14 @@ $(document).on('app_ready', function() { }); } }); + +function attach_selector_button(inner_text, append_loction, context, grid_row) { + let $btn_div = $("
    ").css({"margin-bottom": "10px", "margin-top": "10px"}) + .appendTo(append_loction); + let $btn = $(``) + .appendTo($btn_div); + + $btn.on("click", function() { + context.show_serial_batch_selector(grid_row.frm, grid_row.doc, "", "", true); + }); +} diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.js b/erpnext/stock/doctype/delivery_note/delivery_note.js index 334bdeac9d..7875b9cd87 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.js +++ b/erpnext/stock/doctype/delivery_note/delivery_note.js @@ -273,11 +273,11 @@ erpnext.stock.DeliveryNoteController = erpnext.selling.SellingController.extend( }, items_on_form_rendered: function(doc, grid_row) { - erpnext.setup_serial_no(); + erpnext.setup_serial_or_batch_no(); }, packed_items_on_form_rendered: function(doc, grid_row) { - erpnext.setup_serial_no(); + erpnext.setup_serial_or_batch_no(); }, close_delivery_note: function(doc){ diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index ef7d54ac96..772c8df96e 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -996,7 +996,7 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({ }, items_on_form_rendered: function(doc, grid_row) { - erpnext.setup_serial_no(); + erpnext.setup_serial_or_batch_no(); }, toggle_related_fields: function(doc) { From aa9e1720913527aef4193a053606d5cf38a4f1cf Mon Sep 17 00:00:00 2001 From: Ganga Manoj Date: Sat, 8 May 2021 17:15:33 +0530 Subject: [PATCH 230/326] feat: Add Create Expense Claim button in Delivery Trip (#25526) * feat(Delivery Trip): Add employee_code field * feat(Expense Claim): Add Delivery Trip Number field * feat(Delivery Trip): Add Create Expense Claim button * feat(Delivery Trip): Make Create Expense Claim button show up after save * fix(Delivery Trip): Fix Sider issues * fix(Delivery Trip): Display button after submit * fix(Delivery Trip & Expense Claim): Rename new fields * fix(Delivery Trip): Add button in refresh * fix(Delivery Trip): Remove redundant line * fix(Expense Claim): Display delivery_trip only if non-empty * fix(Delivery Trip): Add test for Create Expense Claim * fix(Delivery Trip): Fix Sider Issue * fix(Delivery Trip): Only display Create Expense Claim if the driver is an employee * fix(Delivery Trip): Fix test * fix(Delivery Trip): Fix make_expense_claim() * fix: sider Co-authored-by: Saqib --- erpnext/hr/doctype/expense_claim/expense_claim.json | 10 +++++++++- .../stock/doctype/delivery_trip/delivery_trip.js | 9 +++++++++ .../stock/doctype/delivery_trip/delivery_trip.json | 11 ++++++++++- .../stock/doctype/delivery_trip/delivery_trip.py | 13 +++++++++++++ .../doctype/delivery_trip/test_delivery_trip.py | 6 +++++- 5 files changed, 46 insertions(+), 3 deletions(-) diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.json b/erpnext/hr/doctype/expense_claim/expense_claim.json index e3e6e80616..a268c15c70 100644 --- a/erpnext/hr/doctype/expense_claim/expense_claim.json +++ b/erpnext/hr/doctype/expense_claim/expense_claim.json @@ -14,6 +14,7 @@ "column_break_5", "expense_approver", "approval_status", + "delivery_trip", "is_paid", "expense_details", "expenses", @@ -365,13 +366,20 @@ "label": "Total Taxes and Charges", "options": "Company:company:default_currency", "read_only": 1 + }, + { + "depends_on": "eval: doc.delivery_trip", + "fieldname": "delivery_trip", + "fieldtype": "Link", + "label": "Delivery Trip", + "options": "Delivery Trip" } ], "icon": "fa fa-money", "idx": 1, "is_submittable": 1, "links": [], - "modified": "2020-09-18 17:26:09.703215", + "modified": "2021-05-04 05:35:12.040199", "modified_by": "Administrator", "module": "HR", "name": "Expense Claim", diff --git a/erpnext/stock/doctype/delivery_trip/delivery_trip.js b/erpnext/stock/doctype/delivery_trip/delivery_trip.js index a6fbb66aa2..68cba2993c 100755 --- a/erpnext/stock/doctype/delivery_trip/delivery_trip.js +++ b/erpnext/stock/doctype/delivery_trip/delivery_trip.js @@ -41,6 +41,15 @@ frappe.ui.form.on('Delivery Trip', { }, refresh: function (frm) { + if (frm.doc.docstatus == 1 && frm.doc.employee) { + frm.add_custom_button(__('Expense Claim'), function() { + frappe.model.open_mapped_doc({ + method: 'erpnext.stock.doctype.delivery_trip.delivery_trip.make_expense_claim', + frm: cur_frm, + }); + }, __("Create")); + } + if (frm.doc.docstatus == 1 && frm.doc.delivery_stops.length > 0) { frm.add_custom_button(__("Notify Customers via Email"), function () { frm.trigger('notify_customers'); diff --git a/erpnext/stock/doctype/delivery_trip/delivery_trip.json b/erpnext/stock/doctype/delivery_trip/delivery_trip.json index 879901f6a8..11b71c2076 100644 --- a/erpnext/stock/doctype/delivery_trip/delivery_trip.json +++ b/erpnext/stock/doctype/delivery_trip/delivery_trip.json @@ -21,6 +21,7 @@ "column_break_4", "vehicle", "departure_time", + "employee", "delivery_service_stops", "delivery_stops", "calculate_arrival_time", @@ -176,11 +177,19 @@ "fieldtype": "Data", "label": "Driver Email", "read_only": 1 + }, + { + "fetch_from": "driver.employee", + "fieldname": "employee", + "fieldtype": "Link", + "label": "Employee", + "options": "Employee", + "read_only": 1 } ], "is_submittable": 1, "links": [], - "modified": "2020-01-26 22:37:14.824021", + "modified": "2021-04-30 21:21:36.610142", "modified_by": "Administrator", "module": "Stock", "name": "Delivery Trip", diff --git a/erpnext/stock/doctype/delivery_trip/delivery_trip.py b/erpnext/stock/doctype/delivery_trip/delivery_trip.py index de85bc3922..81e730126e 100644 --- a/erpnext/stock/doctype/delivery_trip/delivery_trip.py +++ b/erpnext/stock/doctype/delivery_trip/delivery_trip.py @@ -11,6 +11,7 @@ from frappe import _ from frappe.contacts.doctype.address.address import get_address_display from frappe.model.document import Document from frappe.utils import cint, get_datetime, get_link_to_form +from frappe.model.mapper import get_mapped_doc class DeliveryTrip(Document): @@ -394,3 +395,15 @@ def get_driver_email(driver): employee = frappe.db.get_value("Driver", driver, "employee") email = frappe.db.get_value("Employee", employee, "prefered_email") return {"email": email} + +@frappe.whitelist() +def make_expense_claim(source_name, target_doc=None): + doc = get_mapped_doc("Delivery Trip", source_name, + {"Delivery Trip": { + "doctype": "Expense Claim", + "field_map": { + "name" : "delivery_trip" + } + }}, target_doc) + + return doc \ No newline at end of file diff --git a/erpnext/stock/doctype/delivery_trip/test_delivery_trip.py b/erpnext/stock/doctype/delivery_trip/test_delivery_trip.py index eeea6da7a4..1e71603175 100644 --- a/erpnext/stock/doctype/delivery_trip/test_delivery_trip.py +++ b/erpnext/stock/doctype/delivery_trip/test_delivery_trip.py @@ -7,7 +7,7 @@ import unittest import erpnext import frappe -from erpnext.stock.doctype.delivery_trip.delivery_trip import get_contact_and_address, notify_customers +from erpnext.stock.doctype.delivery_trip.delivery_trip import get_contact_and_address, notify_customers, make_expense_claim from erpnext.tests.utils import create_test_contact_and_address from frappe.utils import add_days, flt, now_datetime, nowdate @@ -28,6 +28,10 @@ class TestDeliveryTrip(unittest.TestCase): frappe.db.sql("delete from `tabEmail Template`") frappe.db.sql("delete from `tabDelivery Trip`") + def test_expense_claim_fields_are_fetched_properly(self): + expense_claim = make_expense_claim(self.delivery_trip.name) + self.assertEqual(self.delivery_trip.name, expense_claim.delivery_trip) + def test_delivery_trip_notify_customers(self): notify_customers(delivery_trip=self.delivery_trip.name) self.delivery_trip.load_from_db() From 77154418422074e9c125d6f5505b7891746d7efe Mon Sep 17 00:00:00 2001 From: noahjacob Date: Sun, 9 May 2021 20:02:23 +0530 Subject: [PATCH 231/326] feat: added supplier item group doctype --- .../doctype/supplier_item_group/__init__.py | 0 .../supplier_item_group.js | 8 +++ .../supplier_item_group.json | 51 +++++++++++++++++++ .../supplier_item_group.py | 10 ++++ .../test_supplier_item_group.py | 10 ++++ 5 files changed, 79 insertions(+) create mode 100644 erpnext/buying/doctype/supplier_item_group/__init__.py create mode 100644 erpnext/buying/doctype/supplier_item_group/supplier_item_group.js create mode 100644 erpnext/buying/doctype/supplier_item_group/supplier_item_group.json create mode 100644 erpnext/buying/doctype/supplier_item_group/supplier_item_group.py create mode 100644 erpnext/buying/doctype/supplier_item_group/test_supplier_item_group.py diff --git a/erpnext/buying/doctype/supplier_item_group/__init__.py b/erpnext/buying/doctype/supplier_item_group/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/buying/doctype/supplier_item_group/supplier_item_group.js b/erpnext/buying/doctype/supplier_item_group/supplier_item_group.js new file mode 100644 index 0000000000..f7da90d98d --- /dev/null +++ b/erpnext/buying/doctype/supplier_item_group/supplier_item_group.js @@ -0,0 +1,8 @@ +// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Supplier Item Group', { + // refresh: function(frm) { + + // } +}); diff --git a/erpnext/buying/doctype/supplier_item_group/supplier_item_group.json b/erpnext/buying/doctype/supplier_item_group/supplier_item_group.json new file mode 100644 index 0000000000..1417ec23cf --- /dev/null +++ b/erpnext/buying/doctype/supplier_item_group/supplier_item_group.json @@ -0,0 +1,51 @@ +{ + "actions": [], + "creation": "2021-05-07 18:16:40.621421", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "supplier", + "item_group" + ], + "fields": [ + { + "fieldname": "supplier", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Supplier", + "options": "Supplier" + }, + { + "fieldname": "item_group", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Item Group", + "options": "Item Group" + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2021-05-07 18:16:40.621421", + "modified_by": "Administrator", + "module": "Buying", + "name": "Supplier Item Group", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/buying/doctype/supplier_item_group/supplier_item_group.py b/erpnext/buying/doctype/supplier_item_group/supplier_item_group.py new file mode 100644 index 0000000000..6fbeb37242 --- /dev/null +++ b/erpnext/buying/doctype/supplier_item_group/supplier_item_group.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, 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 SupplierItemGroup(Document): + pass diff --git a/erpnext/buying/doctype/supplier_item_group/test_supplier_item_group.py b/erpnext/buying/doctype/supplier_item_group/test_supplier_item_group.py new file mode 100644 index 0000000000..c75044d44e --- /dev/null +++ b/erpnext/buying/doctype/supplier_item_group/test_supplier_item_group.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestSupplierItemGroup(unittest.TestCase): + pass From 9226cd3932e3a087c7e474c43b7b8d0535221c3c Mon Sep 17 00:00:00 2001 From: Saqib Date: Mon, 10 May 2021 12:36:56 +0530 Subject: [PATCH 232/326] feat(india): reduced rate of depreciation as per IT Act (#25648) * feat(india): reduced rate of depreciation as per IT Act * refactor: check date difference instead of month difference * feat: add test for regional feature --- erpnext/assets/doctype/asset/asset.py | 51 ++++++++++------------ erpnext/assets/doctype/asset/test_asset.py | 39 +++++++++++++++++ erpnext/hooks.py | 3 +- erpnext/regional/india/utils.py | 21 +++++++++ 4 files changed, 84 insertions(+), 30 deletions(-) diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 9aff1440d6..8799275fc4 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -195,8 +195,7 @@ class Asset(AccountsController): # If depreciation is already completed (for double declining balance) if skip_row: continue - depreciation_amount = self.get_depreciation_amount(value_after_depreciation, - d.total_number_of_depreciations, d) + depreciation_amount = get_depreciation_amount(self, value_after_depreciation, d) if not has_pro_rata or n < cint(number_of_pending_depreciations) - 1: schedule_date = add_months(d.depreciation_start_date, @@ -208,7 +207,7 @@ class Asset(AccountsController): # For first row if has_pro_rata and n==0: - depreciation_amount, days, months = get_pro_rata_amt(d, depreciation_amount, + depreciation_amount, days, months = self.get_pro_rata_amt(d, depreciation_amount, self.available_for_use_date, d.depreciation_start_date) # For first depr schedule date will be the start date @@ -220,7 +219,7 @@ class Asset(AccountsController): to_date = add_months(self.available_for_use_date, n * cint(d.frequency_of_depreciation)) - depreciation_amount, days, months = get_pro_rata_amt(d, + depreciation_amount, days, months = self.get_pro_rata_amt(d, depreciation_amount, schedule_date, to_date) monthly_schedule_date = add_months(schedule_date, 1) @@ -365,24 +364,6 @@ class Asset(AccountsController): def get_value_after_depreciation(self, idx): return flt(self.get('finance_books')[cint(idx)-1].value_after_depreciation) - def get_depreciation_amount(self, depreciable_value, total_number_of_depreciations, row): - precision = self.precision("gross_purchase_amount") - - if row.depreciation_method in ("Straight Line", "Manual"): - depreciation_left = (cint(row.total_number_of_depreciations) - cint(self.number_of_depreciations_booked)) - - if not depreciation_left: - frappe.msgprint(_("All the depreciations has been booked")) - depreciation_amount = flt(row.expected_value_after_useful_life) - return depreciation_amount - - depreciation_amount = (flt(row.value_after_depreciation) - - flt(row.expected_value_after_useful_life)) / depreciation_left - else: - depreciation_amount = flt(depreciable_value * (flt(row.rate_of_depreciation) / 100), precision) - - return depreciation_amount - def validate_expected_value_after_useful_life(self): for row in self.get('finance_books'): accumulated_depreciation_after_full_schedule = [d.accumulated_depreciation_amount @@ -575,6 +556,13 @@ class Asset(AccountsController): return 100 * (1 - flt(depreciation_rate, float_precision)) + def get_pro_rata_amt(self, row, depreciation_amount, from_date, to_date): + days = date_diff(to_date, from_date) + months = month_diff(to_date, from_date) + total_days = get_total_days(to_date, row.frequency_of_depreciation) + + return (depreciation_amount * flt(days)) / flt(total_days), days, months + def update_maintenance_status(): assets = frappe.get_all( "Asset", filters={"docstatus": 1, "maintenance_required": 1} @@ -758,15 +746,20 @@ def make_asset_movement(assets, purpose=None): def is_cwip_accounting_enabled(asset_category): return cint(frappe.db.get_value("Asset Category", asset_category, "enable_cwip_accounting")) -def get_pro_rata_amt(row, depreciation_amount, from_date, to_date): - days = date_diff(to_date, from_date) - months = month_diff(to_date, from_date) - total_days = get_total_days(to_date, row.frequency_of_depreciation) - - return (depreciation_amount * flt(days)) / flt(total_days), days, months - def get_total_days(date, frequency): period_start_date = add_months(date, cint(frequency) * -1) return date_diff(date, period_start_date) + +@erpnext.allow_regional +def get_depreciation_amount(asset, depreciable_value, row): + depreciation_left = flt(row.total_number_of_depreciations) - flt(asset.number_of_depreciations_booked) + + if row.depreciation_method in ("Straight Line", "Manual"): + depreciation_amount = (flt(row.value_after_depreciation) - + flt(row.expected_value_after_useful_life)) / depreciation_left + else: + depreciation_amount = flt(depreciable_value * (flt(row.rate_of_depreciation) / 100)) + + return depreciation_amount \ No newline at end of file diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 40a8f85d8d..30a270c204 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -635,6 +635,45 @@ class TestAsset(unittest.TestCase): frappe.db.set_value("Asset Category Account", name, "capital_work_in_progress_account", cwip_acc) frappe.db.get_value("Company", "_Test Company", "capital_work_in_progress_account", cwip_acc) + def test_discounted_wdv_depreciation_rate_for_indian_region(self): + # set indian company + company_flag = frappe.flags.company + frappe.flags.company = "_Test Company" + + pr = make_purchase_receipt(item_code="Macbook Pro", + qty=1, rate=8000.0, location="Test Location") + + asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') + asset = frappe.get_doc('Asset', asset_name) + asset.calculate_depreciation = 1 + asset.available_for_use_date = '2030-06-12' + asset.purchase_date = '2030-01-01' + asset.append("finance_books", { + "expected_value_after_useful_life": 1000, + "depreciation_method": "Written Down Value", + "total_number_of_depreciations": 3, + "frequency_of_depreciation": 12, + "depreciation_start_date": "2030-12-31" + }) + asset.save(ignore_permissions=True) + + self.assertEqual(asset.finance_books[0].rate_of_depreciation, 50.0) + + expected_schedules = [ + ["2030-12-31", 1106.85, 1106.85], + ["2031-12-31", 3446.58, 4553.43], + ["2032-12-31", 1723.29, 6276.72], + ["2033-06-12", 723.28, 7000.00] + ] + + schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)] + for d in asset.get("schedules")] + + self.assertEqual(schedules, expected_schedules) + + # reset indian company + frappe.flags.company = company_flag + def create_asset_data(): if not frappe.db.exists("Asset Category", "Computers"): create_asset_category() diff --git a/erpnext/hooks.py b/erpnext/hooks.py index bb6cd8bdc2..9d1ce9bbbf 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -426,7 +426,8 @@ regional_overrides = { 'erpnext.hr.utils.calculate_annual_eligible_hra_exemption': 'erpnext.regional.india.utils.calculate_annual_eligible_hra_exemption', 'erpnext.hr.utils.calculate_hra_exemption_for_period': 'erpnext.regional.india.utils.calculate_hra_exemption_for_period', 'erpnext.accounts.doctype.purchase_invoice.purchase_invoice.make_regional_gl_entries': 'erpnext.regional.india.utils.make_regional_gl_entries', - 'erpnext.controllers.accounts_controller.validate_einvoice_fields': 'erpnext.regional.india.e_invoice.utils.validate_einvoice_fields' + 'erpnext.controllers.accounts_controller.validate_einvoice_fields': 'erpnext.regional.india.e_invoice.utils.validate_einvoice_fields', + 'erpnext.assets.doctype.asset.asset.get_depreciation_amount': 'erpnext.regional.india.utils.get_depreciation_amount' }, 'United Arab Emirates': { 'erpnext.controllers.taxes_and_totals.update_itemised_tax_data': 'erpnext.regional.united_arab_emirates.utils.update_itemised_tax_data', diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index 6338056698..052d7bdedf 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -879,3 +879,24 @@ def update_taxable_values(doc, method): if total_charges != additional_taxes: diff = additional_taxes - total_charges doc.get('items')[item_count - 1].taxable_value += diff + +def get_depreciation_amount(asset, depreciable_value, row): + depreciation_left = flt(row.total_number_of_depreciations) - flt(asset.number_of_depreciations_booked) + + if row.depreciation_method in ("Straight Line", "Manual"): + depreciation_amount = (flt(row.value_after_depreciation) - + flt(row.expected_value_after_useful_life)) / depreciation_left + else: + rate_of_depreciation = row.rate_of_depreciation + # if its the first depreciation + if depreciable_value == asset.gross_purchase_amount: + # as per IT act, if the asset is purchased in the 2nd half of fiscal year, then rate is divided by 2 + diff = date_diff(asset.available_for_use_date, row.depreciation_start_date) + if diff <= 180: + rate_of_depreciation = rate_of_depreciation / 2 + frappe.msgprint( + _('As per IT Act, the rate of depreciation for the first depreciation entry is reduced by 50%.')) + + depreciation_amount = flt(depreciable_value * (flt(rate_of_depreciation) / 100)) + + return depreciation_amount \ No newline at end of file From 6e179c3092c5f31f43ed61610a654e8d61487993 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 10 May 2021 13:24:26 +0530 Subject: [PATCH 233/326] fix: sync shopify customer addresses (#25481) --- .../doctype/shopify_settings/sync_customer.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/sync_customer.py b/erpnext/erpnext_integrations/doctype/shopify_settings/sync_customer.py index 7866fdea31..2af57f4c89 100644 --- a/erpnext/erpnext_integrations/doctype/shopify_settings/sync_customer.py +++ b/erpnext/erpnext_integrations/doctype/shopify_settings/sync_customer.py @@ -32,10 +32,12 @@ def create_customer(shopify_customer, shopify_settings): raise e def create_customer_address(customer, shopify_customer): - if not shopify_customer.get("addresses"): - return + addresses = shopify_customer.get("addresses", []) - for i, address in enumerate(shopify_customer.get("addresses")): + if not addresses and "default_address" in shopify_customer: + addresses.append(shopify_customer["default_address"]) + + for i, address in enumerate(addresses): address_title, address_type = get_address_title_and_type(customer.customer_name, i) try : frappe.get_doc({ From f2eb8dd1d5b6a156f2d6df9a4eb4d41ca497738b Mon Sep 17 00:00:00 2001 From: Ganga Manoj Date: Mon, 10 May 2021 14:02:58 +0530 Subject: [PATCH 234/326] feat: Transaction Deletion Record (#25354) Co-authored-by: Saqib --- erpnext/controllers/status_updater.py | 4 + erpnext/setup/doctype/company/company.js | 4 +- erpnext/setup/doctype/company/company.py | 11 +- .../company/delete_company_transactions.py | 117 -------------- erpnext/setup/doctype/company/test_company.py | 9 -- .../transaction_deletion_record/__init__.py | 0 .../test_transaction_deletion_record.py | 68 ++++++++ .../transaction_deletion_record.js | 40 +++++ .../transaction_deletion_record.json | 79 ++++++++++ .../transaction_deletion_record.py | 147 ++++++++++++++++++ .../transaction_deletion_record_list.js | 12 ++ .../__init__.py | 0 .../transaction_deletion_record_item.json | 39 +++++ .../transaction_deletion_record_item.py | 10 ++ 14 files changed, 411 insertions(+), 129 deletions(-) delete mode 100644 erpnext/setup/doctype/company/delete_company_transactions.py create mode 100644 erpnext/setup/doctype/transaction_deletion_record/__init__.py create mode 100644 erpnext/setup/doctype/transaction_deletion_record/test_transaction_deletion_record.py create mode 100644 erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.js create mode 100644 erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json create mode 100644 erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py create mode 100644 erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record_list.js create mode 100644 erpnext/setup/doctype/transaction_deletion_record_item/__init__.py create mode 100644 erpnext/setup/doctype/transaction_deletion_record_item/transaction_deletion_record_item.json create mode 100644 erpnext/setup/doctype/transaction_deletion_record_item/transaction_deletion_record_item.py diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py index 4bb6138e5d..ed3aee5c1a 100644 --- a/erpnext/controllers/status_updater.py +++ b/erpnext/controllers/status_updater.py @@ -100,6 +100,10 @@ status_map = { ["Queued", "eval:self.status == 'Queued'"], ["Failed", "eval:self.status == 'Failed'"], ["Cancelled", "eval:self.docstatus == 2"], + ], + "Transaction Deletion Record": [ + ["Draft", None], + ["Completed", "eval:self.docstatus == 1"], ] } diff --git a/erpnext/setup/doctype/company/company.js b/erpnext/setup/doctype/company/company.js index c2b5e4f9a9..9957aad019 100644 --- a/erpnext/setup/doctype/company/company.js +++ b/erpnext/setup/doctype/company/company.js @@ -169,9 +169,9 @@ frappe.ui.form.on("Company", { return; } frappe.call({ - method: "erpnext.setup.doctype.company.delete_company_transactions.delete_company_transactions", + method: "erpnext.setup.doctype.company.company.create_transaction_deletion_request", args: { - company_name: data.company_name + company: data.company_name }, freeze: true, callback: function(r, rt) { diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py index 64e027dd28..077538d479 100644 --- a/erpnext/setup/doctype/company/company.py +++ b/erpnext/setup/doctype/company/company.py @@ -613,4 +613,13 @@ def get_default_company_address(name, sort_key='is_primary_address', existing_ad if out: return sorted(out, key = functools.cmp_to_key(lambda x,y: cmp(y[1], x[1])))[0][0] else: - return None \ No newline at end of file + return None + +@frappe.whitelist() +def create_transaction_deletion_request(company): + tdr = frappe.get_doc({ + 'doctype': 'Transaction Deletion Record', + 'company': company + }) + tdr.insert() + tdr.submit() diff --git a/erpnext/setup/doctype/company/delete_company_transactions.py b/erpnext/setup/doctype/company/delete_company_transactions.py deleted file mode 100644 index 8367a257ea..0000000000 --- a/erpnext/setup/doctype/company/delete_company_transactions.py +++ /dev/null @@ -1,117 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe - -from frappe.utils import cint -from frappe import _ -from frappe.desk.notifications import clear_notifications - -import functools - -@frappe.whitelist() -def delete_company_transactions(company_name): - frappe.only_for("System Manager") - doc = frappe.get_doc("Company", company_name) - - if frappe.session.user != doc.owner and frappe.session.user != 'Administrator': - frappe.throw(_("Transactions can only be deleted by the creator of the Company"), - frappe.PermissionError) - - delete_bins(company_name) - delete_lead_addresses(company_name) - - for doctype in frappe.db.sql_list("""select parent from - tabDocField where fieldtype='Link' and options='Company'"""): - if doctype not in ("Account", "Cost Center", "Warehouse", "Budget", - "Party Account", "Employee", "Sales Taxes and Charges Template", - "Purchase Taxes and Charges Template", "POS Profile", "BOM", - "Company", "Bank Account", "Item Tax Template", "Mode Of Payment", "Mode of Payment Account", - "Item Default", "Customer", "Supplier", "GST Account"): - delete_for_doctype(doctype, company_name) - - # reset company values - doc.total_monthly_sales = 0 - doc.sales_monthly_history = None - doc.save() - # Clear notification counts - clear_notifications() - -def delete_for_doctype(doctype, company_name): - meta = frappe.get_meta(doctype) - company_fieldname = meta.get("fields", {"fieldtype": "Link", - "options": "Company"})[0].fieldname - - if not meta.issingle: - if not meta.istable: - # delete communication - delete_communications(doctype, company_name, company_fieldname) - - # delete children - for df in meta.get_table_fields(): - frappe.db.sql("""delete from `tab{0}` where parent in - (select name from `tab{1}` where `{2}`=%s)""".format(df.options, - doctype, company_fieldname), company_name) - - #delete version log - frappe.db.sql("""delete from `tabVersion` where ref_doctype=%s and docname in - (select name from `tab{0}` where `{1}`=%s)""".format(doctype, - company_fieldname), (doctype, company_name)) - - # delete parent - frappe.db.sql("""delete from `tab{0}` - where {1}= %s """.format(doctype, company_fieldname), company_name) - - # reset series - naming_series = meta.get_field("naming_series") - if naming_series and naming_series.options: - prefixes = sorted(naming_series.options.split("\n"), - key=functools.cmp_to_key(lambda a, b: len(b) - len(a))) - - for prefix in prefixes: - if prefix: - last = frappe.db.sql("""select max(name) from `tab{0}` - where name like %s""".format(doctype), prefix + "%") - if last and last[0][0]: - last = cint(last[0][0].replace(prefix, "")) - else: - last = 0 - - frappe.db.sql("""update tabSeries set current = %s - where name=%s""", (last, prefix)) - -def delete_bins(company_name): - frappe.db.sql("""delete from tabBin where warehouse in - (select name from tabWarehouse where company=%s)""", company_name) - -def delete_lead_addresses(company_name): - """Delete addresses to which leads are linked""" - leads = frappe.get_all("Lead", filters={"company": company_name}) - leads = [ "'%s'"%row.get("name") for row in leads ] - addresses = [] - if leads: - addresses = frappe.db.sql_list("""select parent from `tabDynamic Link` where link_name - in ({leads})""".format(leads=",".join(leads))) - - if addresses: - addresses = ["%s" % frappe.db.escape(addr) for addr in addresses] - - frappe.db.sql("""delete from tabAddress where name in ({addresses}) and - name not in (select distinct dl1.parent from `tabDynamic Link` dl1 - inner join `tabDynamic Link` dl2 on dl1.parent=dl2.parent - and dl1.link_doctype<>dl2.link_doctype)""".format(addresses=",".join(addresses))) - - frappe.db.sql("""delete from `tabDynamic Link` where link_doctype='Lead' - and parenttype='Address' and link_name in ({leads})""".format(leads=",".join(leads))) - - frappe.db.sql("""update tabCustomer set lead_name=NULL where lead_name in ({leads})""".format(leads=",".join(leads))) - -def delete_communications(doctype, company_name, company_fieldname): - reference_docs = frappe.get_all(doctype, filters={company_fieldname:company_name}) - reference_doc_names = [r.name for r in reference_docs] - - communications = frappe.get_all("Communication", filters={"reference_doctype":doctype,"reference_name":["in", reference_doc_names]}) - communication_names = [c.name for c in communications] - - frappe.delete_doc("Communication", communication_names, ignore_permissions=True) diff --git a/erpnext/setup/doctype/company/test_company.py b/erpnext/setup/doctype/company/test_company.py index 29f6c3731d..e1c803a038 100644 --- a/erpnext/setup/doctype/company/test_company.py +++ b/erpnext/setup/doctype/company/test_company.py @@ -86,15 +86,6 @@ class TestCompany(unittest.TestCase): self.delete_mode_of_payment(template) frappe.delete_doc("Company", template) - def test_delete_communication(self): - from erpnext.setup.doctype.company.delete_company_transactions import delete_communications - company = create_child_company() - lead = create_test_lead_in_company(company) - communication = create_company_communication("Lead", lead) - delete_communications("Lead", "Test Company", "company") - self.assertFalse(frappe.db.exists("Communcation", communication)) - self.assertFalse(frappe.db.exists({"doctype":"Comunication Link", "link_name": communication})) - def delete_mode_of_payment(self, company): frappe.db.sql(""" delete from `tabMode of Payment Account` where company =%s """, (company)) diff --git a/erpnext/setup/doctype/transaction_deletion_record/__init__.py b/erpnext/setup/doctype/transaction_deletion_record/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/setup/doctype/transaction_deletion_record/test_transaction_deletion_record.py b/erpnext/setup/doctype/transaction_deletion_record/test_transaction_deletion_record.py new file mode 100644 index 0000000000..bbe68369ff --- /dev/null +++ b/erpnext/setup/doctype/transaction_deletion_record/test_transaction_deletion_record.py @@ -0,0 +1,68 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +import frappe +import unittest + +class TestTransactionDeletionRecord(unittest.TestCase): + def setUp(self): + create_company('Dunder Mifflin Paper Co') + + def tearDown(self): + frappe.db.rollback() + + def test_doctypes_contain_company_field(self): + tdr = create_transaction_deletion_request('Dunder Mifflin Paper Co') + for doctype in tdr.doctypes: + contains_company = False + doctype_fields = frappe.get_meta(doctype.doctype_name).as_dict()['fields'] + for doctype_field in doctype_fields: + if doctype_field['fieldtype'] == 'Link' and doctype_field['options'] == 'Company': + contains_company = True + break + self.assertTrue(contains_company) + + def test_no_of_docs_is_correct(self): + for i in range(5): + create_task('Dunder Mifflin Paper Co') + tdr = create_transaction_deletion_request('Dunder Mifflin Paper Co') + for doctype in tdr.doctypes: + if doctype.doctype_name == 'Task': + self.assertEqual(doctype.no_of_docs, 5) + + def test_deletion_is_successful(self): + create_task('Dunder Mifflin Paper Co') + create_transaction_deletion_request('Dunder Mifflin Paper Co') + tasks_containing_company = frappe.get_all('Task', + filters = { + 'company' : 'Dunder Mifflin Paper Co' + }) + self.assertEqual(tasks_containing_company, []) + +def create_company(company_name): + company = frappe.get_doc({ + 'doctype': 'Company', + 'company_name': company_name, + 'default_currency': 'INR' + }) + company.insert(ignore_if_duplicate = True) + +def create_transaction_deletion_request(company): + tdr = frappe.get_doc({ + 'doctype': 'Transaction Deletion Record', + 'company': company + }) + tdr.insert() + tdr.submit() + return tdr + + +def create_task(company): + task = frappe.get_doc({ + 'doctype': 'Task', + 'company': company, + 'subject': 'Delete' + }) + task.insert() diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.js b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.js new file mode 100644 index 0000000000..20caa15ee4 --- /dev/null +++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.js @@ -0,0 +1,40 @@ +// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Transaction Deletion Record', { + onload: function(frm) { + if (frm.doc.docstatus == 0) { + let doctypes_to_be_ignored_array; + frappe.call({ + method: 'erpnext.setup.doctype.transaction_deletion_record.transaction_deletion_record.get_doctypes_to_be_ignored', + callback: function(r) { + doctypes_to_be_ignored_array = r.message; + populate_doctypes_to_be_ignored(doctypes_to_be_ignored_array, frm); + frm.fields_dict['doctypes_to_be_ignored'].grid.set_column_disp('no_of_docs', false); + frm.refresh_field('doctypes_to_be_ignored'); + } + }); + } + + frm.get_field('doctypes_to_be_ignored').grid.cannot_add_rows = true; + frm.fields_dict['doctypes_to_be_ignored'].grid.set_column_disp('no_of_docs', false); + frm.refresh_field('doctypes_to_be_ignored'); + }, + + refresh: function(frm) { + frm.fields_dict['doctypes_to_be_ignored'].grid.set_column_disp('no_of_docs', false); + frm.refresh_field('doctypes_to_be_ignored'); + } + +}); + +function populate_doctypes_to_be_ignored(doctypes_to_be_ignored_array, frm) { + if (!(frm.doc.doctypes_to_be_ignored)) { + var i; + for (i = 0; i < doctypes_to_be_ignored_array.length; i++) { + frm.add_child('doctypes_to_be_ignored', { + doctype_name: doctypes_to_be_ignored_array[i] + }); + } + } +} diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json new file mode 100644 index 0000000000..9313f95516 --- /dev/null +++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json @@ -0,0 +1,79 @@ +{ + "actions": [], + "autoname": "TDL.####", + "creation": "2021-04-06 20:17:18.404716", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "company", + "doctypes", + "doctypes_to_be_ignored", + "amended_from", + "status" + ], + "fields": [ + { + "fieldname": "company", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Company", + "options": "Company", + "reqd": 1 + }, + { + "fieldname": "doctypes", + "fieldtype": "Table", + "label": "Summary", + "options": "Transaction Deletion Record Item", + "read_only": 1 + }, + { + "fieldname": "doctypes_to_be_ignored", + "fieldtype": "Table", + "label": "Excluded DocTypes", + "options": "Transaction Deletion Record Item" + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Transaction Deletion Record", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "status", + "fieldtype": "Select", + "hidden": 1, + "label": "Status", + "options": "Draft\nCompleted" + } + ], + "index_web_pages_for_search": 1, + "is_submittable": 1, + "links": [], + "modified": "2021-05-08 23:13:48.049879", + "modified_by": "Administrator", + "module": "Setup", + "name": "Transaction Deletion Record", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py new file mode 100644 index 0000000000..38f8de7a66 --- /dev/null +++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py @@ -0,0 +1,147 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +from frappe.utils import cint +import frappe +from frappe.model.document import Document +from frappe import _ +from frappe.desk.notifications import clear_notifications + +class TransactionDeletionRecord(Document): + def validate(self): + frappe.only_for('System Manager') + company_obj = frappe.get_doc('Company', self.company) + if frappe.session.user != company_obj.owner and frappe.session.user != 'Administrator': + frappe.throw(_('Transactions can only be deleted by the creator of the Company or the Administrator.'), + frappe.PermissionError) + doctypes_to_be_ignored_list = get_doctypes_to_be_ignored() + for doctype in self.doctypes_to_be_ignored: + if doctype.doctype_name not in doctypes_to_be_ignored_list: + frappe.throw(_("DocTypes should not be added manually to the 'Excluded DocTypes' table. You are only allowed to remove entries from it. "), title=_("Not Allowed")) + + def before_submit(self): + if not self.doctypes_to_be_ignored: + self.populate_doctypes_to_be_ignored_table() + + self.delete_bins() + self.delete_lead_addresses() + + company_obj = frappe.get_doc('Company', self.company) + # reset company values + company_obj.total_monthly_sales = 0 + company_obj.sales_monthly_history = None + company_obj.save() + # Clear notification counts + clear_notifications() + + singles = frappe.get_all('DocType', filters = {'issingle': 1}, pluck = 'name') + tables = frappe.get_all('DocType', filters = {'istable': 1}, pluck = 'name') + doctypes_to_be_ignored_list = singles + for doctype in self.doctypes_to_be_ignored: + doctypes_to_be_ignored_list.append(doctype.doctype_name) + + docfields = frappe.get_all('DocField', + filters = { + 'fieldtype': 'Link', + 'options': 'Company', + 'parent': ['not in', doctypes_to_be_ignored_list]}, + fields=['parent', 'fieldname']) + + for docfield in docfields: + if docfield['parent'] != self.doctype: + no_of_docs = frappe.db.count(docfield['parent'], { + docfield['fieldname'] : self.company + }) + + if no_of_docs > 0: + self.delete_version_log(docfield['parent'], docfield['fieldname']) + self.delete_communications(docfield['parent'], docfield['fieldname']) + + # populate DocTypes table + if docfield['parent'] not in tables: + self.append('doctypes', { + 'doctype_name' : docfield['parent'], + 'no_of_docs' : no_of_docs + }) + + # delete the docs linked with the specified company + frappe.db.delete(docfield['parent'], { + docfield['fieldname'] : self.company + }) + + naming_series = frappe.db.get_value('DocType', docfield['parent'], 'autoname') + if naming_series: + if '#' in naming_series: + self.update_naming_series(naming_series, docfield['parent']) + + def populate_doctypes_to_be_ignored_table(self): + doctypes_to_be_ignored_list = get_doctypes_to_be_ignored() + for doctype in doctypes_to_be_ignored_list: + self.append('doctypes_to_be_ignored', { + 'doctype_name' : doctype + }) + + def update_naming_series(self, naming_series, doctype_name): + if '.' in naming_series: + prefix, hashes = naming_series.rsplit('.', 1) + else: + prefix, hashes = naming_series.rsplit('{', 1) + last = frappe.db.sql("""select max(name) from `tab{0}` + where name like %s""".format(doctype_name), prefix + '%') + if last and last[0][0]: + last = cint(last[0][0].replace(prefix, '')) + else: + last = 0 + + frappe.db.sql("""update tabSeries set current = %s where name=%s""", (last, prefix)) + + def delete_version_log(self, doctype, company_fieldname): + frappe.db.sql("""delete from `tabVersion` where ref_doctype=%s and docname in + (select name from `tab{0}` where `{1}`=%s)""".format(doctype, + company_fieldname), (doctype, self.company)) + + def delete_communications(self, doctype, company_fieldname): + reference_docs = frappe.get_all(doctype, filters={company_fieldname:self.company}) + reference_doc_names = [r.name for r in reference_docs] + + communications = frappe.get_all('Communication', filters={'reference_doctype':doctype,'reference_name':['in', reference_doc_names]}) + communication_names = [c.name for c in communications] + + frappe.delete_doc('Communication', communication_names, ignore_permissions=True) + + def delete_bins(self): + frappe.db.sql("""delete from tabBin where warehouse in + (select name from tabWarehouse where company=%s)""", self.company) + + def delete_lead_addresses(self): + """Delete addresses to which leads are linked""" + leads = frappe.get_all('Lead', filters={'company': self.company}) + leads = ["'%s'" % row.get("name") for row in leads] + addresses = [] + if leads: + addresses = frappe.db.sql_list("""select parent from `tabDynamic Link` where link_name + in ({leads})""".format(leads=",".join(leads))) + + if addresses: + addresses = ["%s" % frappe.db.escape(addr) for addr in addresses] + + frappe.db.sql("""delete from tabAddress where name in ({addresses}) and + name not in (select distinct dl1.parent from `tabDynamic Link` dl1 + inner join `tabDynamic Link` dl2 on dl1.parent=dl2.parent + and dl1.link_doctype<>dl2.link_doctype)""".format(addresses=",".join(addresses))) + + frappe.db.sql("""delete from `tabDynamic Link` where link_doctype='Lead' + and parenttype='Address' and link_name in ({leads})""".format(leads=",".join(leads))) + + frappe.db.sql("""update tabCustomer set lead_name=NULL where lead_name in ({leads})""".format(leads=",".join(leads))) + +@frappe.whitelist() +def get_doctypes_to_be_ignored(): + doctypes_to_be_ignored_list = ['Account', 'Cost Center', 'Warehouse', 'Budget', + 'Party Account', 'Employee', 'Sales Taxes and Charges Template', + 'Purchase Taxes and Charges Template', 'POS Profile', 'BOM', + 'Company', 'Bank Account', 'Item Tax Template', 'Mode of Payment', + 'Item Default', 'Customer', 'Supplier', 'GST Account'] + return doctypes_to_be_ignored_list diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record_list.js b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record_list.js new file mode 100644 index 0000000000..d7175ddac4 --- /dev/null +++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record_list.js @@ -0,0 +1,12 @@ +// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors +// License: GNU General Public License v3. See license.txt + +frappe.listview_settings['Transaction Deletion Record'] = { + get_indicator: function(doc) { + if (doc.docstatus == 0) { + return [__("Draft"), "red"]; + } else { + return [__("Completed"), "green"]; + } + } +}; \ No newline at end of file diff --git a/erpnext/setup/doctype/transaction_deletion_record_item/__init__.py b/erpnext/setup/doctype/transaction_deletion_record_item/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/setup/doctype/transaction_deletion_record_item/transaction_deletion_record_item.json b/erpnext/setup/doctype/transaction_deletion_record_item/transaction_deletion_record_item.json new file mode 100644 index 0000000000..be0be945c4 --- /dev/null +++ b/erpnext/setup/doctype/transaction_deletion_record_item/transaction_deletion_record_item.json @@ -0,0 +1,39 @@ +{ + "actions": [], + "creation": "2021-04-07 07:34:00.124124", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "doctype_name", + "no_of_docs" + ], + "fields": [ + { + "fieldname": "doctype_name", + "fieldtype": "Link", + "in_list_view": 1, + "label": "DocType", + "options": "DocType", + "reqd": 1 + }, + { + "fieldname": "no_of_docs", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Number of Docs" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-05-08 23:10:46.166744", + "modified_by": "Administrator", + "module": "Setup", + "name": "Transaction Deletion Record Item", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/setup/doctype/transaction_deletion_record_item/transaction_deletion_record_item.py b/erpnext/setup/doctype/transaction_deletion_record_item/transaction_deletion_record_item.py new file mode 100644 index 0000000000..2176cb10de --- /dev/null +++ b/erpnext/setup/doctype/transaction_deletion_record_item/transaction_deletion_record_item.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, 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 TransactionDeletionRecordItem(Document): + pass From 1a48eb49cf912223913698383fce7568c52d510b Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 10 May 2021 14:37:10 +0530 Subject: [PATCH 235/326] fix: Client script breaking while settings tax labels --- erpnext/public/js/controllers/transaction.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index f91b432a39..43eea1357a 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1351,7 +1351,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ }); } - if(this.frm.fields_dict["taxes"]) { + if(this.frm.doc.taxes && this.frm.doc.taxes.length > 0) { this.frm.set_currency_labels(["tax_amount", "total", "tax_amount_after_discount"], this.frm.doc.currency, "taxes"); this.frm.set_currency_labels(["base_tax_amount", "base_total", "base_tax_amount_after_discount"], company_currency, "taxes"); From 13dfb9734cb8a32a88b425177b0803fc7a838505 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 10 May 2021 15:38:32 +0530 Subject: [PATCH 236/326] fix: Lable for transaction child tables --- erpnext/public/js/controllers/transaction.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 43eea1357a..a3f4de48b8 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1329,7 +1329,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ this.toggle_item_grid_columns(company_currency); - if(this.frm.fields_dict["operations"]) { + if(this.frm.doc.operations && this.frm.doc.operations.length > 0) { this.frm.set_currency_labels(["operating_cost", "hour_rate"], this.frm.doc.currency, "operations"); this.frm.set_currency_labels(["base_operating_cost", "base_hour_rate"], company_currency, "operations"); @@ -1340,7 +1340,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ }); } - if(this.frm.fields_dict["scrap_items"]) { + if(this.frm.doc.scrap_items && this.frm.doc.scrap_items.length > 0) { this.frm.set_currency_labels(["rate", "amount"], this.frm.doc.currency, "scrap_items"); this.frm.set_currency_labels(["base_rate", "base_amount"], company_currency, "scrap_items"); @@ -1357,7 +1357,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ this.frm.set_currency_labels(["base_tax_amount", "base_total", "base_tax_amount_after_discount"], company_currency, "taxes"); } - if(this.frm.fields_dict["advances"]) { + if(this.frm.doc.advances && this.frm.doc.advances.length > 0) { this.frm.set_currency_labels(["advance_amount", "allocated_amount"], this.frm.doc.party_account_currency, "advances"); } @@ -1384,7 +1384,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ company_currency, "payment_schedule"); this.frm.set_currency_labels(["payment_amount", "outstanding", "paid_amount"], this.frm.doc.currency, "payment_schedule"); - + var schedule_grid = this.frm.fields_dict["payment_schedule"].grid; $.each(["base_payment_amount", "base_outstanding", "base_paid_amount"], function(i, fname) { if (frappe.meta.get_docfield(schedule_grid.doctype, fname)) From 55d47a2baaeb9d10c991ecfc048fba5ff853869b Mon Sep 17 00:00:00 2001 From: Saqib Date: Mon, 10 May 2021 15:59:37 +0530 Subject: [PATCH 237/326] fix(pos): UI fixes related to overflowing payment section (#25652) * fix: additional fields overflowing in payment section * fix: pos profile filter in pos opening dialog * fix: item quantity pill --- erpnext/public/scss/point-of-sale.scss | 29 ++++++++++++++++++- .../page/point_of_sale/pos_controller.js | 2 +- .../page/point_of_sale/pos_item_selector.js | 10 ++++--- 3 files changed, 35 insertions(+), 6 deletions(-) diff --git a/erpnext/public/scss/point-of-sale.scss b/erpnext/public/scss/point-of-sale.scss index 0bb8e68b69..9bdaa8d1ee 100644 --- a/erpnext/public/scss/point-of-sale.scss +++ b/erpnext/public/scss/point-of-sale.scss @@ -129,11 +129,20 @@ @extend .pointer-no-select; border-radius: var(--border-radius-md); box-shadow: var(--shadow-base); + position: relative; &:hover { transform: scale(1.02, 1.02); } + .item-qty-pill { + position: absolute; + display: flex; + margin: var(--margin-sm); + justify-content: flex-end; + right: 0px; + } + .item-display { display: flex; align-items: center; @@ -766,9 +775,10 @@ > .payment-modes { display: flex; padding-bottom: var(--padding-sm); - margin-bottom: var(--margin-xs); + margin-bottom: var(--margin-sm); overflow-x: scroll; overflow-y: hidden; + flex-shrink: 0; > .payment-mode-wrapper { min-width: 40%; @@ -825,9 +835,24 @@ > .fields-numpad-container { display: flex; flex: 1; + height: 100%; + position: relative; + justify-content: flex-end; > .fields-section { flex: 1; + position: absolute; + display: flex; + flex-direction: column; + width: 50%; + height: 100%; + top: 0; + left: 0; + padding-bottom: var(--margin-md); + + .invoice-fields { + overflow-y: scroll; + } } > .number-pad { @@ -835,6 +860,7 @@ display: flex; justify-content: flex-end; align-items: flex-end; + max-width: 50%; .numpad-container { display: grid; @@ -861,6 +887,7 @@ margin-bottom: var(--margin-sm); justify-content: center; flex-direction: column; + flex-shrink: 0; > .totals { display: flex; diff --git a/erpnext/selling/page/point_of_sale/pos_controller.js b/erpnext/selling/page/point_of_sale/pos_controller.js index 8adf5bf747..8e0a1e1c18 100644 --- a/erpnext/selling/page/point_of_sale/pos_controller.js +++ b/erpnext/selling/page/point_of_sale/pos_controller.js @@ -58,7 +58,7 @@ erpnext.PointOfSale.Controller = class { } const pos_profile_query = { query: 'erpnext.accounts.doctype.pos_profile.pos_profile.pos_profile_query', - filters: { company: frappe.defaults.get_default('company') } + filters: { company: dialog.fields_dict.company.get_value() } } const dialog = new frappe.ui.Dialog({ title: __('Create POS Opening Entry'), diff --git a/erpnext/selling/page/point_of_sale/pos_item_selector.js b/erpnext/selling/page/point_of_sale/pos_item_selector.js index 9384ae5542..b8a82a9eda 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_selector.js +++ b/erpnext/selling/page/point_of_sale/pos_item_selector.js @@ -90,14 +90,16 @@ erpnext.PointOfSale.ItemSelector = class { function get_item_image_html() { if (!me.hide_images && item_image) { - return `
    - ${qty_to_display}
    + return `
    + ${qty_to_display} +
    ${frappe.get_abbr(item.item_name)}
    `; } else { - return `
    - ${qty_to_display}
    + return `
    + ${qty_to_display} +
    ${frappe.get_abbr(item.item_name)}
    `; } } From 9f0823a164e43c740f4aac0e9cb93559b5b06d13 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 10 May 2021 16:07:41 +0530 Subject: [PATCH 238/326] fix: Linting issues --- erpnext/public/js/controllers/transaction.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index a3f4de48b8..7cfd939e95 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1329,7 +1329,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ this.toggle_item_grid_columns(company_currency); - if(this.frm.doc.operations && this.frm.doc.operations.length > 0) { + if (this.frm.doc.operations && this.frm.doc.operations.length > 0) { this.frm.set_currency_labels(["operating_cost", "hour_rate"], this.frm.doc.currency, "operations"); this.frm.set_currency_labels(["base_operating_cost", "base_hour_rate"], company_currency, "operations"); @@ -1340,7 +1340,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ }); } - if(this.frm.doc.scrap_items && this.frm.doc.scrap_items.length > 0) { + if (this.frm.doc.scrap_items && this.frm.doc.scrap_items.length > 0) { this.frm.set_currency_labels(["rate", "amount"], this.frm.doc.currency, "scrap_items"); this.frm.set_currency_labels(["base_rate", "base_amount"], company_currency, "scrap_items"); @@ -1351,13 +1351,13 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ }); } - if(this.frm.doc.taxes && this.frm.doc.taxes.length > 0) { + if (this.frm.doc.taxes && this.frm.doc.taxes.length > 0) { this.frm.set_currency_labels(["tax_amount", "total", "tax_amount_after_discount"], this.frm.doc.currency, "taxes"); this.frm.set_currency_labels(["base_tax_amount", "base_total", "base_tax_amount_after_discount"], company_currency, "taxes"); } - if(this.frm.doc.advances && this.frm.doc.advances.length > 0) { + if (this.frm.doc.advances && this.frm.doc.advances.length > 0) { this.frm.set_currency_labels(["advance_amount", "allocated_amount"], this.frm.doc.party_account_currency, "advances"); } From d2520680bc2f64d5a4692e63c70b129b2b46a8b7 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Mon, 10 May 2021 21:17:06 +0530 Subject: [PATCH 239/326] fix: Error on applying TDS without party (#25632) * fix: Error on applying TDS without party * fix: Add placeholder value --- .../tax_withholding_category/tax_withholding_category.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py index 09db7fee2b..5c1cbaa4aa 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -21,7 +21,10 @@ def get_party_details(inv): else: party_type = 'Supplier' party = inv.supplier - + + if not party: + frappe.throw(_("Please select {0} first").format(party_type)) + return party_type, party def get_party_tax_withholding_details(inv, tax_withholding_category=None): @@ -324,7 +327,7 @@ def get_tds_amount_from_ldc(ldc, parties, fiscal_year, pan_no, tax_details, post net_total, ldc.certificate_limit ): tds_amount = get_ltds_amount(net_total, limit_consumed, ldc.certificate_limit, ldc.rate, tax_details) - + return tds_amount def get_debit_note_amount(suppliers, fiscal_year_details, company=None): From a60c3081cf54b61757c1196c957bdbf3f5738a99 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 11 May 2021 16:38:33 +0530 Subject: [PATCH 240/326] fix: Breaking cost center validation --- erpnext/controllers/accounts_controller.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index c409850734..996c4ed11b 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -368,6 +368,11 @@ class AccountsController(TransactionBase): if self.doctype in ["Purchase Invoice", "Sales Invoice"] and item.meta.get_field('is_fixed_asset'): item.set('is_fixed_asset', ret.get('is_fixed_asset', 0)) + # Double check for cost center + # Items add via promotional scheme may not have cost center set + if hasattr(item, 'cost_center') and not item.get('cost_center'): + item.set('cost_center', self.get('cost_center') or erpnext.get_default_cost_center(self.company)) + if ret.get("pricing_rules"): self.apply_pricing_rule_on_items(item, ret) self.set_pricing_rule_details(item, ret) From a665f14620b453db7b144f4f260fd47ed7d99682 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Tue, 11 May 2021 17:33:59 +0530 Subject: [PATCH 241/326] fix: Error on adding bank account to plaid (#25658) --- .../doctype/plaid_settings/plaid_settings.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py index 16c65733f0..21f1db619e 100644 --- a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py +++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py @@ -90,9 +90,9 @@ def add_bank_accounts(response, bank, company): "bank": bank["bank_name"], "account": default_gl_account.account, "account_name": account["name"], - "account_type": account["type"] or "", - "account_subtype": account["subtype"] or "", - "mask": account["mask"] or "", + "account_type": account.get("type", ""), + "account_subtype": account.get("subtype", ""), + "mask": account.get("mask", ""), "integration_id": account["id"], "is_company_account": 1, "company": company From b1f8c80be3a80a59f4389a5350411c0c4a4ca318 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 11 May 2021 18:27:20 +0530 Subject: [PATCH 242/326] ci: enable semgrep check on v13 branches and update rules (#25647) * ci: enable semgrep on v13 branches * ci: break semgrep steps for nicer output * ci: update semgrep rules inline with frappe repo --- .flake8 | 3 +- .../semgrep_rules/frappe_correctness.py | 64 ++++++++++--- .../semgrep_rules/frappe_correctness.yml | 89 ++++++++++++++++--- .github/helper/semgrep_rules/translate.js | 7 ++ .github/helper/semgrep_rules/translate.yml | 9 +- .github/workflows/semgrep.yml | 12 ++- 6 files changed, 150 insertions(+), 34 deletions(-) diff --git a/.flake8 b/.flake8 index 399b176e1d..56c9b9a369 100644 --- a/.flake8 +++ b/.flake8 @@ -29,4 +29,5 @@ ignore = B950, W191, -max-line-length = 200 \ No newline at end of file +max-line-length = 200 +exclude=.github/helper/semgrep_rules diff --git a/.github/helper/semgrep_rules/frappe_correctness.py b/.github/helper/semgrep_rules/frappe_correctness.py index 4798b927f8..745e6463b8 100644 --- a/.github/helper/semgrep_rules/frappe_correctness.py +++ b/.github/helper/semgrep_rules/frappe_correctness.py @@ -4,25 +4,61 @@ from frappe import _, flt from frappe.model.document import Document +# ruleid: frappe-modifying-but-not-comitting def on_submit(self): if self.value_of_goods == 0: frappe.throw(_('Value of goods cannot be 0')) - # ruleid: frappe-modifying-after-submit self.status = 'Submitted' + +# ok: frappe-modifying-but-not-comitting def on_submit(self): - if flt(self.per_billed) < 100: - self.update_billing_status() - else: - # todook: frappe-modifying-after-submit - self.status = "Completed" - self.db_set("status", "Completed") + if self.value_of_goods == 0: + frappe.throw(_('Value of goods cannot be 0')) + self.status = 'Submitted' + self.db_set('status', 'Submitted') -class TestDoc(Document): - pass +# ok: frappe-modifying-but-not-comitting +def on_submit(self): + if self.value_of_goods == 0: + frappe.throw(_('Value of goods cannot be 0')) + x = "y" + self.status = x + self.db_set('status', x) - def validate(self): - #ruleid: frappe-modifying-child-tables-while-iterating - for item in self.child_table: - if item.value < 0: - self.remove(item) + +# ok: frappe-modifying-but-not-comitting +def on_submit(self): + x = "y" + self.status = x + self.save() + +# ruleid: frappe-modifying-but-not-comitting-other-method +class DoctypeClass(Document): + def on_submit(self): + self.good_method() + self.tainted_method() + + def tainted_method(self): + self.status = "uptate" + + +# ok: frappe-modifying-but-not-comitting-other-method +class DoctypeClass(Document): + def on_submit(self): + self.good_method() + self.tainted_method() + + def tainted_method(self): + self.status = "update" + self.db_set("status", "update") + +# ok: frappe-modifying-but-not-comitting-other-method +class DoctypeClass(Document): + def on_submit(self): + self.good_method() + self.tainted_method() + self.save() + + def tainted_method(self): + self.status = "uptate" diff --git a/.github/helper/semgrep_rules/frappe_correctness.yml b/.github/helper/semgrep_rules/frappe_correctness.yml index 54df062480..faab3344a6 100644 --- a/.github/helper/semgrep_rules/frappe_correctness.yml +++ b/.github/helper/semgrep_rules/frappe_correctness.yml @@ -1,32 +1,93 @@ # This file specifies rules for correctness according to how frappe doctype data model works. rules: -- id: frappe-modifying-after-submit +- id: frappe-modifying-but-not-comitting patterns: - - pattern: self.$ATTR = ... - - pattern-inside: | - def on_submit(self, ...): + - pattern: | + def $METHOD(self, ...): ... + self.$ATTR = ... + - pattern-not: | + def $METHOD(self, ...): + ... + self.$ATTR = ... + ... + self.db_set(..., self.$ATTR, ...) + - pattern-not: | + def $METHOD(self, ...): + ... + self.$ATTR = $SOME_VAR + ... + self.db_set(..., $SOME_VAR, ...) + - pattern-not: | + def $METHOD(self, ...): + ... + self.$ATTR = $SOME_VAR + ... + self.save() - metavariable-regex: metavariable: '$ATTR' # this is negative look-ahead, add more attrs to ignore like (ignore|ignore_this_too|ignore_me) - regex: '^(?!status_updater)(.*)$' + regex: '^(?!ignore_linked_doctypes|status_updater)(.*)$' + - metavariable-regex: + metavariable: "$METHOD" + regex: "(on_submit|on_cancel)" message: | - Doctype modified after submission. Please check if modification of self.$ATTR is commited to database. + DocType modified in self.$METHOD. Please check if modification of self.$ATTR is commited to database. languages: [python] severity: ERROR -- id: frappe-modifying-after-cancel +- id: frappe-modifying-but-not-comitting-other-method patterns: - - pattern: self.$ATTR = ... - - pattern-inside: | - def on_cancel(self, ...): + - pattern: | + class $DOCTYPE(...): + def $METHOD(self, ...): ... - - metavariable-regex: - metavariable: '$ATTR' - regex: '^(?!ignore_linked_doctypes|status_updater)(.*)$' + self.$ANOTHER_METHOD() + ... + + def $ANOTHER_METHOD(self, ...): + ... + self.$ATTR = ... + - pattern-not: | + class $DOCTYPE(...): + def $METHOD(self, ...): + ... + self.$ANOTHER_METHOD() + ... + + def $ANOTHER_METHOD(self, ...): + ... + self.$ATTR = ... + ... + self.db_set(..., self.$ATTR, ...) + - pattern-not: | + class $DOCTYPE(...): + def $METHOD(self, ...): + ... + self.$ANOTHER_METHOD() + ... + + def $ANOTHER_METHOD(self, ...): + ... + self.$ATTR = $SOME_VAR + ... + self.db_set(..., $SOME_VAR, ...) + - pattern-not: | + class $DOCTYPE(...): + def $METHOD(self, ...): + ... + self.$ANOTHER_METHOD() + ... + self.save() + def $ANOTHER_METHOD(self, ...): + ... + self.$ATTR = ... + - metavariable-regex: + metavariable: "$METHOD" + regex: "(on_submit|on_cancel)" message: | - Doctype modified after cancellation. Please check if modification of self.$ATTR is commited to database. + self.$ANOTHER_METHOD is called from self.$METHOD, check if changes to self.$ATTR are commited to database. languages: [python] severity: ERROR diff --git a/.github/helper/semgrep_rules/translate.js b/.github/helper/semgrep_rules/translate.js index 7b92fe2dff..9cdfb75d0b 100644 --- a/.github/helper/semgrep_rules/translate.js +++ b/.github/helper/semgrep_rules/translate.js @@ -35,3 +35,10 @@ __('You have' + 'subscribers in your mailing list.') // ruleid: frappe-translation-js-splitting __('You have {0} subscribers' + 'in your mailing list', [subscribers.length]) + +// ok: frappe-translation-js-splitting +__("Ctrl+Enter to add comment") + +// ruleid: frappe-translation-js-splitting +__('You have {0} subscribers \ + in your mailing list', [subscribers.length]) diff --git a/.github/helper/semgrep_rules/translate.yml b/.github/helper/semgrep_rules/translate.yml index 3737da5a7e..fa4ec9e15d 100644 --- a/.github/helper/semgrep_rules/translate.yml +++ b/.github/helper/semgrep_rules/translate.yml @@ -42,9 +42,10 @@ rules: - id: frappe-translation-python-splitting pattern-either: - - pattern: _(...) + ... + _(...) + - pattern: _(...) + _(...) - pattern: _("..." + "...") - - pattern-regex: '_\([^\)]*\\\s*' + - pattern-regex: '_\([^\)]*\\\s*' # lines broken by `\` + - pattern-regex: '_\(\s*\n' # line breaks allowed by python for using ( ) message: | Do not split strings inside translate function. Do not concatenate using translate functions. Please refer: https://frappeframework.com/docs/user/en/translations @@ -53,8 +54,8 @@ rules: - id: frappe-translation-js-splitting pattern-either: - - pattern-regex: '__\([^\)]*[\+\\]\s*' - - pattern: __('...' + '...') + - pattern-regex: '__\([^\)]*[\\]\s+' + - pattern: __('...' + '...', ...) - pattern: __('...') + __('...') message: | Do not split strings inside translate function. Do not concatenate using translate functions. diff --git a/.github/workflows/semgrep.yml b/.github/workflows/semgrep.yml index df08263236..389524e968 100644 --- a/.github/workflows/semgrep.yml +++ b/.github/workflows/semgrep.yml @@ -4,6 +4,8 @@ on: pull_request: branches: - develop + - version-13-hotfix + - version-13-pre-release jobs: semgrep: name: Frappe Linter @@ -14,11 +16,19 @@ jobs: uses: actions/setup-python@v2 with: python-version: 3.8 - - name: Run semgrep + + - name: Setup semgrep run: | python -m pip install -q semgrep git fetch origin $GITHUB_BASE_REF:$GITHUB_BASE_REF -q + + - name: Semgrep errors + run: | files=$(git diff --name-only --diff-filter=d $GITHUB_BASE_REF) [[ -d .github/helper/semgrep_rules ]] && semgrep --severity ERROR --config=.github/helper/semgrep_rules --quiet --error $files semgrep --config="r/python.lang.correctness" --quiet --error $files + + - name: Semgrep warnings + run: | + files=$(git diff --name-only --diff-filter=d $GITHUB_BASE_REF) [[ -d .github/helper/semgrep_rules ]] && semgrep --severity WARNING --severity INFO --config=.github/helper/semgrep_rules --quiet $files From d984be0ccd892989f3b1135f29c3bf4b8288cf9a Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 12 May 2021 12:32:14 +0530 Subject: [PATCH 243/326] fix: don't map set warehouse from delivery note to purchase receipt --- erpnext/stock/doctype/delivery_note/delivery_note.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index d326a04173..cce51cb9b1 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -732,7 +732,8 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None): "doctype": target_doctype, "postprocess": update_details, "field_no_map": [ - "taxes_and_charges" + "taxes_and_charges", + "set_warehouse" ] }, doctype +" Item": { From e7a2fdd81a0b054b19d4eebc39a91cf34975ddb9 Mon Sep 17 00:00:00 2001 From: Mohammad Hasnain Mohsin Rajan Date: Wed, 12 May 2021 13:02:32 +0530 Subject: [PATCH 244/326] fix: change links in workspace (#25674) --- .../workspace/accounting/accounting.json | 29 ++++--------------- 1 file changed, 5 insertions(+), 24 deletions(-) diff --git a/erpnext/accounts/workspace/accounting/accounting.json b/erpnext/accounts/workspace/accounting/accounting.json index 9ffa481c1c..df68318052 100644 --- a/erpnext/accounts/workspace/accounting/accounting.json +++ b/erpnext/accounts/workspace/accounting/accounting.json @@ -15,6 +15,7 @@ "hide_custom": 0, "icon": "accounting", "idx": 0, + "is_default": 0, "is_standard": 1, "label": "Accounting", "links": [ @@ -625,9 +626,9 @@ "dependencies": "", "hidden": 0, "is_query_report": 0, - "label": "Bank Reconciliation", - "link_to": "bank-reconciliation", - "link_type": "Page", + "label": "Bank Reconciliation Tool", + "link_to": "Bank Reconciliation Tool", + "link_type": "DocType", "onboard": 0, "type": "Link" }, @@ -641,26 +642,6 @@ "onboard": 0, "type": "Link" }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Bank Statement Transaction Entry", - "link_to": "Bank Statement Transaction Entry", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Bank Statement Settings", - "link_to": "Bank Statement Settings", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, { "hidden": 0, "is_query_report": 0, @@ -1071,7 +1052,7 @@ "type": "Link" } ], - "modified": "2021-03-04 00:38:35.349024", + "modified": "2021-05-12 11:48:01.905144", "modified_by": "Administrator", "module": "Accounts", "name": "Accounting", From 9f017a351ba58e4f47e2af78234962d4b002fb2d Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 12 May 2021 16:35:09 +0530 Subject: [PATCH 245/326] fix: updated modified time to pull new fields --- erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index 24e67febca..d3d3ffa17f 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -1380,7 +1380,7 @@ "idx": 204, "is_submittable": 1, "links": [], - "modified": "2021-03-30 22:45:58.334107", + "modified": "2021-04-30 22:45:58.334107", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice", From dc205e805f5875dd2e74bea80f70a9910319b87e Mon Sep 17 00:00:00 2001 From: Afshan <33727827+AfshanKhan@users.noreply.github.com> Date: Wed, 12 May 2021 17:42:06 +0530 Subject: [PATCH 246/326] fix: Dialog variable assignment after definition in POS (#25681) --- erpnext/selling/page/point_of_sale/pos_controller.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/selling/page/point_of_sale/pos_controller.js b/erpnext/selling/page/point_of_sale/pos_controller.js index 8e0a1e1c18..4f4f1b2240 100644 --- a/erpnext/selling/page/point_of_sale/pos_controller.js +++ b/erpnext/selling/page/point_of_sale/pos_controller.js @@ -56,10 +56,6 @@ erpnext.PointOfSale.Controller = class { dialog.fields_dict.balance_details.grid.refresh(); }); } - const pos_profile_query = { - query: 'erpnext.accounts.doctype.pos_profile.pos_profile.pos_profile_query', - filters: { company: dialog.fields_dict.company.get_value() } - } const dialog = new frappe.ui.Dialog({ title: __('Create POS Opening Entry'), static: true, @@ -105,6 +101,10 @@ erpnext.PointOfSale.Controller = class { primary_action_label: __('Submit') }); dialog.show(); + const pos_profile_query = { + query: 'erpnext.accounts.doctype.pos_profile.pos_profile.pos_profile_query', + filters: { company: dialog.fields_dict.company.get_value() } + }; } async prepare_app_defaults(data) { From c3c54fe05819a040fd45411fab5308c0afb5a53b Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 12 May 2021 19:42:04 +0530 Subject: [PATCH 247/326] fix: Woocommerce order sync issue --- .../connectors/woocommerce_connection.py | 30 +++++++++---------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/erpnext/erpnext_integrations/connectors/woocommerce_connection.py b/erpnext/erpnext_integrations/connectors/woocommerce_connection.py index 6dedaa8c53..a505ee09d2 100644 --- a/erpnext/erpnext_integrations/connectors/woocommerce_connection.py +++ b/erpnext/erpnext_integrations/connectors/woocommerce_connection.py @@ -1,6 +1,7 @@ from __future__ import unicode_literals import frappe, base64, hashlib, hmac, json +from frappe.utils import cstr from frappe import _ def verify_request(): @@ -146,22 +147,19 @@ def rename_address(address, customer): def link_items(items_list, woocommerce_settings, sys_lang): for item_data in items_list: - item_woo_com_id = item_data.get("product_id") + item_woo_com_id = cstr(item_data.get("product_id")) - if frappe.get_value("Item", {"woocommerce_id": item_woo_com_id}): - #Edit Item - item = frappe.get_doc("Item", {"woocommerce_id": item_woo_com_id}) - else: + if not frappe.db.get_value("Item", {"woocommerce_id": item_woo_com_id}, 'name'): #Create Item item = frappe.new_doc("Item") + item.item_code = _("woocommerce - {0}", sys_lang).format(item_woo_com_id) + item.stock_uom = woocommerce_settings.uom or _("Nos", sys_lang) + item.item_group = _("WooCommerce Products", sys_lang) - item.item_name = item_data.get("name") - item.item_code = _("woocommerce - {0}", sys_lang).format(item_data.get("product_id")) - item.woocommerce_id = item_data.get("product_id") - item.item_group = _("WooCommerce Products", sys_lang) - item.stock_uom = woocommerce_settings.uom or _("Nos", sys_lang) - item.flags.ignore_mandatory = True - item.save() + item.item_name = item_data.get("name") + item.woocommerce_id = item_woo_com_id + item.flags.ignore_mandatory = True + item.save() def create_sales_order(order, woocommerce_settings, customer_name, sys_lang): new_sales_order = frappe.new_doc("Sales Order") @@ -194,12 +192,12 @@ def set_items_in_sales_order(new_sales_order, woocommerce_settings, order, sys_l for item in order.get("line_items"): woocomm_item_id = item.get("product_id") - found_item = frappe.get_doc("Item", {"woocommerce_id": woocomm_item_id}) + found_item = frappe.get_doc("Item", {"woocommerce_id": cstr(woocomm_item_id)}) ordered_items_tax = item.get("total_tax") - new_sales_order.append("items",{ - "item_code": found_item.item_code, + new_sales_order.append("items", { + "item_code": found_item.name, "item_name": found_item.item_name, "description": found_item.item_name, "delivery_date": new_sales_order.delivery_date, @@ -207,7 +205,7 @@ def set_items_in_sales_order(new_sales_order, woocommerce_settings, order, sys_l "qty": item.get("quantity"), "rate": item.get("price"), "warehouse": woocommerce_settings.warehouse or default_warehouse - }) + }) add_tax_details(new_sales_order, ordered_items_tax, "Ordered Item tax", woocommerce_settings.tax_account) From 0c482fde5f14a1454cd5aa6a6b28fec245bf72d6 Mon Sep 17 00:00:00 2001 From: Anurag Mishra <32095923+Anurag810@users.noreply.github.com> Date: Thu, 13 May 2021 13:20:14 +0530 Subject: [PATCH 248/326] feat: Leave Policy Assignment Refactor (#24327) * feat: Leave Policy Assignment Refactor * fix: Changes Requested * fix: sider * fix: changes requested * test: fixed * test: fixed wrong set query * fix: remove commented code * fix(style): extra space Co-authored-by: Rucha Mahabal --- erpnext/hooks.py | 2 - .../hr/doctype/employee/employee_dashboard.py | 16 +++--- .../hr/doctype/hr_settings/hr_settings.json | 9 +--- .../test_leave_application.py | 2 - .../leave_encashment/test_leave_encashment.py | 4 -- .../leave_policy/leave_policy_dashboard.py | 2 +- .../leave_policy_assignment.js | 43 ++++++---------- .../leave_policy_assignment.py | 49 +++++-------------- .../leave_policy_assignment_dashboard.py | 13 +++++ .../leave_policy_assignment_list.js | 32 +----------- .../test_leave_policy_assignment.py | 2 - erpnext/hr/utils.py | 7 --- 12 files changed, 53 insertions(+), 128 deletions(-) create mode 100644 erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment_dashboard.py diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 9d1ce9bbbf..2a70f2bd39 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -365,10 +365,8 @@ scheduler_events = { "erpnext.setup.doctype.email_digest.email_digest.send", "erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool.update_latest_price_in_all_boms", "erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry.process_expired_allocation", - "erpnext.hr.doctype.leave_policy_assignment.leave_policy_assignment.automatically_allocate_leaves_based_on_leave_policy", "erpnext.hr.utils.generate_leave_encashment", "erpnext.hr.utils.allocate_earned_leaves", - "erpnext.hr.utils.grant_leaves_automatically", "erpnext.loan_management.doctype.process_loan_security_shortfall.process_loan_security_shortfall.create_process_loan_security_shortfall", "erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual.process_loan_interest_accrual_for_term_loans", "erpnext.crm.doctype.lead.lead.daily_open_lead" diff --git a/erpnext/hr/doctype/employee/employee_dashboard.py b/erpnext/hr/doctype/employee/employee_dashboard.py index 0203332164..285374d9f6 100644 --- a/erpnext/hr/doctype/employee/employee_dashboard.py +++ b/erpnext/hr/doctype/employee/employee_dashboard.py @@ -11,8 +11,12 @@ def get_data(): }, 'transactions': [ { - 'label': _('Leave and Attendance'), - 'items': ['Attendance', 'Attendance Request', 'Leave Application', 'Leave Allocation', 'Employee Checkin'] + 'label': _('Attendance'), + 'items': ['Attendance', 'Attendance Request', 'Employee Checkin'] + }, + { + 'label': _('Leave'), + 'items': ['Leave Application', 'Leave Allocation', 'Leave Policy Assignment'] }, { 'label': _('Lifecycle'), @@ -30,10 +34,6 @@ def get_data(): 'label': _('Benefit'), 'items': ['Employee Benefit Application', 'Employee Benefit Claim'] }, - { - 'label': _('Evaluation'), - 'items': ['Appraisal'] - }, { 'label': _('Payroll'), 'items': ['Salary Structure Assignment', 'Salary Slip', 'Additional Salary', 'Timesheet','Employee Incentive', 'Retention Bonus', 'Bank Account'] @@ -42,5 +42,9 @@ def get_data(): 'label': _('Training'), 'items': ['Training Event', 'Training Result', 'Training Feedback', 'Employee Skill Map'] }, + { + 'label': _('Evaluation'), + 'items': ['Appraisal'] + }, ] } diff --git a/erpnext/hr/doctype/hr_settings/hr_settings.json b/erpnext/hr/doctype/hr_settings/hr_settings.json index 3db6c239ef..2396a8eee9 100644 --- a/erpnext/hr/doctype/hr_settings/hr_settings.json +++ b/erpnext/hr/doctype/hr_settings/hr_settings.json @@ -23,7 +23,6 @@ "show_leaves_of_all_department_members_in_calendar", "auto_leave_encashment", "restrict_backdated_leave_application", - "automatically_allocate_leaves_based_on_leave_policy", "hiring_settings", "check_vacancies" ], @@ -133,12 +132,6 @@ "label": "Role Allowed to Create Backdated Leave Application", "options": "Role" }, - { - "default": "0", - "fieldname": "automatically_allocate_leaves_based_on_leave_policy", - "fieldtype": "Check", - "label": "Automatically Allocate Leaves Based On Leave Policy" - }, { "default": "1", "fieldname": "send_leave_notification", @@ -155,7 +148,7 @@ "idx": 1, "issingle": 1, "links": [], - "modified": "2021-04-26 10:52:56.192773", + "modified": "2021-05-11 10:52:56.192773", "modified_by": "Administrator", "module": "HR", "name": "HR Settings", diff --git a/erpnext/hr/doctype/leave_application/test_leave_application.py b/erpnext/hr/doctype/leave_application/test_leave_application.py index a4a96b813e..2832e2fad3 100644 --- a/erpnext/hr/doctype/leave_application/test_leave_application.py +++ b/erpnext/hr/doctype/leave_application/test_leave_application.py @@ -446,8 +446,6 @@ class TestLeaveApplication(unittest.TestCase): leave_policy_assignments = create_assignment_for_multiple_employees([employee.name], frappe._dict(data)) - frappe.get_doc("Leave Policy Assignment", leave_policy_assignments[0]).grant_leave_alloc_for_employee() - from erpnext.hr.utils import allocate_earned_leaves i = 0 while(i<14): diff --git a/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py b/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py index e0ffa5dd41..c1da8b47ff 100644 --- a/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py +++ b/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py @@ -44,10 +44,6 @@ class TestLeaveEncashment(unittest.TestCase): salary_structure = make_salary_structure("Salary Structure for Encashment", "Monthly", self.employee, other_details={"leave_encashment_amount_per_day": 50}) - #grant Leaves - frappe.get_doc("Leave Policy Assignment", leave_policy_assignments[0]).grant_leave_alloc_for_employee() - - def tearDown(self): for dt in ["Leave Period", "Leave Allocation", "Leave Ledger Entry", "Additional Salary", "Leave Encashment", "Salary Structure", "Leave Policy"]: frappe.db.sql("delete from `tab%s`" % dt) diff --git a/erpnext/hr/doctype/leave_policy/leave_policy_dashboard.py b/erpnext/hr/doctype/leave_policy/leave_policy_dashboard.py index e0ec4be2dc..ff7f0422e0 100644 --- a/erpnext/hr/doctype/leave_policy/leave_policy_dashboard.py +++ b/erpnext/hr/doctype/leave_policy/leave_policy_dashboard.py @@ -7,7 +7,7 @@ def get_data(): 'transactions': [ { 'label': _('Leaves'), - 'items': ['Leave Allocation'] + 'items': ['Leave Policy Assignment', 'Leave Allocation'] }, ] } \ No newline at end of file diff --git a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.js b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.js index 7c32a0dde0..0aaf4cf616 100644 --- a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.js +++ b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.js @@ -4,35 +4,22 @@ frappe.ui.form.on('Leave Policy Assignment', { onload: function(frm) { frm.ignore_doctypes_on_cancel_all = ["Leave Ledger Entry"]; - }, - refresh: function(frm) { - if (frm.doc.docstatus === 1 && frm.doc.leaves_allocated === 0) { - frm.add_custom_button(__("Grant Leave"), function() { - - frappe.call({ - doc: frm.doc, - method: "grant_leave_alloc_for_employee", - callback: function(r) { - let leave_allocations = r.message; - let msg = frm.events.get_success_message(leave_allocations); - frappe.msgprint(msg); - cur_frm.refresh(); - } - }); - }); - } - }, - - get_success_message: function(leave_allocations) { - let msg = __("Leaves has been granted successfully"); - msg += "
    "; - msg += ""; - for (let key in leave_allocations) { - msg += ""; - } - msg += "
    "+__('Leave Type')+""+__("Leave Allocation")+""+__("Leaves Granted")+"
    "+key+""+leave_allocations[key]["name"]+""+leave_allocations[key]["leaves"]+"
    "; - return msg; + frm.set_query('leave_policy', function() { + return { + filters: { + "docstatus": 1 + } + }; + }); + frm.set_query('leave_period', function() { + return { + filters: { + "is_active": 1, + "company": frm.doc.company + } + }; + }); }, assignment_based_on: function(frm) { diff --git a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.py b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.py index 462b81df1d..d7cb1c88c9 100644 --- a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.py +++ b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.py @@ -17,6 +17,9 @@ class LeavePolicyAssignment(Document): self.validate_policy_assignment_overlap() self.set_dates() + def on_submit(self): + self.grant_leave_alloc_for_employee() + def set_dates(self): if self.assignment_based_on == "Leave Period": self.effective_from, self.effective_to = frappe.db.get_value("Leave Period", self.leave_period, ["from_date", "to_date"]) @@ -75,7 +78,7 @@ class LeavePolicyAssignment(Document): from_date=self.effective_from, to_date=self.effective_to, new_leaves_allocated=new_leaves_allocated, - leave_period=self.leave_period or None, + leave_period=self.leave_period if self.assignment_based_on == "Leave Policy" else '', leave_policy_assignment = self.name, leave_policy = self.leave_policy, carry_forward=carry_forward @@ -131,22 +134,6 @@ class LeavePolicyAssignment(Document): return new_leaves_allocated -@frappe.whitelist() -def grant_leave_for_multiple_employees(leave_policy_assignments): - leave_policy_assignments = json.loads(leave_policy_assignments) - not_granted = [] - for assignment in leave_policy_assignments: - try: - frappe.get_doc("Leave Policy Assignment", assignment).grant_leave_alloc_for_employee() - except Exception: - not_granted.append(assignment) - - if len(not_granted): - msg = _("Leave not Granted for Assignments:")+ bold(comma_and(not_granted)) + _(". Please Check documents") - else: - msg = _("Leave granted Successfully") - frappe.msgprint(msg) - @frappe.whitelist() def create_assignment_for_multiple_employees(employees, data): @@ -166,29 +153,18 @@ def create_assignment_for_multiple_employees(employees, data): assignment.effective_to = getdate(data.effective_to) or None assignment.leave_period = data.leave_period or None assignment.carry_forward = data.carry_forward - assignment.save() - assignment.submit() + try: + assignment.submit() + except frappe.exceptions.ValidationError: + continue + + frappe.db.commit() + docs_name.append(assignment.name) + return docs_name - -def automatically_allocate_leaves_based_on_leave_policy(): - today = getdate() - automatically_allocate_leaves_based_on_leave_policy = frappe.db.get_single_value( - 'HR Settings', 'automatically_allocate_leaves_based_on_leave_policy' - ) - - pending_assignments = frappe.get_list( - "Leave Policy Assignment", - filters = {"docstatus": 1, "leaves_allocated": 0, "effective_from": today} - ) - - if len(pending_assignments) and automatically_allocate_leaves_based_on_leave_policy: - for assignment in pending_assignments: - frappe.get_doc("Leave Policy Assignment", assignment.name).grant_leave_alloc_for_employee() - - def get_leave_type_details(): leave_type_details = frappe._dict() leave_types = frappe.get_all("Leave Type", @@ -197,4 +173,3 @@ def get_leave_type_details(): for d in leave_types: leave_type_details.setdefault(d.name, d) return leave_type_details - diff --git a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment_dashboard.py b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment_dashboard.py new file mode 100644 index 0000000000..4bb0535cf8 --- /dev/null +++ b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment_dashboard.py @@ -0,0 +1,13 @@ +from __future__ import unicode_literals +from frappe import _ + +def get_data(): + return { + 'fieldname': 'leave_policy_assignment', + 'transactions': [ + { + 'label': _('Leaves'), + 'items': ['Leave Allocation'] + }, + ] + } \ No newline at end of file diff --git a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment_list.js b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment_list.js index 468f243885..8fe4b8f8ef 100644 --- a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment_list.js +++ b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment_list.js @@ -6,6 +6,7 @@ frappe.listview_settings['Leave Policy Assignment'] = { doctype: "Employee", target: cur_list, setters: { + employee_name: '', company: '', department: '', }, @@ -92,37 +93,6 @@ frappe.listview_settings['Leave Policy Assignment'] = { } }); }); - - list_view.page.add_inner_button(__("Grant Leaves"), function () { - me.dialog = new frappe.ui.form.MultiSelectDialog({ - doctype: "Leave Policy Assignment", - target: cur_list, - setters: { - company: '', - employee: '', - }, - get_query() { - return { - filters: { - docstatus: ['=', 1], - leaves_allocated: ['=', 0] - } - }; - }, - add_filters_group: 1, - primary_action_label: "Grant Leaves", - action(leave_policy_assignments) { - frappe.call({ - method: 'erpnext.hr.doctype.leave_policy_assignment.leave_policy_assignment.grant_leave_for_multiple_employees', - async: false, - args: { - leave_policy_assignments: leave_policy_assignments - } - }); - me.dialog.hide(); - } - }); - }); }, set_effective_date: function () { diff --git a/erpnext/hr/doctype/leave_policy_assignment/test_leave_policy_assignment.py b/erpnext/hr/doctype/leave_policy_assignment/test_leave_policy_assignment.py index 838e794795..9a14e3588d 100644 --- a/erpnext/hr/doctype/leave_policy_assignment/test_leave_policy_assignment.py +++ b/erpnext/hr/doctype/leave_policy_assignment/test_leave_policy_assignment.py @@ -35,7 +35,6 @@ class TestLeavePolicyAssignment(unittest.TestCase): leave_policy_assignments = create_assignment_for_multiple_employees([employee.name], frappe._dict(data)) leave_policy_assignment_doc = frappe.get_doc("Leave Policy Assignment", leave_policy_assignments[0]) - leave_policy_assignment_doc.grant_leave_alloc_for_employee() leave_policy_assignment_doc.reload() self.assertEqual(leave_policy_assignment_doc.leaves_allocated, 1) @@ -73,7 +72,6 @@ class TestLeavePolicyAssignment(unittest.TestCase): leave_policy_assignments = create_assignment_for_multiple_employees([employee.name], frappe._dict(data)) leave_policy_assignment_doc = frappe.get_doc("Leave Policy Assignment", leave_policy_assignments[0]) - leave_policy_assignment_doc.grant_leave_alloc_for_employee() leave_policy_assignment_doc.reload() diff --git a/erpnext/hr/utils.py b/erpnext/hr/utils.py index 2540b3db63..80189e87b7 100644 --- a/erpnext/hr/utils.py +++ b/erpnext/hr/utils.py @@ -500,13 +500,6 @@ def get_previous_claimed_amount(employee, payroll_period, non_pro_rata=False, co total_claimed_amount = sum_of_claimed_amount[0].total_amount return total_claimed_amount -def grant_leaves_automatically(): - automatically_allocate_leaves_based_on_leave_policy = frappe.db.get_singles_value("HR Settings", "automatically_allocate_leaves_based_on_leave_policy") - if automatically_allocate_leaves_based_on_leave_policy: - lpa = frappe.db.get_all("Leave Policy Assignment", filters={"effective_from": getdate(), "docstatus": 1, "leaves_allocated":0}) - for assignment in lpa: - frappe.get_doc("Leave Policy Assignment", assignment.name).grant_leave_alloc_for_employee() - def share_doc_with_approver(doc, user): # if approver does not have permissions, share if not frappe.has_permission(doc=doc, ptype="submit", user=user): From 95e05fbdac1398c0818e78d37aa93b90054ac346 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 13 May 2021 14:59:28 +0530 Subject: [PATCH 249/326] fix: Parameter for get_filtered_list_for_consolidated_report in consolidated balance sheet --- erpnext/accounts/report/balance_sheet/balance_sheet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/report/balance_sheet/balance_sheet.py b/erpnext/accounts/report/balance_sheet/balance_sheet.py index 287b8a7484..26bb44f4f7 100644 --- a/erpnext/accounts/report/balance_sheet/balance_sheet.py +++ b/erpnext/accounts/report/balance_sheet/balance_sheet.py @@ -135,7 +135,7 @@ def get_report_summary(period_list, asset, liability, equity, provisional_profit # from consolidated financial statement if filters.get('accumulated_in_group_company'): - period_list = get_filtered_list_for_consolidated_report(period_list) + period_list = get_filtered_list_for_consolidated_report(filters, period_list) for period in period_list: key = period if consolidated else period.key From 27f50d5852d9f2525488193ce694b101b011966e Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 13 May 2021 01:37:12 +0530 Subject: [PATCH 250/326] fix: timeout error while loading warehouse tree --- erpnext/stock/doctype/warehouse/warehouse.py | 46 ++++++++++++++++--- .../stock/doctype/warehouse/warehouse_tree.js | 2 +- 2 files changed, 41 insertions(+), 7 deletions(-) diff --git a/erpnext/stock/doctype/warehouse/warehouse.py b/erpnext/stock/doctype/warehouse/warehouse.py index 6c84f168fd..2062bddc7c 100644 --- a/erpnext/stock/doctype/warehouse/warehouse.py +++ b/erpnext/stock/doctype/warehouse/warehouse.py @@ -3,8 +3,9 @@ from __future__ import unicode_literals import frappe, erpnext -from frappe.utils import cint, nowdate +from frappe.utils import cint, flt from frappe import throw, _ +from collections import defaultdict from frappe.utils.nestedset import NestedSet from erpnext.stock import get_warehouse_account from frappe.contacts.address_and_contact import load_address_and_contact @@ -139,8 +140,6 @@ class Warehouse(NestedSet): @frappe.whitelist() def get_children(doctype, parent=None, company=None, is_root=False): - from erpnext.stock.utils import get_stock_value_from_bin - if is_root: parent = "" @@ -153,13 +152,48 @@ def get_children(doctype, parent=None, company=None, is_root=False): warehouses = frappe.get_list(doctype, fields=fields, filters=filters, order_by='name') + company_currency = '' + if company: + company_currency = frappe.get_cached_value('Company', company, 'default_currency') + + warehouse_wise_value = get_warehouse_wise_stock_value(company) + # return warehouses for wh in warehouses: - wh["balance"] = get_stock_value_from_bin(warehouse=wh.value) - if company: - wh["company_currency"] = frappe.db.get_value('Company', company, 'default_currency') + wh["balance"] = warehouse_wise_value.get(wh.value) + if company_currency: + wh["company_currency"] = company_currency return warehouses +def get_warehouse_wise_stock_value(company): + warehouses = frappe.get_all('Warehouse', + fields = ['name', 'parent_warehouse'], filters = {'company': company}) + parent_warehouse = {d.name : d.parent_warehouse for d in warehouses} + + filters = {'warehouse': ('in', [data.name for data in warehouses])} + bin_data = frappe.get_all('Bin', fields = ['sum(stock_value) as stock_value', 'warehouse'], + filters = filters, group_by = 'warehouse') + + warehouse_wise_stock_value = defaultdict(float) + for row in bin_data: + if not row.stock_value: + continue + + warehouse_wise_stock_value[row.warehouse] = row.stock_value + update_value_in_parent_warehouse(warehouse_wise_stock_value, + parent_warehouse, row.warehouse, row.stock_value) + + return warehouse_wise_stock_value + +def update_value_in_parent_warehouse(warehouse_wise_stock_value, parent_warehouse_dict, warehouse, stock_value): + parent_warehouse = parent_warehouse_dict.get(warehouse) + if not parent_warehouse: + return + + warehouse_wise_stock_value[parent_warehouse] += flt(stock_value) + update_value_in_parent_warehouse(warehouse_wise_stock_value, parent_warehouse_dict, + parent_warehouse, stock_value) + @frappe.whitelist() def add_node(): from frappe.desk.treeview import make_tree_args diff --git a/erpnext/stock/doctype/warehouse/warehouse_tree.js b/erpnext/stock/doctype/warehouse/warehouse_tree.js index 3665c0530f..407d7d1ccd 100644 --- a/erpnext/stock/doctype/warehouse/warehouse_tree.js +++ b/erpnext/stock/doctype/warehouse/warehouse_tree.js @@ -20,7 +20,7 @@ frappe.treeview_settings['Warehouse'] = { onrender: function(node) { if (node.data && node.data.balance!==undefined) { $('' - + format_currency(Math.abs(node.data.balance), node.data.company_currency) + + format_currency((node.data.balance), node.data.company_currency) + '').insertBefore(node.$ul); } } From e85770fe3f7d64380b84d5c5b82412045459e5fe Mon Sep 17 00:00:00 2001 From: Mohammad Hasnain Mohsin Rajan Date: Thu, 13 May 2021 17:33:18 +0530 Subject: [PATCH 251/326] fix: bank statement import via google sheet (#25676) * fix: google sheet bank statement import * fix: quotes Co-authored-by: Ankush Menat * chore: add translation Co-authored-by: Ankush Menat * chore: grammar Co-authored-by: Ankush Menat * fix: remove comment Co-authored-by: Ankush Menat --- .../bank_statement_import.js | 1 + .../bank_statement_import.json | 6 +++--- .../bank_statement_import.py | 18 ++++++++++++++---- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.js b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.js index 3dbd605344..016f29a7b5 100644 --- a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.js +++ b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.js @@ -239,6 +239,7 @@ frappe.ui.form.on("Bank Statement Import", { "withdrawal", "description", "reference_number", + "bank_account" ], }, }); diff --git a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.json b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.json index 5e913cc2aa..7ffff02850 100644 --- a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.json +++ b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.json @@ -146,7 +146,7 @@ }, { "depends_on": "eval:!doc.__islocal && !doc.import_file\n", - "description": "Must be a publicly accessible Google Sheets URL", + "description": "Must be a publicly accessible Google Sheets URL and adding Bank Account column is necessary for importing via Google Sheets", "fieldname": "google_sheets_url", "fieldtype": "Data", "label": "Import from Google Sheets" @@ -202,7 +202,7 @@ ], "hide_toolbar": 1, "links": [], - "modified": "2021-02-10 19:29:59.027325", + "modified": "2021-05-12 14:17:37.777246", "modified_by": "Administrator", "module": "Accounts", "name": "Bank Statement Import", @@ -224,4 +224,4 @@ "sort_field": "modified", "sort_order": "DESC", "track_changes": 1 -} \ No newline at end of file +} 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 9f41b13f4b..5f110e2727 100644 --- a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py +++ b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py @@ -47,6 +47,13 @@ class BankStatementImport(DataImport): def start_import(self): + preview = frappe.get_doc("Bank Statement Import", self.name).get_preview_from_template( + self.import_file, self.google_sheets_url + ) + + if 'Bank Account' not in json.dumps(preview): + frappe.throw(_("Please add the Bank Account column")) + from frappe.core.page.background_jobs.background_jobs import get_info from frappe.utils.scheduler import is_scheduler_inactive @@ -67,6 +74,7 @@ class BankStatementImport(DataImport): data_import=self.name, bank_account=self.bank_account, import_file_path=self.import_file, + google_sheets_url=self.google_sheets_url, bank=self.bank, template_options=self.template_options, now=frappe.conf.developer_mode or frappe.flags.in_test, @@ -90,18 +98,20 @@ def download_errored_template(data_import_name): data_import = frappe.get_doc("Bank Statement Import", data_import_name) data_import.export_errored_rows() -def start_import(data_import, bank_account, import_file_path, bank, template_options): +def start_import(data_import, bank_account, import_file_path, google_sheets_url, bank, template_options): """This method runs in background job""" update_mapping_db(bank, template_options) data_import = frappe.get_doc("Bank Statement Import", data_import) + file = import_file_path if import_file_path else google_sheets_url - import_file = ImportFile("Bank Transaction", file = import_file_path, import_type="Insert New Records") + import_file = ImportFile("Bank Transaction", file = file, import_type="Insert New Records") data = import_file.raw_data - add_bank_account(data, bank_account) - write_files(import_file, data) + if import_file_path: + add_bank_account(data, bank_account) + write_files(import_file, data) try: i = Importer(data_import.reference_doctype, data_import=data_import) From af1376c1dfa501b5f93d06788ffe08a86116a33a Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 13 May 2021 17:39:49 +0530 Subject: [PATCH 252/326] chore: change today to now to get data for reposting --- .../doctype/repost_item_valuation/repost_item_valuation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py index 3f83780569..0971d6fdb9 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import frappe, erpnext from frappe.model.document import Document -from frappe.utils import cint, get_link_to_form, add_to_date, today +from frappe.utils import cint, get_link_to_form, add_to_date, now from erpnext.stock.stock_ledger import repost_future_sle from erpnext.accounts.utils import update_gl_entries_after, check_if_stock_and_account_balance_synced from frappe.utils.user import get_users_with_role @@ -127,7 +127,7 @@ def repost_entries(): check_if_stock_and_account_balance_synced(today(), d.name) def get_repost_item_valuation_entries(): - date = add_to_date(today(), hours=-3) + date = add_to_date(now(), hours=-3) return frappe.db.sql(""" SELECT name from `tabRepost Item Valuation` WHERE status != 'Completed' and creation <= %s and docstatus = 1 From fc44478810e40bae8bb2a3e3d17978b87344a4ce Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Thu, 13 May 2021 17:42:33 +0530 Subject: [PATCH 253/326] Update repost_item_valuation.py --- .../doctype/repost_item_valuation/repost_item_valuation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py index 0971d6fdb9..27b8729ea0 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import frappe, erpnext from frappe.model.document import Document -from frappe.utils import cint, get_link_to_form, add_to_date, now +from frappe.utils import cint, get_link_to_form, add_to_date, now, today from erpnext.stock.stock_ledger import repost_future_sle from erpnext.accounts.utils import update_gl_entries_after, check_if_stock_and_account_balance_synced from frappe.utils.user import get_users_with_role @@ -132,4 +132,4 @@ def get_repost_item_valuation_entries(): return frappe.db.sql(""" SELECT name from `tabRepost Item Valuation` WHERE status != 'Completed' and creation <= %s and docstatus = 1 ORDER BY timestamp(posting_date, posting_time) asc, creation asc - """, date, as_dict=1) \ No newline at end of file + """, date, as_dict=1) From 55fe85d850ca7db434177d3dd540a590c6f05b5e Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Fri, 14 May 2021 12:17:41 +0530 Subject: [PATCH 254/326] feat(India): Multiple GST enhancement and fixes (#25249) * fix: RCM tax calculation * feat(India): ITC Reversal via Journal Entry * fix: Reverse Charge booking logic and validation * fix: Addd patch for availed ITC fields * fix: Hooks method to update availed ITC field * fix: Cleanup and fixes in GSTR3B report * fix: Update params in GSTR-1 report * fix: Debit note using Sales Invoice * fix: Setup and patch * fix: GSTR 3B report cleanup and updates * fix: Add method to get invoices liable to reverse charge * fix: Add taxable value in Purchase Invoice Item * fix: Inward supplies liable to reverse charge * fix: Linting issues * fix: GSTR3B report test --- .../doctype/gst_account/gst_account.json | 260 ++------ .../doctype/sales_invoice/sales_invoice.js | 14 + .../doctype/sales_invoice/sales_invoice.json | 13 +- erpnext/hooks.py | 9 +- erpnext/patches.txt | 1 + .../create_itc_reversal_custom_fields.py | 115 ++++ .../gstr_3b_report/gstr_3b_report.html | 2 +- .../doctype/gstr_3b_report/gstr_3b_report.py | 621 ++++++++---------- .../gstr_3b_report_template.json | 127 ++++ .../gstr_3b_report/test_gstr_3b_report.py | 3 +- erpnext/regional/india/setup.py | 44 +- erpnext/regional/india/utils.py | 146 ++-- erpnext/regional/report/gstr_1/gstr_1.js | 8 +- erpnext/regional/report/gstr_1/gstr_1.py | 106 ++- 14 files changed, 809 insertions(+), 660 deletions(-) create mode 100644 erpnext/patches/v12_0/create_itc_reversal_custom_fields.py create mode 100644 erpnext/regional/doctype/gstr_3b_report/gstr_3b_report_template.json diff --git a/erpnext/accounts/doctype/gst_account/gst_account.json b/erpnext/accounts/doctype/gst_account/gst_account.json index 70673387fe..b6ec8844e1 100644 --- a/erpnext/accounts/doctype/gst_account/gst_account.json +++ b/erpnext/accounts/doctype/gst_account/gst_account.json @@ -1,196 +1,82 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2018-01-02 15:48:58.768352", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", + "actions": [], + "creation": "2018-01-02 15:48:58.768352", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "company", + "cgst_account", + "sgst_account", + "igst_account", + "cess_account", + "is_reverse_charge_account" + ], "fields": [ { - "allow_bulk_edit": 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": 1, - "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": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "columns": 1, + "fieldname": "company", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Company", + "options": "Company", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "cgst_account", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "CGST Account", - "length": 0, - "no_copy": 0, - "options": "Account", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "columns": 2, + "fieldname": "cgst_account", + "fieldtype": "Link", + "in_list_view": 1, + "label": "CGST Account", + "options": "Account", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "sgst_account", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "SGST Account", - "length": 0, - "no_copy": 0, - "options": "Account", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "columns": 2, + "fieldname": "sgst_account", + "fieldtype": "Link", + "in_list_view": 1, + "label": "SGST Account", + "options": "Account", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "igst_account", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "IGST Account", - "length": 0, - "no_copy": 0, - "options": "Account", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "columns": 2, + "fieldname": "igst_account", + "fieldtype": "Link", + "in_list_view": 1, + "label": "IGST Account", + "options": "Account", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "cess_account", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "CESS Account", - "length": 0, - "no_copy": 0, - "options": "Account", - "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, - "unique": 0 + "columns": 2, + "fieldname": "cess_account", + "fieldtype": "Link", + "in_list_view": 1, + "label": "CESS Account", + "options": "Account" + }, + { + "columns": 1, + "default": "0", + "fieldname": "is_reverse_charge_account", + "fieldtype": "Check", + "in_list_view": 1, + "label": "Is Reverse Charge Account" } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2018-01-02 15:52:22.335988", - "modified_by": "Administrator", - "module": "Accounts", - "name": "GST Account", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0 + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-04-09 12:30:25.889993", + "modified_by": "Administrator", + "module": "Accounts", + "name": "GST Account", + "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/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index 7c73ad6c90..5538568de4 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -582,6 +582,16 @@ frappe.ui.form.on('Sales Invoice', { }; }); + frm.set_query("adjustment_against", function() { + return { + filters: { + company: frm.doc.company, + customer: frm.doc.customer, + docstatus: 1 + } + }; + }); + frm.custom_make_buttons = { 'Delivery Note': 'Delivery', 'Sales Invoice': 'Return / Credit Note', @@ -867,6 +877,10 @@ frappe.ui.form.on('Sales Invoice', { }) } + if (frm.doc.is_debit_note) { + frm.set_df_property('return_against', 'label', 'Adjustment Against'); + } + if (frappe.boot.active_domains.includes("Healthcare")) { frm.set_df_property("patient", "hidden", 0); frm.set_df_property("patient_name", "hidden", 0); diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index c6c67b4ddc..7ae20892d2 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -16,6 +16,7 @@ "is_pos", "is_consolidated", "is_return", + "is_debit_note", "update_billed_amount_in_sales_order", "column_break1", "company", @@ -392,7 +393,7 @@ "read_only": 1 }, { - "depends_on": "return_against", + "depends_on": "eval:doc.return_against || doc.is_debit_note", "fieldname": "return_against", "fieldtype": "Link", "hide_days": 1, @@ -401,7 +402,7 @@ "no_copy": 1, "options": "Sales Invoice", "print_hide": 1, - "read_only": 1, + "read_only_depends_on": "eval:doc.is_return", "search_index": 1 }, { @@ -1953,6 +1954,12 @@ }, { "default": "0", + "fieldname": "is_debit_note", + "fieldtype": "Check", + "label": "Is Debit Note" + }, + { + "default": 0, "depends_on": "grand_total", "fieldname": "disable_rounded_total", "fieldtype": "Check", @@ -1969,7 +1976,7 @@ "link_fieldname": "consolidated_invoice" } ], - "modified": "2021-04-15 23:57:58.766651", + "modified": "2021-04-23 22:36:32.916354", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 2a70f2bd39..55169dffba 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -268,10 +268,12 @@ doc_events = { }, "Purchase Invoice": { "validate": [ - "erpnext.regional.india.utils.update_grand_total_for_rcm", + "erpnext.regional.india.utils.validate_reverse_charge_transaction", + "erpnext.regional.india.utils.update_itc_availed_fields", "erpnext.regional.united_arab_emirates.utils.update_grand_total_for_rcm", - "erpnext.regional.united_arab_emirates.utils.validate_returns" - ] + "erpnext.regional.united_arab_emirates.utils.validate_returns", + "erpnext.regional.india.utils.update_taxable_values" + ] }, "Payment Entry": { "on_submit": ["erpnext.regional.create_transaction_log", "erpnext.accounts.doctype.payment_request.payment_request.update_payment_req_status", "erpnext.accounts.doctype.dunning.dunning.resolve_dunning"], @@ -423,7 +425,6 @@ regional_overrides = { 'erpnext.controllers.taxes_and_totals.get_regional_round_off_accounts': 'erpnext.regional.india.utils.get_regional_round_off_accounts', 'erpnext.hr.utils.calculate_annual_eligible_hra_exemption': 'erpnext.regional.india.utils.calculate_annual_eligible_hra_exemption', 'erpnext.hr.utils.calculate_hra_exemption_for_period': 'erpnext.regional.india.utils.calculate_hra_exemption_for_period', - 'erpnext.accounts.doctype.purchase_invoice.purchase_invoice.make_regional_gl_entries': 'erpnext.regional.india.utils.make_regional_gl_entries', 'erpnext.controllers.accounts_controller.validate_einvoice_fields': 'erpnext.regional.india.e_invoice.utils.validate_einvoice_fields', 'erpnext.assets.doctype.asset.asset.get_depreciation_amount': 'erpnext.regional.india.utils.get_depreciation_amount' }, diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 82d223cada..9b3ddd09e8 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -769,6 +769,7 @@ erpnext.patches.v13_0.rename_discharge_date_in_ip_record erpnext.patches.v12_0.create_taxable_value_field erpnext.patches.v12_0.add_gst_category_in_delivery_note erpnext.patches.v12_0.purchase_receipt_status +erpnext.patches.v12_0.create_itc_reversal_custom_fields erpnext.patches.v13_0.fix_non_unique_represents_company erpnext.patches.v12_0.add_document_type_field_for_italy_einvoicing erpnext.patches.v13_0.make_non_standard_user_type #13-04-2021 diff --git a/erpnext/patches/v12_0/create_itc_reversal_custom_fields.py b/erpnext/patches/v12_0/create_itc_reversal_custom_fields.py new file mode 100644 index 0000000000..0078a53cd6 --- /dev/null +++ b/erpnext/patches/v12_0/create_itc_reversal_custom_fields.py @@ -0,0 +1,115 @@ +from __future__ import unicode_literals +import frappe +from frappe.custom.doctype.custom_field.custom_field import create_custom_fields +from frappe.custom.doctype.property_setter.property_setter import make_property_setter +from erpnext.regional.india.utils import get_gst_accounts + +def execute(): + company = frappe.get_all('Company', filters = {'country': 'India'}, fields=['name']) + if not company: + return + + frappe.reload_doc("regional", "doctype", "gst_settings") + frappe.reload_doc("accounts", "doctype", "gst_account") + + journal_entry_types = frappe.get_meta("Journal Entry").get_options("voucher_type").split("\n") + ['Reversal Of ITC'] + make_property_setter('Journal Entry', 'voucher_type', 'options', '\n'.join(journal_entry_types), '') + + custom_fields = { + 'Journal Entry': [ + dict(fieldname='reversal_type', label='Reversal Type', + fieldtype='Select', insert_after='voucher_type', print_hide=1, + options="As per rules 42 & 43 of CGST Rules\nOthers", + depends_on="eval:doc.voucher_type=='Reversal Of ITC'", + mandatory_depends_on="eval:doc.voucher_type=='Reversal Of ITC'"), + dict(fieldname='company_address', label='Company Address', + fieldtype='Link', options='Address', insert_after='reversal_type', + print_hide=1, depends_on="eval:doc.voucher_type=='Reversal Of ITC'", + mandatory_depends_on="eval:doc.voucher_type=='Reversal Of ITC'"), + dict(fieldname='company_gstin', label='Company GSTIN', + fieldtype='Data', read_only=1, insert_after='company_address', print_hide=1, + fetch_from='company_address.gstin', + depends_on="eval:doc.voucher_type=='Reversal Of ITC'", + mandatory_depends_on="eval:doc.voucher_type=='Reversal Of ITC'") + ], + 'Purchase Invoice': [ + dict(fieldname='eligibility_for_itc', label='Eligibility For ITC', + fieldtype='Select', insert_after='reason_for_issuing_document', print_hide=1, + options='Input Service Distributor\nImport Of Service\nImport Of Capital Goods\nITC on Reverse Charge\nIneligible As Per Section 17(5)\nIneligible Others\nAll Other ITC', + default="All Other ITC") + ], + 'Purchase Invoice Item': [ + dict(fieldname='taxable_value', label='Taxable Value', + fieldtype='Currency', insert_after='base_net_amount', hidden=1, options="Company:company:default_currency", + print_hide=1) + ] + } + + create_custom_fields(custom_fields, update=True) + + # Patch ITC Availed fields from Data to Currency + # Patch Availed ITC for current fiscal_year + + gst_accounts = get_gst_accounts(only_non_reverse_charge=1) + + frappe.db.sql(""" + UPDATE `tabCustom Field` SET fieldtype='Currency', options='Company:company:default_currency' + WHERE dt = 'Purchase Invoice' and fieldname in ('itc_integrated_tax', 'itc_state_tax', 'itc_central_tax', + 'itc_cess_amount') + """) + + frappe.db.sql("""UPDATE `tabPurchase Invoice` set itc_integrated_tax = '0' + WHERE trim(coalesce(itc_integrated_tax, '')) = '' """) + + frappe.db.sql("""UPDATE `tabPurchase Invoice` set itc_state_tax = '0' + WHERE trim(coalesce(itc_state_tax, '')) = '' """) + + frappe.db.sql("""UPDATE `tabPurchase Invoice` set itc_central_tax = '0' + WHERE trim(coalesce(itc_central_tax, '')) = '' """) + + frappe.db.sql("""UPDATE `tabPurchase Invoice` set itc_cess_amount = '0' + WHERE trim(coalesce(itc_cess_amount, '')) = '' """) + + # Get purchase invoices + invoices = frappe.get_all('Purchase Invoice', + {'posting_date': ('>=', '2021-04-01'), 'eligibility_for_itc': ('!=', 'Ineligible')}, + ['name']) + + amount_map = {} + + if invoices: + invoice_list = set([d.name for d in invoices]) + + # Get GST applied + amounts = frappe.db.sql(""" + SELECT parent, account_head, sum(base_tax_amount_after_discount_amount) as amount + FROM `tabPurchase Taxes and Charges` + where parent in %s + GROUP BY parent, account_head + """, (invoice_list), as_dict=1) + + for d in amounts: + amount_map.setdefault(d.parent, + { + 'itc_integrated_tax': 0, + 'itc_state_tax': 0, + 'itc_central_tax': 0, + 'itc_cess_amount': 0 + }) + + if d.account_head in gst_accounts.get('igst_account'): + amount_map[d.parent]['itc_integrated_tax'] += d.amount + if d.account_head in gst_accounts.get('cgst_account'): + amount_map[d.parent]['itc_central_tax'] += d.amount + if d.account_head in gst_accounts.get('sgst_account'): + amount_map[d.parent]['itc_state_tax'] += d.amount + if d.account_head in gst_accounts.get('cess_account'): + amount_map[d.parent]['itc_cess_amount'] += d.amount + + for invoice, values in amount_map.items(): + frappe.db.set_value('Purchase Invoice', invoice, { + 'itc_integrated_tax': values.get('itc_integrated_tax'), + 'itc_central_tax': values.get('itc_central_tax'), + 'itc_state_tax': values['itc_state_tax'], + 'itc_cess_amount': values['itc_cess_amount'], + }) \ No newline at end of file diff --git a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.html b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.html index 369a4001ef..3b6a45a3b4 100644 --- a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.html +++ b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.html @@ -172,7 +172,7 @@ - (A) {{__("ITC Available (whether in full op part)")}} + (A) {{__("ITC Available (whether in full or part)")}} diff --git a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py index a5dd5a2e09..3ddcc58867 100644 --- a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py +++ b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py @@ -3,148 +3,21 @@ # For license information, please see license.txt from __future__ import unicode_literals +import os +import json import frappe +from six import iteritems from frappe import _ from frappe.model.document import Document -import json -from six import iteritems -from frappe.utils import flt, getdate +from frappe.utils import flt, cstr from erpnext.regional.india import state_numbers class GSTR3BReport(Document): - def before_save(self): - + def validate(self): self.get_data() def get_data(self): - - self.report_dict = { - "gstin": "", - "ret_period": "", - "inward_sup": { - "isup_details": [ - { - "ty": "GST", - "intra": 0, - "inter": 0 - }, - { - "ty": "NONGST", - "inter": 0, - "intra": 0 - } - ] - }, - "sup_details": { - "osup_zero": { - "csamt": 0, - "txval": 0, - "iamt": 0 - }, - "osup_nil_exmp": { - "txval": 0 - }, - "osup_det": { - "samt": 0, - "csamt": 0, - "txval": 0, - "camt": 0, - "iamt": 0 - }, - "isup_rev": { - "samt": 0, - "csamt": 0, - "txval": 0, - "camt": 0, - "iamt": 0 - }, - "osup_nongst": { - "txval": 0, - } - }, - "inter_sup": { - "unreg_details": [], - "comp_details": [], - "uin_details": [] - }, - "itc_elg": { - "itc_avl": [ - { - "csamt": 0, - "samt": 0, - "ty": "IMPG", - "camt": 0, - "iamt": 0 - }, - { - "csamt": 0, - "samt": 0, - "ty": "IMPS", - "camt": 0, - "iamt": 0 - }, - { - "samt": 0, - "csamt": 0, - "ty": "ISRC", - "camt": 0, - "iamt": 0 - }, - { - "ty": "ISD", - "iamt": 0, - "camt": 0, - "samt": 0, - "csamt": 0 - }, - { - "samt": 0, - "csamt": 0, - "ty": "OTH", - "camt": 0, - "iamt": 0 - } - ], - "itc_rev": [ - { - "ty": "RUL", - "iamt": 0, - "camt": 0, - "samt": 0, - "csamt": 0 - }, - { - "ty": "OTH", - "iamt": 0, - "camt": 0, - "samt": 0, - "csamt": 0 - } - ], - "itc_net": { - "samt": 0, - "csamt": 0, - "camt": 0, - "iamt": 0 - }, - "itc_inelg": [ - { - "ty": "RUL", - "iamt": 0, - "camt": 0, - "samt": 0, - "csamt": 0 - }, - { - "ty": "OTH", - "iamt": 0, - "camt": 0, - "samt": 0, - "csamt": 0 - } - ] - } - } + self.report_dict = json.loads(get_json('gstr_3b_report_template')) self.gst_details = self.get_company_gst_details() self.report_dict["gstin"] = self.gst_details.get("gstin") @@ -152,23 +25,19 @@ class GSTR3BReport(Document): self.month_no = get_period(self.month) self.account_heads = self.get_account_heads() - outward_supply_tax_amounts = self.get_tax_amounts("Sales Invoice") - inward_supply_tax_amounts = self.get_tax_amounts("Purchase Invoice", reverse_charge="Y") + self.get_outward_supply_details("Sales Invoice") + self.set_outward_taxable_supplies() + + self.get_outward_supply_details("Purchase Invoice", reverse_charge=True) + self.set_supplies_liable_to_reverse_charge() + itc_details = self.get_itc_details() - - self.prepare_data("Sales Invoice", outward_supply_tax_amounts, "sup_details", "osup_det", ["Registered Regular"]) - self.prepare_data("Sales Invoice", outward_supply_tax_amounts, "sup_details", "osup_zero", ["SEZ", "Deemed Export", "Overseas"]) - self.prepare_data("Purchase Invoice", inward_supply_tax_amounts, "sup_details", "isup_rev", ["Unregistered", "Overseas"], reverse_charge="Y") - self.report_dict["sup_details"]["osup_nil_exmp"]["txval"] = flt(self.get_nil_rated_supply_value(), 2) self.set_itc_details(itc_details) - - inter_state_supplies = self.get_inter_state_supplies(self.gst_details.get("gst_state_number")) + self.get_itc_reversal_entries() inward_nil_exempt = self.get_inward_nil_exempt(self.gst_details.get("gst_state")) - self.set_inter_state_supply(inter_state_supplies) self.set_inward_nil_exempt(inward_nil_exempt) self.missing_field_invoices = self.get_missing_field_invoices() - self.json_output = frappe.as_json(self.report_dict) def set_inward_nil_exempt(self, inward_nil_exempt): @@ -178,189 +47,95 @@ class GSTR3BReport(Document): self.report_dict["inward_sup"]["isup_details"][1]["intra"] = flt(inward_nil_exempt.get("non_gst").get("intra"), 2) def set_itc_details(self, itc_details): - - itc_type_map = { + itc_eligible_type_map = { 'IMPG': 'Import Of Capital Goods', 'IMPS': 'Import Of Service', + 'ISRC': 'ITC on Reverse Charge', 'ISD': 'Input Service Distributor', 'OTH': 'All Other ITC' } + itc_ineligible_map = { + 'RUL': 'Ineligible As Per Section 17(5)', + 'OTH': 'Ineligible Others' + } + net_itc = self.report_dict["itc_elg"]["itc_net"] for d in self.report_dict["itc_elg"]["itc_avl"]: - - itc_type = itc_type_map.get(d["ty"]) - - if d["ty"] == 'ISRC': - reverse_charge = ["Y"] - itc_type = 'All Other ITC' - gst_category = ['Unregistered', 'Overseas'] - else: - gst_category = ['Unregistered', 'Overseas', 'Registered Regular'] - reverse_charge = ["N", "Y"] - - for account_head in self.account_heads: - for category in gst_category: - for charge_type in reverse_charge: - for key in [['iamt', 'igst_account'], ['camt', 'cgst_account'], ['samt', 'sgst_account'], ['csamt', 'cess_account']]: - d[key[0]] += flt(itc_details.get((category, itc_type, charge_type, account_head.get(key[1])), {}).get("amount"), 2) - + itc_type = itc_eligible_type_map.get(d["ty"]) for key in ['iamt', 'camt', 'samt', 'csamt']: + d[key] = flt(itc_details.get(itc_type, {}).get(key)) net_itc[key] += flt(d[key], 2) - for account_head in self.account_heads: - itc_inelg = self.report_dict["itc_elg"]["itc_inelg"][1] - for key in [['iamt', 'igst_account'], ['camt', 'cgst_account'], ['samt', 'sgst_account'], ['csamt', 'cess_account']]: - itc_inelg[key[0]] = flt(itc_details.get(("Ineligible", "N", account_head.get(key[1])), {}).get("amount"), 2) + for d in self.report_dict["itc_elg"]["itc_inelg"]: + itc_type = itc_ineligible_map.get(d["ty"]) + for key in ['iamt', 'camt', 'samt', 'csamt']: + d[key] = flt(itc_details.get(itc_type, {}).get(key)) - def prepare_data(self, doctype, tax_details, supply_type, supply_category, gst_category_list, reverse_charge="N"): + def get_itc_reversal_entries(self): + reversal_entries = frappe.db.sql(""" + SELECT ja.account, j.reversal_type, sum(credit_in_account_currency) as amount + FROM `tabJournal Entry` j, `tabJournal Entry Account` ja + where j.docstatus = 1 + and j.is_opening = 'No' + and ja.parent = j.name + and j.voucher_type = 'Reversal Of ITC' + and month(j.posting_date) = %s and year(j.posting_date) = %s + and j.company = %s and j.company_gstin = %s + GROUP BY ja.account, j.reversal_type""", (self.month_no, self.year, self.company, + self.gst_details.get("gstin")), as_dict=1) - account_map = { - 'sgst_account': 'samt', - 'cess_account': 'csamt', - 'cgst_account': 'camt', - 'igst_account': 'iamt' - } + net_itc = self.report_dict["itc_elg"]["itc_net"] - txval = 0 - total_taxable_value = self.get_total_taxable_value(doctype, reverse_charge) + for entry in reversal_entries: + if entry.reversal_type == 'As per rules 42 & 43 of CGST Rules': + index = 0 + else: + index = 1 - for gst_category in gst_category_list: - txval += total_taxable_value.get(gst_category,0) - for account_head in self.account_heads: - for account_type, account_name in iteritems(account_head): - if account_map.get(account_type) in self.report_dict.get(supply_type).get(supply_category): - self.report_dict[supply_type][supply_category][account_map.get(account_type)] += \ - flt(tax_details.get((account_name, gst_category), {}).get("amount"), 2) - - self.report_dict[supply_type][supply_category]["txval"] += flt(txval, 2) - - def set_inter_state_supply(self, inter_state_supply): - osup_det = self.report_dict["sup_details"]["osup_det"] - - for key, value in iteritems(inter_state_supply): - if key[0] == "Unregistered": - self.report_dict["inter_sup"]["unreg_details"].append(value) - - if key[0] == "Registered Composition": - self.report_dict["inter_sup"]["comp_details"].append(value) - - if key[0] == "UIN Holders": - self.report_dict["inter_sup"]["uin_details"].append(value) - - def get_total_taxable_value(self, doctype, reverse_charge): - - return frappe._dict(frappe.db.sql(""" - select gst_category, sum(net_total) as total - from `tab{doctype}` - where docstatus = 1 and month(posting_date) = %s - and year(posting_date) = %s and reverse_charge = %s - and company = %s and company_gstin = %s - group by gst_category - """ #nosec - .format(doctype = doctype), (self.month_no, self.year, reverse_charge, self.company, self.gst_details.get("gstin")))) + for key in ['camt', 'samt', 'iamt', 'csamt']: + if entry.account in self.account_heads.get(key): + self.report_dict["itc_elg"]["itc_rev"][index][key] += flt(entry.amount) + net_itc[key] -= flt(entry.amount) def get_itc_details(self): - itc_amount = frappe.db.sql(""" - select s.gst_category, sum(t.base_tax_amount_after_discount_amount) as tax_amount, - t.account_head, s.eligibility_for_itc, s.reverse_charge - from `tabPurchase Invoice` s , `tabPurchase Taxes and Charges` t - where s.docstatus = 1 and t.parent = s.name - and month(s.posting_date) = %s and year(s.posting_date) = %s and s.company = %s - and s.company_gstin = %s - group by t.account_head, s.gst_category, s.eligibility_for_itc - """, - (self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1) + itc_amounts = frappe.db.sql(""" + SELECT eligibility_for_itc, sum(itc_integrated_tax) as itc_integrated_tax, + sum(itc_central_tax) as itc_central_tax, + sum(itc_state_tax) as itc_state_tax, + sum(itc_cess_amount) as itc_cess_amount + FROM `tabPurchase Invoice` + WHERE docstatus = 1 + and is_opening = 'No' + and month(posting_date) = %s and year(posting_date) = %s and company = %s + and company_gstin = %s + GROUP BY eligibility_for_itc + """, (self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1) itc_details = {} - - for d in itc_amount: - itc_details.setdefault((d.gst_category, d.eligibility_for_itc, d.reverse_charge, d.account_head),{ - "amount": d.tax_amount + for d in itc_amounts: + itc_details.setdefault(d.eligibility_for_itc, { + 'iamt': d.itc_integrated_tax, + 'camt': d.itc_central_tax, + 'samt': d.itc_state_tax, + 'csamt': d.itc_cess_amount }) return itc_details - def get_nil_rated_supply_value(self): - - return frappe.db.sql(""" - select sum(i.base_amount) as total from - `tabSales Invoice Item` i, `tabSales Invoice` s - where s.docstatus = 1 and i.parent = s.name and i.is_nil_exempt = 1 - and month(s.posting_date) = %s and year(s.posting_date) = %s - and s.company = %s and s.company_gstin = %s""", - (self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1)[0].total - - def get_inter_state_supplies(self, state_number): - inter_state_supply_tax = frappe.db.sql(""" select t.account_head, t.tax_amount_after_discount_amount as tax_amount, - s.name, s.net_total, s.place_of_supply, s.gst_category from `tabSales Invoice` s, `tabSales Taxes and Charges` t - where t.parent = s.name and s.docstatus = 1 and month(s.posting_date) = %s and year(s.posting_date) = %s - and s.company = %s and s.company_gstin = %s and s.gst_category in ('Unregistered', 'Registered Composition', 'UIN Holders') - """, (self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1) - - inter_state_supply_tax_mapping = {} - inter_state_supply_details = {} - - for d in inter_state_supply_tax: - inter_state_supply_tax_mapping.setdefault(d.name, { - 'place_of_supply': d.place_of_supply, - 'taxable_value': d.net_total, - 'gst_category': d.gst_category, - 'camt': 0.0, - 'samt': 0.0, - 'iamt': 0.0, - 'csamt': 0.0 - }) - - if d.account_head in [a.cgst_account for a in self.account_heads]: - inter_state_supply_tax_mapping[d.name]['camt'] += d.tax_amount - - if d.account_head in [a.sgst_account for a in self.account_heads]: - inter_state_supply_tax_mapping[d.name]['samt'] += d.tax_amount - - if d.account_head in [a.igst_account for a in self.account_heads]: - inter_state_supply_tax_mapping[d.name]['iamt'] += d.tax_amount - - if d.account_head in [a.cess_account for a in self.account_heads]: - inter_state_supply_tax_mapping[d.name]['csamt'] += d.tax_amount - - for key, value in iteritems(inter_state_supply_tax_mapping): - if value.get('place_of_supply'): - osup_det = self.report_dict["sup_details"]["osup_det"] - osup_det["txval"] = flt(osup_det["txval"] + value['taxable_value'], 2) - osup_det["iamt"] = flt(osup_det["iamt"] + value['iamt'], 2) - osup_det["camt"] = flt(osup_det["camt"] + value['camt'], 2) - osup_det["samt"] = flt(osup_det["samt"] + value['samt'], 2) - osup_det["csamt"] = flt(osup_det["csamt"] + value['csamt'], 2) - - if state_number != value.get('place_of_supply').split("-")[0]: - inter_state_supply_details.setdefault((value.get('gst_category'), value.get('place_of_supply')), { - "txval": 0.0, - "pos": value.get('place_of_supply').split("-")[0], - "iamt": 0.0 - }) - - inter_state_supply_details[(value.get('gst_category'), value.get('place_of_supply'))]['txval'] += value['taxable_value'] - inter_state_supply_details[(value.get('gst_category'), value.get('place_of_supply'))]['iamt'] += value['iamt'] - - return inter_state_supply_details - def get_inward_nil_exempt(self, state): - inward_nil_exempt = frappe.db.sql(""" select p.place_of_supply, sum(i.base_amount) as base_amount, - i.is_nil_exempt, i.is_non_gst from `tabPurchase Invoice` p , `tabPurchase Invoice Item` i - where p.docstatus = 1 and p.name = i.parent + inward_nil_exempt = frappe.db.sql(""" + SELECT p.place_of_supply, sum(i.base_amount) as base_amount, i.is_nil_exempt, i.is_non_gst + FROM `tabPurchase Invoice` p , `tabPurchase Invoice Item` i + WHERE p.docstatus = 1 and p.name = i.parent + and p.is_opening = 'No' and p.gst_category != 'Registered Composition' - and (i.is_nil_exempt = 1 or i.is_non_gst = 1) and - month(p.posting_date) = %s and year(p.posting_date) = %s and p.company = %s and p.company_gstin = %s - group by p.place_of_supply, i.is_nil_exempt, i.is_non_gst""", (self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1) - - inward_nil_exempt += frappe.db.sql("""SELECT sum(base_net_total) as base_amount, gst_category, place_of_supply - FROM `tabPurchase Invoice` - WHERE docstatus = 1 and gst_category = 'Registered Composition' - and month(posting_date) = %s and year(posting_date) = %s - and company = %s and company_gstin = %s - group by place_of_supply""", (self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1) + and (i.is_nil_exempt = 1 or i.is_non_gst = 1 or p.gst_category = 'Registered Composition') and + month(p.posting_date) = %s and year(p.posting_date) = %s + and p.company = %s and p.company_gstin = %s + GROUP BY p.place_of_supply, i.is_nil_exempt, i.is_non_gst""", + (self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1) inward_nil_exempt_details = { "gst": { @@ -388,37 +163,193 @@ class GSTR3BReport(Document): return inward_nil_exempt_details - def get_tax_amounts(self, doctype, reverse_charge="N"): + def get_outward_supply_details(self, doctype, reverse_charge=None): + self.get_outward_tax_invoices(doctype, reverse_charge=reverse_charge) + self.get_outward_items(doctype) + self.get_outward_tax_details(doctype) + def get_outward_tax_invoices(self, doctype, reverse_charge=None): + self.invoices = [] + self.invoice_detail_map = {} + condition = '' + + if reverse_charge: + condition += "AND reverse_charge = 'Y'" + + invoice_details = frappe.db.sql(""" + SELECT + name, gst_category, export_type, place_of_supply + FROM + `tab{doctype}` + WHERE + docstatus = 1 + AND month(posting_date) = %s + AND year(posting_date) = %s + AND company = %s + AND company_gstin = %s + AND is_opening = 'No' + {reverse_charge} + ORDER BY name + """.format(doctype=doctype, reverse_charge=condition), (self.month_no, self.year, + self.company, self.gst_details.get("gstin")), as_dict=1) + + for d in invoice_details: + self.invoice_detail_map.setdefault(d.name, d) + self.invoices.append(d.name) + + def get_outward_items(self, doctype): + self.invoice_items = frappe._dict() + self.is_nil_exempt = [] + self.is_non_gst = [] + + if self.get('invoices'): + item_details = frappe.db.sql(""" + SELECT + item_code, parent, taxable_value, base_net_amount, item_tax_rate, + is_nil_exempt, is_non_gst + FROM + `tab%s Item` + WHERE parent in (%s) + """ % (doctype, ', '.join(['%s']*len(self.invoices))), tuple(self.invoices), as_dict=1) + + for d in item_details: + if d.item_code not in self.invoice_items.get(d.parent, {}): + self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code, + sum((i.get('taxable_value', 0) or i.get('base_net_amount', 0)) for i in item_details + if i.item_code == d.item_code and i.parent == d.parent)) + + if d.is_nil_exempt and d.item_code not in self.is_nil_exempt: + self.is_nil_exempt.append(d.item_code) + + if d.is_non_gst and d.item_code not in self.is_non_gst: + self.is_non_gst.append(d.item_code) + + def get_outward_tax_details(self, doctype): if doctype == "Sales Invoice": tax_template = 'Sales Taxes and Charges' elif doctype == "Purchase Invoice": tax_template = 'Purchase Taxes and Charges' - tax_amounts = frappe.db.sql(""" - select s.gst_category, sum(t.base_tax_amount_after_discount_amount) as tax_amount, t.account_head - from `tab{doctype}` s , `tab{template}` t - where s.docstatus = 1 and t.parent = s.name and s.reverse_charge = %s - and month(s.posting_date) = %s and year(s.posting_date) = %s and s.company = %s - and s.company_gstin = %s - group by t.account_head, s.gst_category - """ #nosec - .format(doctype=doctype, template=tax_template), - (reverse_charge, self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1) + self.items_based_on_tax_rate = {} + self.invoice_cess = frappe._dict() + self.cgst_sgst_invoices = [] - tax_details = {} + if self.get('invoices'): + tax_details = frappe.db.sql(""" + SELECT + parent, account_head, item_wise_tax_detail, base_tax_amount_after_discount_amount + FROM `tab%s` + WHERE + parenttype = %s and docstatus = 1 + and parent in (%s) + ORDER BY account_head + """ % (tax_template, '%s', ', '.join(['%s']*len(self.invoices))), + tuple([doctype] + list(self.invoices))) - for d in tax_amounts: - tax_details.setdefault( - (d.account_head,d.gst_category),{ - "amount": d.get("tax_amount"), - } - ) + for parent, account, item_wise_tax_detail, tax_amount in tax_details: + if account in self.account_heads.get('csamt'): + self.invoice_cess.setdefault(parent, tax_amount) + else: + if item_wise_tax_detail: + try: + item_wise_tax_detail = json.loads(item_wise_tax_detail) + cgst_or_sgst = False + if account in self.account_heads.get('camt') \ + or account in self.account_heads.get('samt'): + cgst_or_sgst = True - return tax_details + for item_code, tax_amounts in item_wise_tax_detail.items(): + if not (cgst_or_sgst or account in self.account_heads.get('iamt') or + (item_code in self.is_non_gst + self.is_nil_exempt)): + continue + + tax_rate = tax_amounts[0] + if tax_rate: + if cgst_or_sgst: + tax_rate *= 2 + if parent not in self.cgst_sgst_invoices: + self.cgst_sgst_invoices.append(parent) + + rate_based_dict = self.items_based_on_tax_rate\ + .setdefault(parent, {}).setdefault(tax_rate, []) + if item_code not in rate_based_dict: + rate_based_dict.append(item_code) + except ValueError: + continue + + + if self.get('invoice_items'): + # Build itemised tax for export invoices, nil and exempted where tax table is blank + for invoice, items in iteritems(self.invoice_items): + if invoice not in self.items_based_on_tax_rate and (self.invoice_detail_map.get(invoice, {}).get('export_type') + == "Without Payment of Tax"): + self.items_based_on_tax_rate.setdefault(invoice, {}).setdefault(0, items.keys()) + + def set_outward_taxable_supplies(self): + inter_state_supply_details = {} + + for inv, items_based_on_rate in self.items_based_on_tax_rate.items(): + for rate, items in items_based_on_rate.items(): + for item_code, taxable_value in self.invoice_items.get(inv).items(): + if item_code in items: + if item_code in self.is_nil_exempt: + self.report_dict['sup_details']['osup_nil_exmp']['txval'] += taxable_value + elif item_code in self.is_non_gst: + self.report_dict['sup_details']['osup_nongst']['txval'] += taxable_value + elif rate == 0: + self.report_dict['sup_details']['osup_zero']['txval'] += taxable_value + #self.report_dict['sup_details']['osup_zero'][key] += tax_amount + else: + if inv in self.cgst_sgst_invoices: + tax_rate = rate/2 + self.report_dict['sup_details']['osup_det']['camt'] += (taxable_value * tax_rate /100) + self.report_dict['sup_details']['osup_det']['samt'] += (taxable_value * tax_rate /100) + self.report_dict['sup_details']['osup_det']['txval'] += taxable_value + else: + self.report_dict['sup_details']['osup_det']['iamt'] += (taxable_value * rate /100) + self.report_dict['sup_details']['osup_det']['txval'] += taxable_value + + gst_category = self.invoice_detail_map.get(inv, {}).get('gst_category') + place_of_supply = self.invoice_detail_map.get(inv, {}).get('place_of_supply', '00-Other Territory') + + if gst_category in ['Unregistered', 'Registered Composition', 'UIN Holders'] and \ + self.gst_details.get("gst_state") != place_of_supply.split("-")[1]: + inter_state_supply_details.setdefault((gst_category, place_of_supply), { + "txval": 0.0, + "pos": place_of_supply.split("-")[0], + "iamt": 0.0 + }) + inter_state_supply_details[(gst_category, place_of_supply)]['txval'] += taxable_value + inter_state_supply_details[(gst_category, place_of_supply)]['iamt'] += (taxable_value * rate /100) + + self.set_inter_state_supply(inter_state_supply_details) + + def set_supplies_liable_to_reverse_charge(self): + for inv, items_based_on_rate in self.items_based_on_tax_rate.items(): + for rate, items in items_based_on_rate.items(): + for item_code, taxable_value in self.invoice_items.get(inv).items(): + if item_code in items: + if inv in self.cgst_sgst_invoices: + tax_rate = rate/2 + self.report_dict['sup_details']['isup_rev']['camt'] += (taxable_value * tax_rate /100) + self.report_dict['sup_details']['isup_rev']['samt'] += (taxable_value * tax_rate /100) + self.report_dict['sup_details']['isup_rev']['txval'] += taxable_value + else: + self.report_dict['sup_details']['isup_rev']['iamt'] += (taxable_value * rate /100) + self.report_dict['sup_details']['isup_rev']['txval'] += taxable_value + + def set_inter_state_supply(self, inter_state_supply): + for key, value in iteritems(inter_state_supply): + if key[0] == "Unregistered": + self.report_dict["inter_sup"]["unreg_details"].append(value) + + if key[0] == "Registered Composition": + self.report_dict["inter_sup"]["comp_details"].append(value) + + if key[0] == "UIN Holders": + self.report_dict["inter_sup"]["uin_details"].append(value) def get_company_gst_details(self): - gst_details = frappe.get_all("Address", fields=["gstin", "gst_state", "gst_state_number"], filters={ @@ -431,20 +362,28 @@ class GSTR3BReport(Document): frappe.throw(_("Please enter GSTIN and state for the Company Address {0}").format(self.company_address)) def get_account_heads(self): + account_map = { + 'sgst_account': 'samt', + 'cess_account': 'csamt', + 'cgst_account': 'camt', + 'igst_account': 'iamt' + } - account_heads = frappe.get_all("GST Account", - fields=["cgst_account", "sgst_account", "igst_account", "cess_account"], - filters={ - "company":self.company - }) + account_heads = {} + gst_settings_accounts = frappe.get_all("GST Account", + filters={'company': self.company, 'is_reverse_charge_account': 0}, + fields=["cgst_account", "sgst_account", "igst_account", "cess_account"]) - if account_heads: - return account_heads - else: - frappe.throw(_("Please set account heads in GST Settings for Compnay {0}").format(self.company)) + if not gst_settings_accounts: + frappe.throw(_("Please set GST Accounts in GST Settings")) + + for d in gst_settings_accounts: + for acc, val in d.items(): + account_heads.setdefault(account_map.get(acc), []).append(val) + + return account_heads def get_missing_field_invoices(self): - missing_field_invoices = [] for doctype in ["Sales Invoice", "Purchase Invoice"]: @@ -456,26 +395,32 @@ class GSTR3BReport(Document): party_type = 'Supplier' party = 'supplier' - docnames = frappe.db.sql(""" - select t1.name from `tab{doctype}` t1, `tab{party_type}` t2 - where t1.docstatus = 1 and month(t1.posting_date) = %s and year(t1.posting_date) = %s + docnames = frappe.db.sql( + """ + SELECT t1.name FROM `tab{doctype}` t1, `tab{party_type}` t2 + WHERE t1.docstatus = 1 and t1.is_opening = 'No' + and month(t1.posting_date) = %s and year(t1.posting_date) = %s and t1.company = %s and t1.place_of_supply IS NULL and t1.{party} = t2.name and t2.gst_category != 'Overseas' - """.format(doctype = doctype, party_type = party_type, party=party), (self.month_no, self.year, self.company), as_dict=1) #nosec + """.format(doctype = doctype, party_type = party_type, + party=party) ,(self.month_no, self.year, self.company), as_dict=1) #nosec for d in docnames: missing_field_invoices.append(d.name) return ",".join(missing_field_invoices) -def get_state_code(state): +def get_json(template): + file_path = os.path.join(os.path.dirname(__file__), '{template}.json'.format(template=template)) + with open(file_path, 'r') as f: + return cstr(f.read()) +def get_state_code(state): state_code = state_numbers.get(state) return state_code def get_period(month, year=None): - month_no = { "January": 1, "February": 2, @@ -499,13 +444,11 @@ def get_period(month, year=None): @frappe.whitelist() def view_report(name): - json_data = frappe.get_value("GSTR 3B Report", name, 'json_output') return json.loads(json_data) @frappe.whitelist() def make_json(name): - json_data = frappe.get_value("GSTR 3B Report", name, 'json_output') file_name = "GST3B.json" frappe.local.response.filename = file_name diff --git a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report_template.json b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report_template.json new file mode 100644 index 0000000000..a68bd6a6e5 --- /dev/null +++ b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report_template.json @@ -0,0 +1,127 @@ +{ + "gstin": "", + "ret_period": "", + "inward_sup": { + "isup_details": [ + { + "ty": "GST", + "intra": 0, + "inter": 0 + }, + { + "ty": "NONGST", + "inter": 0, + "intra": 0 + } + ] + }, + "sup_details": { + "osup_zero": { + "csamt": 0, + "txval": 0, + "iamt": 0 + }, + "osup_nil_exmp": { + "txval": 0 + }, + "osup_det": { + "samt": 0, + "csamt": 0, + "txval": 0, + "camt": 0, + "iamt": 0 + }, + "isup_rev": { + "samt": 0, + "csamt": 0, + "txval": 0, + "camt": 0, + "iamt": 0 + }, + "osup_nongst": { + "txval": 0 + } + }, + "inter_sup": { + "unreg_details": [], + "comp_details": [], + "uin_details": [] + }, + "itc_elg": { + "itc_avl": [ + { + "csamt": 0, + "samt": 0, + "ty": "IMPG", + "camt": 0, + "iamt": 0 + }, + { + "csamt": 0, + "samt": 0, + "ty": "IMPS", + "camt": 0, + "iamt": 0 + }, + { + "samt": 0, + "csamt": 0, + "ty": "ISRC", + "camt": 0, + "iamt": 0 + }, + { + "ty": "ISD", + "iamt": 0, + "camt": 0, + "samt": 0, + "csamt": 0 + }, + { + "samt": 0, + "csamt": 0, + "ty": "OTH", + "camt": 0, + "iamt": 0 + } + ], + "itc_rev": [ + { + "ty": "RUL", + "iamt": 0, + "camt": 0, + "samt": 0, + "csamt": 0 + }, + { + "ty": "OTH", + "iamt": 0, + "camt": 0, + "samt": 0, + "csamt": 0 + } + ], + "itc_net": { + "samt": 0, + "csamt": 0, + "camt": 0, + "iamt": 0 + }, + "itc_inelg": [ + { + "ty": "RUL", + "iamt": 0, + "camt": 0, + "samt": 0, + "csamt": 0 + }, + { + "ty": "OTH", + "iamt": 0, + "camt": 0, + "samt": 0, + "csamt": 0 + } + ] + } +} \ No newline at end of file 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 ef8af24c42..3857ce1cdb 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 @@ -60,8 +60,7 @@ class TestGSTR3BReport(unittest.TestCase): output = json.loads(report.json_output) - self.assertEqual(output["sup_details"]["osup_det"]["iamt"], 36), - self.assertEqual(output["sup_details"]["osup_zero"]["iamt"], 18), + self.assertEqual(output["sup_details"]["osup_det"]["iamt"], 54) self.assertEqual(output["inter_sup"]["unreg_details"][0]["iamt"], 18), self.assertEqual(output["sup_details"]["osup_nil_exmp"]["txval"], 100), self.assertEqual(output["inward_sup"]["isup_details"][0]["intra"], 250) diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index b12e152b14..229e0c031e 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -114,9 +114,12 @@ def add_print_formats(): def make_property_setters(patch=False): # GST rules do not allow for an invoice no. bigger than 16 characters + journal_entry_types = frappe.get_meta("Journal Entry").get_options("voucher_type").split("\n") + ['Reversal Of ITC'] + if not patch: make_property_setter('Sales Invoice', 'naming_series', 'options', 'SINV-.YY.-\nSRET-.YY.-', '') make_property_setter('Purchase Invoice', 'naming_series', 'options', 'PINV-.YY.-\nPRET-.YY.-', '') + make_property_setter('Journal Entry', 'voucher_type', 'options', '\n'.join(journal_entry_types), '') def make_custom_fields(update=True): hsn_sac_field = dict(fieldname='gst_hsn_code', label='HSN/SAC', @@ -198,15 +201,20 @@ def make_custom_fields(update=True): purchase_invoice_itc_fields = [ dict(fieldname='eligibility_for_itc', label='Eligibility For ITC', fieldtype='Select', insert_after='reason_for_issuing_document', print_hide=1, - options='Input Service Distributor\nImport Of Service\nImport Of Capital Goods\nIneligible\nAll Other ITC', default="All Other ITC"), + options='Input Service Distributor\nImport Of Service\nImport Of Capital Goods\nITC on Reverse Charge\nIneligible As Per Section 17(5)\nIneligible Others\nAll Other ITC', + default="All Other ITC"), dict(fieldname='itc_integrated_tax', label='Availed ITC Integrated Tax', - fieldtype='Data', insert_after='eligibility_for_itc', print_hide=1), + fieldtype='Currency', insert_after='eligibility_for_itc', + options='Company:company:default_currency', print_hide=1), dict(fieldname='itc_central_tax', label='Availed ITC Central Tax', - fieldtype='Data', insert_after='itc_integrated_tax', print_hide=1), + fieldtype='Currency', insert_after='itc_integrated_tax', + options='Company:company:default_currency', print_hide=1), dict(fieldname='itc_state_tax', label='Availed ITC State/UT Tax', - fieldtype='Data', insert_after='itc_central_tax', print_hide=1), + fieldtype='Currency', insert_after='itc_central_tax', + options='Company:company:default_currency', print_hide=1), dict(fieldname='itc_cess_amount', label='Availed ITC Cess', - fieldtype='Data', insert_after='itc_state_tax', print_hide=1), + fieldtype='Currency', insert_after='itc_state_tax', + options='Company:company:default_currency', print_hide=1), ] sales_invoice_gst_fields = [ @@ -236,6 +244,23 @@ def make_custom_fields(update=True): depends_on="eval:doc.gst_category=='Overseas' "), ] + journal_entry_fields = [ + dict(fieldname='reversal_type', label='Reversal Type', + fieldtype='Select', insert_after='voucher_type', print_hide=1, + options="As per rules 42 & 43 of CGST Rules\nOthers", + depends_on="eval:doc.voucher_type=='Reversal Of ITC'", + mandatory_depends_on="eval:doc.voucher_type=='Reversal Of ITC'"), + dict(fieldname='company_address', label='Company Address', + fieldtype='Link', options='Address', insert_after='reversal_type', + print_hide=1, depends_on="eval:doc.voucher_type=='Reversal Of ITC'", + mandatory_depends_on="eval:doc.voucher_type=='Reversal Of ITC'"), + dict(fieldname='company_gstin', label='Company GSTIN', + fieldtype='Data', read_only=1, insert_after='company_address', print_hide=1, + fetch_from='company_address.gstin', + depends_on="eval:doc.voucher_type=='Reversal Of ITC'", + mandatory_depends_on="eval:doc.voucher_type=='Reversal Of ITC'") + ] + inter_state_gst_field = [ dict(fieldname='is_inter_state', label='Is Inter State', fieldtype='Check', insert_after='disabled', print_hide=1), @@ -430,13 +455,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', @@ -469,6 +494,7 @@ def make_custom_fields(update=True): 'Purchase Receipt': purchase_invoice_gst_fields, 'Sales Invoice': sales_invoice_gst_category + invoice_gst_fields + sales_invoice_shipping_fields + sales_invoice_gst_fields + si_ewaybill_fields + si_einvoice_fields, 'Delivery Note': sales_invoice_gst_fields + ewaybill_fields + sales_invoice_shipping_fields + delivery_note_gst_category, + 'Journal Entry': journal_entry_fields, 'Sales Order': sales_invoice_gst_fields, 'Tax Category': inter_state_gst_field, 'Item': [ @@ -486,7 +512,7 @@ def make_custom_fields(update=True): 'Sales Invoice Item': [hsn_sac_field, nil_rated_exempt, is_non_gst, taxable_value], 'Purchase Order Item': [hsn_sac_field, nil_rated_exempt, is_non_gst], 'Purchase Receipt Item': [hsn_sac_field, nil_rated_exempt, is_non_gst], - 'Purchase Invoice Item': [hsn_sac_field, nil_rated_exempt, is_non_gst], + 'Purchase Invoice Item': [hsn_sac_field, nil_rated_exempt, is_non_gst, taxable_value], 'Material Request Item': [hsn_sac_field, nil_rated_exempt, is_non_gst], 'Salary Component': [ dict(fieldname= 'component_type', diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index 052d7bdedf..ca679e43d2 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -204,8 +204,6 @@ def get_regional_address_details(party_details, doctype, company): if doctype in ("Sales Invoice", "Delivery Note", "Sales Order"): master_doctype = "Sales Taxes and Charges Template" - - get_tax_template_for_sez(party_details, master_doctype, company, 'Customer') get_tax_template_based_on_category(master_doctype, company, party_details) if party_details.get('taxes_and_charges'): @@ -216,7 +214,6 @@ def get_regional_address_details(party_details, doctype, company): elif doctype in ("Purchase Invoice", "Purchase Order", "Purchase Receipt"): master_doctype = "Purchase Taxes and Charges Template" - get_tax_template_for_sez(party_details, master_doctype, company, 'Supplier') get_tax_template_based_on_category(master_doctype, company, party_details) if party_details.get('taxes_and_charges'): @@ -283,20 +280,6 @@ def get_tax_template(master_doctype, company, is_inter_state, state_code): {'company': company, 'disabled': 0, 'tax_category': tax_category.name}, 'name') return default_tax -def get_tax_template_for_sez(party_details, master_doctype, company, party_type): - - gst_details = frappe.db.get_value(party_type, {'name': party_details.get(frappe.scrub(party_type))}, - ['gst_category', 'export_type'], as_dict=1) - - if gst_details: - if gst_details.gst_category == 'SEZ' and gst_details.export_type == 'With Payment of Tax': - default_tax = frappe.db.get_value(master_doctype, {"company": company, "is_inter_state":1, "disabled":0, - "gst_state": number_state_mapping[party_details.company_gstin[:2]]}) - - party_details["taxes_and_charges"] = default_tax - party_details.taxes = get_taxes_and_charges(master_doctype, default_tax) - - def calculate_annual_eligible_hra_exemption(doc): basic_component, hra_component = frappe.db.get_value('Company', doc.company, ["basic_component", "hra_component"]) if not (basic_component and hra_component): @@ -697,10 +680,19 @@ def validate_state_code(state_code, address): return int(state_code) @frappe.whitelist() -def get_gst_accounts(company, account_wise=False): +def get_gst_accounts(company=None, account_wise=False, only_reverse_charge=0, only_non_reverse_charge=0): + filters={"parent": "GST Settings"} + + if company: + filters.update({'company': company}) + if only_reverse_charge: + filters.update({'is_reverse_charge_account': 1}) + elif only_non_reverse_charge: + filters.update({'is_reverse_charge_account': 0}) + gst_accounts = frappe._dict() gst_settings_accounts = frappe.get_all("GST Account", - filters={"parent": "GST Settings", "company": company}, + filters=filters, fields=["cgst_account", "sgst_account", "igst_account", "cess_account"]) if not gst_settings_accounts and not frappe.flags.in_test: @@ -715,101 +707,63 @@ def get_gst_accounts(company, account_wise=False): return gst_accounts -def update_grand_total_for_rcm(doc, method): +def validate_reverse_charge_transaction(doc, method): country = frappe.get_cached_value('Company', doc.company, 'country') if country != 'India': return - gst_tax, base_gst_tax = get_gst_tax_amount(doc) - - if not base_gst_tax: - return + base_gst_tax = 0 + base_reverse_charge_booked = 0 if doc.reverse_charge == 'Y': - doc.taxes_and_charges_added -= gst_tax - doc.total_taxes_and_charges -= gst_tax - doc.base_taxes_and_charges_added -= base_gst_tax - doc.base_total_taxes_and_charges -= base_gst_tax + gst_accounts = get_gst_accounts(doc.company, only_reverse_charge=1) + reverse_charge_accounts = gst_accounts.get('cgst_account') + gst_accounts.get('sgst_account') \ + + gst_accounts.get('igst_account') - update_totals(gst_tax, base_gst_tax, doc) - -def update_totals(gst_tax, base_gst_tax, doc): - doc.base_grand_total -= base_gst_tax - doc.grand_total -= gst_tax - - if doc.meta.get_field("rounded_total"): - if doc.is_rounded_total_disabled(): - doc.outstanding_amount = doc.grand_total - else: - doc.rounded_total = round_based_on_smallest_currency_fraction(doc.grand_total, - doc.currency, doc.precision("rounded_total")) - - doc.rounding_adjustment += flt(doc.rounded_total - doc.grand_total, - doc.precision("rounding_adjustment")) - - doc.outstanding_amount = doc.rounded_total or doc.grand_total - - doc.in_words = money_in_words(doc.grand_total, doc.currency) - doc.base_in_words = money_in_words(doc.base_grand_total, erpnext.get_company_currency(doc.company)) - doc.set_payment_schedule() - -def make_regional_gl_entries(gl_entries, doc): - country = frappe.get_cached_value('Company', doc.company, 'country') - - if country != 'India': - return gl_entries - - gst_tax, base_gst_tax = get_gst_tax_amount(doc) - - if not base_gst_tax: - return gl_entries - - if doc.reverse_charge == 'Y': - gst_accounts = get_gst_accounts(doc.company) - gst_account_list = gst_accounts.get('cgst_account') + gst_accounts.get('sgst_account') \ + gst_accounts = get_gst_accounts(doc.company, only_non_reverse_charge=1) + non_reverse_charge_accounts = gst_accounts.get('cgst_account') + gst_accounts.get('sgst_account') \ + gst_accounts.get('igst_account') for tax in doc.get('taxes'): - if tax.category not in ("Total", "Valuation and Total"): - continue + if tax.account_head in non_reverse_charge_accounts: + if tax.add_deduct_tax == 'Add': + base_gst_tax += tax.base_tax_amount_after_discount_amount + else: + base_gst_tax += tax.base_tax_amount_after_discount_amount + elif tax.account_head in reverse_charge_accounts: + if tax.add_deduct_tax == 'Add': + base_reverse_charge_booked += tax.base_tax_amount_after_discount_amount + else: + base_reverse_charge_booked += tax.base_tax_amount_after_discount_amount - dr_or_cr = "credit" if tax.add_deduct_tax == "Add" else "debit" - if flt(tax.base_tax_amount_after_discount_amount) and tax.account_head in gst_account_list: - account_currency = get_account_currency(tax.account_head) + if base_gst_tax != base_reverse_charge_booked: + msg = _("Booked reverse charge is not equal to applied tax amount") + msg += "
    " + msg += _("Please refer {gst_document_link} to learn more about how to setup and create reverse charge invoice").format( + gst_document_link='GST Documentation') - gl_entries.append(doc.get_gl_dict( - { - "account": tax.account_head, - "cost_center": tax.cost_center, - "posting_date": doc.posting_date, - "against": doc.supplier, - dr_or_cr: tax.base_tax_amount_after_discount_amount, - dr_or_cr + "_in_account_currency": tax.base_tax_amount_after_discount_amount \ - if account_currency==doc.company_currency \ - else tax.tax_amount_after_discount_amount - }, account_currency, item=tax) - ) + frappe.throw(msg) - return gl_entries +def update_itc_availed_fields(doc, method): + country = frappe.get_cached_value('Company', doc.company, 'country') -def get_gst_tax_amount(doc): - gst_accounts = get_gst_accounts(doc.company) - gst_account_list = gst_accounts.get('cgst_account', []) + gst_accounts.get('sgst_account', []) \ - + gst_accounts.get('igst_account', []) + if country != 'India': + return - base_gst_tax = 0 - gst_tax = 0 + # Initialize values + doc.itc_integrated_tax = doc.itc_state_tax = doc.itc_central_tax = doc.itc_cess_amount = 0 + gst_accounts = get_gst_accounts(doc.company, only_non_reverse_charge=1) for tax in doc.get('taxes'): - if tax.category not in ("Total", "Valuation and Total"): - continue - - if flt(tax.base_tax_amount_after_discount_amount) and tax.account_head in gst_account_list: - base_gst_tax += tax.base_tax_amount_after_discount_amount - gst_tax += tax.tax_amount_after_discount_amount - - return gst_tax, base_gst_tax + if tax.account_head in gst_accounts.get('igst_account', []): + doc.itc_integrated_tax += flt(tax.base_tax_amount_after_discount_amount) + if tax.account_head in gst_accounts.get('sgst_account', []): + doc.itc_state_tax += flt(tax.base_tax_amount_after_discount_amount) + if tax.account_head in gst_accounts.get('cgst_account', []): + doc.itc_central_tax += flt(tax.base_tax_amount_after_discount_amount) + if tax.account_head in gst_accounts.get('cess_account', []): + doc.itc_cess_amount += flt(tax.base_tax_amount_after_discount_amount) @frappe.whitelist() def get_regional_round_off_accounts(company, account_list): diff --git a/erpnext/regional/report/gstr_1/gstr_1.js b/erpnext/regional/report/gstr_1/gstr_1.js index 1a7ff2bf5a..444f5dbb8c 100644 --- a/erpnext/regional/report/gstr_1/gstr_1.js +++ b/erpnext/regional/report/gstr_1/gstr_1.js @@ -46,7 +46,13 @@ frappe.query_reports["GSTR-1"] = { "label": __("Type of Business"), "fieldtype": "Select", "reqd": 1, - "options": ["B2B", "B2C Large", "B2C Small", "CDNR", "EXPORT"], + "options": [ + { "value": "B2B", "label": __("B2B Invoices - 4A, 4B, 4C, 6B, 6C") }, + { "value": "B2C Large", "label": __("B2C(Large) Invoices - 5A, 5B") }, + { "value": "B2C Small", "label": __("B2C(Small) Invoices - 7") }, + { "value": "CDNR-REG", "label": __("Credit/Debit Notes (Registered) - 9B") }, + { "value": "EXPORT", "label": __("Export Invoice - 6A") } + ], "default": "B2B" } ], diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py index 808fd3a2cc..1e28a40f81 100644 --- a/erpnext/regional/report/gstr_1/gstr_1.py +++ b/erpnext/regional/report/gstr_1/gstr_1.py @@ -32,6 +32,7 @@ class Gstr1Report(object): reverse_charge, return_against, is_return, + is_debit_note, gst_category, export_type, port_code, @@ -42,7 +43,7 @@ class Gstr1Report(object): def run(self): self.get_columns() - self.gst_accounts = get_gst_accounts(self.filters.company) + self.gst_accounts = get_gst_accounts(self.filters.company, only_non_reverse_charge=1) self.get_invoice_data() if self.invoices: @@ -62,9 +63,9 @@ class Gstr1Report(object): for rate, items in items_based_on_rate.items(): row, taxable_value = self.get_row_data_for_invoice(inv, invoice_details, rate, items) - if self.filters.get("type_of_business") == "CDNR": + if self.filters.get("type_of_business") == "CDNR-REG": row.append("Y" if invoice_details.posting_date <= date(2017, 7, 1) else "N") - row.append("C" if invoice_details.return_against else "R") + row.append("C" if invoice_details.is_return else "D") if taxable_value: self.data.append(row) @@ -105,7 +106,7 @@ class Gstr1Report(object): def get_row_data_for_invoice(self, invoice, invoice_details, tax_rate, items): row = [] for fieldname in self.invoice_fields: - if self.filters.get("type_of_business") == "CDNR" and fieldname == "invoice_value": + if self.filters.get("type_of_business") == "CDNR-REG" and fieldname == "invoice_value": row.append(abs(invoice_details.base_rounded_total) or abs(invoice_details.base_grand_total)) elif fieldname == "invoice_value": row.append(invoice_details.base_rounded_total or invoice_details.base_grand_total) @@ -171,7 +172,7 @@ class Gstr1Report(object): if self.filters.get("type_of_business") == "B2B": - conditions += "and ifnull(gst_category, '') in ('Registered Regular', 'Deemed Export', 'SEZ') and is_return != 1" + conditions += "AND IFNULL(gst_category, '') in ('Registered Regular', 'Deemed Export', 'SEZ') AND is_return != 1" if self.filters.get("type_of_business") in ("B2C Large", "B2C Small"): b2c_limit = frappe.db.get_single_value('GST Settings', 'b2c_limit') @@ -179,19 +180,19 @@ class Gstr1Report(object): frappe.throw(_("Please set B2C Limit in GST Settings.")) if self.filters.get("type_of_business") == "B2C Large": - conditions += """ and ifnull(SUBSTR(place_of_supply, 1, 2),'') != ifnull(SUBSTR(company_gstin, 1, 2),'') - and grand_total > {0} and is_return != 1 and gst_category ='Unregistered' """.format(flt(b2c_limit)) + conditions += """ AND ifnull(SUBSTR(place_of_supply, 1, 2),'') != ifnull(SUBSTR(company_gstin, 1, 2),'') + AND grand_total > {0} AND is_return != 1 and gst_category ='Unregistered' """.format(flt(b2c_limit)) elif self.filters.get("type_of_business") == "B2C Small": - conditions += """ and ( + conditions += """ AND ( SUBSTR(place_of_supply, 1, 2) = SUBSTR(company_gstin, 1, 2) - or grand_total <= {0}) and is_return != 1 and gst_category ='Unregistered' """.format(flt(b2c_limit)) + OR grand_total <= {0}) and is_return != 1 AND gst_category ='Unregistered' """.format(flt(b2c_limit)) - elif self.filters.get("type_of_business") == "CDNR": - conditions += """ and is_return = 1 """ + elif self.filters.get("type_of_business") == "CDNR-REG": + conditions += """ AND (is_return = 1 OR is_debit_note = 1) AND IFNULL(gst_category, '') in ('Registered Regular', 'Deemed Export', 'SEZ')""" elif self.filters.get("type_of_business") == "EXPORT": - conditions += """ and is_return !=1 and gst_category = 'Overseas' """ + conditions += """ AND is_return !=1 and gst_category = 'Overseas' """ return conditions def get_invoice_items(self): @@ -403,7 +404,7 @@ class Gstr1Report(object): "width": 100 } ] - elif self.filters.get("type_of_business") == "CDNR": + elif self.filters.get("type_of_business") == "CDNR-REG": self.invoice_columns = [ { "fieldname": "customer_gstin", @@ -437,6 +438,17 @@ class Gstr1Report(object): "options": "Sales Invoice", "width":120 }, + { + "fieldname": "reverse_charge", + "label": "Reverse Charge", + "fieldtype": "Data" + }, + { + "fieldname": "export_type", + "label": "Export Type", + "fieldtype": "Data", + "hidden": 1 + }, { "fieldname": "reason_for_issuing_document", "label": "Reason For Issuing document", @@ -449,6 +461,11 @@ class Gstr1Report(object): "fieldtype": "Data", "width": 120 }, + { + "fieldname": "gst_category", + "label": "GST Category", + "fieldtype": "Data" + }, { "fieldname": "invoice_value", "label": "Invoice Value", @@ -458,10 +475,10 @@ class Gstr1Report(object): ] self.other_columns = [ { - "fieldname": "cess_amount", - "label": "Cess Amount", - "fieldtype": "Currency", - "width": 100 + "fieldname": "cess_amount", + "label": "Cess Amount", + "fieldtype": "Currency", + "width": 100 }, { "fieldname": "pre_gst", @@ -589,6 +606,12 @@ def get_json(filters, report_name, data): out = get_export_json(res) gst_json["exp"] = out + elif filters["type_of_business"] == 'CDNR-REG': + for item in report_data[:-1]: + res.setdefault(item["customer_gstin"], {}).setdefault(item["invoice_number"],[]).append(item) + + out = get_cdnr_reg_json(res, gstin) + gst_json["cdnr"] = out return { 'report_name': report_name, @@ -628,7 +651,6 @@ def get_b2b_json(res, gstin): return out def get_b2cs_json(data, gstin): - company_state_number = gstin[0:2] out = [] @@ -713,6 +735,54 @@ def get_export_json(res): return out +def get_cdnr_reg_json(res, gstin): + out = [] + + for gst_in in res: + cdnr_item, inv = {"ctin": gst_in, "nt": []}, [] + if not gst_in: continue + + for number, invoice in iteritems(res[gst_in]): + if not invoice[0]["place_of_supply"]: + frappe.throw(_("""{0} not entered in Invoice {1}. + Please update and try again""").format(frappe.bold("Place Of Supply"), + frappe.bold(invoice[0]['invoice_number']))) + + inv_item = { + "nt_num": invoice[0]["invoice_number"], + "nt_dt": getdate(invoice[0]["posting_date"]).strftime('%d-%m-%Y'), + "val": abs(flt(invoice[0]["invoice_value"])), + "ntty": invoice[0]["document_type"], + "pos": "%02d" % int(invoice[0]["place_of_supply"].split('-')[0]), + "rchrg": invoice[0]["reverse_charge"], + "inv_type": get_invoice_type_for_cdnr(invoice[0]) + } + + inv_item["itms"] = [] + for item in invoice: + inv_item["itms"].append(get_rate_and_tax_details(item, gstin)) + + inv.append(inv_item) + + if not inv: continue + cdnr_item["nt"] = inv + out.append(cdnr_item) + + return out + +def get_invoice_type_for_cdnr(row): + if row.get('gst_category') == 'SEZ': + if row.get('export_type') == 'WPAY': + invoice_type = 'SEWP' + else: + invoice_type = 'SEWOP' + elif row.get('gst_category') == 'Deemed Export': + row.invoice_type = 'DE' + elif row.get('gst_category') == 'Registered Regular': + invoice_type = 'R' + + return invoice_type + def get_basic_invoice_detail(row): return { "inum": row["invoice_number"], From be3cde931303df533e673082954604ab2cca6ce8 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Fri, 14 May 2021 12:20:38 +0530 Subject: [PATCH 255/326] chore: Code clean up for purchase receipt GL (#25379) * chore: Code clean up for purchase receipt GL * fix: add params for debit and credit in account curreny * chore: Asset GL entry code cleanup * fix: Syntax error * fix: Update variable names * fix: function naming * fix: Add undefined variables * fix: Supplier warehouse fetching * fix: Linting issues --- .../purchase_receipt/purchase_receipt.py | 204 ++++++++---------- 1 file changed, 87 insertions(+), 117 deletions(-) diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 61e60f3922..f1292d8cbd 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -243,16 +243,23 @@ class PurchaseReceipt(BuyingController): def get_gl_entries(self, warehouse_account=None): from erpnext.accounts.general_ledger import process_gl_map + gl_entries = [] + self.make_item_gl_entries(gl_entries, warehouse_account=warehouse_account) + self.make_tax_gl_entries(gl_entries) + self.get_asset_gl_entry(gl_entries) + + return process_gl_map(gl_entries) + + def make_item_gl_entries(self, gl_entries, warehouse_account=None): stock_rbnb = self.get_company_default("stock_received_but_not_billed") landed_cost_entries = get_item_account_wise_additional_cost(self.name) expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation") auto_accounting_for_non_stock_items = cint(frappe.db.get_value('Company', self.company, 'enable_perpetual_inventory_for_non_stock_items')) - gl_entries = [] warehouse_with_no_account = [] - negative_expense_to_be_booked = 0.0 stock_items = self.get_stock_items() + for d in self.get("items"): if d.item_code in stock_items and flt(d.valuation_rate) and flt(d.qty): if warehouse_account.get(d.warehouse): @@ -263,21 +270,22 @@ class PurchaseReceipt(BuyingController): if not stock_value_diff: continue + warehouse_account_name = warehouse_account[d.warehouse]["account"] + warehouse_account_currency = warehouse_account[d.warehouse]["account_currency"] + supplier_warehouse_account = warehouse_account.get(self.supplier_warehouse, {}).get("account") + supplier_warehouse_account_currency = warehouse_account.get(self.supplier_warehouse, {}).get("account_currency") + remarks = self.get("remarks") or _("Accounting Entry for Stock") + # If PR is sub-contracted and fg item rate is zero - # in that case if account for shource and target warehouse are same, + # in that case if account for source and target warehouse are same, # then GL entries should not be posted if flt(stock_value_diff) == flt(d.rm_supp_cost) \ and warehouse_account.get(self.supplier_warehouse) \ - and warehouse_account[d.warehouse]["account"] == warehouse_account[self.supplier_warehouse]["account"]: + and warehouse_account_name == supplier_warehouse_account: continue - gl_entries.append(self.get_gl_dict({ - "account": warehouse_account[d.warehouse]["account"], - "against": stock_rbnb, - "cost_center": d.cost_center, - "remarks": self.get("remarks") or _("Accounting Entry for Stock"), - "debit": stock_value_diff - }, warehouse_account[d.warehouse]["account_currency"], item=d)) + self.add_gl_entry(gl_entries, warehouse_account_name, d.cost_center, stock_value_diff, 0.0, remarks, + stock_rbnb, account_currency=warehouse_account_currency, item=d) # GL Entry for from warehouse or Stock Received but not billed # Intentionally passed negative debit amount to avoid incorrect GL Entry validation @@ -287,43 +295,28 @@ class PurchaseReceipt(BuyingController): credit_amount = flt(d.base_net_amount, d.precision("base_net_amount")) \ if credit_currency == self.company_currency else flt(d.net_amount, d.precision("net_amount")) if credit_amount: - gl_entries.append(self.get_gl_dict({ - "account": warehouse_account[d.from_warehouse]['account'] \ - if d.from_warehouse else stock_rbnb, - "against": warehouse_account[d.warehouse]["account"], - "cost_center": d.cost_center, - "remarks": self.get("remarks") or _("Accounting Entry for Stock"), - "debit": -1 * flt(d.base_net_amount, d.precision("base_net_amount")), - "debit_in_account_currency": -1 * credit_amount - }, credit_currency, item=d)) + account = warehouse_account[d.from_warehouse]['account'] \ + if d.from_warehouse else stock_rbnb - negative_expense_to_be_booked += flt(d.item_tax_amount) + self.add_gl_entry(gl_entries, account, d.cost_center, + -1 * flt(d.base_net_amount, d.precision("base_net_amount")), 0.0, remarks, warehouse_account_name, + debit_in_account_currency=-1 * credit_amount, account_currency=credit_currency, item=d) - # Amount added through landed-cost-voucher + # Amount added through landed-cos-voucher if d.landed_cost_voucher_amount and landed_cost_entries: for account, amount in iteritems(landed_cost_entries[(d.item_code, d.name)]): account_currency = get_account_currency(account) - gl_entries.append(self.get_gl_dict({ - "account": account, - "account_currency": account_currency, - "against": warehouse_account[d.warehouse]["account"], - "cost_center": d.cost_center, - "remarks": self.get("remarks") or _("Accounting Entry for Stock"), - "credit": (flt(amount["base_amount"]) if (amount["base_amount"] or - account_currency!=self.company_currency) else flt(amount["amount"])), - "credit_in_account_currency": flt(amount["amount"]), - "project": d.project - }, item=d)) + credit_amount = (flt(amount["base_amount"]) if (amount["base_amount"] or + account_currency!=self.company_currency) else flt(amount["amount"])) + + self.add_gl_entry(gl_entries, account, d.cost_center, 0.0, credit_amount, remarks, + warehouse_account_name, credit_in_account_currency=flt(amount["amount"]), + account_currency=account_currency, project=d.project, item=d) # sub-contracting warehouse if flt(d.rm_supp_cost) and warehouse_account.get(self.supplier_warehouse): - gl_entries.append(self.get_gl_dict({ - "account": warehouse_account[self.supplier_warehouse]["account"], - "against": warehouse_account[d.warehouse]["account"], - "cost_center": d.cost_center, - "remarks": self.get("remarks") or _("Accounting Entry for Stock"), - "credit": flt(d.rm_supp_cost) - }, warehouse_account[self.supplier_warehouse]["account_currency"], item=d)) + self.add_gl_entry(gl_entries, supplier_warehouse_account, d.cost_center, 0.0, flt(d.rm_supp_cost), + remarks, warehouse_account_name, account_currency=supplier_warehouse_account_currency, item=d) # divisional loss adjustment valuation_amount_as_per_doc = flt(d.base_net_amount, d.precision("base_net_amount")) + \ @@ -340,46 +333,32 @@ class PurchaseReceipt(BuyingController): cost_center = d.cost_center or frappe.get_cached_value("Company", self.company, "cost_center") - gl_entries.append(self.get_gl_dict({ - "account": loss_account, - "against": warehouse_account[d.warehouse]["account"], - "cost_center": cost_center, - "remarks": self.get("remarks") or _("Accounting Entry for Stock"), - "debit": divisional_loss, - "project": d.project - }, credit_currency, item=d)) + self.add_gl_entry(gl_entries, loss_account, cost_center, divisional_loss, 0.0, remarks, + warehouse_account_name, account_currency=credit_currency, project=d.project, item=d) elif d.warehouse not in warehouse_with_no_account or \ d.rejected_warehouse not in warehouse_with_no_account: warehouse_with_no_account.append(d.warehouse) elif d.item_code not in stock_items and not d.is_fixed_asset and flt(d.qty) and auto_accounting_for_non_stock_items: - service_received_but_not_billed_account = self.get_company_default("service_received_but_not_billed") credit_currency = get_account_currency(service_received_but_not_billed_account) - - gl_entries.append(self.get_gl_dict({ - "account": service_received_but_not_billed_account, - "against": d.expense_account, - "cost_center": d.cost_center, - "remarks": self.get("remarks") or _("Accounting Entry for Service"), - "project": d.project, - "credit": d.amount, - "voucher_detail_no": d.name - }, credit_currency, item=d)) - debit_currency = get_account_currency(d.expense_account) + remarks = self.get("remarks") or _("Accounting Entry for Service") - gl_entries.append(self.get_gl_dict({ - "account": d.expense_account, - "against": service_received_but_not_billed_account, - "cost_center": d.cost_center, - "remarks": self.get("remarks") or _("Accounting Entry for Service"), - "project": d.project, - "debit": d.amount, - "voucher_detail_no": d.name - }, debit_currency, item=d)) + self.add_gl_entry(gl_entries, service_received_but_not_billed_account, d.cost_center, 0.0, d.amount, + remarks, d.expense_account, account_currency=credit_currency, project=d.project, + voucher_detail_no=d.name, item=d) - self.get_asset_gl_entry(gl_entries) + self.add_gl_entry(gl_entries, d.expense_account, d.cost_center, d.amount, 0.0, remarks, service_received_but_not_billed_account, + account_currency = debit_currency, project=d.project, voucher_detail_no=d.name, item=d) + + if warehouse_with_no_account: + frappe.msgprint(_("No accounting entries for the following warehouses") + ": \n" + + "\n".join(warehouse_with_no_account)) + + def make_tax_gl_entries(self, gl_entries): + expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation") + negative_expense_to_be_booked = sum([flt(d.item_tax_amount) for d in self.get('items')]) # Cost center-wise amount breakup for other charges included for valuation valuation_tax = {} for tax in self.get("taxes"): @@ -420,23 +399,33 @@ class PurchaseReceipt(BuyingController): applicable_amount = negative_expense_to_be_booked * (valuation_tax[tax.name] / total_valuation_amount) amount_including_divisional_loss -= applicable_amount - gl_entries.append( - self.get_gl_dict({ - "account": account, - "cost_center": tax.cost_center, - "credit": applicable_amount, - "remarks": self.remarks or _("Accounting Entry for Stock"), - "against": against_account - }, item=tax) - ) + self.add_gl_entry(gl_entries, account, tax.cost_center, 0.0, applicable_amount, self.remarks or _("Accounting Entry for Stock"), + against_account, item=tax) i += 1 - if warehouse_with_no_account: - frappe.msgprint(_("No accounting entries for the following warehouses") + ": \n" + - "\n".join(warehouse_with_no_account)) + def add_gl_entry(self, gl_entries, account, cost_center, debit, credit, remarks, against_account, + debit_in_account_currency=None, credit_in_account_currency=None, account_currency=None, + project=None, voucher_detail_no=None, item=None): + gl_entry = { + "account": account, + "cost_center": cost_center, + "debit": debit, + "credit": credit, + "against_account": against_account, + "remarks": remarks, + } - return process_gl_map(gl_entries) + if voucher_detail_no: + gl_entry.update({"voucher_detail_no": voucher_detail_no}) + + if debit_in_account_currency: + gl_entry.update({"debit_in_account_currency": debit_in_account_currency}) + + if credit_in_account_currency: + gl_entry.update({"credit_in_account_currency": credit_in_account_currency}) + + gl_entries.append(self.get_gl_dict(gl_entry, item=item)) def get_asset_gl_entry(self, gl_entries): for item in self.get("items"): @@ -458,30 +447,21 @@ class PurchaseReceipt(BuyingController): asset_amount = flt(item.net_amount) + flt(item.item_tax_amount/self.conversion_rate) base_asset_amount = flt(item.base_net_amount + item.item_tax_amount) + remarks = self.get("remarks") or _("Accounting Entry for Asset") cwip_account_currency = get_account_currency(cwip_account) # debit cwip account - gl_entries.append(self.get_gl_dict({ - "account": cwip_account, - "against": arbnb_account, - "cost_center": item.cost_center, - "remarks": self.get("remarks") or _("Accounting Entry for Asset"), - "debit": base_asset_amount, - "debit_in_account_currency": (base_asset_amount - if cwip_account_currency == self.company_currency else asset_amount) - }, item=item)) + debit_in_account_currency = (base_asset_amount + if cwip_account_currency == self.company_currency else asset_amount) + self.add_gl_entry(gl_entries, cwip_account, item.cost_center, base_asset_amount, 0.0, remarks, + arbnb_account, debit_in_account_currency=debit_in_account_currency, item=item) asset_rbnb_currency = get_account_currency(arbnb_account) # credit arbnb account - gl_entries.append(self.get_gl_dict({ - "account": arbnb_account, - "against": cwip_account, - "cost_center": item.cost_center, - "remarks": self.get("remarks") or _("Accounting Entry for Asset"), - "credit": base_asset_amount, - "credit_in_account_currency": (base_asset_amount - if asset_rbnb_currency == self.company_currency else asset_amount) - }, item=item)) + credit_in_account_currency = (base_asset_amount + if asset_rbnb_currency == self.company_currency else asset_amount) + self.add_gl_entry(gl_entries, arbnb_account, item.cost_center, 0.0, base_asset_amount, remarks, + cwip_account, credit_in_account_currency=credit_in_account_currency, item=item) def add_lcv_gl_entries(self, item, gl_entries): expenses_included_in_asset_valuation = self.get_company_default("expenses_included_in_asset_valuation") @@ -492,23 +472,13 @@ class PurchaseReceipt(BuyingController): # This returns company's default cwip account asset_account = get_asset_account("capital_work_in_progress_account", company=self.company) - gl_entries.append(self.get_gl_dict({ - "account": expenses_included_in_asset_valuation, - "against": asset_account, - "cost_center": item.cost_center, - "remarks": self.get("remarks") or _("Accounting Entry for Stock"), - "credit": flt(item.landed_cost_voucher_amount), - "project": item.project - }, item=item)) + remarks = self.get("remarks") or _("Accounting Entry for Stock") - gl_entries.append(self.get_gl_dict({ - "account": asset_account, - "against": expenses_included_in_asset_valuation, - "cost_center": item.cost_center, - "remarks": self.get("remarks") or _("Accounting Entry for Stock"), - "debit": flt(item.landed_cost_voucher_amount), - "project": item.project - }, item=item)) + self.add_gl_entry(gl_entries, expenses_included_in_asset_valuation, item.cost_center, 0.0, flt(item.landed_cost_voucher_amount), + remarks, asset_account, project=item.project, item=item) + + self.add_gl_entry(gl_entries, asset_account, item.cost_center, 0.0, flt(item.landed_cost_voucher_amount), + remarks, expenses_included_in_asset_valuation, project=item.project, item=item) def update_assets(self, item, valuation_rate): assets = frappe.db.get_all('Asset', From eca86290bc33773288af2110f214aca7993c497b Mon Sep 17 00:00:00 2001 From: Alan <2.alan.tom@gmail.com> Date: Fri, 14 May 2021 12:21:38 +0530 Subject: [PATCH 256/326] fix: show uom for item in selector dialog (#25697) --- erpnext/public/js/utils/serial_no_batch_selector.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js index 3333d569a7..a289ec415b 100644 --- a/erpnext/public/js/utils/serial_no_batch_selector.js +++ b/erpnext/public/js/utils/serial_no_batch_selector.js @@ -77,6 +77,14 @@ erpnext.SerialNoBatchSelector = Class.extend({ label: __(me.has_batch && !me.has_serial_no ? 'Total Qty' : 'Qty'), default: flt(me.item.stock_qty), }, + { + fieldname: 'uom', + read_only: 1, + fieldtype: 'Link', + options: 'UOM', + label: __('UOM'), + default: me.item.uom + }, { fieldname: 'auto_fetch_button', fieldtype:'Button', From 98fc4195b34f5ff47c4062352244105e8074913b Mon Sep 17 00:00:00 2001 From: Alan <2.alan.tom@gmail.com> Date: Fri, 14 May 2021 12:22:28 +0530 Subject: [PATCH 257/326] fix: send emails on rfq submit (#25695) * fix: send emails on rfq submit * fix: check if email is present for supplier --- .../doctype/request_for_quotation/request_for_quotation.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py index b530d1ab24..180ba93666 100644 --- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py +++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py @@ -62,6 +62,7 @@ class RequestforQuotation(BuyingController): for supplier in self.suppliers: supplier.email_sent = 0 supplier.quote_status = 'Pending' + self.send_to_supplier() def on_cancel(self): frappe.db.set(self, 'status', 'Cancelled') @@ -81,7 +82,7 @@ class RequestforQuotation(BuyingController): def send_to_supplier(self): """Sends RFQ mail to involved suppliers.""" for rfq_supplier in self.suppliers: - if rfq_supplier.send_email: + if rfq_supplier.email_id is not None and rfq_supplier.send_email: self.validate_email_id(rfq_supplier) # make new user if required From 2aa401826e1ac2b8627bb11e94b13fbe568c6eb7 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Fri, 14 May 2021 12:34:50 +0530 Subject: [PATCH 258/326] fix: validation message of quality inspection in purchase receipt (#25666) --- erpnext/controllers/stock_controller.py | 3 +-- .../doctype/quality_inspection/test_quality_inspection.py | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index b14c274515..41ca404d9b 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -379,8 +379,7 @@ class StockController(AccountsController): link = frappe.utils.get_link_to_form('Quality Inspection', d.quality_inspection) frappe.throw(_("Quality Inspection: {0} is not submitted for the item: {1} in row {2}").format(link, d.item_code, d.idx), QualityInspectionNotSubmittedError) - qa_failed = any([r.status=="Rejected" for r in qa_doc.readings]) - if qa_failed: + if qa_doc.status != 'Accepted': frappe.throw(_("Row {0}: Quality Inspection rejected for item {1}") .format(d.idx, d.item_code), QualityInspectionRejectedError) elif qa_required : diff --git a/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py b/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py index a7dfc9ee28..56b046a92e 100644 --- a/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py +++ b/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py @@ -27,10 +27,11 @@ class TestQualityInspection(unittest.TestCase): dn.reload() self.assertRaises(QualityInspectionRejectedError, dn.submit) - frappe.db.set_value("Quality Inspection Reading", {"parent": qa.name}, "status", "Accepted") + frappe.db.set_value("Quality Inspection", qa.name, "status", "Accepted") dn.reload() dn.submit() + qa.reload() qa.cancel() dn.reload() dn.cancel() From 55c2fec6832152813f1abd9b2b8093cd0880e9fe Mon Sep 17 00:00:00 2001 From: Alan <2.alan.tom@gmail.com> Date: Fri, 14 May 2021 12:36:41 +0530 Subject: [PATCH 259/326] feat: add pending qty section to batch/serial selector dialog (#25519) * feat: add pending qty section to batch/serial selector dialog * fix: call attach in setup and refresh, fix conditional * refactor: camel to snake casing --- .../js/utils/serial_no_batch_selector.js | 76 ++++++++++++++++++- .../stock/doctype/stock_entry/stock_entry.js | 21 +++++ 2 files changed, 96 insertions(+), 1 deletion(-) diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js index a289ec415b..b5d3981ba7 100644 --- a/erpnext/public/js/utils/serial_no_batch_selector.js +++ b/erpnext/public/js/utils/serial_no_batch_selector.js @@ -74,9 +74,10 @@ erpnext.SerialNoBatchSelector = Class.extend({ fieldname: 'qty', fieldtype:'Float', read_only: me.has_batch && !me.has_serial_no, - label: __(me.has_batch && !me.has_serial_no ? 'Total Qty' : 'Qty'), + label: __(me.has_batch && !me.has_serial_no ? 'Selected Qty' : 'Qty'), default: flt(me.item.stock_qty), }, + ...get_pending_qty_fields(me), { fieldname: 'uom', read_only: 1, @@ -181,6 +182,7 @@ erpnext.SerialNoBatchSelector = Class.extend({ if (this.has_batch && !this.has_serial_no) { this.update_total_qty(); + this.update_pending_qtys(); } this.dialog.show(); @@ -321,7 +323,21 @@ erpnext.SerialNoBatchSelector = Class.extend({ qty_field.set_input(total_qty); }, + update_pending_qtys: function() { + const pending_qty_field = this.dialog.fields_dict.pending_qty; + const total_selected_qty_field = this.dialog.fields_dict.total_selected_qty; + if (!pending_qty_field || !total_selected_qty_field) return; + + const me = this; + const required_qty = this.dialog.fields_dict.required_qty.value; + const selected_qty = this.dialog.fields_dict.qty.value; + const total_selected_qty = selected_qty + calc_total_selected_qty(me); + const pending_qty = required_qty - total_selected_qty; + + pending_qty_field.set_input(pending_qty); + total_selected_qty_field.set_input(total_selected_qty); + }, get_batch_fields: function() { var me = this; @@ -423,6 +439,7 @@ erpnext.SerialNoBatchSelector = Class.extend({ } me.update_total_qty(); + me.update_pending_qtys(); } }, ], @@ -519,3 +536,60 @@ erpnext.SerialNoBatchSelector = Class.extend({ ]; } }); + +function get_pending_qty_fields(me) { + if (!check_can_calculate_pending_qty(me)) return []; + const { frm: { doc: { fg_completed_qty }}, item: { item_code, stock_qty }} = me; + const { qty_consumed_per_unit } = erpnext.stock.bom.items[item_code]; + + const total_selected_qty = calc_total_selected_qty(me); + const required_qty = flt(fg_completed_qty) * flt(qty_consumed_per_unit); + const pending_qty = required_qty - (flt(stock_qty) + total_selected_qty); + + const pending_qty_fields = [ + { fieldtype: 'Section Break', label: __('Pending Quantity') }, + { + fieldname: 'required_qty', + read_only: 1, + fieldtype: 'Float', + label: __('Required Qty'), + default: required_qty + }, + { fieldtype: 'Column Break' }, + { + fieldname: 'total_selected_qty', + read_only: 1, + fieldtype: 'Float', + label: __('Total Selected Qty'), + default: total_selected_qty + }, + { fieldtype: 'Column Break' }, + { + fieldname: 'pending_qty', + read_only: 1, + fieldtype: 'Float', + label: __('Pending Qty'), + default: pending_qty + }, + ]; + return pending_qty_fields; +} + +function calc_total_selected_qty(me) { + const { frm: { doc: { items }}, item: { name, item_code }} = me; + const totalSelectedQty = items + .filter( item => ( item.name !== name ) && ( item.item_code === item_code ) ) + .map( item => flt(item.qty) ) + .reduce( (i, j) => i + j, 0); + return totalSelectedQty; +} + +function check_can_calculate_pending_qty(me) { + const { frm: { doc }, item } = me; + const docChecks = doc.bom_no + && doc.fg_completed_qty + && erpnext.stock.bom + && erpnext.stock.bom.name === doc.bom_no; + const itemChecks = !!item; + return docChecks && itemChecks; +} diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index 772c8df96e..daa1e51182 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -107,6 +107,7 @@ frappe.ui.form.on('Stock Entry', { frappe.flags.hide_serial_batch_dialog = true; } }); + attach_bom_items(frm.doc.bom_no); }, setup_quality_inspection: function(frm) { @@ -311,6 +312,7 @@ frappe.ui.form.on('Stock Entry', { } frm.trigger("setup_quality_inspection"); + attach_bom_items(frm.doc.bom_no) }, stock_entry_type: function(frm){ @@ -919,6 +921,7 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({ method: "get_items", callback: function(r) { if(!r.exc) refresh_field("items"); + if(me.frm.doc.bom_no) attach_bom_items(me.frm.doc.bom_no) } }); } @@ -1064,4 +1067,22 @@ erpnext.stock.select_batch_and_serial_no = (frm, item) => { } +function attach_bom_items(bom_no) { + if (check_should_not_attach_bom_items(bom_no)) return + frappe.db.get_doc("BOM",bom_no).then(bom => { + const {name, items} = bom + erpnext.stock.bom = {name, items:{}} + items.forEach(item => { + erpnext.stock.bom.items[item.item_code] = item; + }); + }); +} + +function check_should_not_attach_bom_items(bom_no) { + return ( + bom_no === undefined || + (erpnext.stock.bom && erpnext.stock.bom.name === bom_no) + ); +} + $.extend(cur_frm.cscript, new erpnext.stock.StockEntry({frm: cur_frm})); From d8de7fccc2f27efe3b45b6ae1ba89ba8ab96521a Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 14 May 2021 19:17:28 +0530 Subject: [PATCH 260/326] feat: Show net values in Party Accounts --- .../report/general_ledger/general_ledger.js | 5 ++++ .../report/general_ledger/general_ledger.py | 27 +++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/erpnext/accounts/report/general_ledger/general_ledger.js b/erpnext/accounts/report/general_ledger/general_ledger.js index fb0d359926..84f786814d 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.js +++ b/erpnext/accounts/report/general_ledger/general_ledger.js @@ -166,6 +166,11 @@ frappe.query_reports["General Ledger"] = { "fieldname": "show_cancelled_entries", "label": __("Show Cancelled Entries"), "fieldtype": "Check" + }, + { + "fieldname": "show_net_values_in_party_account", + "label": __("Show Net Values in Party Account"), + "fieldtype": "Check" } ] } diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index b5d7992604..562df4f6f7 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -344,6 +344,9 @@ def get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map): consolidated_gle = OrderedDict() group_by = group_by_field(filters.get('group_by')) + if filters.get('show_net_values_in_party_account'): + account_type_map = get_account_type_map(filters.get('company')) + def update_value_in_dict(data, key, gle): data[key].debit += flt(gle.debit) data[key].credit += flt(gle.credit) @@ -351,6 +354,24 @@ def get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map): data[key].debit_in_account_currency += flt(gle.debit_in_account_currency) data[key].credit_in_account_currency += flt(gle.credit_in_account_currency) + if filters.get('show_net_values_in_party_account') and \ + account_type_map.get(data[key].account) in ('Receivable', 'Payable'): + net_value = flt(data[key].debit) - flt(data[key].credit) + net_value_in_account_currency = flt(data[key].debit_in_account_currency) \ + - flt(data[key].credit_in_account_currency) + + if net_value < 0: + dr_or_cr = 'credit' + rev_dr_or_cr = 'debit' + else: + dr_or_cr = 'debit' + rev_dr_or_cr = 'credit' + + data[key][dr_or_cr] = abs(net_value) + data[key][dr_or_cr+'_in_account_currency'] = abs(net_value_in_account_currency) + data[key][rev_dr_or_cr] = 0 + data[key][rev_dr_or_cr+'_in_account_currency'] = 0 + if data[key].against_voucher and gle.against_voucher: data[key].against_voucher += ', ' + gle.against_voucher @@ -388,6 +409,12 @@ def get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map): return totals, entries +def get_account_type_map(company): + account_type_map = frappe._dict(frappe.get_all('Account', fields=['name', 'account_type'], + filters={'company': company}, as_list=1)) + + return account_type_map + def get_result_as_list(data, filters): balance, balance_in_account_currency = 0, 0 inv_details = get_supplier_invoice_details() From fd4743cc314c4c1f89ec2d6f7968f9fd478ae9b4 Mon Sep 17 00:00:00 2001 From: Anupam Date: Wed, 12 May 2021 16:17:32 +0530 Subject: [PATCH 261/326] refactor: timesheet --- .../doctype/sales_invoice/sales_invoice.js | 51 +- .../doctype/sales_invoice/sales_invoice.json | 3 +- .../doctype/sales_invoice/sales_invoice.py | 24 +- .../sales_invoice_timesheet.json | 233 +--- erpnext/hooks.py | 3 +- erpnext/patches.txt | 1 + .../v7_0/convert_timelog_to_timesheet.py | 2 +- .../doctype/timesheet/test_timesheet.py | 14 +- .../projects/doctype/timesheet/timesheet.js | 87 +- .../projects/doctype/timesheet/timesheet.json | 24 +- .../projects/doctype/timesheet/timesheet.py | 65 +- .../timesheet_detail/timesheet_detail.json | 1160 +++-------------- erpnext/projects/report/billing_summary.py | 4 +- ...ee_hours_utilization_based_on_timesheet.py | 6 +- .../test_employee_util.py | 4 +- .../test_project_profitability.py | 2 +- erpnext/public/js/utils.js | 12 + 17 files changed, 498 insertions(+), 1197 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index 7c73ad6c90..2b79e18358 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -685,14 +685,16 @@ frappe.ui.form.on('Sales Invoice', { }, project: function(frm){ - frm.call({ - method: "add_timesheet_data", - doc: frm.doc, - callback: function(r, rt) { - refresh_field(['timesheets']) - } - }) - frm.refresh(); + if (!frm.doc.is_return) { + frm.call({ + method: "add_timesheet_data", + doc: frm.doc, + callback: function(r, rt) { + refresh_field(['timesheets']) + } + }) + frm.refresh(); + } }, onload: function(frm) { @@ -808,27 +810,45 @@ frappe.ui.form.on('Sales Invoice', { }, refresh: function(frm) { - if (frm.doc.project) { + if (frm.doc.project && frm.doc.docstatus===0 && !frm.doc.is_return) { frm.add_custom_button(__('Fetch Timesheet'), function() { let d = new frappe.ui.Dialog({ title: __('Fetch Timesheet'), fields: [ { - "label" : "From", + "label" : __("From"), "fieldname": "from_time", "fieldtype": "Date", "reqd": 1, }, + { + "label" : __("Currency"), + "fieldname": "currency", + "fieldtype": "Link", + "options": "Currency", + "default": frm.doc.currency, + "reqd": 1, + "read_only": 1 + }, { fieldtype: 'Column Break', fieldname: 'col_break_1', }, { - "label" : "To", + "label" : __("To"), "fieldname": "to_time", "fieldtype": "Date", "reqd": 1, - } + }, + { + "label" : __("Project"), + "fieldname": "project", + "fieldtype": "Link", + "options": "Project", + "default": frm.doc.project, + "reqd": 1, + "read_only": 1 + }, ], primary_action: function() { let data = d.get_values(); @@ -837,7 +857,8 @@ frappe.ui.form.on('Sales Invoice', { args: { from_time: data.from_time, to_time: data.to_time, - project: frm.doc.project + project: data.project, + currency: data.currency }, callback: function(r) { if(!r.exc) { @@ -845,9 +866,11 @@ frappe.ui.form.on('Sales Invoice', { frm.clear_table('timesheets') r.message.forEach((d) => { frm.add_child('timesheets',{ + 'activity_type': d.activity_type, + 'description': d.description, 'time_sheet': d.parent, 'billing_hours': d.billing_hours, - 'billing_amount': d.billing_amt, + 'billing_amount': d.billing_amount, 'timesheet_detail': d.name }); }); diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index c6c67b4ddc..607e4c43d0 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -748,6 +748,7 @@ { "collapsible": 1, "collapsible_depends_on": "eval:doc.total_billing_amount > 0", + "depends_on": "eval: !doc.is_return", "fieldname": "time_sheet_list", "fieldtype": "Section Break", "hide_days": 1, @@ -1969,7 +1970,7 @@ "link_fieldname": "consolidated_invoice" } ], - "modified": "2021-04-15 23:57:58.766651", + "modified": "2021-05-13 17:53:26.185370", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index bb74a02606..a008742390 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -125,6 +125,8 @@ class SalesInvoice(SellingController): self.validate_multiple_billing("Delivery Note", "dn_detail", "amount", "items") if not self.is_return: self.validate_serial_numbers() + else: + self.timesheets = [] self.update_packing_list() self.set_billing_hours_and_amount() self.update_timesheet_billing_for_project() @@ -337,7 +339,7 @@ class SalesInvoice(SellingController): if "Healthcare" in active_domains: manage_invoice_submit_cancel(self, "on_cancel") - + self.unlink_sales_invoice_from_timesheets() self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry', 'Repost Item Valuation') def update_status_updater_args(self): @@ -393,6 +395,18 @@ class SalesInvoice(SellingController): if validate_against_credit_limit: check_credit_limit(self.customer, self.company, bypass_credit_limit_check_at_sales_order) + def unlink_sales_invoice_from_timesheets(self): + for row in self.timesheets: + timesheet = frappe.get_doc('Timesheet', row.time_sheet) + for time_log in timesheet.time_logs: + if time_log.sales_invoice == self.name: + time_log.sales_invoice = None + timesheet.calculate_total_amounts() + timesheet.calculate_percentage_billed() + timesheet.flags.ignore_validate_update_after_submit = True + timesheet.set_status() + timesheet.db_update_all() + @frappe.whitelist() def set_missing_values(self, for_validate=False): pos = self.set_pos_fields(for_validate) @@ -427,7 +441,7 @@ class SalesInvoice(SellingController): timesheet.calculate_percentage_billed() timesheet.flags.ignore_validate_update_after_submit = True timesheet.set_status() - timesheet.save() + timesheet.db_update_all() def update_time_sheet_detail(self, timesheet, args, sales_invoice): for data in timesheet.time_logs: @@ -741,8 +755,10 @@ class SalesInvoice(SellingController): self.append('timesheets', { 'time_sheet': data.parent, 'billing_hours': data.billing_hours, - 'billing_amount': data.billing_amt, - 'timesheet_detail': data.name + 'billing_amount': data.billing_amount, + 'timesheet_detail': data.name, + 'activity_type': data.activity_type, + 'description': data.description }) self.calculate_billing_amount_for_timesheet() diff --git a/erpnext/accounts/doctype/sales_invoice_timesheet/sales_invoice_timesheet.json b/erpnext/accounts/doctype/sales_invoice_timesheet/sales_invoice_timesheet.json index f7b9aef96c..9321630829 100644 --- a/erpnext/accounts/doctype/sales_invoice_timesheet/sales_invoice_timesheet.json +++ b/erpnext/accounts/doctype/sales_invoice_timesheet/sales_invoice_timesheet.json @@ -1,172 +1,77 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2016-06-14 19:21:34.321662", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", + "actions": [], + "creation": "2016-06-14 19:21:34.321662", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "activity_type", + "description", + "billing_hours", + "billing_amount", + "time_sheet", + "timesheet_detail" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "time_sheet", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 1, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Time Sheet", - "length": 0, - "no_copy": 0, - "options": "Timesheet", - "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": "time_sheet", + "fieldtype": "Link", + "in_global_search": 1, + "in_list_view": 1, + "label": "Time Sheet", + "options": "Timesheet", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "billing_hours", - "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Billing Hours", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "billing_hours", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Billing Hours", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "billing_amount", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Billing Amount", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "billing_amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Billing Amount", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "timesheet_detail", - "fieldtype": "Data", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Timesheet Detail", - "length": 0, - "no_copy": 1, - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "allow_on_submit": 1, + "fieldname": "timesheet_detail", + "fieldtype": "Data", + "hidden": 1, + "label": "Timesheet Detail", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "activity_type", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Activity Type", + "options": "Activity Type", + "read_only": 1 + }, + { + "fieldname": "description", + "fieldtype": "Small Text", + "in_list_view": 1, + "label": "Description", + "read_only": 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": 1, - "max_attachments": 0, - "modified": "2019-02-18 18:50:44.770361", - "modified_by": "Administrator", - "module": "Accounts", - "name": "Sales Invoice Timesheet", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 + ], + "istable": 1, + "links": [], + "modified": "2021-05-13 16:52:32.995266", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Sales Invoice Timesheet", + "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/hooks.py b/erpnext/hooks.py index bb6cd8bdc2..ca8ca871bd 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -304,7 +304,8 @@ doc_events = { # if payment entry not in auto cancel exempted doctypes it will cancel payment entry. auto_cancel_exempted_doctypes= [ "Payment Entry", - "Inpatient Medication Entry" + "Inpatient Medication Entry", + "Timesheet" ] after_migrate = ["erpnext.setup.install.update_select_perm_after_install"] diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 82d223cada..cec2dd5341 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -778,3 +778,4 @@ erpnext.patches.v12_0.add_ewaybill_validity_field erpnext.patches.v13_0.germany_make_custom_fields erpnext.patches.v13_0.germany_fill_debtor_creditor_number erpnext.patches.v13_0.set_pos_closing_as_failed +erpnext.patches.v13_0.rename_billable_to_is_billable_in_timesheet diff --git a/erpnext/patches/v7_0/convert_timelog_to_timesheet.py b/erpnext/patches/v7_0/convert_timelog_to_timesheet.py index 3af6622d96..8c60b5b71e 100644 --- a/erpnext/patches/v7_0/convert_timelog_to_timesheet.py +++ b/erpnext/patches/v7_0/convert_timelog_to_timesheet.py @@ -51,7 +51,7 @@ def execute(): def get_timelog_data(data): return { - 'billable': data.billable, + 'is_billable': data.billable, 'from_time': data.from_time, 'hours': data.hours, 'to_time': data.to_time, diff --git a/erpnext/projects/doctype/timesheet/test_timesheet.py b/erpnext/projects/doctype/timesheet/test_timesheet.py index d21ac0f2f0..2b0c3abdd7 100644 --- a/erpnext/projects/doctype/timesheet/test_timesheet.py +++ b/erpnext/projects/doctype/timesheet/test_timesheet.py @@ -37,7 +37,7 @@ class TestTimesheet(unittest.TestCase): emp = make_employee("test_employee_6@salary.com") make_salary_structure_for_timesheet(emp) - timesheet = make_timesheet(emp, simulate=True, billable=1) + timesheet = make_timesheet(emp, simulate=True, is_billable=1) self.assertEqual(timesheet.total_hours, 2) self.assertEqual(timesheet.total_billable_hours, 2) @@ -49,7 +49,7 @@ class TestTimesheet(unittest.TestCase): emp = make_employee("test_employee_6@salary.com") make_salary_structure_for_timesheet(emp) - timesheet = make_timesheet(emp, simulate=True, billable=0) + timesheet = make_timesheet(emp, simulate=True, is_billable=0) self.assertEqual(timesheet.total_hours, 2) self.assertEqual(timesheet.total_billable_hours, 0) @@ -61,7 +61,7 @@ class TestTimesheet(unittest.TestCase): emp = make_employee("test_employee_6@salary.com", company="_Test Company") salary_structure = make_salary_structure_for_timesheet(emp) - timesheet = make_timesheet(emp, simulate = True, billable=1) + timesheet = make_timesheet(emp, simulate = True, is_billable=1) salary_slip = make_salary_slip(timesheet.name) salary_slip.submit() @@ -82,7 +82,7 @@ class TestTimesheet(unittest.TestCase): def test_sales_invoice_from_timesheet(self): emp = make_employee("test_employee_6@salary.com") - timesheet = make_timesheet(emp, simulate=True, billable=1) + timesheet = make_timesheet(emp, simulate=True, is_billable=1) sales_invoice = make_sales_invoice(timesheet.name, '_Test Item', '_Test Customer') sales_invoice.due_date = nowdate() sales_invoice.submit() @@ -100,7 +100,7 @@ class TestTimesheet(unittest.TestCase): emp = make_employee("test_employee_6@salary.com") project = frappe.get_value("Project", {"project_name": "_Test Project"}) - timesheet = make_timesheet(emp, simulate=True, billable=1, project=project, company='_Test Company') + timesheet = make_timesheet(emp, simulate=True, is_billable=1, project=project, company='_Test Company') sales_invoice = create_sales_invoice(do_not_save=True) sales_invoice.project = project sales_invoice.submit() @@ -171,13 +171,13 @@ def make_salary_structure_for_timesheet(employee, company=None): return salary_structure -def make_timesheet(employee, simulate=False, billable = 0, activity_type="_Test Activity Type", project=None, task=None, company=None): +def make_timesheet(employee, simulate=False, is_billable = 0, activity_type="_Test Activity Type", project=None, task=None, company=None): update_activity_type(activity_type) timesheet = frappe.new_doc("Timesheet") timesheet.employee = employee timesheet.company = company or '_Test Company' timesheet_detail = timesheet.append('time_logs', {}) - timesheet_detail.billable = billable + timesheet_detail.is_billable = is_billable timesheet_detail.activity_type = activity_type timesheet_detail.from_time = now_datetime() timesheet_detail.hours = 2 diff --git a/erpnext/projects/doctype/timesheet/timesheet.js b/erpnext/projects/doctype/timesheet/timesheet.js index b123af5d18..5554ed9264 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.js +++ b/erpnext/projects/doctype/timesheet/timesheet.js @@ -90,17 +90,50 @@ frappe.ui.form.on("Timesheet", { } if(frm.doc.per_billed > 0) { frm.fields_dict["time_logs"].grid.toggle_enable("billing_hours", false); - frm.fields_dict["time_logs"].grid.toggle_enable("billable", false); + frm.fields_dict["time_logs"].grid.toggle_enable("is_billable", false); } + frm.trigger('setup_filters'); + }, + + customer: function(frm) { + frm.set_query('parent_project', function(doc) { + return { + filters: { + "customer": doc.customer + } + }; + }); + frm.set_query('project', 'time_logs', function(doc) { + return { + filters: { + "customer": doc.customer + } + }; + }); + frm.refresh(); }, make_invoice: function(frm) { + let fields = [{ + "fieldtype": "Link", + "label": __("Item Code"), + "fieldname": "item_code", + "options": "Item" + }] + + if (!frm.doc.customer) { + fields.push({ + "fieldtype": "Link", + "label": __("Customer"), + "fieldname": "customer", + "options": "Customer", + "default": frm.doc.customer + }); + } + let dialog = new frappe.ui.Dialog({ - title: __("Select Item (optional)"), - fields: [ - {"fieldtype": "Link", "label": __("Item Code"), "fieldname": "item_code", "options":"Item"}, - {"fieldtype": "Link", "label": __("Customer"), "fieldname": "customer", "options":"Customer"} - ] + title: __("Create Sales Invoice"), + fields: fields }); dialog.set_primary_action(__('Create Sales Invoice'), () => { @@ -113,7 +146,8 @@ frappe.ui.form.on("Timesheet", { args: { "source_name": frm.doc.name, "item_code": args.item_code, - "customer": args.customer + "customer": frm.doc.customer || args.customer, + "currency": frm.doc.currency }, freeze: true, callback: function(r) { @@ -136,8 +170,7 @@ frappe.ui.form.on("Timesheet", { parent_project: function(frm) { set_project_in_timelog(frm); - }, - + } }); frappe.ui.form.on("Timesheet Detail", { @@ -196,7 +229,7 @@ frappe.ui.form.on("Timesheet Detail", { calculate_billing_costing_amount(frm, cdt, cdn); }, - billable: function(frm, cdt, cdn) { + is_billable: function(frm, cdt, cdn) { update_billing_hours(frm, cdt, cdn); update_time_rates(frm, cdt, cdn); calculate_billing_costing_amount(frm, cdt, cdn); @@ -239,9 +272,9 @@ var calculate_end_time = function(frm, cdt, cdn) { } }; -var update_billing_hours = function(frm, cdt, cdn){ - var child = locals[cdt][cdn]; - if(!child.billable) { +var update_billing_hours = function(frm, cdt, cdn) { + let child = frappe.get_doc(cdt, cdn); + if (!child.is_billable) { frappe.model.set_value(cdt, cdn, 'billing_hours', 0.0); } else { // bill all hours by default @@ -249,19 +282,19 @@ var update_billing_hours = function(frm, cdt, cdn){ } }; -var update_time_rates = function(frm, cdt, cdn){ - var child = locals[cdt][cdn]; - if(!child.billable){ +var update_time_rates = function(frm, cdt, cdn) { + let child = frappe.get_doc(cdt, cdn); + if (!child.is_billable) { frappe.model.set_value(cdt, cdn, 'billing_rate', 0.0); } }; -var calculate_billing_costing_amount = function(frm, cdt, cdn){ - var child = locals[cdt][cdn]; - var billing_amount = 0.0; - var costing_amount = 0.0; +var calculate_billing_costing_amount = function(frm, cdt, cdn) { + let child = frappe.get_doc(cdt, cdn); + let billing_amount = 0.0; + let costing_amount = 0.0; - if(child.billing_hours && child.billable){ + if (child.billing_hours && child.is_billable) { billing_amount = (child.billing_hours * child.billing_rate); } costing_amount = flt(child.costing_rate * child.hours); @@ -271,18 +304,18 @@ var calculate_billing_costing_amount = function(frm, cdt, cdn){ }; var calculate_time_and_amount = function(frm) { - var tl = frm.doc.time_logs || []; - var total_working_hr = 0; - var total_billing_hr = 0; - var total_billable_amount = 0; - var total_costing_amount = 0; + let tl = frm.doc.time_logs || []; + let total_working_hr = 0; + let total_billing_hr = 0; + let total_billable_amount = 0; + let total_costing_amount = 0; for(var i=0; i Date: Sat, 15 May 2021 20:40:20 +0530 Subject: [PATCH 262/326] adding patch --- .../v13_0/rename_billable_to_is_billable_in_timesheet.py | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 erpnext/patches/v13_0/rename_billable_to_is_billable_in_timesheet.py diff --git a/erpnext/patches/v13_0/rename_billable_to_is_billable_in_timesheet.py b/erpnext/patches/v13_0/rename_billable_to_is_billable_in_timesheet.py new file mode 100644 index 0000000000..6860a37559 --- /dev/null +++ b/erpnext/patches/v13_0/rename_billable_to_is_billable_in_timesheet.py @@ -0,0 +1,7 @@ +from __future__ import unicode_literals +import frappe +from frappe.model.utils.rename_field import rename_field + +def execute(): + if "billable" in frappe.db.get_table_columns("Timesheet Detail"): + rename_field("Timesheet Detail", "billable", "is_billable") \ No newline at end of file From 42d2f663fa9f22542b2ef29a39f667774d56a907 Mon Sep 17 00:00:00 2001 From: Anupam Date: Mon, 17 May 2021 11:08:26 +0530 Subject: [PATCH 263/326] fix: sider fixes --- erpnext/projects/doctype/timesheet/timesheet.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/projects/doctype/timesheet/timesheet.js b/erpnext/projects/doctype/timesheet/timesheet.js index 5554ed9264..28535d7a34 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.js +++ b/erpnext/projects/doctype/timesheet/timesheet.js @@ -119,7 +119,7 @@ frappe.ui.form.on("Timesheet", { "label": __("Item Code"), "fieldname": "item_code", "options": "Item" - }] + }]; if (!frm.doc.customer) { fields.push({ From 41ac8be6f20c490c2700de884257314a07a6ba63 Mon Sep 17 00:00:00 2001 From: noahjacob Date: Mon, 17 May 2021 13:58:27 +0530 Subject: [PATCH 264/326] refactor: base_amount field moved below amount field and renamed --- .../landed_cost_taxes_and_charges.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/stock/doctype/landed_cost_taxes_and_charges/landed_cost_taxes_and_charges.json b/erpnext/stock/doctype/landed_cost_taxes_and_charges/landed_cost_taxes_and_charges.json index 4fcdb4c10c..9c59c13ac0 100644 --- a/erpnext/stock/doctype/landed_cost_taxes_and_charges/landed_cost_taxes_and_charges.json +++ b/erpnext/stock/doctype/landed_cost_taxes_and_charges/landed_cost_taxes_and_charges.json @@ -10,8 +10,8 @@ "exchange_rate", "description", "col_break3", - "base_amount", - "amount" + "amount", + "base_amount" ], "fields": [ { @@ -59,7 +59,7 @@ { "fieldname": "base_amount", "fieldtype": "Currency", - "label": "Base Amount", + "label": "Amount (Company Currency)", "options": "Company:company:default_currency", "read_only": 1 } @@ -67,7 +67,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2020-12-26 01:07:23.233604", + "modified": "2021-05-17 13:57:10.807980", "modified_by": "Administrator", "module": "Stock", "name": "Landed Cost Taxes and Charges", From f3b3d81e0bba3996c11b8e5aa93a2aec3a04ac5b Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 17 May 2021 16:43:38 +0530 Subject: [PATCH 265/326] fix: escape company name in deferred_revenue --- erpnext/accounts/deferred_revenue.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/deferred_revenue.py b/erpnext/accounts/deferred_revenue.py index d5ab1c1704..0f47b953af 100644 --- a/erpnext/accounts/deferred_revenue.py +++ b/erpnext/accounts/deferred_revenue.py @@ -41,7 +41,7 @@ def build_conditions(process_type, account, company): if account: conditions += "AND %s='%s'"%(deferred_account, account) elif company: - conditions += "AND p.company='%s'"%(company) + conditions += f"AND p.company = {frappe.db.escape(company)}" return conditions From b6783b158f1f4346fb5fab3a9f0110e91bdff221 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 17 May 2021 17:06:12 +0530 Subject: [PATCH 266/326] chore: translation fixes --- erpnext/accounts/deferred_revenue.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/deferred_revenue.py b/erpnext/accounts/deferred_revenue.py index 0f47b953af..dd346bc240 100644 --- a/erpnext/accounts/deferred_revenue.py +++ b/erpnext/accounts/deferred_revenue.py @@ -360,12 +360,10 @@ def make_gl_entries(doc, credit_account, debit_account, against, frappe.flags.deferred_accounting_error = True def send_mail(deferred_process): - title = _("Error while processing deferred accounting for {0}".format(deferred_process)) - content = _(""" - Deferred accounting failed for some invoices: - Please check Process Deferred Accounting {0} - and submit manually after resolving errors - """).format(get_link_to_form('Process Deferred Accounting', deferred_process)) + title = _("Error while processing deferred accounting for {0}").format(deferred_process) + link = get_link_to_form('Process Deferred Accounting', deferred_process) + content = _("Deferred accounting failed for some invoices:") + "\n" + content += _("Please check Process Deferred Accounting {0} and submit manually after resolving errors.").format(link) sendmail_to_system_managers(title, content) def book_revenue_via_journal_entry(doc, credit_account, debit_account, against, From ca2fb47d445a6cfeacf67fcd5538ed385105dd83 Mon Sep 17 00:00:00 2001 From: noahjacob Date: Wed, 12 May 2021 16:25:07 +0530 Subject: [PATCH 267/326] feat: updates item_code filters if item_group is linked to supplier --- erpnext/controllers/queries.py | 15 +++++++++++++-- erpnext/public/js/controllers/buying.js | 4 ++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index bc1ac5ea06..e71b056c6e 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -204,7 +204,7 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals if "description" in searchfields: searchfields.remove("description") - + columns = '' extra_searchfields = [field for field in searchfields if not field in ["name", "item_group", "description"]] @@ -216,11 +216,22 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals if not field in searchfields] searchfields = " or ".join([field + " like %(txt)s" for field in searchfields]) + if filters.get('supplier'): + item_group_list = frappe.get_list('Supplier Item Group', filters = {'supplier': filters.get('supplier')}, fields = ['item_group']) + + item_groups = [] + for i in item_group_list: + item_groups.append(i.item_group) + + del filters['supplier'] + + if item_groups: + filters['item_group'] = ['in', item_groups] + description_cond = '' if frappe.db.count('Item', cache=True) < 50000: # scan description only if items are less than 50000 description_cond = 'or tabItem.description LIKE %(txt)s' - return frappe.db.sql("""select tabItem.name, if(length(tabItem.item_name) > 40, concat(substr(tabItem.item_name, 1, 40), "..."), item_name) as item_name, diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js index cdfd909b04..e7dcd41068 100644 --- a/erpnext/public/js/controllers/buying.js +++ b/erpnext/public/js/controllers/buying.js @@ -84,13 +84,13 @@ erpnext.buying.BuyingController = erpnext.TransactionController.extend({ if (me.frm.doc.is_subcontracted == "Yes") { return{ query: "erpnext.controllers.queries.item_query", - filters:{ 'is_sub_contracted_item': 1 } + filters:{ 'supplier': me.frm.doc.supplier, 'is_sub_contracted_item': 1 } } } else { return{ query: "erpnext.controllers.queries.item_query", - filters: {'is_purchase_item': 1} + filters: { 'supplier': me.frm.doc.supplier, 'is_purchase_item': 1 } } } }); From 0ab0fcdd51cd4b982c67518217d5ca77fa4ea4f0 Mon Sep 17 00:00:00 2001 From: noahjacob Date: Tue, 18 May 2021 15:21:24 +0530 Subject: [PATCH 268/326] feat: added supplier item group link in supplier dashboard --- erpnext/buying/doctype/supplier/supplier.json | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/erpnext/buying/doctype/supplier/supplier.json b/erpnext/buying/doctype/supplier/supplier.json index 4cc5753cbd..38b8dfdf48 100644 --- a/erpnext/buying/doctype/supplier/supplier.json +++ b/erpnext/buying/doctype/supplier/supplier.json @@ -383,8 +383,14 @@ "icon": "fa fa-user", "idx": 370, "image_field": "image", - "links": [], - "modified": "2021-01-06 19:51:40.939087", + "links": [ + { + "group": "Item Group", + "link_doctype": "Supplier Item Group", + "link_fieldname": "supplier" + } + ], + "modified": "2021-05-18 15:10:11.087191", "modified_by": "Administrator", "module": "Buying", "name": "Supplier", From bdba064fa6f76a98d5d0d7822c7648a6f24d8491 Mon Sep 17 00:00:00 2001 From: Devin Slauenwhite Date: Tue, 18 May 2021 06:42:53 -0400 Subject: [PATCH 269/326] fix: duplicate stock entry (#25486) Co-authored-by: Ankush Menat --- erpnext/stock/dashboard/item_dashboard.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/stock/dashboard/item_dashboard.js b/erpnext/stock/dashboard/item_dashboard.js index 933ca8ab3d..db0573954f 100644 --- a/erpnext/stock/dashboard/item_dashboard.js +++ b/erpnext/stock/dashboard/item_dashboard.js @@ -230,6 +230,7 @@ erpnext.stock.move_item = function (item, source, target, actual_qty, rate, call }, ], }); + var submitted = false; dialog.show(); dialog.get_field('item_code').set_input(item); @@ -253,6 +254,7 @@ erpnext.stock.move_item = function (item, source, target, actual_qty, rate, call } dialog.set_primary_action(__('Submit'), function () { + if(submitted) return; var values = dialog.get_values(); if (!values) { return; @@ -265,6 +267,7 @@ erpnext.stock.move_item = function (item, source, target, actual_qty, rate, call frappe.msgprint(__('Source and target warehouse must be different')); } + submitted = true; frappe.call({ method: 'erpnext.stock.doctype.stock_entry.stock_entry_utils.make_stock_entry', args: values, From 042b8524ccb25b0930313fd13fbf2844c2ac5341 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 18 May 2021 16:31:55 +0530 Subject: [PATCH 270/326] fix: disable submit button to avoid multiple calls --- erpnext/stock/dashboard/item_dashboard.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/stock/dashboard/item_dashboard.js b/erpnext/stock/dashboard/item_dashboard.js index db0573954f..dfb9e44283 100644 --- a/erpnext/stock/dashboard/item_dashboard.js +++ b/erpnext/stock/dashboard/item_dashboard.js @@ -271,7 +271,9 @@ erpnext.stock.move_item = function (item, source, target, actual_qty, rate, call frappe.call({ method: 'erpnext.stock.doctype.stock_entry.stock_entry_utils.make_stock_entry', args: values, + btn: dialog.get_primary_btn(), freeze: true, + freeze_message: __('Creating Stock Entry'), callback: function (r) { frappe.show_alert(__('Stock Entry {0} created', ['' + r.message.name + ''])); From ecbb8cbc844ca451b76fb0fa4fdc30fd7592aad2 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 18 May 2021 16:33:15 +0530 Subject: [PATCH 271/326] revert: "fix: duplicate stock entry (#25486)" Not required anymore, submit button is disabled. --- erpnext/stock/dashboard/item_dashboard.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/erpnext/stock/dashboard/item_dashboard.js b/erpnext/stock/dashboard/item_dashboard.js index dfb9e44283..a657ecf105 100644 --- a/erpnext/stock/dashboard/item_dashboard.js +++ b/erpnext/stock/dashboard/item_dashboard.js @@ -230,7 +230,6 @@ erpnext.stock.move_item = function (item, source, target, actual_qty, rate, call }, ], }); - var submitted = false; dialog.show(); dialog.get_field('item_code').set_input(item); @@ -254,7 +253,6 @@ erpnext.stock.move_item = function (item, source, target, actual_qty, rate, call } dialog.set_primary_action(__('Submit'), function () { - if(submitted) return; var values = dialog.get_values(); if (!values) { return; @@ -267,7 +265,6 @@ erpnext.stock.move_item = function (item, source, target, actual_qty, rate, call frappe.msgprint(__('Source and target warehouse must be different')); } - submitted = true; frappe.call({ method: 'erpnext.stock.doctype.stock_entry.stock_entry_utils.make_stock_entry', args: values, From 426b04003cba3935ca1bf4f369f9f4b8b2ffed84 Mon Sep 17 00:00:00 2001 From: Syed Mujeer Hashmi Date: Sat, 15 May 2021 12:12:26 +0530 Subject: [PATCH 272/326] fix: Project filter for Kanban Board Signed-off-by: Syed Mujeer Hashmi --- erpnext/projects/doctype/project/project.js | 2 +- erpnext/projects/doctype/project/project.py | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/erpnext/projects/doctype/project/project.js b/erpnext/projects/doctype/project/project.js index c5265e23c0..31460f66ea 100644 --- a/erpnext/projects/doctype/project/project.js +++ b/erpnext/projects/doctype/project/project.js @@ -87,7 +87,7 @@ frappe.ui.form.on("Project", { frm.add_custom_button(__("Kanban Board"), () => { frappe.call('erpnext.projects.doctype.project.project.create_kanban_board_if_not_exists', { - project: frm.doc.project_name + project: frm.doc.name }).then(() => { frappe.set_route('List', 'Task', 'Kanban', frm.doc.project_name); }); diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py index 55c5149a9c..c8fbe0bf7b 100644 --- a/erpnext/projects/doctype/project/project.py +++ b/erpnext/projects/doctype/project/project.py @@ -523,8 +523,9 @@ def update_project_sales_billing(): def create_kanban_board_if_not_exists(project): from frappe.desk.doctype.kanban_board.kanban_board import quick_kanban_board - if not frappe.db.exists('Kanban Board', project): - quick_kanban_board('Task', project, 'status', project) + project = frappe.get_doc('Project', project) + if not frappe.db.exists('Kanban Board', project.project_name): + quick_kanban_board('Task', project.project_name, 'status', project.name) return True From 8d7d4b0ba7d92551957505923f57eef0e47a2840 Mon Sep 17 00:00:00 2001 From: Saqib Date: Tue, 18 May 2021 18:39:35 +0530 Subject: [PATCH 273/326] fix: expected amount in pos closing payments table (#25737) --- .../pos_closing_entry/pos_closing_entry.js | 39 ++++++++++++------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js index aa0c53e228..8c5a34a0d8 100644 --- a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js +++ b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js @@ -101,15 +101,24 @@ frappe.ui.form.on('POS Closing Entry', { }, before_save: function(frm) { + frm.set_value("grand_total", 0); + frm.set_value("net_total", 0); + frm.set_value("total_quantity", 0); + frm.set_value("taxes", []); + + for (let row of frm.doc.payment_reconciliation) { + row.expected_amount = 0; + } + for (let row of frm.doc.pos_transactions) { frappe.db.get_doc("POS Invoice", row.pos_invoice).then(doc => { - cur_frm.doc.grand_total -= flt(doc.grand_total); - cur_frm.doc.net_total -= flt(doc.net_total); - cur_frm.doc.total_quantity -= flt(doc.total_qty); - refresh_payments(doc, cur_frm, 1); - refresh_taxes(doc, cur_frm, 1); - refresh_fields(cur_frm); - set_html_data(cur_frm); + frm.doc.grand_total += flt(doc.grand_total); + frm.doc.net_total += flt(doc.net_total); + frm.doc.total_quantity += flt(doc.total_qty); + refresh_payments(doc, frm); + refresh_taxes(doc, frm); + refresh_fields(frm); + set_html_data(frm); }); } } @@ -118,7 +127,7 @@ frappe.ui.form.on('POS Closing Entry', { frappe.ui.form.on('POS Closing Entry Detail', { closing_amount: (frm, cdt, cdn) => { const row = locals[cdt][cdn]; - frappe.model.set_value(cdt, cdn, "difference", flt(row.expected_amount - row.closing_amount)) + frappe.model.set_value(cdt, cdn, "difference", flt(row.expected_amount - row.closing_amount)); } }) @@ -142,28 +151,28 @@ function add_to_pos_transaction(d, frm) { }) } -function refresh_payments(d, frm, remove) { +function refresh_payments(d, frm) { d.payments.forEach(p => { const payment = frm.doc.payment_reconciliation.find(pay => pay.mode_of_payment === p.mode_of_payment); if (payment) { - if (!remove) payment.expected_amount += flt(p.amount); - else payment.expected_amount -= flt(p.amount); + payment.expected_amount += flt(p.amount); + payment.difference = payment.closing_amount - payment.expected_amount; } else { frm.add_child("payment_reconciliation", { mode_of_payment: p.mode_of_payment, opening_amount: 0, - expected_amount: p.amount + expected_amount: p.amount, + closing_amount: 0 }) } }) } -function refresh_taxes(d, frm, remove) { +function refresh_taxes(d, frm) { d.taxes.forEach(t => { const tax = frm.doc.taxes.find(tx => tx.account_head === t.account_head && tx.rate === t.rate); if (tax) { - if (!remove) tax.amount += flt(t.tax_amount); - else tax.amount -= flt(t.tax_amount); + tax.amount += flt(t.tax_amount); } else { frm.add_child("taxes", { account_head: t.account_head, From 90f7ec840cf97ffdc8c523a33f3c3ba667eb7b0e Mon Sep 17 00:00:00 2001 From: anushka19 <37659765+anushka19@users.noreply.github.com> Date: Tue, 18 May 2021 22:21:42 +0530 Subject: [PATCH 274/326] fix: Accumulated depreciation (#25748) * fix: Accumulated depreciation * fix: Sider issues --- erpnext/assets/doctype/asset_category/asset_category.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/assets/doctype/asset_category/asset_category.js b/erpnext/assets/doctype/asset_category/asset_category.js index 74963c2aa9..51ce157a81 100644 --- a/erpnext/assets/doctype/asset_category/asset_category.js +++ b/erpnext/assets/doctype/asset_category/asset_category.js @@ -4,7 +4,7 @@ frappe.ui.form.on('Asset Category', { onload: function(frm) { frm.add_fetch('company_name', 'accumulated_depreciation_account', 'accumulated_depreciation_account'); - frm.add_fetch('company_name', 'depreciation_expense_account', 'accumulated_depreciation_account'); + frm.add_fetch('company_name', 'depreciation_expense_account', 'depreciation_expense_account'); frm.set_query('fixed_asset_account', 'accounts', function(doc, cdt, cdn) { var d = locals[cdt][cdn]; From 9bd779401d23902d161c70502ce091548989a37e Mon Sep 17 00:00:00 2001 From: Anupam Date: Tue, 18 May 2021 22:41:28 +0530 Subject: [PATCH 275/326] added multi-currency fields --- .../doctype/activity_type/activity_type.js | 4 + .../projects/doctype/timesheet/timesheet.js | 81 +++++++++++++++---- .../projects/doctype/timesheet/timesheet.py | 17 +++- .../timesheet_detail/timesheet_detail.json | 36 ++++++++- 4 files changed, 115 insertions(+), 23 deletions(-) diff --git a/erpnext/projects/doctype/activity_type/activity_type.js b/erpnext/projects/doctype/activity_type/activity_type.js index 7eb3571af1..f1ba882812 100644 --- a/erpnext/projects/doctype/activity_type/activity_type.js +++ b/erpnext/projects/doctype/activity_type/activity_type.js @@ -1,4 +1,8 @@ frappe.ui.form.on("Activity Type", { + onload: function(frm) { + frm.set_currency_labels(["billing_rate", "costing_rate"], frappe.defaults.get_global_default('currency')); + }, + refresh: function(frm) { frm.add_custom_button(__("Activity Cost per Employee"), function() { frappe.route_options = {"activity_type": frm.doc.name}; diff --git a/erpnext/projects/doctype/timesheet/timesheet.js b/erpnext/projects/doctype/timesheet/timesheet.js index 28535d7a34..532d64994f 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.js +++ b/erpnext/projects/doctype/timesheet/timesheet.js @@ -93,6 +93,7 @@ frappe.ui.form.on("Timesheet", { frm.fields_dict["time_logs"].grid.toggle_enable("is_billable", false); } frm.trigger('setup_filters'); + frm.trigger('set_dynamic_field_label'); }, customer: function(frm) { @@ -113,6 +114,48 @@ frappe.ui.form.on("Timesheet", { frm.refresh(); }, + currency: function(frm) { + let base_currency = frappe.defaults.get_global_default('currency'); + if (base_currency != frm.doc.company) { + frappe.call({ + method: "erpnext.setup.utils.get_exchange_rate", + args: { + from_currency: frm.doc.currency, + to_currency: base_currency + }, + callback: function(r) { + if (r.message) { + frm.set_value('exchange_rate', flt(r.message)); + frm.set_df_property("exchange_rate", "description", "1 " + frm.doc.currency + " = [?] " + base_currency); + } + } + }); + } + frm.trigger('set_dynamic_field_label'); + }, + + exchange_rate: function(frm) { + $.each(frm.doc.time_logs, function(i, d) { + calculate_billing_costing_amount(frm, d.doctype, d.name); + }); + calculate_time_and_amount(frm); + }, + + set_dynamic_field_label: function(frm) { + let base_currency = frappe.defaults.get_global_default('currency'); + frm.set_currency_labels(["base_total_costing_amount", "base_total_billable_amount", "base_total_billed_amount"], base_currency); + frm.set_currency_labels(["total_costing_amount", "total_billable_amount", "total_billed_amount"], frm.doc.currency); + frm.set_currency_labels(["base_billing_rate", "base_billing_amount", "base_costing_rate", "base_costing_amount"], base_currency, "time_logs"); + frm.set_currency_labels(["billing_rate", "billing_amount", "costing_rate", "costing_amount"], frm.doc.currency, "time_logs"); + + let time_logs_grid = frm.fields_dict.time_logs.grid; + $.each(["base_billing_rate", "base_billing_amount", "base_costing_rate", "base_costing_amount"], function(i, d) { + if(frappe.meta.get_docfield(time_logs_grid.doctype, d)) + time_logs_grid.set_column_disp(d, frm.doc.currency != base_currency); + }); + frm.refresh_fields(); + }, + make_invoice: function(frm) { let fields = [{ "fieldtype": "Link", @@ -204,35 +247,34 @@ frappe.ui.form.on("Timesheet Detail", { if(frm.doc.parent_project) { frappe.model.set_value(cdt, cdn, 'project', frm.doc.parent_project); } - - var $trigger_again = $('.form-grid').find('.grid-row').find('.btn-open-row'); - $trigger_again.on('click', () => { - $('.form-grid') - .find('[data-fieldname="timer"]') - .append(frappe.render_template("timesheet")); - frm.trigger("control_timer"); - }); }, + hours: function(frm, cdt, cdn) { calculate_end_time(frm, cdt, cdn); + calculate_billing_costing_amount(frm, cdt, cdn); + calculate_time_and_amount(frm); }, billing_hours: function(frm, cdt, cdn) { calculate_billing_costing_amount(frm, cdt, cdn); + calculate_time_and_amount(frm); }, billing_rate: function(frm, cdt, cdn) { calculate_billing_costing_amount(frm, cdt, cdn); + calculate_time_and_amount(frm); }, costing_rate: function(frm, cdt, cdn) { calculate_billing_costing_amount(frm, cdt, cdn); + calculate_time_and_amount(frm); }, is_billable: function(frm, cdt, cdn) { update_billing_hours(frm, cdt, cdn); update_time_rates(frm, cdt, cdn); calculate_billing_costing_amount(frm, cdt, cdn); + calculate_time_and_amount(frm); }, activity_type: function(frm, cdt, cdn) { @@ -240,7 +282,8 @@ frappe.ui.form.on("Timesheet Detail", { method: "erpnext.projects.doctype.timesheet.timesheet.get_activity_cost", args: { employee: frm.doc.employee, - activity_type: frm.selected_doc.activity_type + activity_type: frm.selected_doc.activity_type, + currency: frm.doc.currency }, callback: function(r){ if(r.message){ @@ -290,17 +333,21 @@ var update_time_rates = function(frm, cdt, cdn) { }; var calculate_billing_costing_amount = function(frm, cdt, cdn) { - let child = frappe.get_doc(cdt, cdn); + let row = frappe.get_doc(cdt, cdn); let billing_amount = 0.0; - let costing_amount = 0.0; - - if (child.billing_hours && child.is_billable) { - billing_amount = (child.billing_hours * child.billing_rate); + let base_billing_amount = 0.0; + let exchange_rate = flt(frm.doc.exchange_rate); + frappe.model.set_value(cdt, cdn, 'base_billing_rate', flt(row.billing_rate) * exchange_rate); + frappe.model.set_value(cdt, cdn, 'base_costing_rate', flt(row.costing_rate) * exchange_rate); + if (row.billing_hours && row.is_billable) { + base_billing_amount = flt(row.billing_hours) * flt(row.base_billing_rate); + billing_amount = flt(row.billing_hours) * flt(row.billing_rate); } - costing_amount = flt(child.costing_rate * child.hours); + + frappe.model.set_value(cdt, cdn, 'base_billing_amount', base_billing_amount); + frappe.model.set_value(cdt, cdn, 'base_costing_amount', flt(row.base_costing_rate) * flt(row.hours)); frappe.model.set_value(cdt, cdn, 'billing_amount', billing_amount); - frappe.model.set_value(cdt, cdn, 'costing_amount', costing_amount); - calculate_time_and_amount(frm); + frappe.model.set_value(cdt, cdn, 'costing_amount', flt(row.costing_rate) * flt(row.hours)); }; var calculate_time_and_amount = function(frm) { diff --git a/erpnext/projects/doctype/timesheet/timesheet.py b/erpnext/projects/doctype/timesheet/timesheet.py index d864c75279..1ee59aef8b 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.py +++ b/erpnext/projects/doctype/timesheet/timesheet.py @@ -14,6 +14,7 @@ from frappe.model.document import Document from erpnext.manufacturing.doctype.workstation.workstation import (check_if_within_operating_hours, WorkstationHolidayError) from erpnext.manufacturing.doctype.manufacturing_settings.manufacturing_settings import get_mins_between_operations +from erpnext.setup.utils import get_exchange_rate class OverlapError(frappe.ValidationError): pass class OverWorkLoggedError(frappe.ValidationError): pass @@ -37,9 +38,9 @@ class Timesheet(Document): self.total_hours = 0.0 self.total_billable_hours = 0.0 self.total_billed_hours = 0.0 - self.total_billable_amount = 0.0 - self.total_costing_amount = 0.0 - self.total_billed_amount = 0.0 + self.total_billable_amount = self.base_total_billable_amount = 0.0 + self.total_costing_amount = self.base_total_costing_amount = 0.0 + self.total_billed_amount = self.base_total_billed_amount = 0.0 for d in self.get("time_logs"): self.update_billing_hours(d) @@ -47,10 +48,13 @@ class Timesheet(Document): self.total_hours += flt(d.hours) self.total_costing_amount += flt(d.costing_amount) + self.base_total_costing_amount += flt(d.base_costing_amount) if d.is_billable: self.total_billable_hours += flt(d.billing_hours) self.total_billable_amount += flt(d.billing_amount) + self.base_total_billable_amount += flt(d.base_billing_amount) self.total_billed_amount += flt(d.billing_amount) if d.sales_invoice else 0.0 + self.base_total_billed_amount += flt(d.base_billing_amount) if d.sales_invoice else 0.0 self.total_billed_hours += flt(d.billing_hours) if d.sales_invoice else 0.0 def calculate_percentage_billed(self): @@ -330,12 +334,17 @@ def set_missing_values(time_sheet, target): }) @frappe.whitelist() -def get_activity_cost(employee=None, activity_type=None): +def get_activity_cost(employee=None, activity_type=None, currency=None): + base_currency = frappe.defaults.get_global_default('currency') rate = frappe.db.get_values("Activity Cost", {"employee": employee, "activity_type": activity_type}, ["costing_rate", "billing_rate"], as_dict=True) if not rate: rate = frappe.db.get_values("Activity Type", {"activity_type": activity_type}, ["costing_rate", "billing_rate"], as_dict=True) + if rate and currency and currency!=base_currency: + exchnage_rate = get_exchange_rate(base_currency, currency) + rate[0]["costing_rate"] = rate[0]["costing_rate"] * exchnage_rate + rate[0]["billing_rate"] = rate[0]["billing_rate"] * exchnage_rate return rate[0] if rate else {} diff --git a/erpnext/projects/doctype/timesheet_detail/timesheet_detail.json b/erpnext/projects/doctype/timesheet_detail/timesheet_detail.json index 0c9ed0bf20..ee04c612c9 100644 --- a/erpnext/projects/doctype/timesheet_detail/timesheet_detail.json +++ b/erpnext/projects/doctype/timesheet_detail/timesheet_detail.json @@ -31,9 +31,13 @@ "column_break_8", "billing_hours", "section_break_11", + "base_billing_rate", + "base_billing_amount", + "base_costing_rate", + "base_costing_amount", + "column_break_14", "billing_rate", "billing_amount", - "column_break_14", "costing_rate", "costing_amount" ], @@ -230,12 +234,40 @@ "fieldname": "description", "fieldtype": "Small Text", "label": "Description" + }, + { + "fieldname": "base_billing_rate", + "fieldtype": "Currency", + "label": "Billing Rate", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "base_billing_amount", + "fieldtype": "Currency", + "label": "Billing Amount", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "base_costing_rate", + "fieldtype": "Currency", + "label": "Costing Rate", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "base_costing_amount", + "fieldtype": "Currency", + "label": "Costing Amount", + "print_hide": 1, + "read_only": 1 } ], "idx": 1, "istable": 1, "links": [], - "modified": "2021-05-15 16:16:10.688694", + "modified": "2021-05-18 12:19:33.205940", "modified_by": "Administrator", "module": "Projects", "name": "Timesheet Detail", From 5456873641be6ef354eefe01c7e37dd36962a336 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Sun, 16 May 2021 16:48:44 +0530 Subject: [PATCH 276/326] fix: run scheduler for reposting if there is no scheduler is running for the reposting --- .../repost_item_valuation/repost_item_valuation.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py index 27b8729ea0..5b626ea345 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py @@ -4,8 +4,9 @@ from __future__ import unicode_literals import frappe, erpnext +from rq.timeouts import JobTimeoutException from frappe.model.document import Document -from frappe.utils import cint, get_link_to_form, add_to_date, now, today +from frappe.utils import cint, get_link_to_form, add_to_date, now, today, time_diff_in_hours from erpnext.stock.stock_ledger import repost_future_sle from erpnext.accounts.utils import update_gl_entries_after, check_if_stock_and_account_balance_synced from frappe.utils.user import get_users_with_role @@ -57,7 +58,8 @@ def repost(doc): repost_gl_entries(doc) doc.set_status('Completed') - except Exception: + + except (Exception, JobTimeoutException): frappe.db.rollback() traceback = frappe.get_traceback() frappe.log_error(traceback) @@ -113,6 +115,12 @@ def notify_error_to_stock_managers(doc, traceback): frappe.sendmail(recipients=recipients, subject=subject, message=message) def repost_entries(): + job_log = frappe.get_all('Scheduled Job Log', fields = ['status', 'creation'], + filters = {'scheduled_job_type': 'repost_item_valuation.repost_entries'}, order_by='creation desc', limit=1) + + if job_log and job_log[0]['status'] == 'Start' and time_diff_in_hours(now(), job_log[0]['creation']) < 2: + return + riv_entries = get_repost_item_valuation_entries() for row in riv_entries: From f79ef5d8cf91d9c4a3d7d390e024c47cb1ec963c Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 19 May 2021 13:04:44 +0530 Subject: [PATCH 277/326] fix: missing cost center message on creating gl entries --- erpnext/accounts/doctype/gl_entry/gl_entry.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py index 78febf9c2e..948c51364e 100644 --- a/erpnext/accounts/doctype/gl_entry/gl_entry.py +++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py @@ -75,8 +75,13 @@ class GLEntry(Document): def pl_must_have_cost_center(self): if frappe.get_cached_value("Account", self.account, "report_type") == "Profit and Loss": if not self.cost_center and self.voucher_type != 'Period Closing Voucher': - frappe.throw(_("{0} {1}: Cost Center is required for 'Profit and Loss' account {2}. Please set up a default Cost Center for the Company.") - .format(self.voucher_type, self.voucher_no, self.account)) + msg = _("{0} {1}: Cost Center is required for 'Profit and Loss' account {2}.").format( + self.voucher_type, self.voucher_no, self.account) + msg += " " + msg += _("Please set the cost center field in {0} or setup a default Cost Center for the Company.").format( + self.voucher_type) + + frappe.throw(msg, title=_("Missing Cost Center")) def validate_dimensions_for_pl_and_bs(self): account_type = frappe.db.get_value("Account", self.account, "report_type") From 3768216dca811aea0bddcffcf08ef8199c42747d Mon Sep 17 00:00:00 2001 From: noahjacob Date: Wed, 19 May 2021 13:50:35 +0530 Subject: [PATCH 278/326] refactor: updated permissions and mandatory fields --- .../supplier_item_group.json | 32 +++++++++++++++++-- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/erpnext/buying/doctype/supplier_item_group/supplier_item_group.json b/erpnext/buying/doctype/supplier_item_group/supplier_item_group.json index 1417ec23cf..1971458f61 100644 --- a/erpnext/buying/doctype/supplier_item_group/supplier_item_group.json +++ b/erpnext/buying/doctype/supplier_item_group/supplier_item_group.json @@ -14,19 +14,21 @@ "fieldtype": "Link", "in_list_view": 1, "label": "Supplier", - "options": "Supplier" + "options": "Supplier", + "reqd": 1 }, { "fieldname": "item_group", "fieldtype": "Link", "in_list_view": 1, "label": "Item Group", - "options": "Item Group" + "options": "Item Group", + "reqd": 1 } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2021-05-07 18:16:40.621421", + "modified": "2021-05-19 13:48:16.742303", "modified_by": "Administrator", "module": "Buying", "name": "Supplier Item Group", @@ -43,6 +45,30 @@ "role": "System Manager", "share": 1, "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Purchase User", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Purchase Manager", + "share": 1, + "write": 1 } ], "sort_field": "modified", From 8e34c49ac9130a7b523c83cb8cc0c005421e545c Mon Sep 17 00:00:00 2001 From: noahjacob Date: Wed, 19 May 2021 13:51:36 +0530 Subject: [PATCH 279/326] refactor: using get_all instead of get_list --- erpnext/controllers/queries.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index e71b056c6e..f5668c51fe 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -217,7 +217,7 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals searchfields = " or ".join([field + " like %(txt)s" for field in searchfields]) if filters.get('supplier'): - item_group_list = frappe.get_list('Supplier Item Group', filters = {'supplier': filters.get('supplier')}, fields = ['item_group']) + item_group_list = frappe.get_all('Supplier Item Group', filters = {'supplier': filters.get('supplier')}, fields = ['item_group']) item_groups = [] for i in item_group_list: From bf7f0530e6cc14cb7722c767259c0c814fb1a8b8 Mon Sep 17 00:00:00 2001 From: noahjacob Date: Wed, 19 May 2021 13:53:22 +0530 Subject: [PATCH 280/326] fix: added error handling if entry already exists --- .../supplier_item_group/supplier_item_group.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/erpnext/buying/doctype/supplier_item_group/supplier_item_group.py b/erpnext/buying/doctype/supplier_item_group/supplier_item_group.py index 6fbeb37242..3a2e5d6dce 100644 --- a/erpnext/buying/doctype/supplier_item_group/supplier_item_group.py +++ b/erpnext/buying/doctype/supplier_item_group/supplier_item_group.py @@ -3,8 +3,16 @@ # For license information, please see license.txt from __future__ import unicode_literals -# import frappe +import frappe +from frappe import _ from frappe.model.document import Document class SupplierItemGroup(Document): - pass + def validate(self): + exists = frappe.db.exists({ + 'doctype': 'Supplier Item Group', + 'supplier': self.supplier, + 'item_group': self.item_group + }) + if exists: + frappe.throw(_("Item Group has already been linked to this supplier.")) \ No newline at end of file From 4e73c8a79f1838de0f0d828da71aaad5110b4e3c Mon Sep 17 00:00:00 2001 From: Anupam Date: Wed, 19 May 2021 14:58:12 +0530 Subject: [PATCH 281/326] restructuring timesheet fields --- .../projects/doctype/timesheet/timesheet.js | 20 +++++++---- .../projects/doctype/timesheet/timesheet.json | 36 +++++++++++++++++-- 2 files changed, 46 insertions(+), 10 deletions(-) diff --git a/erpnext/projects/doctype/timesheet/timesheet.js b/erpnext/projects/doctype/timesheet/timesheet.js index 532d64994f..4512244027 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.js +++ b/erpnext/projects/doctype/timesheet/timesheet.js @@ -145,14 +145,20 @@ frappe.ui.form.on("Timesheet", { let base_currency = frappe.defaults.get_global_default('currency'); frm.set_currency_labels(["base_total_costing_amount", "base_total_billable_amount", "base_total_billed_amount"], base_currency); frm.set_currency_labels(["total_costing_amount", "total_billable_amount", "total_billed_amount"], frm.doc.currency); - frm.set_currency_labels(["base_billing_rate", "base_billing_amount", "base_costing_rate", "base_costing_amount"], base_currency, "time_logs"); - frm.set_currency_labels(["billing_rate", "billing_amount", "costing_rate", "costing_amount"], frm.doc.currency, "time_logs"); - let time_logs_grid = frm.fields_dict.time_logs.grid; - $.each(["base_billing_rate", "base_billing_amount", "base_costing_rate", "base_costing_amount"], function(i, d) { - if(frappe.meta.get_docfield(time_logs_grid.doctype, d)) - time_logs_grid.set_column_disp(d, frm.doc.currency != base_currency); - }); + frm.toggle_display(["base_total_costing_amount", "base_total_billable_amount", "base_total_billed_amount"], + frm.doc.currency != base_currency) + + if (frm.doc?.time_logs.length > 0) { + frm.set_currency_labels(["base_billing_rate", "base_billing_amount", "base_costing_rate", "base_costing_amount"], base_currency, "time_logs"); + frm.set_currency_labels(["billing_rate", "billing_amount", "costing_rate", "costing_amount"], frm.doc.currency, "time_logs"); + + let time_logs_grid = frm.fields_dict.time_logs.grid; + $.each(["base_billing_rate", "base_billing_amount", "base_costing_rate", "base_costing_amount"], function(i, d) { + if (frappe.meta.get_docfield(time_logs_grid.doctype, d)) + time_logs_grid.set_column_disp(d, frm.doc.currency != base_currency); + }); + } frm.refresh_fields(); }, diff --git a/erpnext/projects/doctype/timesheet/timesheet.json b/erpnext/projects/doctype/timesheet/timesheet.json index 23e6ede967..75f7478ed1 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.json +++ b/erpnext/projects/doctype/timesheet/timesheet.json @@ -13,6 +13,7 @@ "company", "customer", "currency", + "exchange_rate", "sales_invoice", "column_break_3", "salary_slip", @@ -32,11 +33,14 @@ "total_hours", "billing_details", "total_billable_hours", - "total_billed_hours", - "total_costing_amount", + "base_total_billable_amount", + "base_total_billed_amount", + "base_total_costing_amount", "column_break_10", + "total_billed_hours", "total_billable_amount", "total_billed_amount", + "total_costing_amount", "per_billed", "section_break_18", "note", @@ -283,13 +287,39 @@ "fieldtype": "Link", "label": "Currency", "options": "Currency" + }, + { + "fieldname": "base_total_costing_amount", + "fieldtype": "Currency", + "label": "Total Costing Amount", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "base_total_billable_amount", + "fieldtype": "Currency", + "label": "Total Billable Amount", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "base_total_billed_amount", + "fieldtype": "Currency", + "label": "Total Billed Amount", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "exchange_rate", + "fieldtype": "Float", + "label": "Exchange Rate" } ], "icon": "fa fa-clock-o", "idx": 1, "is_submittable": 1, "links": [], - "modified": "2021-05-13 17:13:29.954960", + "modified": "2021-05-18 16:10:08.249619", "modified_by": "Administrator", "module": "Projects", "name": "Timesheet", From 4bd641367b24a49fd8c4e2e896a960455c560f00 Mon Sep 17 00:00:00 2001 From: Saqib Date: Wed, 19 May 2021 16:38:53 +0530 Subject: [PATCH 282/326] fix: address template with upper filter throws jinja error (#25756) --- erpnext/regional/address_template/templates/united_states.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/address_template/templates/united_states.html b/erpnext/regional/address_template/templates/united_states.html index 089315e4e4..77fce46b9d 100644 --- a/erpnext/regional/address_template/templates/united_states.html +++ b/erpnext/regional/address_template/templates/united_states.html @@ -1,4 +1,4 @@ {{ address_line1 }}
    {% if address_line2 %}{{ address_line2 }}
    {% endif -%} {{ city }}, {% if state %}{{ state }}{% endif -%}{% if pincode %} {{ pincode }}
    {% endif -%} -{% if country != "United States" %}{{ country|upper }}{% endif -%} +{% if country != "United States" %}{{ country }}{% endif -%} From 7fb385a89f12f4ed22d01282020793686c415ce2 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 19 May 2021 18:47:06 +0530 Subject: [PATCH 283/326] fix: cannot bypass e-invoicing for non gst item invoices --- erpnext/regional/india/e_invoice/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index b4e7a8889e..0315c83fd1 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -43,8 +43,9 @@ def validate_eligibility(doc): invalid_supply_type = doc.get('gst_category') not in ['Registered Regular', 'SEZ', 'Overseas', 'Deemed Export'] company_transaction = doc.get('billing_address_gstin') == doc.get('company_gstin') no_taxes_applied = not doc.get('taxes') + has_non_gst_item = any(d for d in doc.get('items') if frappe.db.get_value('Item', d.get('item_code'), 'is_non_gst')) - if invalid_company or invalid_supply_type or company_transaction or no_taxes_applied: + if invalid_company or invalid_supply_type or company_transaction or no_taxes_applied or has_non_gst_item: return False return True From b9ad385232d5e6a66fa418bdb879b3eb85c263b3 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 19 May 2021 19:13:06 +0530 Subject: [PATCH 284/326] fix: remove uncessary query --- erpnext/regional/india/e_invoice/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 0315c83fd1..70e6d07eab 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -43,7 +43,7 @@ def validate_eligibility(doc): invalid_supply_type = doc.get('gst_category') not in ['Registered Regular', 'SEZ', 'Overseas', 'Deemed Export'] company_transaction = doc.get('billing_address_gstin') == doc.get('company_gstin') no_taxes_applied = not doc.get('taxes') - has_non_gst_item = any(d for d in doc.get('items') if frappe.db.get_value('Item', d.get('item_code'), 'is_non_gst')) + has_non_gst_item = any(d for d in doc.get('items') if d.get('is_non_gst')) if invalid_company or invalid_supply_type or company_transaction or no_taxes_applied or has_non_gst_item: return False From aeb88385bbae9470956aefec57d69eb3a6fbc0fa Mon Sep 17 00:00:00 2001 From: Anupam Date: Thu, 20 May 2021 14:11:36 +0530 Subject: [PATCH 285/326] patch: timesheet changes --- erpnext/patches.txt | 2 +- ...me_billable_to_is_billable_in_timesheet.py | 7 ------ .../patches/v13_0/update_timesheet_changes.py | 24 +++++++++++++++++++ 3 files changed, 25 insertions(+), 8 deletions(-) delete mode 100644 erpnext/patches/v13_0/rename_billable_to_is_billable_in_timesheet.py create mode 100644 erpnext/patches/v13_0/update_timesheet_changes.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 0caad586e5..d4655e19b9 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -779,4 +779,4 @@ erpnext.patches.v12_0.add_ewaybill_validity_field erpnext.patches.v13_0.germany_make_custom_fields erpnext.patches.v13_0.germany_fill_debtor_creditor_number erpnext.patches.v13_0.set_pos_closing_as_failed -erpnext.patches.v13_0.rename_billable_to_is_billable_in_timesheet +erpnext.patches.v13_0.update_timesheet_changes diff --git a/erpnext/patches/v13_0/rename_billable_to_is_billable_in_timesheet.py b/erpnext/patches/v13_0/rename_billable_to_is_billable_in_timesheet.py deleted file mode 100644 index 6860a37559..0000000000 --- a/erpnext/patches/v13_0/rename_billable_to_is_billable_in_timesheet.py +++ /dev/null @@ -1,7 +0,0 @@ -from __future__ import unicode_literals -import frappe -from frappe.model.utils.rename_field import rename_field - -def execute(): - if "billable" in frappe.db.get_table_columns("Timesheet Detail"): - rename_field("Timesheet Detail", "billable", "is_billable") \ No newline at end of file diff --git a/erpnext/patches/v13_0/update_timesheet_changes.py b/erpnext/patches/v13_0/update_timesheet_changes.py new file mode 100644 index 0000000000..87178b2f84 --- /dev/null +++ b/erpnext/patches/v13_0/update_timesheet_changes.py @@ -0,0 +1,24 @@ +from __future__ import unicode_literals +import frappe +from frappe.model.utils.rename_field import rename_field + +def execute(): + if "billable" in frappe.db.get_table_columns("Timesheet Detail"): + rename_field("Timesheet Detail", "billable", "is_billable") + + base_currency = frappe.defaults.get_global_default('currency') + frappe.reload_doc("projects", "doctype", "timesheet") + frappe.reload_doc("projects", "doctype", "timesheet_detail") + + frappe.db.sql("""UPDATE `tabTimesheet Detail` + SET base_billing_rate = billing_rate, + base_billing_amount = billing_amount, + base_costing_rate = costing_rate, + base_costing_amount = costing_amount""") + + frappe.db.sql("""UPDATE `tabTimesheet` + SET currency = '{0}', + exchange_rate = 1.0, + base_total_billable_amount = total_billable_amount, + base_total_billed_amount = total_billed_amount, + base_total_costing_amount = total_costing_amount""".format(base_currency)) \ No newline at end of file From aa516e5d178769a07fe1c400f60e96ca54ac36a7 Mon Sep 17 00:00:00 2001 From: Anupam Date: Thu, 20 May 2021 14:12:18 +0530 Subject: [PATCH 286/326] fix: review changes --- erpnext/projects/doctype/timesheet/timesheet.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/projects/doctype/timesheet/timesheet.js b/erpnext/projects/doctype/timesheet/timesheet.js index 4512244027..9bb9c38532 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.js +++ b/erpnext/projects/doctype/timesheet/timesheet.js @@ -116,7 +116,7 @@ frappe.ui.form.on("Timesheet", { currency: function(frm) { let base_currency = frappe.defaults.get_global_default('currency'); - if (base_currency != frm.doc.company) { + if (base_currency != frm.doc.currency) { frappe.call({ method: "erpnext.setup.utils.get_exchange_rate", args: { From 1270febfffc16fc6c548487727629b56f57fa354 Mon Sep 17 00:00:00 2001 From: Anupam Date: Thu, 20 May 2021 14:15:58 +0530 Subject: [PATCH 287/326] fix: sider issues --- erpnext/patches/v13_0/update_timesheet_changes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/patches/v13_0/update_timesheet_changes.py b/erpnext/patches/v13_0/update_timesheet_changes.py index 87178b2f84..3acce18c63 100644 --- a/erpnext/patches/v13_0/update_timesheet_changes.py +++ b/erpnext/patches/v13_0/update_timesheet_changes.py @@ -11,13 +11,13 @@ def execute(): frappe.reload_doc("projects", "doctype", "timesheet_detail") frappe.db.sql("""UPDATE `tabTimesheet Detail` - SET base_billing_rate = billing_rate, + SET base_billing_rate = billing_rate, base_billing_amount = billing_amount, base_costing_rate = costing_rate, base_costing_amount = costing_amount""") frappe.db.sql("""UPDATE `tabTimesheet` - SET currency = '{0}', + SET currency = '{0}', exchange_rate = 1.0, base_total_billable_amount = total_billable_amount, base_total_billed_amount = total_billed_amount, From 4427390ab386c54b7de9e78d551bfa8ed1d6c502 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 20 May 2021 17:19:24 +0530 Subject: [PATCH 288/326] fix: Do not throw error in migrate --- erpnext/regional/india/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index ca679e43d2..fc227defbf 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -695,7 +695,7 @@ def get_gst_accounts(company=None, account_wise=False, only_reverse_charge=0, on filters=filters, fields=["cgst_account", "sgst_account", "igst_account", "cess_account"]) - if not gst_settings_accounts and not frappe.flags.in_test: + if not gst_settings_accounts and not frappe.flags.in_test and not frappe.flags.in_migrate: frappe.throw(_("Please set GST Accounts in GST Settings")) for d in gst_settings_accounts: From 21e662f67870ed37a887c5ffbffb18622d84bb6c Mon Sep 17 00:00:00 2001 From: Mohammad Hasnain Mohsin Rajan Date: Thu, 20 May 2021 17:20:05 +0530 Subject: [PATCH 289/326] fix: apply permission while selecting projects (#25765) --- erpnext/projects/doctype/task/task.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/erpnext/projects/doctype/task/task.js b/erpnext/projects/doctype/task/task.js index 6a9d2d1424..3cd92ee719 100644 --- a/erpnext/projects/doctype/task/task.js +++ b/erpnext/projects/doctype/task/task.js @@ -5,12 +5,6 @@ frappe.provide("erpnext.projects"); frappe.ui.form.on("Task", { setup: function (frm) { - frm.set_query("project", function () { - return { - query: "erpnext.projects.doctype.task.task.get_project" - } - }); - frm.make_methods = { 'Timesheet': () => frappe.model.open_mapped_doc({ method: 'erpnext.projects.doctype.task.task.make_timesheet', From 0d8b9a9d0a110d7379ebb9eba9e96d61bb1ca02b Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Thu, 20 May 2021 19:53:30 +0530 Subject: [PATCH 290/326] fix(patch): billable field not renamed --- erpnext/patches/v13_0/update_timesheet_changes.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/erpnext/patches/v13_0/update_timesheet_changes.py b/erpnext/patches/v13_0/update_timesheet_changes.py index 3acce18c63..93b7f8e59a 100644 --- a/erpnext/patches/v13_0/update_timesheet_changes.py +++ b/erpnext/patches/v13_0/update_timesheet_changes.py @@ -3,19 +3,20 @@ import frappe from frappe.model.utils.rename_field import rename_field def execute(): - if "billable" in frappe.db.get_table_columns("Timesheet Detail"): - rename_field("Timesheet Detail", "billable", "is_billable") - - base_currency = frappe.defaults.get_global_default('currency') frappe.reload_doc("projects", "doctype", "timesheet") frappe.reload_doc("projects", "doctype", "timesheet_detail") + if frappe.db.has_column("Timesheet Detail", "billable"): + rename_field("Timesheet Detail", "billable", "is_billable") + + base_currency = frappe.defaults.get_global_default('currency') + frappe.db.sql("""UPDATE `tabTimesheet Detail` SET base_billing_rate = billing_rate, base_billing_amount = billing_amount, base_costing_rate = costing_rate, base_costing_amount = costing_amount""") - + frappe.db.sql("""UPDATE `tabTimesheet` SET currency = '{0}', exchange_rate = 1.0, From be247ec3ded28cec31228a61f9ea8e8ceb29cc88 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Thu, 20 May 2021 22:17:20 +0530 Subject: [PATCH 291/326] fix: error message placeholders and sider issues --- erpnext/projects/doctype/timesheet/timesheet.js | 4 ++-- erpnext/projects/doctype/timesheet/timesheet.py | 16 ++++++++-------- erpnext/public/js/utils.js | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/erpnext/projects/doctype/timesheet/timesheet.js b/erpnext/projects/doctype/timesheet/timesheet.js index 9bb9c38532..63078ea7bd 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.js +++ b/erpnext/projects/doctype/timesheet/timesheet.js @@ -146,10 +146,10 @@ frappe.ui.form.on("Timesheet", { frm.set_currency_labels(["base_total_costing_amount", "base_total_billable_amount", "base_total_billed_amount"], base_currency); frm.set_currency_labels(["total_costing_amount", "total_billable_amount", "total_billed_amount"], frm.doc.currency); - frm.toggle_display(["base_total_costing_amount", "base_total_billable_amount", "base_total_billed_amount"], + frm.toggle_display(["base_total_costing_amount", "base_total_billable_amount", "base_total_billed_amount"], frm.doc.currency != base_currency) - if (frm.doc?.time_logs.length > 0) { + if (frm.doc.time_logs.length > 0) { frm.set_currency_labels(["base_billing_rate", "base_billing_amount", "base_costing_rate", "base_costing_amount"], base_currency, "time_logs"); frm.set_currency_labels(["billing_rate", "billing_amount", "costing_rate", "costing_amount"], frm.doc.currency, "time_logs"); diff --git a/erpnext/projects/doctype/timesheet/timesheet.py b/erpnext/projects/doctype/timesheet/timesheet.py index 1ee59aef8b..d3c21a3728 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.py +++ b/erpnext/projects/doctype/timesheet/timesheet.py @@ -150,7 +150,7 @@ class Timesheet(Document): def validate_project(self, data): if self.parent_project and self.parent_project != data.project: - frappe.throw(_("Row {0}: Project must be same as the one set in the Timesheet: {1}.")).format(data.idx, self.parent_project) + frappe.throw(_("Row {0}: Project must be same as the one set in the Timesheet: {1}.").format(data.idx, self.parent_project)) def validate_overlap_for(self, fieldname, args, value, ignore_validation=False): if not value or ignore_validation: @@ -221,14 +221,14 @@ def get_projectwise_timesheet_data(project=None, parent=None, from_time=None, to if from_time and to_time: condition += "AND CAST(tsd.from_time as DATE) BETWEEN %(from_time)s AND %(to_time)s" - return frappe.db.sql("""SELECT tsd.name as name, - tsd.parent as parent, tsd.billing_hours as billing_hours, - tsd.billing_amount as billing_amount, tsd.activity_type as activity_type, + return frappe.db.sql("""SELECT tsd.name as name, + tsd.parent as parent, tsd.billing_hours as billing_hours, + tsd.billing_amount as billing_amount, tsd.activity_type as activity_type, tsd.description as description, ts.currency as currency - FROM `tabTimesheet Detail` tsd - INNER JOIN `tabTimesheet` ts ON ts.name = tsd.parent - WHERE tsd.parenttype = 'Timesheet' - and tsd.docstatus=1 {0} + FROM `tabTimesheet Detail` tsd + INNER JOIN `tabTimesheet` ts ON ts.name = tsd.parent + WHERE tsd.parenttype = 'Timesheet' + and tsd.docstatus=1 {0} and tsd.is_billable = 1 and tsd.sales_invoice is null""".format(condition), {'project': project, 'parent': parent, 'from_time': from_time, 'to_time': to_time}, as_dict=1) diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index 39004503a0..ce40ced11f 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -734,7 +734,7 @@ frappe.form.link_formatters['Project'] = function(value, doc) { // if value is blank in report view or project name and name are the same, return as is return value; } -} +}; // add description on posting time $(document).on('app_ready', function() { From 8a407f1ec308ef4a4c142746cebf39e18c72ed53 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Thu, 20 May 2021 22:23:44 +0530 Subject: [PATCH 292/326] fix: sider issues --- erpnext/projects/doctype/timesheet/timesheet.js | 2 +- erpnext/projects/doctype/timesheet/timesheet.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/projects/doctype/timesheet/timesheet.js b/erpnext/projects/doctype/timesheet/timesheet.js index 63078ea7bd..84c7b8118b 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.js +++ b/erpnext/projects/doctype/timesheet/timesheet.js @@ -147,7 +147,7 @@ frappe.ui.form.on("Timesheet", { frm.set_currency_labels(["total_costing_amount", "total_billable_amount", "total_billed_amount"], frm.doc.currency); frm.toggle_display(["base_total_costing_amount", "base_total_billable_amount", "base_total_billed_amount"], - frm.doc.currency != base_currency) + frm.doc.currency != base_currency); if (frm.doc.time_logs.length > 0) { frm.set_currency_labels(["base_billing_rate", "base_billing_amount", "base_costing_rate", "base_costing_amount"], base_currency, "time_logs"); diff --git a/erpnext/projects/doctype/timesheet/timesheet.py b/erpnext/projects/doctype/timesheet/timesheet.py index d3c21a3728..d42c6ab175 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.py +++ b/erpnext/projects/doctype/timesheet/timesheet.py @@ -137,7 +137,7 @@ class Timesheet(Document): def validate_time_logs(self): for data in self.get('time_logs'): self.validate_overlap(data) - self.validate_task_project(data) + self.set_project(data) self.validate_project(data) def validate_overlap(self, data): @@ -145,7 +145,7 @@ class Timesheet(Document): self.validate_overlap_for("user", data, self.user, settings.ignore_user_time_overlap) self.validate_overlap_for("employee", data, self.employee, settings.ignore_employee_time_overlap) - def validate_task_project(self, data): + def set_project(self, data): data.project = data.project or frappe.db.get_value("Task", data.task, "project") def validate_project(self, data): From a7d0dbb085f23f3cedc1b88b546bcc64cb029d56 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Thu, 20 May 2021 23:02:11 +0530 Subject: [PATCH 293/326] fix: calculate total billing amount on fetching timesheets - show timesheet billing amounts in doc currency --- .../doctype/sales_invoice/sales_invoice.js | 57 +++++++++---------- .../doctype/sales_invoice/sales_invoice.json | 5 +- .../sales_invoice_timesheet.json | 3 +- .../projects/doctype/timesheet/timesheet.py | 6 +- 4 files changed, 35 insertions(+), 36 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index 747d0a931a..1808005f62 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -820,7 +820,7 @@ frappe.ui.form.on('Sales Invoice', { }, add_timesheet_row: function(frm, row, exchange_rate) { - frm.add_child('timesheets',{ + frm.add_child('timesheets', { 'activity_type': row.activity_type, 'description': row.description, 'time_sheet': row.parent, @@ -828,7 +828,8 @@ frappe.ui.form.on('Sales Invoice', { 'billing_amount': flt(row.billing_amount) * flt(exchange_rate), 'timesheet_detail': row.name }); - frm.refresh_field('timesheets') + frm.refresh_field('timesheets'); + calculate_total_billing_amount(frm); }, refresh: function(frm) { @@ -871,36 +872,32 @@ frappe.ui.form.on('Sales Invoice', { project: data.project }, callback: function(r) { - if(!r.exc) { - if(r.message.length > 0) { - frm.clear_table('timesheets') - r.message.forEach((d) => { - let exchange_rate = 1.0; - if (frm.doc.currency != d.currency) { - frappe.call({ - method: "erpnext.setup.utils.get_exchange_rate", - args: { - from_currency: d.currency, - to_currency: frm.doc.currency - }, - callback: function(r) { - if (r.message) { - exchange_rate = r.message; - frm.events.add_timesheet_row(frm, d, exchange_rate); - } + if (!r.exc && r.message.length > 0) { + frm.clear_table('timesheets') + r.message.forEach((d) => { + let exchange_rate = 1.0; + if (frm.doc.currency != d.currency) { + frappe.call({ + method: 'erpnext.setup.utils.get_exchange_rate', + args: { + from_currency: d.currency, + to_currency: frm.doc.currency + }, + callback: function(r) { + if (r.message) { + exchange_rate = r.message; + frm.events.add_timesheet_row(frm, d, exchange_rate); } - }); - } - else { - frm.events.add_timesheet_row(frm, d, exchange_rate); - } - }); - } - else { - frappe.msgprint(__('No Timesheet Found.')) - } - d.hide(); + } + }); + } else { + frm.events.add_timesheet_row(frm, d, exchange_rate); + } + }); + } else { + frappe.msgprint(__('No Timesheets found with the selected filters.')) } + d.hide(); } }); }, diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index 48d644cb43..e7dd6b8a60 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -772,6 +772,7 @@ "hide_days": 1, "hide_seconds": 1, "label": "Total Billing Amount", + "options": "currency", "print_hide": 1, "read_only": 1 }, @@ -1960,7 +1961,7 @@ "label": "Is Debit Note" }, { - "default": 0, + "default": "0", "depends_on": "grand_total", "fieldname": "disable_rounded_total", "fieldtype": "Check", @@ -1977,7 +1978,7 @@ "link_fieldname": "consolidated_invoice" } ], - "modified": "2021-05-13 17:53:26.185370", + "modified": "2021-05-20 22:48:33.988881", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", diff --git a/erpnext/accounts/doctype/sales_invoice_timesheet/sales_invoice_timesheet.json b/erpnext/accounts/doctype/sales_invoice_timesheet/sales_invoice_timesheet.json index 9321630829..f069e8dd0b 100644 --- a/erpnext/accounts/doctype/sales_invoice_timesheet/sales_invoice_timesheet.json +++ b/erpnext/accounts/doctype/sales_invoice_timesheet/sales_invoice_timesheet.json @@ -34,6 +34,7 @@ "fieldtype": "Currency", "in_list_view": 1, "label": "Billing Amount", + "options": "currency", "read_only": 1 }, { @@ -64,7 +65,7 @@ ], "istable": 1, "links": [], - "modified": "2021-05-13 16:52:32.995266", + "modified": "2021-05-20 22:33:57.234846", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice Timesheet", diff --git a/erpnext/projects/doctype/timesheet/timesheet.py b/erpnext/projects/doctype/timesheet/timesheet.py index d42c6ab175..a3e4577f90 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.py +++ b/erpnext/projects/doctype/timesheet/timesheet.py @@ -342,9 +342,9 @@ def get_activity_cost(employee=None, activity_type=None, currency=None): rate = frappe.db.get_values("Activity Type", {"activity_type": activity_type}, ["costing_rate", "billing_rate"], as_dict=True) if rate and currency and currency!=base_currency: - exchnage_rate = get_exchange_rate(base_currency, currency) - rate[0]["costing_rate"] = rate[0]["costing_rate"] * exchnage_rate - rate[0]["billing_rate"] = rate[0]["billing_rate"] * exchnage_rate + exchange_rate = get_exchange_rate(base_currency, currency) + rate[0]["costing_rate"] = rate[0]["costing_rate"] * exchange_rate + rate[0]["billing_rate"] = rate[0]["billing_rate"] * exchange_rate return rate[0] if rate else {} From 605ea044f37510cf8f12daf2a5f82a5ea44d3002 Mon Sep 17 00:00:00 2001 From: Anuja P Date: Fri, 21 May 2021 11:24:33 +0530 Subject: [PATCH 294/326] fix: update process SOA format --- .../process_statement_of_accounts.html | 118 ++++++++++-------- .../process_statement_of_accounts.json | 19 ++- .../process_statement_of_accounts.py | 8 +- 3 files changed, 92 insertions(+), 53 deletions(-) diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html index f61aacbce2..7328f168e3 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html @@ -1,24 +1,42 @@ -

    {{ filters.party[0] }}

    -

    {{ _("Statement of Accounts") }}

    +
    +
    + {% if letter_head %} +
    {{ letter_head.content }}
    +
    + {% endif %} +
    + +

    {{ _("STATEMENTS OF ACCOUNTS") }}

    +
    +
    {{ _("Customer: ") }} {{filters.party[0] }}
    +
    + {{ _("Date: ") }} + {{ frappe.format(filters.from_date, 'Date')}} + {{ _("to") }} + {{ frappe.format(filters.to_date, 'Date')}} +
    +
    +
    -
    - {{ frappe.format(filters.from_date, 'Date')}} - {{ _("to") }} - {{ frappe.format(filters.to_date, 'Date')}} -
    - - - - - - - - - - - - - +
    {{ _("Date") }}{{ _("Ref") }}{{ _("Party") }}{{ _("Debit") }}{{ _("Credit") }}{{ _("Balance (Dr - Cr)") }}
    + + + + + + + + + + + {% for row in data %} {% if(row.posting_date) %} @@ -58,32 +76,34 @@ {% endfor %} -
    {{ _("Date") }}{{ _("Reference") }}{{ _("Remarks") }}{{ _("Debit") }}{{ _("Credit") }}{{ _("Balance (Dr - Cr)") }}
    -

    -{% if ageing %} -

    {{ _("Ageing Report Based On ") }} {{ ageing.ageing_based_on }}

    -
    - {{ _("Up to " ) }} {{ frappe.format(filters.to_date, 'Date')}} -
    -
    - - - - - - - - - - - - - - - - - - -
    30 Days60 Days90 Days120 Days
    {{ frappe.utils.fmt_money(ageing.range1, currency=filters.presentation_currency) }}{{ frappe.utils.fmt_money(ageing.range2, currency=filters.presentation_currency) }}{{ frappe.utils.fmt_money(ageing.range3, currency=filters.presentation_currency) }}{{ frappe.utils.fmt_money(ageing.range4, currency=filters.presentation_currency) }}
    -{% endif %} -

    Printed On {{ frappe.format(frappe.utils.get_datetime(), 'Datetime') }}

    \ No newline at end of file + +
    + {% if ageing %} +

    {{ _("Ageing Report based on ") }} {{ ageing.ageing_based_on }} + {{ _("up to " ) }} {{ frappe.format(filters.to_date, 'Date')}} +

    + + + + + + + + + + + + + + + + + +
    30 Days60 Days90 Days120 Days
    {{ frappe.utils.fmt_money(ageing.range1, currency=filters.presentation_currency) }}{{ frappe.utils.fmt_money(ageing.range2, currency=filters.presentation_currency) }}{{ frappe.utils.fmt_money(ageing.range3, currency=filters.presentation_currency) }}{{ frappe.utils.fmt_money(ageing.range4, currency=filters.presentation_currency) }}
    + {% endif %} + {% if terms_and_conditions %} +
    + {{ terms_and_conditions }} +
    + {% endif %} +
    \ No newline at end of file diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json index 4be0e2ec06..27a5f50ce2 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json @@ -1,6 +1,5 @@ { "actions": [], - "allow_workflow": 1, "autoname": "Prompt", "creation": "2020-05-22 16:46:18.712954", "doctype": "DocType", @@ -28,9 +27,11 @@ "customers", "preferences", "orientation", - "section_break_14", "include_ageing", "ageing_based_on", + "section_break_14", + "letter_head", + "terms_and_conditions", "section_break_1", "enable_auto_email", "section_break_18", @@ -270,10 +271,22 @@ "fieldname": "body", "fieldtype": "Text Editor", "label": "Body" + }, + { + "fieldname": "letter_head", + "fieldtype": "Link", + "label": "Letter Head", + "options": "Letter Head" + }, + { + "fieldname": "terms_and_conditions", + "fieldtype": "Link", + "label": "Terms and Conditions", + "options": "Terms and Conditions" } ], "links": [], - "modified": "2020-08-08 08:47:09.185728", + "modified": "2021-05-21 10:14:22.426672", "modified_by": "Administrator", "module": "Accounts", "name": "Process Statement Of Accounts", diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py index a0dbff3db4..2ad455c48f 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py @@ -64,6 +64,9 @@ def get_report_pdf(doc, consolidated=True): tax_id = frappe.get_doc('Customer', entry.customer).tax_id presentation_currency = get_party_account_currency('Customer', entry.customer, doc.company) \ or doc.currency or get_company_currency(doc.company) + if doc.letter_head: + from frappe.www.printview import get_letter_head + letter_head = get_letter_head(doc, 0) filters= frappe._dict({ 'from_date': doc.from_date, @@ -91,7 +94,10 @@ def get_report_pdf(doc, consolidated=True): continue html = frappe.render_template(template_path, \ - {"filters": filters, "data": res, "ageing": ageing[0] if (doc.include_ageing and ageing) else None}) + {"filters": filters, "data": res, "ageing": ageing[0] if doc.include_ageing else None, + "letter_head": letter_head if doc.letter_head else None, + "terms_and_conditions": frappe.db.get_value('Terms and Conditions', doc.terms_and_conditions, 'terms') + if doc.terms_and_conditions else None}) html = frappe.render_template(base_template_path, {"body": html, \ "css": get_print_style(), "title": "Statement For " + entry.customer}) From 795909fdcd0b682c54129b7226a93b99f1240db1 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 21 May 2021 11:23:20 +0530 Subject: [PATCH 295/326] fix: warehouse not found in stock entry --- erpnext/stock/doctype/stock_entry/stock_entry.js | 4 ++-- erpnext/stock/doctype/stock_entry/stock_entry.json | 4 +++- .../doctype/stock_entry_type/stock_entry_type.json | 12 ++++++++++-- .../doctype/stock_entry_type/stock_entry_type.py | 4 +++- 4 files changed, 18 insertions(+), 6 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index daa1e51182..a40ef001ee 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -600,7 +600,6 @@ frappe.ui.form.on('Stock Entry', { add_to_transit: function(frm) { if(frm.doc.add_to_transit && frm.doc.purpose=='Material Transfer') { frm.set_value('to_warehouse', ''); - frm.set_value('stock_entry_type', 'Material Transfer'); frm.fields_dict.to_warehouse.get_query = function() { return { filters:{ @@ -615,7 +614,8 @@ frappe.ui.form.on('Stock Entry', { }, set_tansit_warehouse: function(frm) { - if(frm.doc.add_to_transit && frm.doc.purpose == 'Material Transfer' && !frm.doc.to_warehouse) { + if(frm.doc.add_to_transit && frm.doc.purpose == 'Material Transfer' && !frm.doc.to_warehouse + && frm.doc.from_warehouse) { let dt = frm.doc.from_warehouse ? 'Warehouse' : 'Company'; let dn = frm.doc.from_warehouse ? frm.doc.from_warehouse : frm.doc.company; frappe.db.get_value(dt, dn, 'default_in_transit_warehouse', (r) => { diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.json b/erpnext/stock/doctype/stock_entry/stock_entry.json index 98c047a09e..7f94591005 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.json +++ b/erpnext/stock/doctype/stock_entry/stock_entry.json @@ -637,6 +637,8 @@ { "default": "0", "depends_on": "eval: doc.purpose=='Material Transfer' && !doc.outgoing_stock_entry", + "fetch_from": "stock_entry_type.add_to_transit", + "fetch_if_empty": 1, "fieldname": "add_to_transit", "fieldtype": "Check", "label": "Add to Transit", @@ -655,7 +657,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2020-12-09 14:58:13.267321", + "modified": "2021-05-21 11:29:11.917161", "modified_by": "Administrator", "module": "Stock", "name": "Stock Entry", diff --git a/erpnext/stock/doctype/stock_entry_type/stock_entry_type.json b/erpnext/stock/doctype/stock_entry_type/stock_entry_type.json index 0f2b55ec34..eee38be027 100644 --- a/erpnext/stock/doctype/stock_entry_type/stock_entry_type.json +++ b/erpnext/stock/doctype/stock_entry_type/stock_entry_type.json @@ -6,7 +6,8 @@ "editable_grid": 1, "engine": "InnoDB", "field_order": [ - "purpose" + "purpose", + "add_to_transit" ], "fields": [ { @@ -18,10 +19,17 @@ "options": "\nMaterial Issue\nMaterial Receipt\nMaterial Transfer\nMaterial Transfer for Manufacture\nMaterial Consumption for Manufacture\nManufacture\nRepack\nSend to Subcontractor", "reqd": 1, "set_only_once": 1 + }, + { + "default": "0", + "depends_on": "eval: doc.purpose == 'Material Transfer'", + "fieldname": "add_to_transit", + "fieldtype": "Check", + "label": "Add to Transit" } ], "links": [], - "modified": "2020-08-10 23:24:37.160817", + "modified": "2021-05-21 11:27:01.144110", "modified_by": "Administrator", "module": "Stock", "name": "Stock Entry Type", diff --git a/erpnext/stock/doctype/stock_entry_type/stock_entry_type.py b/erpnext/stock/doctype/stock_entry_type/stock_entry_type.py index a4116aba2c..1069ec8713 100644 --- a/erpnext/stock/doctype/stock_entry_type/stock_entry_type.py +++ b/erpnext/stock/doctype/stock_entry_type/stock_entry_type.py @@ -7,4 +7,6 @@ from __future__ import unicode_literals from frappe.model.document import Document class StockEntryType(Document): - pass + def validate(self): + if self.add_to_transit and self.purpose != 'Material Transfer': + self.add_to_transit = 0 From 9979cf5fcc20cf6f267b94bd23a63ce6c4d761c6 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 21 May 2021 12:12:42 +0530 Subject: [PATCH 296/326] fix: wrong quantity after transaction for parallel stock transactions When two transactions are inserted parallelly then previous SLE could be incorrect for some of them. Locking SLE table would prevent reading from it till transaction is complete. --- erpnext/stock/stock_ledger.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 9729987d2d..790318a625 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -232,7 +232,8 @@ class update_entries_after(object): 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""", args, as_dict=1) + limit 1 + for update""", args, as_dict=1) return sle[0] if sle else frappe._dict() From 4dcac4ae8198ec6e594e1bdfe2408fc33f627940 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 21 May 2021 13:12:30 +0530 Subject: [PATCH 297/326] refactor(minor): Use identity instead of equality Ignore false positive. --- erpnext/stock/stock_ledger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 790318a625..b2825fc26f 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -624,7 +624,7 @@ class update_entries_after(object): break # If no entry found with outgoing rate, collapse stack - if index == None: + if index is None: # nosemgrep new_stock_value = sum((d[0]*d[1] for d in self.wh_data.stock_queue)) - qty_to_pop*outgoing_rate new_stock_qty = sum((d[0] for d in self.wh_data.stock_queue)) - qty_to_pop self.wh_data.stock_queue = [[new_stock_qty, new_stock_value/new_stock_qty if new_stock_qty > 0 else outgoing_rate]] From 1097dc89c599435ff78d786d24be64c9b383b858 Mon Sep 17 00:00:00 2001 From: Alan <2.alan.tom@gmail.com> Date: Fri, 21 May 2021 14:04:03 +0530 Subject: [PATCH 298/326] fix: show allow zero valuation only when auto checked (#25778) --- .../stock_reconciliation_item.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json b/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json index 85c7ebe263..6bbba051f9 100644 --- a/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json +++ b/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json @@ -1,4 +1,5 @@ { + "actions": [], "creation": "2015-02-17 01:06:05.072764", "doctype": "DocType", "document_type": "Other", @@ -170,6 +171,7 @@ }, { "default": "0", + "depends_on": "allow_zero_valuation_rate", "fieldname": "allow_zero_valuation_rate", "fieldtype": "Check", "label": "Allow Zero Valuation Rate", @@ -179,7 +181,7 @@ ], "istable": 1, "links": [], - "modified": "2021-03-23 11:09:44.407157", + "modified": "2021-05-21 12:13:33.041266", "modified_by": "Administrator", "module": "Stock", "name": "Stock Reconciliation Item", @@ -189,4 +191,4 @@ "sort_field": "modified", "sort_order": "DESC", "track_changes": 1 -} +} \ No newline at end of file From e930456be2e3bea0e7dfbf514470d26cf7e4e4b7 Mon Sep 17 00:00:00 2001 From: Saqib Date: Fri, 21 May 2021 16:31:12 +0530 Subject: [PATCH 299/326] fix(e-invoicing): 'NoneType' object is not iterable (#25781) --- erpnext/regional/india/e_invoice/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 70e6d07eab..7f25812b47 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -43,7 +43,7 @@ def validate_eligibility(doc): invalid_supply_type = doc.get('gst_category') not in ['Registered Regular', 'SEZ', 'Overseas', 'Deemed Export'] company_transaction = doc.get('billing_address_gstin') == doc.get('company_gstin') no_taxes_applied = not doc.get('taxes') - has_non_gst_item = any(d for d in doc.get('items') if d.get('is_non_gst')) + has_non_gst_item = any(d for d in doc.get('items', []) if d.get('is_non_gst')) if invalid_company or invalid_supply_type or company_transaction or no_taxes_applied or has_non_gst_item: return False From 0b5e340b6e7f68a41a09cad0ab8dca8105ec905d Mon Sep 17 00:00:00 2001 From: Saqib Date: Fri, 21 May 2021 16:32:56 +0530 Subject: [PATCH 300/326] fix(pos): return case for item with available qty equal to one (#25760) --- .../pos_closing_entry_detail.json | 3 ++- .../accounts/doctype/pos_invoice/pos_invoice.py | 16 +++++----------- .../pos_invoice_merge_log.py | 6 +++--- 3 files changed, 10 insertions(+), 15 deletions(-) diff --git a/erpnext/accounts/doctype/pos_closing_entry_detail/pos_closing_entry_detail.json b/erpnext/accounts/doctype/pos_closing_entry_detail/pos_closing_entry_detail.json index 6e7768dc54..bbf1ba0020 100644 --- a/erpnext/accounts/doctype/pos_closing_entry_detail/pos_closing_entry_detail.json +++ b/erpnext/accounts/doctype/pos_closing_entry_detail/pos_closing_entry_detail.json @@ -46,6 +46,7 @@ "reqd": 1 }, { + "default": "0", "fieldname": "closing_amount", "fieldtype": "Currency", "in_list_view": 1, @@ -57,7 +58,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2020-10-23 16:45:43.662034", + "modified": "2021-05-19 20:08:44.523861", "modified_by": "Administrator", "module": "Accounts", "name": "POS Closing Entry Detail", diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py index 473db565fa..f55fdab21c 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py @@ -455,32 +455,26 @@ class POSInvoice(SalesInvoice): @frappe.whitelist() def get_stock_availability(item_code, warehouse): - latest_sle = frappe.db.sql("""select qty_after_transaction - from `tabStock Ledger Entry` + bin_qty = frappe.db.sql("""select actual_qty from `tabBin` where item_code = %s and warehouse = %s - order by posting_date desc, posting_time desc limit 1""", (item_code, warehouse), as_dict=1) pos_sales_qty = get_pos_reserved_qty(item_code, warehouse) - sle_qty = latest_sle[0].qty_after_transaction or 0 if latest_sle else 0 + bin_qty = bin_qty[0].actual_qty or 0 if bin_qty else 0 - if sle_qty and pos_sales_qty: - return sle_qty - pos_sales_qty - else: - return sle_qty + return bin_qty - pos_sales_qty def get_pos_reserved_qty(item_code, warehouse): reserved_qty = frappe.db.sql("""select sum(p_item.qty) as qty from `tabPOS Invoice` p, `tabPOS Invoice Item` p_item where p.name = p_item.parent - and p.consolidated_invoice is NULL - and p.docstatus = 1 + and ifnull(p.consolidated_invoice, '') = '' and p_item.docstatus = 1 and p_item.item_code = %s and p_item.warehouse = %s """, (item_code, warehouse), as_dict=1) - + return reserved_qty[0].qty or 0 if reserved_qty else 0 @frappe.whitelist() diff --git a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py index bc7874305c..b0ddea304c 100644 --- a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py +++ b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py @@ -56,12 +56,12 @@ class POSInvoiceMergeLog(Document): sales = [d for d in pos_invoice_docs if d.get('is_return') == 0] sales_invoice, credit_note = "", "" - if sales: - sales_invoice = self.process_merging_into_sales_invoice(sales) - if returns: credit_note = self.process_merging_into_credit_note(returns) + if sales: + sales_invoice = self.process_merging_into_sales_invoice(sales) + self.save() # save consolidated_sales_invoice & consolidated_credit_note ref in merge log self.update_pos_invoices(pos_invoice_docs, sales_invoice, credit_note) From b0019bcf5be87ddb88846af76baf018ae532d7aa Mon Sep 17 00:00:00 2001 From: marination Date: Fri, 21 May 2021 21:27:55 +0530 Subject: [PATCH 301/326] fix: Item Variant Details Report Co-authored-by: gavindsouza - Handling of variants with special characters - Set data in appropriately named columns - Code cleanup --- .../item_variant_details.py | 262 +++++++++++------- 1 file changed, 157 insertions(+), 105 deletions(-) diff --git a/erpnext/stock/report/item_variant_details/item_variant_details.py b/erpnext/stock/report/item_variant_details/item_variant_details.py index e8449cc33e..d8563d7927 100644 --- a/erpnext/stock/report/item_variant_details/item_variant_details.py +++ b/erpnext/stock/report/item_variant_details/item_variant_details.py @@ -14,47 +14,58 @@ def get_data(item): if not item: return [] item_dicts = [] - variants = None - variant_results = frappe.db.sql("""select name from `tabItem` - where variant_of = %s""", item, as_dict=1) + variant_results = frappe.db.get_all( + "Item", + fields=["name"], + filters={ + "variant_of": ["=", item], + "disabled": 0 + } + ) + if not variant_results: - frappe.msgprint(_("There isn't any item variant for the selected item")) + frappe.msgprint(_("There aren't any item variants for the selected item")) return [] else: - variants = ", ".join([frappe.db.escape(variant['name']) for variant in variant_results]) + variant_list = [variant['name'] for variant in variant_results] - order_count_map = get_open_sales_orders_map(variants) - stock_details_map = get_stock_details_map(variants) - buying_price_map = get_buying_price_map(variants) - selling_price_map = get_selling_price_map(variants) - attr_val_map = get_attribute_values_map(variants) + order_count_map = get_open_sales_orders_count(variant_list) + stock_details_map = get_stock_details_map(variant_list) + buying_price_map = get_buying_price_map(variant_list) + selling_price_map = get_selling_price_map(variant_list) + attr_val_map = get_attribute_values_map(variant_list) - attribute_list = [d[0] for d in frappe.db.sql("""select attribute - from `tabItem Variant Attribute` - where parent in ({variants}) group by attribute""".format(variants=variants))] + attributes = frappe.db.get_all( + "Item Variant Attribute", + fields=["attribute"], + filters={ + "parent": ["in", variant_list] + }, + group_by="attribute" + ) + attribute_list = [row.get("attribute") for row in attributes] # Prepare dicts variant_dicts = [{"variant_name": d['name']} for d in variant_results] for item_dict in variant_dicts: - name = item_dict["variant_name"] + name = item_dict.get("variant_name") - for d in attribute_list: - attr_dict = attr_val_map[name] - if attr_dict and attr_dict.get(d): - item_dict[d] = attr_val_map[name][d] + for attribute in attribute_list: + attr_dict = attr_val_map.get(name) + if attr_dict and attr_dict.get(attribute): + item_dict[frappe.scrub(attribute)] = attr_val_map.get(name).get(attribute) - item_dict["Open Orders"] = order_count_map.get(name) or 0 + item_dict["open_orders"] = order_count_map.get(name) or 0 if stock_details_map.get(name): - item_dict["Inventory"] = stock_details_map.get(name)["Inventory"] or 0 - item_dict["In Production"] = stock_details_map.get(name)["In Production"] or 0 - item_dict["Available Selling"] = stock_details_map.get(name)["Available Selling"] or 0 + item_dict["current_stock"] = stock_details_map.get(name)["Inventory"] or 0 + item_dict["in_production"] = stock_details_map.get(name)["In Production"] or 0 else: - item_dict["Inventory"] = item_dict["In Production"] = item_dict["Available Selling"] = 0 + item_dict["current_stock"] = item_dict["in_production"] = 0 - item_dict["Avg. Buying Price List Rate"] = buying_price_map.get(name) or 0 - item_dict["Avg. Selling Price List Rate"] = selling_price_map.get(name) or 0 + item_dict["avg_buying_price_list_rate"] = buying_price_map.get(name) or 0 + item_dict["avg_selling_price_list_rate"] = selling_price_map.get(name) or 0 item_dicts.append(item_dict) @@ -71,117 +82,158 @@ def get_columns(item): item_doc = frappe.get_doc("Item", item) - for d in item_doc.attributes: - columns.append(d.attribute + ":Data:100") + for entry in item_doc.attributes: + columns.append({ + "fieldname": frappe.scrub(entry.attribute), + "label": entry.attribute, + "fieldtype": "Data", + "width": 100 + }) - columns += [_("Avg. Buying Price List Rate") + ":Currency:110", _("Avg. Selling Price List Rate") + ":Currency:110", - _("Inventory") + ":Float:100", _("In Production") + ":Float:100", - _("Open Orders") + ":Float:100", _("Available Selling") + ":Float:100" + additional_columns = [ + { + "fieldname": "avg_buying_price_list_rate", + "label": _("Avg. Buying Price List Rate"), + "fieldtype": "Currency", + "width": 150 + }, + { + "fieldname": "avg_selling_price_list_rate", + "label": _("Avg. Selling Price List Rate"), + "fieldtype": "Currency", + "width": 150 + }, + { + "fieldname": "current_stock", + "label": _("Current Stock"), + "fieldtype": "Float", + "width": 120 + }, + { + "fieldname": "in_production", + "label": _("In Production"), + "fieldtype": "Float", + "width": 150 + }, + { + "fieldname": "open_orders", + "label": _("Open Sales Orders"), + "fieldtype": "Float", + "width": 150 + } ] + columns.extend(additional_columns) return columns -def get_open_sales_orders_map(variants): - open_sales_orders = frappe.db.sql(""" - select - count(*) as count, - item_code - from - `tabSales Order Item` - where - docstatus = 1 and - qty > ifnull(delivered_qty, 0) and - item_code in ({variants}) - group by - item_code - """.format(variants=variants), as_dict=1) +def get_open_sales_orders_count(variants_list): + open_sales_orders = frappe.db.get_list( + "Sales Order", + fields=[ + "name", + "`tabSales Order Item`.item_code" + ], + filters=[ + ["Sales Order", "docstatus", "=", 1], + ["Sales Order Item", "item_code", "in", variants_list] + ], + distinct=1 + ) order_count_map = {} - for d in open_sales_orders: - order_count_map[d["item_code"]] = d["count"] + for row in open_sales_orders: + item_code = row.get("item_code") + if order_count_map.get(item_code) is None: + order_count_map[item_code] = 1 + else: + order_count_map[item_code] += 1 return order_count_map -def get_stock_details_map(variants): - stock_details = frappe.db.sql(""" - select - sum(planned_qty) as planned_qty, - sum(actual_qty) as actual_qty, - sum(projected_qty) as projected_qty, - item_code - from - `tabBin` - where - item_code in ({variants}) - group by - item_code - """.format(variants=variants), as_dict=1) +def get_stock_details_map(variant_list): + stock_details = frappe.db.get_all( + "Bin", + fields=[ + "sum(planned_qty) as planned_qty", + "sum(actual_qty) as actual_qty", + "sum(projected_qty) as projected_qty", + "item_code", + ], + filters={ + "item_code": ["in", variant_list] + }, + group_by="item_code" + ) stock_details_map = {} - for d in stock_details: - name = d["item_code"] + for row in stock_details: + name = row.get("item_code") stock_details_map[name] = { - "Inventory" :d["actual_qty"], - "In Production" :d["planned_qty"], - "Available Selling" :d["projected_qty"] + "Inventory": row.get("actual_qty"), + "In Production": row.get("planned_qty") } return stock_details_map -def get_buying_price_map(variants): - buying = frappe.db.sql(""" - select - avg(price_list_rate) as avg_rate, - item_code - from - `tabItem Price` - where - item_code in ({variants}) and buying=1 - group by - item_code - """.format(variants=variants), as_dict=1) +def get_buying_price_map(variant_list): + buying = frappe.db.get_all( + "Item Price", + fields=[ + "avg(price_list_rate) as avg_rate", + "item_code", + ], + filters={ + "item_code": ["in", variant_list], + "buying": 1 + }, + group_by="item_code" + ) buying_price_map = {} - for d in buying: - buying_price_map[d["item_code"]] = d["avg_rate"] + for row in buying: + buying_price_map[row.get("item_code")] = row.get("avg_rate") return buying_price_map -def get_selling_price_map(variants): - selling = frappe.db.sql(""" - select - avg(price_list_rate) as avg_rate, - item_code - from - `tabItem Price` - where - item_code in ({variants}) and selling=1 - group by - item_code - """.format(variants=variants), as_dict=1) +def get_selling_price_map(variant_list): + selling = frappe.db.get_all( + "Item Price", + fields=[ + "avg(price_list_rate) as avg_rate", + "item_code", + ], + filters={ + "item_code": ["in", variant_list], + "selling": 1 + }, + group_by="item_code" + ) selling_price_map = {} - for d in selling: - selling_price_map[d["item_code"]] = d["avg_rate"] + for row in selling: + selling_price_map[row.get("item_code")] = row.get("avg_rate") return selling_price_map -def get_attribute_values_map(variants): - list_attr = frappe.db.sql(""" - select - attribute, attribute_value, parent - from - `tabItem Variant Attribute` - where - parent in ({variants}) - """.format(variants=variants), as_dict=1) +def get_attribute_values_map(variant_list): + attribute_list = frappe.db.get_all( + "Item Variant Attribute", + fields=[ + "attribute", + "attribute_value", + "parent" + ], + filters={ + "parent": ["in", variant_list] + } + ) attr_val_map = {} - for d in list_attr: - name = d["parent"] + for row in attribute_list: + name = row.get("parent") if not attr_val_map.get(name): attr_val_map[name] = {} - attr_val_map[name][d["attribute"]] = d["attribute_value"] + attr_val_map[name][row.get("attribute")] = row.get("attribute_value") return attr_val_map From e1ab290911cbdfb62b9150e3c85da8567868d8e5 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sun, 23 May 2021 11:25:35 +0530 Subject: [PATCH 302/326] chore: remove woocommerce package (#25736) This is not used anywhere. It was added in this commit https://github.com/frappe/erpnext/commit/df83148d7ccb15ba18b59c7d9b761a3651bb1dec even there it isn't being used. --- requirements.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index f1ffeb8f48..32da48e9d5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,4 +10,3 @@ python-youtube~=0.8.0 taxjar~=1.9.2 tweepy~=3.10.0 Unidecode~=1.2.0 -WooCommerce~=3.0.0 From a2d6cf3125fff2ba04e0478021212ffb99777a4f Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sun, 23 May 2021 18:38:14 +0530 Subject: [PATCH 303/326] chore: remove pytlint config (#25796) - We have flake8 config and it runs in Sider. Flake8 and pylint have huge overlap, no point in using both tools. - The config is not valid pylint config. So it's useless anyway. --- .pylintrc | 1 - 1 file changed, 1 deletion(-) delete mode 100644 .pylintrc diff --git a/.pylintrc b/.pylintrc deleted file mode 100644 index 4b2ea0a564..0000000000 --- a/.pylintrc +++ /dev/null @@ -1 +0,0 @@ -disable=access-member-before-definition \ No newline at end of file From 7b4a38c71e850223d982c30be2ed448764897c81 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sun, 23 May 2021 18:49:26 +0530 Subject: [PATCH 304/326] chore: remove print from account controller (#25807) This is polluting test output and it's not useful for debugging without context. --- erpnext/controllers/accounts_controller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 996c4ed11b..544e624725 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1011,7 +1011,7 @@ class AccountsController(TransactionBase): else: grand_total -= self.get("total_advance") base_grand_total = flt(grand_total * self.get("conversion_rate"), self.precision("base_grand_total")) - print(grand_total, base_grand_total) + if total != flt(grand_total, self.precision("grand_total")) or \ base_total != flt(base_grand_total, self.precision("base_grand_total")): frappe.throw(_("Total Payment Amount in Payment Schedule must be equal to Grand / Rounded Total")) From 5457db06013792defa1b1ccb58260245eeb932e9 Mon Sep 17 00:00:00 2001 From: krishnagirishp <80564074+krishnagirishp@users.noreply.github.com> Date: Sun, 23 May 2021 21:13:44 +0530 Subject: [PATCH 305/326] chore: remove uses of six.PY2 in codebase (#25062) * remove uses of six.py2 in codebase * changes based on pr feedback * Update amazon_mws_api.py Co-authored-by: Ankush Menat --- .../doctype/amazon_mws_settings/amazon_mws_api.py | 7 ++++--- erpnext/regional/germany/utils/datev/datev_csv.py | 3 +-- erpnext/regional/india/e_invoice/utils.py | 4 +--- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_api.py b/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_api.py index f713684d37..7fd3b34fd5 100755 --- a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_api.py +++ b/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_api.py @@ -7,6 +7,7 @@ from __future__ import unicode_literals import urllib +from urllib.parse import quote import hashlib import hmac import base64 @@ -68,8 +69,9 @@ def calc_md5(string): """ md = hashlib.md5() md.update(string) - return base64.encodestring(md.digest()).strip('\n') if six.PY2 \ - else base64.encodebytes(md.digest()).decode().strip() + return base64.encodebytes(md.digest()).decode().strip() + + def remove_empty(d): """ @@ -177,7 +179,6 @@ class MWS(object): 'SignatureMethod': 'HmacSHA256', } params.update(extra_data) - quote = urllib.quote if six.PY2 else urllib.parse.quote request_description = '&'.join(['%s=%s' % (k, quote(params[k], safe='-_.~')) for k in sorted(params)]) signature = self.calc_signature(method, request_description) url = '%s%s?%s&Signature=%s' % (self.domain, self.uri, request_description, quote(signature)) diff --git a/erpnext/regional/germany/utils/datev/datev_csv.py b/erpnext/regional/germany/utils/datev/datev_csv.py index 826d51f712..122c15fd81 100644 --- a/erpnext/regional/germany/utils/datev/datev_csv.py +++ b/erpnext/regional/germany/utils/datev/datev_csv.py @@ -55,8 +55,7 @@ def get_datev_csv(data, filters, csv_class): quoting=QUOTE_NONNUMERIC ) - if not six.PY2: - data = data.encode('latin_1', errors='replace') + data = data.encode('latin_1', errors='replace') header = get_header(filters, csv_class) header = ';'.join(header).encode('latin_1', errors='replace') diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 7f25812b47..843fb012b9 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -534,11 +534,9 @@ def santize_einvoice_fields(einvoice): return einvoice def safe_json_load(json_string): - JSONDecodeError = ValueError if six.PY2 else json.JSONDecodeError - try: return json.loads(json_string) - except JSONDecodeError as e: + except json.JSONDecodeError as e: # print a snippet of 40 characters around the location where error occured pos = e.pos start, end = max(0, pos-20), min(len(json_string)-1, pos+20) From ce88c945cd420c001bf786476a62f6e17ebf7692 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Mon, 24 May 2021 11:53:27 +0530 Subject: [PATCH 306/326] Update stock_entry.js --- erpnext/stock/doctype/stock_entry/stock_entry.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index a40ef001ee..de23e769f8 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -609,11 +609,11 @@ frappe.ui.form.on('Stock Entry', { } }; }; - frm.trigger('set_tansit_warehouse'); + frm.trigger('set_transit_warehouse'); } }, - set_tansit_warehouse: function(frm) { + set_transit_warehouse: function(frm) { if(frm.doc.add_to_transit && frm.doc.purpose == 'Material Transfer' && !frm.doc.to_warehouse && frm.doc.from_warehouse) { let dt = frm.doc.from_warehouse ? 'Warehouse' : 'Company'; @@ -985,7 +985,7 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({ }, from_warehouse: function(doc) { - this.frm.trigger('set_tansit_warehouse'); + this.frm.trigger('set_transit_warehouse'); this.set_warehouse_in_children(doc.items, "s_warehouse", doc.from_warehouse); }, From c12264f6bcded0c914885f9d238fa5c63d665282 Mon Sep 17 00:00:00 2001 From: marination Date: Mon, 24 May 2021 13:20:22 +0530 Subject: [PATCH 307/326] chore: Cleanup Customer and Supplier Details section in Stock Entry - Changed depends on value to "Send to Subcontractor" for supplier fields - Removed Customer fields as they are not relevant to any Stock Entry purpose - Renamed section to "Supplier Details" visibe on subcontracting transfer --- .../doctype/stock_entry/stock_entry.json | 51 +++---------------- 1 file changed, 6 insertions(+), 45 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.json b/erpnext/stock/doctype/stock_entry/stock_entry.json index 98c047a09e..567b2ac3b0 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.json +++ b/erpnext/stock/doctype/stock_entry/stock_entry.json @@ -59,10 +59,6 @@ "supplier_name", "supplier_address", "address_display", - "column_break_39", - "customer", - "customer_name", - "customer_address", "accounting_dimensions_section", "project", "dimension_col_break", @@ -435,13 +431,13 @@ }, { "collapsible": 1, - "depends_on": "eval: in_list([\"Sales Return\", \"Purchase Return\", \"Send to Subcontractor\"], doc.purpose)", + "depends_on": "eval:doc.purpose === \"Send to Subcontractor\"", "fieldname": "contact_section", "fieldtype": "Section Break", - "label": "Customer or Supplier Details" + "label": "Supplier Details" }, { - "depends_on": "eval:doc.purpose==\"Purchase Return\" || doc.purpose==\"Send to Subcontractor\"", + "depends_on": "eval:doc.purpose === \"Send to Subcontractor\"", "fieldname": "supplier", "fieldtype": "Link", "label": "Supplier", @@ -453,7 +449,7 @@ }, { "bold": 1, - "depends_on": "eval:doc.purpose==\"Purchase Return\" || doc.purpose==\"Send to Subcontractor\"", + "depends_on": "eval:doc.purpose === \"Send to Subcontractor\"", "fieldname": "supplier_name", "fieldtype": "Data", "label": "Supplier Name", @@ -463,7 +459,7 @@ "read_only": 1 }, { - "depends_on": "eval:doc.purpose==\"Purchase Return\" || doc.purpose==\"Send to Subcontractor\"", + "depends_on": "eval:doc.purpose === \"Send to Subcontractor\"", "fieldname": "supplier_address", "fieldtype": "Link", "label": "Supplier Address", @@ -477,41 +473,6 @@ "fieldtype": "Small Text", "label": "Address" }, - { - "fieldname": "column_break_39", - "fieldtype": "Column Break" - }, - { - "depends_on": "eval:doc.purpose==\"Sales Return\"", - "fieldname": "customer", - "fieldtype": "Link", - "label": "Customer", - "no_copy": 1, - "oldfieldname": "customer", - "oldfieldtype": "Link", - "options": "Customer", - "print_hide": 1 - }, - { - "bold": 1, - "depends_on": "eval:doc.purpose==\"Sales Return\"", - "fieldname": "customer_name", - "fieldtype": "Data", - "label": "Customer Name", - "no_copy": 1, - "oldfieldname": "customer_name", - "oldfieldtype": "Data", - "read_only": 1 - }, - { - "depends_on": "eval:doc.purpose==\"Sales Return\"", - "fieldname": "customer_address", - "fieldtype": "Small Text", - "label": "Customer Address", - "no_copy": 1, - "oldfieldname": "customer_address", - "oldfieldtype": "Small Text" - }, { "collapsible": 1, "fieldname": "printing_settings", @@ -655,7 +616,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2020-12-09 14:58:13.267321", + "modified": "2021-05-24 11:32:23.904307", "modified_by": "Administrator", "module": "Stock", "name": "Stock Entry", From dc7afa743c20505883df39f3aa7a151a58ef498f Mon Sep 17 00:00:00 2001 From: Jannat Patel <31363128+pateljannat@users.noreply.github.com> Date: Mon, 24 May 2021 17:33:48 +0530 Subject: [PATCH 308/326] fix: profitability test (#25812) * fix: profitability test * fix: replaced class method * fix: removed print statement --- .../project_profitability/test_project_profitability.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/projects/report/project_profitability/test_project_profitability.py b/erpnext/projects/report/project_profitability/test_project_profitability.py index f2b9c2509a..ea6bdb54ca 100644 --- a/erpnext/projects/report/project_profitability/test_project_profitability.py +++ b/erpnext/projects/report/project_profitability/test_project_profitability.py @@ -8,7 +8,7 @@ from erpnext.projects.doctype.timesheet.timesheet import make_salary_slip, make_ from erpnext.projects.report.project_profitability.project_profitability import execute class TestProjectProfitability(unittest.TestCase): - @classmethod + def setUp(self): emp = make_employee('test_employee_9@salary.com', company='_Test Company') if not frappe.db.exists('Salary Component', 'Timesheet Component'): @@ -21,7 +21,7 @@ class TestProjectProfitability(unittest.TestCase): self.sales_invoice.due_date = nowdate() self.sales_invoice.submit() - frappe.db.set_value("HR Settings", "HR Settings", "standard_working_hours", 8) + frappe.db.set_value('HR Settings', None, 'standard_working_hours', 8) def test_project_profitability(self): filters = { @@ -55,4 +55,4 @@ class TestProjectProfitability(unittest.TestCase): def tearDown(self): frappe.get_doc("Sales Invoice", self.sales_invoice.name).cancel() frappe.get_doc("Salary Slip", self.salary_slip.name).cancel() - frappe.get_doc("Timesheet", self.timesheet.name).cancel() \ No newline at end of file + frappe.get_doc("Timesheet", self.timesheet.name).cancel() From b6d061fa8c90c678cfd2095577fbe7e3eab1f218 Mon Sep 17 00:00:00 2001 From: Saqib Date: Mon, 24 May 2021 18:22:03 +0530 Subject: [PATCH 309/326] fix(pos): handle db lock timeout error while pos closing (#25813) --- .../pos_invoice_merge_log.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py index b0ddea304c..08e072e204 100644 --- a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py +++ b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py @@ -42,8 +42,9 @@ class POSInvoiceMergeLog(Document): if return_against_status != "Consolidated": # if return entry is not getting merged in the current pos closing and if it is not consolidated bold_unconsolidated = frappe.bold("not Consolidated") - msg = (_("Row #{}: Original Invoice {} of return invoice {} is {}. ") + msg = (_("Row #{}: Original Invoice {} of return invoice {} is {}.") .format(d.idx, bold_return_against, bold_pos_invoice, bold_unconsolidated)) + msg += " " msg += _("Original invoice should be consolidated before or along with the return invoice.") msg += "

    " msg += _("You can add original invoice {} manually to proceed.").format(bold_return_against) @@ -274,9 +275,9 @@ def create_merge_logs(invoice_by_customer, closing_entry=None): closing_entry.db_set('error_message', '') closing_entry.update_opening_entry() - except Exception: + except Exception as e: frappe.db.rollback() - message_log = frappe.message_log.pop() + message_log = frappe.message_log.pop() if frappe.message_log else str(e) error_message = safe_load_json(message_log) if closing_entry: @@ -300,9 +301,9 @@ def cancel_merge_logs(merge_logs, closing_entry=None): closing_entry.db_set('error_message', '') closing_entry.update_opening_entry(for_cancel=True) - except Exception: + except Exception as e: frappe.db.rollback() - message_log = frappe.message_log.pop() + message_log = frappe.message_log.pop() if frappe.message_log else str(e) error_message = safe_load_json(message_log) if closing_entry: @@ -348,11 +349,9 @@ def job_already_enqueued(job_name): return True def safe_load_json(message): - JSONDecodeError = ValueError if six.PY2 else json.JSONDecodeError - try: json_message = json.loads(message).get('message') - except JSONDecodeError: + except Exception: json_message = message return json_message \ No newline at end of file From dd5b31f8c45ac78b12f07b6cff31bedffbb68762 Mon Sep 17 00:00:00 2001 From: Saqib Date: Mon, 24 May 2021 18:23:03 +0530 Subject: [PATCH 310/326] fix: set disable rounded total if it is globally enabled (#25789) --- erpnext/setup/doctype/global_defaults/global_defaults.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/setup/doctype/global_defaults/global_defaults.py b/erpnext/setup/doctype/global_defaults/global_defaults.py index e587217181..a0ba1efb5b 100644 --- a/erpnext/setup/doctype/global_defaults/global_defaults.py +++ b/erpnext/setup/doctype/global_defaults/global_defaults.py @@ -59,13 +59,15 @@ class GlobalDefaults(Document): # Make property setters to hide rounded total fields for doctype in ("Quotation", "Sales Order", "Sales Invoice", "Delivery Note", - "Supplier Quotation", "Purchase Order", "Purchase Invoice"): + "Supplier Quotation", "Purchase Order", "Purchase Invoice", "Purchase Receipt"): make_property_setter(doctype, "base_rounded_total", "hidden", self.disable_rounded_total, "Check", validate_fields_for_doctype=False) make_property_setter(doctype, "base_rounded_total", "print_hide", 1, "Check", validate_fields_for_doctype=False) make_property_setter(doctype, "rounded_total", "hidden", self.disable_rounded_total, "Check", validate_fields_for_doctype=False) make_property_setter(doctype, "rounded_total", "print_hide", self.disable_rounded_total, "Check", validate_fields_for_doctype=False) + make_property_setter(doctype, "disable_rounded_total", "default", cint(self.disable_rounded_total), "Text", validate_fields_for_doctype=False) + def toggle_in_words(self): self.disable_in_words = cint(self.disable_in_words) From c348215f61abb478fb0ddb64f4ed9b6e251897ff Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 18 May 2021 08:38:49 +0530 Subject: [PATCH 311/326] fix: Cashlfow mapper not showing data --- erpnext/accounts/report/cash_flow/custom_cash_flow.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/report/cash_flow/custom_cash_flow.py b/erpnext/accounts/report/cash_flow/custom_cash_flow.py index fe2bc725e0..ff87276a87 100644 --- a/erpnext/accounts/report/cash_flow/custom_cash_flow.py +++ b/erpnext/accounts/report/cash_flow/custom_cash_flow.py @@ -165,7 +165,7 @@ def add_data_for_operating_activities( if profit_data: profit_data.update({ "indent": 1, - "parent_account": get_mapper_for(light_mappers, position=0)['section_header'] + "parent_account": get_mapper_for(light_mappers, position=1)['section_header'] }) data.append(profit_data) section_data.append(profit_data) @@ -312,10 +312,10 @@ def add_data_for_other_activities( def compute_data(filters, company_currency, profit_data, period_list, light_mappers, full_mapper): data = [] - operating_activities_mapper = get_mapper_for(light_mappers, position=0) + operating_activities_mapper = get_mapper_for(light_mappers, position=1) other_mappers = [ - get_mapper_for(light_mappers, position=1), - get_mapper_for(light_mappers, position=2) + get_mapper_for(light_mappers, position=2), + get_mapper_for(light_mappers, position=3) ] if operating_activities_mapper: From 25112244ed5c3a4dc62a14bacd5362d3febff356 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 14 May 2021 21:31:22 +0530 Subject: [PATCH 312/326] fix: Ignore rounding diff while importig JV using data import --- erpnext/accounts/doctype/journal_entry/journal_entry.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index fefab82efc..ed1bd28223 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -39,7 +39,11 @@ class JournalEntry(AccountsController): self.validate_multi_currency() self.set_amounts_in_company_currency() self.validate_debit_credit_amount() - self.validate_total_debit_and_credit() + + # Do not validate while importing via data import + if not frappe.flags.in_import: + self.validate_total_debit_and_credit() + self.validate_against_jv() self.validate_reference_doc() self.set_against_account() From 5670cf43862d4de29e905e185a25fe9d50b5038c Mon Sep 17 00:00:00 2001 From: Anurag Mishra <32095923+Anurag810@users.noreply.github.com> Date: Mon, 24 May 2021 20:24:20 +0530 Subject: [PATCH 313/326] feat: enhancements in Training Event (#25782) * feat: Some Minor fixes * fix: patch file * fix: patch Co-authored-by: Rucha Mahabal --- .../doctype/training_event/training_event.js | 32 +- .../training_event_employee.json | 301 ++++-------------- .../training_scheduled.json | 8 +- .../training_scheduled/training_scheduled.md | 3 + erpnext/patches.txt | 1 + .../v13_0/set_training_event_attendance.py | 9 + 6 files changed, 113 insertions(+), 241 deletions(-) create mode 100644 erpnext/patches/v13_0/set_training_event_attendance.py diff --git a/erpnext/hr/doctype/training_event/training_event.js b/erpnext/hr/doctype/training_event/training_event.js index 12bc920b18..b7d34b178a 100644 --- a/erpnext/hr/doctype/training_event/training_event.js +++ b/erpnext/hr/doctype/training_event/training_event.js @@ -2,23 +2,41 @@ // For license information, please see license.txt frappe.ui.form.on('Training Event', { - onload_post_render: function(frm) { + onload_post_render: function (frm) { frm.get_field("employees").grid.set_multiple_add("employee"); }, - refresh: function(frm) { - if(!frm.doc.__islocal) { - frm.add_custom_button(__("Training Result"), function() { + refresh: function (frm) { + if (!frm.doc.__islocal) { + frm.add_custom_button(__("Training Result"), function () { frappe.route_options = { training_event: frm.doc.name - } + }; frappe.set_route("List", "Training Result"); }); - frm.add_custom_button(__("Training Feedback"), function() { + frm.add_custom_button(__("Training Feedback"), function () { frappe.route_options = { training_event: frm.doc.name - } + }; frappe.set_route("List", "Training Feedback"); }); } } }); + +frappe.ui.form.on("Training Event Employee", { + employee: function (frm) { + let emp = []; + for (let d in frm.doc.employees) { + if (frm.doc.employees[d].employee) { + emp.push(frm.doc.employees[d].employee); + } + } + frm.set_query("employee", "employees", function () { + return { + filters: { + name: ["NOT IN", emp] + } + }; + }); + } +}); 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 e3a40649b4..2d313e9fac 100644 --- a/erpnext/hr/doctype/training_event_employee/training_event_employee.json +++ b/erpnext/hr/doctype/training_event_employee/training_event_employee.json @@ -1,241 +1,80 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2016-08-08 05:33:39.965305", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, + "actions": [], + "creation": "2016-08-08 05:33:39.965305", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "employee", + "employee_name", + "department", + "column_break_3", + "status", + "attendance", + "is_mandatory" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "employee", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Employee", - "length": 0, - "no_copy": 0, - "options": "Employee", - "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": "employee", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Employee", + "options": "Employee" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "employee.employee_name", - "fieldname": "employee_name", - "fieldtype": "Read Only", - "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": "Employee Name", - "length": 0, - "no_copy": 0, - "options": "", - "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 - }, + "fetch_from": "employee.employee_name", + "fieldname": "employee_name", + "fieldtype": "Read Only", + "label": "Employee Name" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "employee.department", - "fieldname": "department", - "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": "Department", - "length": 0, - "no_copy": 0, - "options": "Department", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fetch_from": "employee.department", + "fieldname": "department", + "fieldtype": "Link", + "label": "Department", + "options": "Department", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_3", - "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_3", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Open", - "fieldname": "status", - "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": 0, - "label": "Status", - "length": 0, - "no_copy": 1, - "options": "Open\nInvited\nCompleted\nFeedback Submitted", - "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 - }, + "allow_on_submit": 1, + "default": "Open", + "fieldname": "status", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Status", + "no_copy": 1, + "options": "Open\nInvited\nCompleted\nFeedback Submitted" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "attendance", - "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": 0, - "label": "Attendance", - "length": 0, - "no_copy": 0, - "options": "Mandatory\nOptional", - "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": "attendance", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Attendance", + "options": "Present\nAbsent" + }, + { + "columns": 2, + "default": "1", + "fieldname": "is_mandatory", + "fieldtype": "Check", + "in_list_view": 1, + "label": "Is Mandatory" } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2019-01-30 11:28:16.170333", - "modified_by": "Administrator", - "module": "HR", - "name": "Training Event Employee", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 0, - "track_seen": 0, - "track_views": 0 + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-05-21 12:41:59.336237", + "modified_by": "Administrator", + "module": "HR", + "name": "Training Event Employee", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC" } \ No newline at end of file diff --git a/erpnext/hr/notification/training_scheduled/training_scheduled.json b/erpnext/hr/notification/training_scheduled/training_scheduled.json index 966b887572..e49541e321 100644 --- a/erpnext/hr/notification/training_scheduled/training_scheduled.json +++ b/erpnext/hr/notification/training_scheduled/training_scheduled.json @@ -11,16 +11,18 @@ "event": "Submit", "idx": 0, "is_standard": 1, - "message": "\n \n \n \n \n \n \n \n
    \n
    \n {{_(\"Training Event:\")}} {{ doc.event_name }}\n
    \n
    \n\n\n \n \n \n \n \n \n \n
    \n
    \n
      \n
    • {{ doc.introduction }}
    • \n
    • {{_(\"Event Location\")}}: {{ doc.location }}
    • \n {% set start = frappe.utils.get_datetime(doc.start_time) %}\n {% set end = frappe.utils.get_datetime(doc.end_time) %}\n {% if start.date() == end.date() %}\n
    • {{_(\"Date\")}}: {{ start.strftime(\"%A, %d %b %Y\") }}
    • \n
    • \n {{_(\"Timing\")}}: {{ start.strftime(\"%I:%M %p\") + ' to ' + end.strftime(\"%I:%M %p\") }}\n
    • \n {% else %}\n
    • {{_(\"Start Time\")}}: {{ start.strftime(\"%A, %d %b %Y at %I:%M %p\") }}\n
    • \n
    • {{_(\"End Time\")}}: {{ end.strftime(\"%A, %d %b %Y at %I:%M %p\") }}\n
    • \n {% endif %}\n
    \n {{ _('Event Link') }}: {{ frappe.utils.get_link_to_form(doc.doctype, doc.name) }}\n
    \n
    ", - "modified": "2019-11-29 15:38:31.805409", + "message": "\n \n \n \n \n \n \n \n
    \n
    \n {{_(\"Training Event:\")}} {{ doc.event_name }}\n
    \n
    \n\n\n \n \n \n \n \n \n \n
    \n
    \n {{ doc.introduction }}\n
      \n
    • {{_(\"Event Location\")}}: {{ doc.location }}
    • \n {% set start = frappe.utils.get_datetime(doc.start_time) %}\n {% set end = frappe.utils.get_datetime(doc.end_time) %}\n {% if start.date() == end.date() %}\n
    • {{_(\"Date\")}}: {{ start.strftime(\"%A, %d %b %Y\") }}
    • \n
    • \n {{_(\"Timing\")}}: {{ start.strftime(\"%I:%M %p\") + ' to ' + end.strftime(\"%I:%M %p\") }}\n
    • \n {% else %}\n
    • {{_(\"Start Time\")}}: {{ start.strftime(\"%A, %d %b %Y at %I:%M %p\") }}\n
    • \n
    • {{_(\"End Time\")}}: {{ end.strftime(\"%A, %d %b %Y at %I:%M %p\") }}\n
    • \n {% endif %}\n
    • {{ _('Event Link') }}: {{ frappe.utils.get_link_to_form(doc.doctype, doc.name) }}
    • \n {% if doc.is_mandatory %}\n
    • Note: This Training Event is mandatory
    • \n {% endif %}\n
    \n
    \n
    ", + "modified": "2021-05-24 16:29:13.165930", "modified_by": "Administrator", "module": "HR", "name": "Training Scheduled", "owner": "Administrator", "recipients": [ { - "email_by_document_field": "employee_emails" + "receiver_by_document_field": "employee_emails" } ], + "send_system_notification": 0, + "send_to_all_assignees": 0, "subject": "Training Scheduled: {{ doc.name }}" } \ No newline at end of file diff --git a/erpnext/hr/notification/training_scheduled/training_scheduled.md b/erpnext/hr/notification/training_scheduled/training_scheduled.md index 374038ac20..418fd4990e 100644 --- a/erpnext/hr/notification/training_scheduled/training_scheduled.md +++ b/erpnext/hr/notification/training_scheduled/training_scheduled.md @@ -35,6 +35,9 @@ {% endif %}
  • {{ _('Event Link') }}: {{ frappe.utils.get_link_to_form(doc.doctype, doc.name) }}
  • + {% if doc.is_mandatory %} +
  • Note: This Training Event is mandatory
  • + {% endif %}
    diff --git a/erpnext/patches.txt b/erpnext/patches.txt index d4655e19b9..1e8ce3c658 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -780,3 +780,4 @@ erpnext.patches.v13_0.germany_make_custom_fields erpnext.patches.v13_0.germany_fill_debtor_creditor_number erpnext.patches.v13_0.set_pos_closing_as_failed erpnext.patches.v13_0.update_timesheet_changes +erpnext.patches.v13_0.set_training_event_attendance diff --git a/erpnext/patches/v13_0/set_training_event_attendance.py b/erpnext/patches/v13_0/set_training_event_attendance.py new file mode 100644 index 0000000000..18cad8d86c --- /dev/null +++ b/erpnext/patches/v13_0/set_training_event_attendance.py @@ -0,0 +1,9 @@ +from __future__ import unicode_literals +import frappe + +def execute(): + frappe.reload_doc('hr', 'doctype', 'training_event') + frappe.reload_doc('hr', 'doctype', 'training_event_employee') + + frappe.db.sql("update `tabTraining Event Employee` set `attendance` = 'Present'") + frappe.db.sql("update `tabTraining Event Employee` set `is_mandatory` = 1 where `attendance` = 'Mandatory'") \ No newline at end of file From b4f0347c02ded2df18156b5a999ca50b82a5ce38 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 24 May 2021 20:12:24 +0530 Subject: [PATCH 314/326] fix: removed serial no validation for sales invoice --- .../doctype/sales_invoice/sales_invoice.py | 21 ------------------- .../sales_invoice/test_sales_invoice.py | 6 ------ 2 files changed, 27 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index bb74a02606..1a1f889657 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -1121,7 +1121,6 @@ class SalesInvoice(SellingController): """ self.set_serial_no_against_delivery_note() self.validate_serial_against_delivery_note() - self.validate_serial_against_sales_invoice() def set_serial_no_against_delivery_note(self): for item in self.items: @@ -1152,26 +1151,6 @@ class SalesInvoice(SellingController): frappe.throw(_("Row {0}: {1} Serial numbers required for Item {2}. You have provided {3}.").format( item.idx, item.qty, item.item_code, len(si_serial_nos))) - def validate_serial_against_sales_invoice(self): - """ check if serial number is already used in other sales invoice """ - for item in self.items: - if not item.serial_no: - continue - - for serial_no in item.serial_no.split("\n"): - serial_no_details = frappe.db.get_value("Serial No", serial_no, - ["sales_invoice", "item_code"], as_dict=1) - - if not serial_no_details: - continue - - if serial_no_details.sales_invoice and serial_no_details.item_code == item.item_code \ - and self.name != serial_no_details.sales_invoice: - sales_invoice_company = frappe.db.get_value("Sales Invoice", serial_no_details.sales_invoice, "company") - if sales_invoice_company == self.company: - frappe.throw(_("Serial Number: {0} is already referenced in Sales Invoice: {1}") - .format(serial_no, serial_no_details.sales_invoice)) - def update_project(self): if self.project: project = frappe.get_doc("Project", self.project) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 9059d0b040..df6d483904 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -933,12 +933,6 @@ class TestSalesInvoice(unittest.TestCase): self.assertFalse(frappe.db.get_value("Serial No", serial_nos[0], "warehouse")) self.assertEqual(frappe.db.get_value("Serial No", serial_nos[0], "delivery_document_no"), si.name) - self.assertEqual(frappe.db.get_value("Serial No", serial_nos[0], "sales_invoice"), - si.name) - - # check if the serial number is already linked with any other Sales Invoice - _si = frappe.copy_doc(si.as_dict()) - self.assertRaises(frappe.ValidationError, _si.insert) return si From 073dcf7e4265539ee521c489202aceb0c38cf84a Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 25 May 2021 14:06:10 +0530 Subject: [PATCH 315/326] ci(semgrep): fix false positives (#25823) --- .github/helper/semgrep_rules/translate.py | 8 ++++++++ .github/helper/semgrep_rules/translate.yml | 4 ++-- .github/helper/semgrep_rules/ux.js | 9 +++++++++ .github/helper/semgrep_rules/ux.py | 18 ++++++++--------- .github/helper/semgrep_rules/ux.yml | 23 ++++++++++++++++++---- 5 files changed, 47 insertions(+), 15 deletions(-) create mode 100644 .github/helper/semgrep_rules/ux.js diff --git a/.github/helper/semgrep_rules/translate.py b/.github/helper/semgrep_rules/translate.py index bd6cd9126c..9de6aa94f0 100644 --- a/.github/helper/semgrep_rules/translate.py +++ b/.github/helper/semgrep_rules/translate.py @@ -51,3 +51,11 @@ _(f"what" + f"this is also not cool") _("") # ruleid: frappe-translation-empty-string _('') + + +class Test: + # ok: frappe-translation-python-splitting + def __init__( + args + ): + pass diff --git a/.github/helper/semgrep_rules/translate.yml b/.github/helper/semgrep_rules/translate.yml index fa4ec9e15d..5f03fb9fd0 100644 --- a/.github/helper/semgrep_rules/translate.yml +++ b/.github/helper/semgrep_rules/translate.yml @@ -44,8 +44,8 @@ rules: pattern-either: - pattern: _(...) + _(...) - pattern: _("..." + "...") - - pattern-regex: '_\([^\)]*\\\s*' # lines broken by `\` - - pattern-regex: '_\(\s*\n' # line breaks allowed by python for using ( ) + - pattern-regex: '[\s\.]_\([^\)]*\\\s*' # lines broken by `\` + - pattern-regex: '[\s\.]_\(\s*\n' # line breaks allowed by python for using ( ) message: | Do not split strings inside translate function. Do not concatenate using translate functions. Please refer: https://frappeframework.com/docs/user/en/translations diff --git a/.github/helper/semgrep_rules/ux.js b/.github/helper/semgrep_rules/ux.js new file mode 100644 index 0000000000..ae73f9cc60 --- /dev/null +++ b/.github/helper/semgrep_rules/ux.js @@ -0,0 +1,9 @@ + +// ok: frappe-missing-translate-function-js +frappe.msgprint('{{ _("Both login and password required") }}'); + +// ruleid: frappe-missing-translate-function-js +frappe.msgprint('What'); + +// ok: frappe-missing-translate-function-js +frappe.throw(' {{ _("Both login and password required") }}. '); diff --git a/.github/helper/semgrep_rules/ux.py b/.github/helper/semgrep_rules/ux.py index 4a74457435..a00d3cd8ae 100644 --- a/.github/helper/semgrep_rules/ux.py +++ b/.github/helper/semgrep_rules/ux.py @@ -2,30 +2,30 @@ import frappe from frappe import msgprint, throw, _ -# ruleid: frappe-missing-translate-function +# ruleid: frappe-missing-translate-function-python throw("Error Occured") -# ruleid: frappe-missing-translate-function +# ruleid: frappe-missing-translate-function-python frappe.throw("Error Occured") -# ruleid: frappe-missing-translate-function +# ruleid: frappe-missing-translate-function-python frappe.msgprint("Useful message") -# ruleid: frappe-missing-translate-function +# ruleid: frappe-missing-translate-function-python msgprint("Useful message") -# ok: frappe-missing-translate-function +# ok: frappe-missing-translate-function-python translatedmessage = _("Hello") -# ok: frappe-missing-translate-function +# ok: frappe-missing-translate-function-python throw(translatedmessage) -# ok: frappe-missing-translate-function +# ok: frappe-missing-translate-function-python msgprint(translatedmessage) -# ok: frappe-missing-translate-function +# ok: frappe-missing-translate-function-python msgprint(_("Helpful message")) -# ok: frappe-missing-translate-function +# ok: frappe-missing-translate-function-python frappe.throw(_("Error occured")) diff --git a/.github/helper/semgrep_rules/ux.yml b/.github/helper/semgrep_rules/ux.yml index ed06a6a80c..dd667f36c0 100644 --- a/.github/helper/semgrep_rules/ux.yml +++ b/.github/helper/semgrep_rules/ux.yml @@ -1,15 +1,30 @@ rules: -- id: frappe-missing-translate-function +- id: frappe-missing-translate-function-python pattern-either: - patterns: - pattern: frappe.msgprint("...", ...) - pattern-not: frappe.msgprint(_("..."), ...) - - pattern-not: frappe.msgprint(__("..."), ...) - patterns: - pattern: frappe.throw("...", ...) - pattern-not: frappe.throw(_("..."), ...) - - pattern-not: frappe.throw(__("..."), ...) message: | All user facing text must be wrapped in translate function. Please refer to translation documentation. https://frappeframework.com/docs/user/en/guides/basics/translations - languages: [python, javascript, json] + languages: [python] + severity: ERROR + +- id: frappe-missing-translate-function-js + pattern-either: + - patterns: + - pattern: frappe.msgprint("...", ...) + - pattern-not: frappe.msgprint(__("..."), ...) + # ignore microtemplating e.g. msgprint("{{ _("server side translation") }}") + - pattern-not: frappe.msgprint("=~/\{\{.*\_.*\}\}/i", ...) + - patterns: + - pattern: frappe.throw("...", ...) + - pattern-not: frappe.throw(__("..."), ...) + # ignore microtemplating + - pattern-not: frappe.throw("=~/\{\{.*\_.*\}\}/i", ...) + message: | + All user facing text must be wrapped in translate function. Please refer to translation documentation. https://frappeframework.com/docs/user/en/guides/basics/translations + languages: [javascript] severity: ERROR From 507a211c81c13cf8ea2b2ed401f703d6675b207c Mon Sep 17 00:00:00 2001 From: Rakshith N <36509967+rakshithrddy@users.noreply.github.com> Date: Tue, 25 May 2021 19:03:29 +0530 Subject: [PATCH 316/326] fix: fetch email id from dialog box in pos past order summary' (#25808) --- erpnext/selling/page/point_of_sale/pos_past_order_summary.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/selling/page/point_of_sale/pos_past_order_summary.js b/erpnext/selling/page/point_of_sale/pos_past_order_summary.js index acf4eb371f..cec831d616 100644 --- a/erpnext/selling/page/point_of_sale/pos_past_order_summary.js +++ b/erpnext/selling/page/point_of_sale/pos_past_order_summary.js @@ -241,7 +241,7 @@ erpnext.PointOfSale.PastOrderSummary = class { send_email() { const frm = this.events.get_frm(); - const recipients = this.email_dialog.get_values().recipients; + const recipients = this.email_dialog.get_values().email_id; const doc = this.doc || frm.doc; const print_format = frm.pos_print_format; From 6a62ad325f8d5abb350326a14f81a1e6f91db836 Mon Sep 17 00:00:00 2001 From: Laurynas Sakalauskas Date: Tue, 25 May 2021 16:39:44 +0300 Subject: [PATCH 317/326] fix(plaid): withdrawals and deposits are recorded incorrectly (#25784) --- .../doctype/plaid_settings/plaid_settings.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py index 21f1db619e..ce15e47c5e 100644 --- a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py +++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py @@ -183,11 +183,11 @@ def new_bank_transaction(transaction): bank_account = frappe.db.get_value("Bank Account", dict(integration_id=transaction["account_id"])) if float(transaction["amount"]) >= 0: - debit = float(transaction["amount"]) - credit = 0 - else: debit = 0 - credit = abs(float(transaction["amount"])) + credit = float(transaction["amount"]) + else: + debit = abs(float(transaction["amount"])) + credit = 0 status = "Pending" if transaction["pending"] == "True" else "Settled" From 4d61fa249786f294110afa85e6278ffb5a282b85 Mon Sep 17 00:00:00 2001 From: Afshan <33727827+AfshanKhan@users.noreply.github.com> Date: Tue, 25 May 2021 19:16:02 +0530 Subject: [PATCH 318/326] fix: incorrect cr/dr shown in general ledger for multi-currency transactions (#25654) --- erpnext/accounts/report/utils.py | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/erpnext/accounts/report/utils.py b/erpnext/accounts/report/utils.py index 9de8d19f2a..b020d0a506 100644 --- a/erpnext/accounts/report/utils.py +++ b/erpnext/accounts/report/utils.py @@ -81,8 +81,7 @@ def convert_to_presentation_currency(gl_entries, currency_info, company): presentation_currency = currency_info['presentation_currency'] company_currency = currency_info['company_currency'] - pl_accounts = [d.name for d in frappe.get_list('Account', - filters={'report_type': 'Profit and Loss', 'company': company})] + account_currencies = list(set(entry['account_currency'] for entry in gl_entries)) for entry in gl_entries: account = entry['account'] @@ -92,10 +91,15 @@ def convert_to_presentation_currency(gl_entries, currency_info, company): credit_in_account_currency = flt(entry['credit_in_account_currency']) account_currency = entry['account_currency'] - if account_currency != presentation_currency: - value = debit or credit + if len(account_currencies) == 1 and account_currency == presentation_currency: + if entry.get('debit'): + entry['debit'] = debit_in_account_currency - date = entry['posting_date'] if account in pl_accounts else currency_info['report_date'] + if entry.get('credit'): + entry['credit'] = credit_in_account_currency + else: + value = debit or credit + date = currency_info['report_date'] converted_value = convert(value, presentation_currency, company_currency, date) if entry.get('debit'): @@ -104,13 +108,6 @@ def convert_to_presentation_currency(gl_entries, currency_info, company): if entry.get('credit'): entry['credit'] = converted_value - elif account_currency == presentation_currency: - if entry.get('debit'): - entry['debit'] = debit_in_account_currency - - if entry.get('credit'): - entry['credit'] = credit_in_account_currency - converted_gl_list.append(entry) return converted_gl_list From 99636c6aca8a3a0faf6e69f777d75f0d75c3c73b Mon Sep 17 00:00:00 2001 From: Ganga Manoj Date: Tue, 25 May 2021 19:17:01 +0530 Subject: [PATCH 319/326] fix: rearrange buttons for company doctype (#25617) --- erpnext/setup/doctype/company/company.js | 27 ++++++++-------------- erpnext/setup/doctype/company/company.json | 8 +------ 2 files changed, 11 insertions(+), 24 deletions(-) diff --git a/erpnext/setup/doctype/company/company.js b/erpnext/setup/doctype/company/company.js index 9957aad019..b24048d1ce 100644 --- a/erpnext/setup/doctype/company/company.js +++ b/erpnext/setup/doctype/company/company.js @@ -90,12 +90,6 @@ frappe.ui.form.on("Company", { frm.toggle_enable("default_currency", (frm.doc.__onload && !frm.doc.__onload.transactions_exist)); - if (frm.has_perm('write')) { - frm.add_custom_button(__('Create Tax Template'), function() { - frm.trigger("make_default_tax_template"); - }); - } - if (frappe.perm.has_perm("Cost Center", 0, 'read')) { frm.add_custom_button(__('Cost Centers'), function() { frappe.set_route('Tree', 'Cost Center', {'company': frm.doc.name}); @@ -121,17 +115,21 @@ frappe.ui.form.on("Company", { } if (frm.has_perm('write')) { - frm.add_custom_button(__('Default Tax Template'), function() { + frm.add_custom_button(__('Create Tax Template'), function() { frm.trigger("make_default_tax_template"); - }, __('Create')); + }, __('Manage')); + } + + if (frappe.user.has_role('System Manager')) { + if (frm.has_perm('write')) { + frm.add_custom_button(__('Delete Transactions'), function() { + frm.trigger("delete_company_transactions"); + }, __('Manage')); + } } } erpnext.company.set_chart_of_accounts_options(frm.doc); - - if (!frappe.user.has_role('System Manager')) { - frm.get_field("delete_company_transactions").hide(); - } }, make_default_tax_template: function(frm) { @@ -145,11 +143,6 @@ frappe.ui.form.on("Company", { }) }, - onload_post_render: function(frm) { - if(frm.get_field("delete_company_transactions").$input) - frm.get_field("delete_company_transactions").$input.addClass("btn-danger"); - }, - country: function(frm) { erpnext.company.set_chart_of_accounts_options(frm.doc); }, diff --git a/erpnext/setup/doctype/company/company.json b/erpnext/setup/doctype/company/company.json index 83cbf475ab..061986d92d 100644 --- a/erpnext/setup/doctype/company/company.json +++ b/erpnext/setup/doctype/company/company.json @@ -99,7 +99,6 @@ "company_description", "registration_info", "registration_details", - "delete_company_transactions", "lft", "rgt", "old_parent" @@ -666,11 +665,6 @@ "oldfieldname": "registration_details", "oldfieldtype": "Code" }, - { - "fieldname": "delete_company_transactions", - "fieldtype": "Button", - "label": "Delete Company Transactions" - }, { "fieldname": "lft", "fieldtype": "Int", @@ -747,7 +741,7 @@ "image_field": "company_logo", "is_tree": 1, "links": [], - "modified": "2021-02-16 15:53:37.167589", + "modified": "2021-05-07 03:11:28.189740", "modified_by": "Administrator", "module": "Setup", "name": "Company", From 18cfced0320f567bd985db050a4260bd3db31331 Mon Sep 17 00:00:00 2001 From: Ganga Manoj Date: Tue, 25 May 2021 19:54:07 +0530 Subject: [PATCH 320/326] fix(Material Request): Make status on list and form view the same (#24856) --- erpnext/controllers/status_updater.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py index ed3aee5c1a..83d4c33140 100644 --- a/erpnext/controllers/status_updater.py +++ b/erpnext/controllers/status_updater.py @@ -76,12 +76,12 @@ status_map = { ["Stopped", "eval:self.status == 'Stopped'"], ["Cancelled", "eval:self.docstatus == 2"], ["Pending", "eval:self.status != 'Stopped' and self.per_ordered == 0 and self.docstatus == 1"], - ["Partially Ordered", "eval:self.status != 'Stopped' and self.per_ordered < 100 and self.per_ordered > 0 and self.docstatus == 1"], ["Ordered", "eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Purchase'"], ["Transferred", "eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Material Transfer'"], ["Issued", "eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Material Issue'"], ["Received", "eval:self.status != 'Stopped' and self.per_received == 100 and self.docstatus == 1 and self.material_request_type == 'Purchase'"], ["Partially Received", "eval:self.status != 'Stopped' and self.per_received > 0 and self.per_received < 100 and self.docstatus == 1 and self.material_request_type == 'Purchase'"], + ["Partially Ordered", "eval:self.status != 'Stopped' and self.per_ordered < 100 and self.per_ordered > 0 and self.docstatus == 1"], ["Manufactured", "eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Manufacture'"] ], "Bank Transaction": [ From 81376ea44f8d3afc4db1aad34028ce80eeeed94d Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Tue, 25 May 2021 20:39:17 +0530 Subject: [PATCH 321/326] feat: Show net values in Party Accounts (#25714) --- .../report/general_ledger/general_ledger.js | 5 ++++ .../report/general_ledger/general_ledger.py | 27 +++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/erpnext/accounts/report/general_ledger/general_ledger.js b/erpnext/accounts/report/general_ledger/general_ledger.js index fb0d359926..84f786814d 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.js +++ b/erpnext/accounts/report/general_ledger/general_ledger.js @@ -166,6 +166,11 @@ frappe.query_reports["General Ledger"] = { "fieldname": "show_cancelled_entries", "label": __("Show Cancelled Entries"), "fieldtype": "Check" + }, + { + "fieldname": "show_net_values_in_party_account", + "label": __("Show Net Values in Party Account"), + "fieldtype": "Check" } ] } diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index b5d7992604..562df4f6f7 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -344,6 +344,9 @@ def get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map): consolidated_gle = OrderedDict() group_by = group_by_field(filters.get('group_by')) + if filters.get('show_net_values_in_party_account'): + account_type_map = get_account_type_map(filters.get('company')) + def update_value_in_dict(data, key, gle): data[key].debit += flt(gle.debit) data[key].credit += flt(gle.credit) @@ -351,6 +354,24 @@ def get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map): data[key].debit_in_account_currency += flt(gle.debit_in_account_currency) data[key].credit_in_account_currency += flt(gle.credit_in_account_currency) + if filters.get('show_net_values_in_party_account') and \ + account_type_map.get(data[key].account) in ('Receivable', 'Payable'): + net_value = flt(data[key].debit) - flt(data[key].credit) + net_value_in_account_currency = flt(data[key].debit_in_account_currency) \ + - flt(data[key].credit_in_account_currency) + + if net_value < 0: + dr_or_cr = 'credit' + rev_dr_or_cr = 'debit' + else: + dr_or_cr = 'debit' + rev_dr_or_cr = 'credit' + + data[key][dr_or_cr] = abs(net_value) + data[key][dr_or_cr+'_in_account_currency'] = abs(net_value_in_account_currency) + data[key][rev_dr_or_cr] = 0 + data[key][rev_dr_or_cr+'_in_account_currency'] = 0 + if data[key].against_voucher and gle.against_voucher: data[key].against_voucher += ', ' + gle.against_voucher @@ -388,6 +409,12 @@ def get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map): return totals, entries +def get_account_type_map(company): + account_type_map = frappe._dict(frappe.get_all('Account', fields=['name', 'account_type'], + filters={'company': company}, as_list=1)) + + return account_type_map + def get_result_as_list(data, filters): balance, balance_in_account_currency = 0, 0 inv_details = get_supplier_invoice_details() From c262705143bd8c8d52d7dfa58f8a82bd0e4bebf1 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Tue, 25 May 2021 20:39:28 +0530 Subject: [PATCH 322/326] feat: Show net values in Party Accounts (#25714) From ff96bdf0c19525b1baec6d647a4ce7a389699958 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 25 May 2021 19:54:40 +0530 Subject: [PATCH 323/326] fix(ux): fix unstranslated text in msgprint/throw --- .../accounting_dimension/accounting_dimension.py | 2 +- .../doctype/accounts_settings/accounts_settings.py | 3 ++- .../chart_of_accounts_importer.py | 4 ++-- .../process_statement_of_accounts.js | 8 ++++---- erpnext/crm/doctype/appointment/appointment.py | 10 +++++----- .../course_scheduling_tool/course_scheduling_tool.py | 2 +- erpnext/loan_management/doctype/loan/loan.py | 2 +- erpnext/non_profit/doctype/member/member.py | 2 +- erpnext/public/js/controllers/transaction.js | 6 +++--- erpnext/setup/install.py | 2 +- erpnext/www/book_appointment/index.js | 4 ++-- 11 files changed, 23 insertions(+), 22 deletions(-) diff --git a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py index 0ebf0eb541..7cd1e7736c 100644 --- a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py +++ b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py @@ -27,7 +27,7 @@ class AccountingDimension(Document): exists = frappe.db.get_value("Accounting Dimension", {'document_type': self.document_type}, ['name']) if exists and self.is_new(): - frappe.throw("Document Type already used as a dimension") + frappe.throw(_("Document Type already used as a dimension")) if not self.is_new(): self.validate_document_type_change() diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py index 4d3388090d..ac4a2d6f16 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py @@ -5,6 +5,7 @@ from __future__ import unicode_literals import frappe +from frappe import _ from frappe.utils import cint from frappe.model.document import Document from frappe.custom.doctype.property_setter.property_setter import make_property_setter @@ -24,7 +25,7 @@ class AccountsSettings(Document): def validate_stale_days(self): if not self.allow_stale and cint(self.stale_days) <= 0: frappe.msgprint( - "Stale Days should start from 1.", title='Error', indicator='red', + _("Stale Days should start from 1."), title='Error', indicator='red', raise_exception=1) def enable_payment_schedule_in_print(self): diff --git a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py index f96f59169e..ef44626b37 100644 --- a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py +++ b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py @@ -22,7 +22,7 @@ def validate_company(company): 'allow_account_creation_against_child_company']) if parent_company and (not allow_account_creation_against_child_company): - msg = _("{} is a child company. ").format(frappe.bold(company)) + msg = _("{} is a child company.").format(frappe.bold(company)) + " " msg += _("Please import accounts against parent company or enable {} in company master.").format( frappe.bold('Allow Account Creation Against Child Company')) frappe.throw(msg, title=_('Wrong Company')) @@ -56,7 +56,7 @@ def get_file(file_name): extension = extension.lstrip(".") if extension not in ('csv', 'xlsx', 'xls'): - frappe.throw("Only CSV and Excel files can be used to for importing data. Please check the file format you are trying to upload") + frappe.throw(_("Only CSV and Excel files can be used to for importing data. Please check the file format you are trying to upload")) return file_doc, extension diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.js b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.js index 6dc46430e0..088c190f45 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.js +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.js @@ -19,7 +19,7 @@ frappe.ui.form.on('Process Statement Of Accounts', { frappe.show_alert({message: __('Emails Queued'), indicator: 'blue'}); } else{ - frappe.msgprint('No Records for these settings.') + frappe.msgprint(__('No Records for these settings.')) } } }); @@ -33,7 +33,7 @@ frappe.ui.form.on('Process Statement Of Accounts', { type: 'GET', success: function(result) { if(jQuery.isEmptyObject(result)){ - frappe.msgprint('No Records for these settings.'); + frappe.msgprint(__('No Records for these settings.')); } else{ window.location = url; @@ -92,7 +92,7 @@ frappe.ui.form.on('Process Statement Of Accounts', { frm.refresh_field('customers'); } else{ - frappe.throw('No Customers found with selected options.'); + frappe.throw(__('No Customers found with selected options.')); } } } @@ -129,4 +129,4 @@ frappe.ui.form.on('Process Statement Of Accounts Customer', { } }) } -}); \ No newline at end of file +}); diff --git a/erpnext/crm/doctype/appointment/appointment.py b/erpnext/crm/doctype/appointment/appointment.py index 2009ebf7cb..df73f09c49 100644 --- a/erpnext/crm/doctype/appointment/appointment.py +++ b/erpnext/crm/doctype/appointment/appointment.py @@ -38,7 +38,7 @@ class Appointment(Document): number_of_agents = frappe.db.get_single_value('Appointment Booking Settings', 'number_of_agents') if not number_of_agents == 0: if (number_of_appointments_in_same_slot >= number_of_agents): - frappe.throw('Time slot is not available') + frappe.throw(_('Time slot is not available')) # Link lead if not self.party: lead = self.find_lead_by_email() @@ -75,10 +75,10 @@ class Appointment(Document): subject=_('Appointment Confirmation')) if frappe.session.user == "Guest": frappe.msgprint( - 'Please check your email to confirm the appointment') + _('Please check your email to confirm the appointment')) else : frappe.msgprint( - 'Appointment was created. But no lead was found. Please check the email to confirm') + _('Appointment was created. But no lead was found. Please check the email to confirm')) def on_change(self): # Sync Calendar @@ -91,7 +91,7 @@ class Appointment(Document): def set_verified(self, email): if not email == self.customer_email: - frappe.throw('Email verification failed.') + frappe.throw(_('Email verification failed.')) # Create new lead self.create_lead_and_link() # Remove unverified status @@ -184,7 +184,7 @@ class Appointment(Document): appointment_event.insert(ignore_permissions=True) self.calendar_event = appointment_event.name self.save(ignore_permissions=True) - + def _get_verify_url(self): verify_route = '/book_appointment/verify' params = { diff --git a/erpnext/education/doctype/course_scheduling_tool/course_scheduling_tool.py b/erpnext/education/doctype/course_scheduling_tool/course_scheduling_tool.py index 6a0dcf460a..0f2ea96a58 100644 --- a/erpnext/education/doctype/course_scheduling_tool/course_scheduling_tool.py +++ b/erpnext/education/doctype/course_scheduling_tool/course_scheduling_tool.py @@ -75,7 +75,7 @@ class CourseSchedulingTool(Document): """Validates if Course Start Date is greater than Course End Date""" if self.course_start_date > self.course_end_date: frappe.throw( - "Course Start Date cannot be greater than Course End Date.") + _("Course Start Date cannot be greater than Course End Date.")) def delete_course_schedule(self, rescheduled, reschedule_errors): """Delete all course schedule within the Date range and specified filters""" diff --git a/erpnext/loan_management/doctype/loan/loan.py b/erpnext/loan_management/doctype/loan/loan.py index 230475f2d1..69d11a8653 100644 --- a/erpnext/loan_management/doctype/loan/loan.py +++ b/erpnext/loan_management/doctype/loan/loan.py @@ -264,7 +264,7 @@ def make_loan_write_off(loan, company=None, posting_date=None, amount=0, as_dict pending_amount = amounts['pending_principal_amount'] if amount and (amount > pending_amount): - frappe.throw('Write Off amount cannot be greater than pending loan amount') + frappe.throw(_('Write Off amount cannot be greater than pending loan amount')) if not amount: amount = pending_amount diff --git a/erpnext/non_profit/doctype/member/member.py b/erpnext/non_profit/doctype/member/member.py index efc072ee97..30be585e9a 100644 --- a/erpnext/non_profit/doctype/member/member.py +++ b/erpnext/non_profit/doctype/member/member.py @@ -28,7 +28,7 @@ class Member(Document): def setup_subscription(self): non_profit_settings = frappe.get_doc('Non Profit Settings') if not non_profit_settings.enable_razorpay_for_memberships: - frappe.throw('Please check Enable Razorpay for Memberships in {0} to setup subscription').format( + frappe.throw(_('Please check Enable Razorpay for Memberships in {0} to setup subscription')).format( get_link_to_form('Non Profit Settings', 'Non Profit Settings')) controller = get_payment_gateway_controller("Razorpay") diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index e153e6ccbc..ad1976d2d2 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -953,15 +953,15 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ (this.frm.doc.payment_schedule && this.frm.doc.payment_schedule.length)) { var message1 = ""; var message2 = ""; - var final_message = "Please clear the "; + var final_message = __("Please clear the") + " "; if (this.frm.doc.payment_terms_template) { - message1 = "selected Payment Terms Template"; + message1 = __("selected Payment Terms Template"); final_message = final_message + message1; } if ((this.frm.doc.payment_schedule || []).length) { - message2 = "Payment Schedule Table"; + message2 = __("Payment Schedule Table"); if (message1.length !== 0) message2 = " and " + message2; final_message = final_message + message2; } diff --git a/erpnext/setup/install.py b/erpnext/setup/install.py index c7220cbc07..bbee74cafb 100644 --- a/erpnext/setup/install.py +++ b/erpnext/setup/install.py @@ -39,7 +39,7 @@ def check_setup_wizard_not_completed(): if cint(frappe.db.get_single_value('System Settings', 'setup_complete') or 0): message = """ERPNext can only be installed on a fresh site where the setup wizard is not completed. You can reinstall this site (after saving your data) using: bench --site [sitename] reinstall""" - frappe.throw(message) + frappe.throw(message) # nosemgrep def set_single_defaults(): diff --git a/erpnext/www/book_appointment/index.js b/erpnext/www/book_appointment/index.js index 377a3cc097..5562cbd471 100644 --- a/erpnext/www/book_appointment/index.js +++ b/erpnext/www/book_appointment/index.js @@ -48,7 +48,7 @@ function setup_date_picker() { function hide_next_button() { let next_button = document.getElementById('next-button'); next_button.disabled = true; - next_button.onclick = () => frappe.msgprint("Please select a date and time"); + next_button.onclick = () => frappe.msgprint(__("Please select a date and time")); } function show_next_button() { @@ -63,7 +63,7 @@ function on_date_or_timezone_select() { if (date_picker.value === '') { clear_time_slots(); hide_next_button(); - frappe.throw('Please select a date'); + frappe.throw(__('Please select a date')); } window.selected_date = date_picker.value; window.selected_timezone = timezone.value; From 0e804e292a65edca819ecd8d7b77ca88ab970151 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 26 May 2021 10:54:16 +0530 Subject: [PATCH 324/326] fix(gstr-1): incorrect gstin fetched incase of branch company address --- erpnext/regional/report/gstr_1/gstr_1.py | 36 ++++++++++++++---------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py index 1e28a40f81..b7c096248f 100644 --- a/erpnext/regional/report/gstr_1/gstr_1.py +++ b/erpnext/regional/report/gstr_1/gstr_1.py @@ -574,7 +574,7 @@ class Gstr1Report(object): def get_json(filters, report_name, data): filters = json.loads(filters) report_data = json.loads(data) - gstin = get_company_gstin_number(filters["company"]) + gstin = get_company_gstin_number(filters["company"], filters["company_address"]) fp = "%02d%s" % (getdate(filters["to_date"]).month, getdate(filters["to_date"]).year) @@ -810,23 +810,29 @@ def get_rate_and_tax_details(row, gstin): return {"num": int(num), "itm_det": itm_det} -def get_company_gstin_number(company): - filters = [ - ["is_your_company_address", "=", 1], - ["Dynamic Link", "link_doctype", "=", "Company"], - ["Dynamic Link", "link_name", "=", company], - ["Dynamic Link", "parenttype", "=", "Address"], - ] +def get_company_gstin_number(company, address=None): + if address: + gstin = frappe.db.get_value("Address", address, "gstin") - gstin = frappe.get_all("Address", filters=filters, fields=["gstin"]) - - if gstin: - return gstin[0]["gstin"] - else: - frappe.throw(_("Please set valid GSTIN No. in Company Address for company {0}").format( - frappe.bold(company) + if not gstin: + filters = [ + ["is_your_company_address", "=", 1], + ["Dynamic Link", "link_doctype", "=", "Company"], + ["Dynamic Link", "link_name", "=", company], + ["Dynamic Link", "parenttype", "=", "Address"], + ] + gstin = frappe.get_all("Address", filters=filters, pluck="gstin") + if gstin: + gstin[0] + + if not gstin: + address = frappe.bold(address) if address else "" + frappe.throw(_("Please set valid GSTIN No. in Company Address {} for company {}").format( + address, frappe.bold(company) )) + return gstin + @frappe.whitelist() def download_json_file(): ''' download json content in a file ''' From 2e0e4a7861ddc800589a788198c0df32a6e9ccb7 Mon Sep 17 00:00:00 2001 From: Anurag Mishra <32095923+Anurag810@users.noreply.github.com> Date: Wed, 26 May 2021 11:13:19 +0530 Subject: [PATCH 325/326] refactor: Additional Salary form clean up (#25785) * feat: additional salary clean up * fix: Label and description * fix: labels Co-authored-by: Rucha Mahabal --- .../additional_salary/additional_salary.json | 51 +++++++++++++++---- 1 file changed, 40 insertions(+), 11 deletions(-) diff --git a/erpnext/payroll/doctype/additional_salary/additional_salary.json b/erpnext/payroll/doctype/additional_salary/additional_salary.json index 5e17a5cbb7..d9efe458dc 100644 --- a/erpnext/payroll/doctype/additional_salary/additional_salary.json +++ b/erpnext/payroll/doctype/additional_salary/additional_salary.json @@ -7,25 +7,30 @@ "editable_grid": 1, "engine": "InnoDB", "field_order": [ + "employee_details_section", "naming_series", "employee", "employee_name", - "salary_component", - "type", - "amount", - "ref_doctype", - "ref_docname", - "amended_from", "column_break_5", "company", "department", + "salary_details_section", + "salary_component", + "type", "currency", + "amount", + "column_break_13", + "is_recurring", + "payroll_date", "from_date", "to_date", - "payroll_date", - "is_recurring", + "properties_and_references_section", + "deduct_full_tax_on_selected_payroll_date", + "ref_doctype", + "ref_docname", + "column_break_22", "overwrite_salary_structure_amount", - "deduct_full_tax_on_selected_payroll_date" + "amended_from" ], "fields": [ { @@ -81,7 +86,7 @@ }, { "depends_on": "eval:(doc.is_recurring==0)", - "description": "Date on which this component is applied", + "description": "The date on which Salary Component with Amount will contribute for Earnings/Deduction in Salary Slip. ", "fieldname": "payroll_date", "fieldtype": "Date", "in_list_view": 1, @@ -159,6 +164,7 @@ "fieldname": "ref_docname", "fieldtype": "Dynamic Link", "label": "Reference Document", + "no_copy": 1, "options": "ref_doctype", "read_only": 1 }, @@ -171,11 +177,34 @@ "print_hide": 1, "read_only": 1, "reqd": 1 + }, + { + "fieldname": "employee_details_section", + "fieldtype": "Section Break", + "label": "Employee Details" + }, + { + "fieldname": "column_break_13", + "fieldtype": "Column Break" + }, + { + "fieldname": "column_break_22", + "fieldtype": "Column Break" + }, + { + "fieldname": "salary_details_section", + "fieldtype": "Section Break", + "label": "Salary Details" + }, + { + "fieldname": "properties_and_references_section", + "fieldtype": "Section Break", + "label": "Properties and References" } ], "is_submittable": 1, "links": [], - "modified": "2021-03-31 22:33:59.098532", + "modified": "2021-05-26 11:10:00.812698", "modified_by": "Administrator", "module": "Payroll", "name": "Additional Salary", From 349ef8274beee540626635ba46987c4a7b21d9d8 Mon Sep 17 00:00:00 2001 From: Anupam Kumar Date: Wed, 26 May 2021 12:14:02 +0530 Subject: [PATCH 326/326] fix: student invalid password reset link (#25826) --- erpnext/education/doctype/student/student.py | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/education/doctype/student/student.py b/erpnext/education/doctype/student/student.py index 2dc0f634f0..6be9e7104b 100644 --- a/erpnext/education/doctype/student/student.py +++ b/erpnext/education/doctype/student/student.py @@ -74,7 +74,6 @@ class Student(Document): student_user.flags.ignore_permissions = True student_user.add_roles("Student") student_user.save() - update_password_link = student_user.reset_password() def update_applicant_status(self): """Updates Student Applicant status to Admitted"""