From 5e10e103291f5c70fee3647503ba858c84348f3c Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 31 Aug 2021 18:23:28 +0530 Subject: [PATCH 001/116] feat: Validity dates in Tax Withholding Rates --- .../tax_withholding_category.py | 111 ++++---- .../test_tax_withholding_category.py | 17 +- .../tax_withholding_rate.json | 256 +++++------------- erpnext/patches.txt | 3 +- ...pdate_dates_in_tax_withholding_category.py | 22 ++ 5 files changed, 159 insertions(+), 250 deletions(-) create mode 100644 erpnext/patches/v13_0/update_dates_in_tax_withholding_category.py 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 0cb872c4b81..c871af94289 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -10,7 +10,24 @@ from frappe.utils import flt, getdate, cint from erpnext.accounts.utils import get_fiscal_year class TaxWithholdingCategory(Document): - pass + def validate(self): + self.validate_dates() + self.validate_thresholds() + + def validate_dates(self): + last_date = None + for d in self.get('rates'): + if getdate(d.from_date) >= getdate(d.to_date): + frappe.throw(_("Row #{0}: From Date cannot be before To Date").format(d.idx)) + + # validate overlapping of dates + if last_date and getdate(r.to_date) < getdate(last_date): + frappe.throw(_("Row #{0}: Dates overlapping with other row").format(d.idx)) + + def validate_thresholds(self): + for d in self.get('rates'): + if d.cumulative_threshold and d.cumulative_threshold < d.single_threshold: + frappe.throw(_("Row #{0}: Cumulative threshold cannot be less than Single Transaction threshold").format(d.idx)) def get_party_details(inv): party_type, party = '', '' @@ -49,8 +66,8 @@ def get_party_tax_withholding_details(inv, tax_withholding_category=None): if not parties: parties.append(party) - fiscal_year = get_fiscal_year(inv.get('posting_date') or inv.get('transaction_date'), company=inv.company) - tax_details = get_tax_withholding_details(tax_withholding_category, fiscal_year[0], inv.company) + posting_date = inv.get('posting_date') or inv.get('transaction_date') + tax_details = get_tax_withholding_details(tax_withholding_category, posting_date, inv.company) if not tax_details: frappe.throw(_('Please set associated account in Tax Withholding Category {0} against Company {1}') @@ -64,7 +81,7 @@ def get_party_tax_withholding_details(inv, tax_withholding_category=None): tax_amount, tax_deducted = get_tax_amount( party_type, parties, inv, tax_details, - fiscal_year, pan_no + posting_date, pan_no ) if party_type == 'Supplier': @@ -74,16 +91,18 @@ def get_party_tax_withholding_details(inv, tax_withholding_category=None): return tax_row -def get_tax_withholding_details(tax_withholding_category, fiscal_year, company): +def get_tax_withholding_details(tax_withholding_category, posting_date, company): tax_withholding = frappe.get_doc("Tax Withholding Category", tax_withholding_category) - tax_rate_detail = get_tax_withholding_rates(tax_withholding, fiscal_year) + tax_rate_detail = get_tax_withholding_rates(tax_withholding, posting_date) for account_detail in tax_withholding.accounts: if company == account_detail.company: return frappe._dict({ "account_head": account_detail.account, "rate": tax_rate_detail.tax_withholding_rate, + "from_date": tax_rate_detail.from_date, + "to_date": tax_rate_detail.to_date, "threshold": tax_rate_detail.single_threshold, "cumulative_threshold": tax_rate_detail.cumulative_threshold, "description": tax_withholding.category_name if tax_withholding.category_name else tax_withholding_category, @@ -92,13 +111,13 @@ def get_tax_withholding_details(tax_withholding_category, fiscal_year, company): "round_off_tax_amount": tax_withholding.round_off_tax_amount }) -def get_tax_withholding_rates(tax_withholding, fiscal_year): +def get_tax_withholding_rates(tax_withholding, posting_date): # returns the row that matches with the fiscal year from posting date for rate in tax_withholding.rates: - if rate.fiscal_year == fiscal_year: + if getdate(rate.from_date) <= getdate(posting_date) <= getdate(rate.to_date): return rate - frappe.throw(_("No Tax Withholding data found for the current Fiscal Year.")) + frappe.throw(_("No Tax Withholding data found for the current posting date.")) def get_tax_row_for_tcs(inv, tax_details, tax_amount, tax_deducted): row = { @@ -140,38 +159,38 @@ def get_tax_row_for_tds(tax_details, tax_amount): "account_head": tax_details.account_head } -def get_lower_deduction_certificate(fiscal_year, pan_no): - ldc_name = frappe.db.get_value('Lower Deduction Certificate', { 'pan_no': pan_no, 'fiscal_year': fiscal_year }, 'name') +def get_lower_deduction_certificate(tax_details, pan_no): + ldc_name = frappe.db.get_value('Lower Deduction Certificate', + { + 'pan_no': pan_no, + 'valid_from': ('>=', tax_details.from_date), + 'valid_upto': ('<=', tax_details.to_date) + }, 'name') + if ldc_name: return frappe.get_doc('Lower Deduction Certificate', ldc_name) -def get_tax_amount(party_type, parties, inv, tax_details, fiscal_year_details, pan_no=None): - fiscal_year = fiscal_year_details[0] - - - vouchers = get_invoice_vouchers(parties, fiscal_year, inv.company, party_type=party_type) - advance_vouchers = get_advance_vouchers(parties, fiscal_year, inv.company, party_type=party_type) +def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=None): + vouchers = get_invoice_vouchers(parties, tax_details, inv.company, party_type=party_type) + advance_vouchers = get_advance_vouchers(parties, company=inv.company, from_date=tax_details.from_date, + to_date=tax_details.to_date, party_type=party_type) taxable_vouchers = vouchers + advance_vouchers tax_deducted = 0 if taxable_vouchers: - tax_deducted = get_deducted_tax(taxable_vouchers, fiscal_year, tax_details) + tax_deducted = get_deducted_tax(taxable_vouchers, tax_details) tax_amount = 0 - posting_date = inv.get('posting_date') or inv.get('transaction_date') if party_type == 'Supplier': - ldc = get_lower_deduction_certificate(fiscal_year, pan_no) + ldc = get_lower_deduction_certificate(tax_details, pan_no) if tax_deducted: net_total = inv.net_total if ldc: - tax_amount = get_tds_amount_from_ldc(ldc, parties, fiscal_year, pan_no, tax_details, posting_date, net_total) + tax_amount = get_tds_amount_from_ldc(ldc, parties, pan_no, tax_details, posting_date, net_total) else: tax_amount = net_total * tax_details.rate / 100 if net_total > 0 else 0 else: - tax_amount = get_tds_amount( - ldc, parties, inv, tax_details, - fiscal_year_details, tax_deducted, vouchers - ) + tax_amount = get_tds_amount(ldc, parties, inv, tax_details, tax_deducted, vouchers) elif party_type == 'Customer': if tax_deducted: @@ -180,14 +199,11 @@ def get_tax_amount(party_type, parties, inv, tax_details, fiscal_year_details, p else: # if no TCS has been charged in FY, # then chargeable value is "prev invoices + advances" value which cross the threshold - tax_amount = get_tcs_amount( - parties, inv, tax_details, - fiscal_year_details, vouchers, advance_vouchers - ) + tax_amount = get_tcs_amount(parties, inv, tax_details, vouchers, advance_vouchers) return tax_amount, tax_deducted -def get_invoice_vouchers(parties, fiscal_year, company, party_type='Supplier'): +def get_invoice_vouchers(parties, tax_details, company, party_type='Supplier'): dr_or_cr = 'credit' if party_type == 'Supplier' else 'debit' filters = { @@ -195,14 +211,14 @@ def get_invoice_vouchers(parties, fiscal_year, company, party_type='Supplier'): 'company': company, 'party_type': party_type, 'party': ['in', parties], - 'fiscal_year': fiscal_year, + 'posting_date': ['between', (tax_details.from_date, tax_details.to_date)], 'is_opening': 'No', 'is_cancelled': 0 } return frappe.get_all('GL Entry', filters=filters, distinct=1, pluck="voucher_no") or [""] -def get_advance_vouchers(parties, fiscal_year=None, company=None, from_date=None, to_date=None, party_type='Supplier'): +def get_advance_vouchers(parties, company=None, from_date=None, to_date=None, party_type='Supplier'): # for advance vouchers, debit and credit is reversed dr_or_cr = 'debit' if party_type == 'Supplier' else 'credit' @@ -215,8 +231,6 @@ def get_advance_vouchers(parties, fiscal_year=None, company=None, from_date=None 'against_voucher': ['is', 'not set'] } - if fiscal_year: - filters['fiscal_year'] = fiscal_year if company: filters['company'] = company if from_date and to_date: @@ -224,20 +238,21 @@ def get_advance_vouchers(parties, fiscal_year=None, company=None, from_date=None return frappe.get_all('GL Entry', filters=filters, distinct=1, pluck='voucher_no') or [""] -def get_deducted_tax(taxable_vouchers, fiscal_year, tax_details): +def get_deducted_tax(taxable_vouchers, tax_details): # check if TDS / TCS account is already charged on taxable vouchers filters = { 'is_cancelled': 0, 'credit': ['>', 0], - 'fiscal_year': fiscal_year, + 'posting_date': ['between', (tax_details.from_date, tax_details.to_date)], 'account': tax_details.account_head, 'voucher_no': ['in', taxable_vouchers], } - field = "sum(credit)" + field = "credit" - return frappe.db.get_value('GL Entry', filters, field) or 0.0 + entries = frappe.db.get_all('GL Entry', filters, pluck=field) + return sum(entries) -def get_tds_amount(ldc, parties, inv, tax_details, fiscal_year_details, tax_deducted, vouchers): +def get_tds_amount(ldc, parties, inv, tax_details, tax_deducted, vouchers): tds_amount = 0 invoice_filters = { 'name': ('in', vouchers), @@ -261,7 +276,7 @@ def get_tds_amount(ldc, parties, inv, tax_details, fiscal_year_details, tax_dedu supp_credit_amt += supp_jv_credit_amt supp_credit_amt += inv.net_total - debit_note_amount = get_debit_note_amount(parties, fiscal_year_details, inv.company) + debit_note_amount = get_debit_note_amount(parties, tax_details.from_date, tax_details.to_date, inv.company) supp_credit_amt -= debit_note_amount threshold = tax_details.get('threshold', 0) @@ -289,9 +304,8 @@ def get_tds_amount(ldc, parties, inv, tax_details, fiscal_year_details, tax_dedu return tds_amount -def get_tcs_amount(parties, inv, tax_details, fiscal_year_details, vouchers, adv_vouchers): +def get_tcs_amount(parties, inv, tax_details, vouchers, adv_vouchers): tcs_amount = 0 - fiscal_year, _, _ = fiscal_year_details # sum of debit entries made from sales invoices invoiced_amt = frappe.db.get_value('GL Entry', { @@ -310,14 +324,14 @@ def get_tcs_amount(parties, inv, tax_details, fiscal_year_details, vouchers, adv }, 'sum(credit)') or 0.0 # sum of credit entries made from sales invoice - credit_note_amt = frappe.db.get_value('GL Entry', { + credit_note_amt = sum(frappe.db.get_all('GL Entry', { 'is_cancelled': 0, 'credit': ['>', 0], 'party': ['in', parties], - 'fiscal_year': fiscal_year, + 'posting_date': ['between', (tax_details.from_date, tax_details.to_date)], 'company': inv.company, 'voucher_type': 'Sales Invoice', - }, 'sum(credit)') or 0.0 + }, pluck='credit')) cumulative_threshold = tax_details.get('cumulative_threshold', 0) @@ -336,7 +350,7 @@ def get_invoice_total_without_tcs(inv, tax_details): return inv.grand_total - tcs_tax_row_amount -def get_tds_amount_from_ldc(ldc, parties, fiscal_year, pan_no, tax_details, posting_date, net_total): +def get_tds_amount_from_ldc(ldc, parties, pan_no, tax_details, posting_date, net_total): tds_amount = 0 limit_consumed = frappe.db.get_value('Purchase Invoice', { 'supplier': ('in', parties), @@ -353,14 +367,13 @@ def get_tds_amount_from_ldc(ldc, parties, fiscal_year, pan_no, tax_details, post return tds_amount -def get_debit_note_amount(suppliers, fiscal_year_details, company=None): - _, year_start_date, year_end_date = fiscal_year_details +def get_debit_note_amount(suppliers, from_date, to_date, company=None): filters = { 'supplier': ['in', suppliers], 'is_return': 1, 'docstatus': 1, - 'posting_date': ['between', (year_start_date, year_end_date)] + 'posting_date': ['between', (from_date, to_date)] } fields = ['abs(sum(net_total)) as net_total'] 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 0f921db678d..138aaec6ab0 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 @@ -312,16 +312,16 @@ def create_records(): }).insert() def create_tax_with_holding_category(): - fiscal_year = get_fiscal_year(today(), company="_Test Company")[0] - - # Cummulative thresold + fiscal_year = get_fiscal_year(today(), company="_Test Company") + # Cumulative threshold if not frappe.db.exists("Tax Withholding Category", "Cumulative Threshold TDS"): frappe.get_doc({ "doctype": "Tax Withholding Category", "name": "Cumulative Threshold TDS", "category_name": "10% TDS", "rates": [{ - 'fiscal_year': fiscal_year, + 'from_date': fiscal_year[1], + 'to_date': fiscal_year[2], 'tax_withholding_rate': 10, 'single_threshold': 0, 'cumulative_threshold': 30000.00 @@ -338,7 +338,8 @@ def create_tax_with_holding_category(): "name": "Cumulative Threshold TCS", "category_name": "10% TCS", "rates": [{ - 'fiscal_year': fiscal_year, + 'from_date': fiscal_year[1], + 'to_date': fiscal_year[2], 'tax_withholding_rate': 10, 'single_threshold': 0, 'cumulative_threshold': 30000.00 @@ -356,7 +357,8 @@ def create_tax_with_holding_category(): "name": "Single Threshold TDS", "category_name": "10% TDS", "rates": [{ - 'fiscal_year': fiscal_year, + 'from_date': fiscal_year[1], + 'to_date': fiscal_year[2], 'tax_withholding_rate': 10, 'single_threshold': 20000.00, 'cumulative_threshold': 0 @@ -376,7 +378,8 @@ def create_tax_with_holding_category(): "consider_party_ledger_amount": 1, "tax_on_excess_amount": 1, "rates": [{ - 'fiscal_year': fiscal_year, + 'from_date': fiscal_year[1], + 'to_date': fiscal_year[2], 'tax_withholding_rate': 10, 'single_threshold': 0, 'cumulative_threshold': 30000 diff --git a/erpnext/accounts/doctype/tax_withholding_rate/tax_withholding_rate.json b/erpnext/accounts/doctype/tax_withholding_rate/tax_withholding_rate.json index 1e8194af6e4..d2c505c6300 100644 --- a/erpnext/accounts/doctype/tax_withholding_rate/tax_withholding_rate.json +++ b/erpnext/accounts/doctype/tax_withholding_rate/tax_withholding_rate.json @@ -1,202 +1,72 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2018-07-17 16:53:13.716665", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", + "actions": [], + "creation": "2018-07-17 16:53:13.716665", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "from_date", + "to_date", + "tax_withholding_rate", + "column_break_3", + "single_threshold", + "cumulative_threshold" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 2, - "fieldname": "fiscal_year", - "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": "Fiscal Year", - "length": 0, - "no_copy": 0, - "options": "Fiscal Year", - "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, - "translatable": 0, - "unique": 0 - }, + "columns": 1, + "fieldname": "tax_withholding_rate", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Tax Withholding Rate", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 2, - "fieldname": "tax_withholding_rate", - "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": "Tax Withholding Rate", - "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": 1, - "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": 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 - }, + "columns": 2, + "fieldname": "single_threshold", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Single Transaction Threshold" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 3, - "fieldname": "single_threshold", - "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": "Single Transaction Threshold", - "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 - }, + "columns": 3, + "fieldname": "cumulative_threshold", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Cumulative Transaction Threshold" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 3, - "fieldname": "cumulative_threshold", - "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": "Cumulative Transaction Threshold", - "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 + "columns": 2, + "fieldname": "from_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "From Date", + "reqd": 1 + }, + { + "columns": 2, + "fieldname": "to_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "To Date", + "reqd": 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-07-17 17:13:09.819580", - "modified_by": "Administrator", - "module": "Accounts", - "name": "Tax Withholding Rate", - "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 + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-08-31 11:42:12.213977", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Tax Withholding Rate", + "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/patches.txt b/erpnext/patches.txt index 05c385b6bac..ae83d5eb961 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -300,4 +300,5 @@ erpnext.patches.v13_0.reset_clearance_date_for_intracompany_payment_entries erpnext.patches.v13_0.einvoicing_deprecation_warning erpnext.patches.v14_0.delete_einvoicing_doctypes erpnext.patches.v13_0.set_operation_time_based_on_operating_cost -erpnext.patches.v13_0.validate_options_for_data_field \ No newline at end of file +erpnext.patches.v13_0.validate_options_for_data_field +erpnext.patches.v13_0.update_dates_in_tax_withholding_category \ No newline at end of file diff --git a/erpnext/patches/v13_0/update_dates_in_tax_withholding_category.py b/erpnext/patches/v13_0/update_dates_in_tax_withholding_category.py new file mode 100644 index 00000000000..2563d8a8c4b --- /dev/null +++ b/erpnext/patches/v13_0/update_dates_in_tax_withholding_category.py @@ -0,0 +1,22 @@ +# Copyright (c) 2021, Frappe and Contributors +# License: GNU General Public License v3. See license.txt + +import frappe +from erpnext.accounts.utils import get_fiscal_year + +def execute(): + frappe.reload_doc('accounts', 'doctype', 'Tax Withholding Rate') + tds_category_rates = frappe.get_all('Tax Withholding Rate', fields=['name', 'fiscal_year']) + + fiscal_year_map = {} + for rate in tds_category_rates: + if not fiscal_year_map.get(rate.fiscal_year): + fiscal_year_map[rate.fiscal_year] = get_fiscal_year(fiscal_year=rate.fiscal_year) + + from_date = fiscal_year_map.get(rate.fiscal_year)[1] + to_date = fiscal_year_map.get(rate.fiscal_year)[2] + + frappe.db.set_value('Tax Withholding Rate', rate.name, { + 'from_date': from_date, + 'to_date': to_date + }) \ No newline at end of file From d06221ad7a0c622d3b2c9e8992c97af28f38f79f Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 1 Sep 2021 10:05:10 +0530 Subject: [PATCH 002/116] fix: Advance TDS test case --- .../accounts/doctype/purchase_invoice/test_purchase_invoice.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 37ff52c6109..88acebcf494 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -1226,7 +1226,8 @@ def update_tax_witholding_category(company, account, date): {'parent': 'TDS - 194 - Dividends - Individual', 'fiscal_year': fiscal_year[0]}): tds_category = frappe.get_doc('Tax Withholding Category', 'TDS - 194 - Dividends - Individual') tds_category.append('rates', { - 'fiscal_year': fiscal_year[0], + 'from_date': fiscal_year[1], + 'to_date': fiscal_year[2], 'tax_withholding_rate': 10, 'single_threshold': 2500, 'cumulative_threshold': 0 From b6d0b17ed6930b43157c51c0e0fec8457881f7bb Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 1 Sep 2021 10:11:18 +0530 Subject: [PATCH 003/116] fix: Linting and patch fixes --- .../tax_withholding_category.py | 2 +- ...pdate_dates_in_tax_withholding_category.py | 24 ++++++++++--------- 2 files changed, 14 insertions(+), 12 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 c871af94289..06b8df1dfe8 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,7 @@ class TaxWithholdingCategory(Document): frappe.throw(_("Row #{0}: From Date cannot be before To Date").format(d.idx)) # validate overlapping of dates - if last_date and getdate(r.to_date) < getdate(last_date): + if last_date and getdate(d.to_date) < getdate(last_date): frappe.throw(_("Row #{0}: Dates overlapping with other row").format(d.idx)) def validate_thresholds(self): diff --git a/erpnext/patches/v13_0/update_dates_in_tax_withholding_category.py b/erpnext/patches/v13_0/update_dates_in_tax_withholding_category.py index 2563d8a8c4b..33c49428533 100644 --- a/erpnext/patches/v13_0/update_dates_in_tax_withholding_category.py +++ b/erpnext/patches/v13_0/update_dates_in_tax_withholding_category.py @@ -6,17 +6,19 @@ from erpnext.accounts.utils import get_fiscal_year def execute(): frappe.reload_doc('accounts', 'doctype', 'Tax Withholding Rate') - tds_category_rates = frappe.get_all('Tax Withholding Rate', fields=['name', 'fiscal_year']) - fiscal_year_map = {} - for rate in tds_category_rates: - if not fiscal_year_map.get(rate.fiscal_year): - fiscal_year_map[rate.fiscal_year] = get_fiscal_year(fiscal_year=rate.fiscal_year) + if frappe.db.has_column('Tax Withholding Rate', 'fiscal_year'): + tds_category_rates = frappe.get_all('Tax Withholding Rate', fields=['name', 'fiscal_year']) - from_date = fiscal_year_map.get(rate.fiscal_year)[1] - to_date = fiscal_year_map.get(rate.fiscal_year)[2] + fiscal_year_map = {} + for rate in tds_category_rates: + if not fiscal_year_map.get(rate.fiscal_year): + fiscal_year_map[rate.fiscal_year] = get_fiscal_year(fiscal_year=rate.fiscal_year) - frappe.db.set_value('Tax Withholding Rate', rate.name, { - 'from_date': from_date, - 'to_date': to_date - }) \ No newline at end of file + from_date = fiscal_year_map.get(rate.fiscal_year)[1] + to_date = fiscal_year_map.get(rate.fiscal_year)[2] + + frappe.db.set_value('Tax Withholding Rate', rate.name, { + 'from_date': from_date, + 'to_date': to_date + }) \ No newline at end of file From d5a736d11e5096f2861c3e5e6267584488df6d7d Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 1 Sep 2021 21:15:24 +0530 Subject: [PATCH 004/116] test: Update test case --- .../accounts/doctype/purchase_invoice/test_purchase_invoice.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 88acebcf494..06f4fdb799c 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -1223,7 +1223,8 @@ def update_tax_witholding_category(company, account, date): fiscal_year = get_fiscal_year(date=date, company=company) if not frappe.db.get_value('Tax Withholding Rate', - {'parent': 'TDS - 194 - Dividends - Individual', 'fiscal_year': fiscal_year[0]}): + {'parent': 'TDS - 194 - Dividends - Individual', 'from_date': ('>=', fiscal_year[1]), + 'to_date': ('<=', fiscal_year[2])}): tds_category = frappe.get_doc('Tax Withholding Category', 'TDS - 194 - Dividends - Individual') tds_category.append('rates', { 'from_date': fiscal_year[1], From 092d41ecda917c68db8329c824ca68ced82db195 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 3 Sep 2021 20:04:18 +0530 Subject: [PATCH 005/116] fix: Debug CI --- .../accounts/doctype/purchase_invoice/test_purchase_invoice.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 51f58b74027..a7298d5e308 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -1230,6 +1230,7 @@ def update_tax_witholding_category(company, account, date): from erpnext.accounts.utils import get_fiscal_year fiscal_year = get_fiscal_year(date=date, company=company) + print(fiscal_year[0], fiscal_year[1], fiscal_year[2], "$#$#$#") if not frappe.db.get_value('Tax Withholding Rate', {'parent': 'TDS - 194 - Dividends - Individual', 'from_date': ('>=', fiscal_year[1]), From 51d9572fe72902593e363a19bc52967d65f56b88 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 5 Sep 2021 17:56:12 +0530 Subject: [PATCH 006/116] fix: Hardcode fiscal year and posting date --- .../doctype/fiscal_year/fiscal_year_dashboard.py | 2 +- .../doctype/purchase_invoice/test_purchase_invoice.py | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/fiscal_year/fiscal_year_dashboard.py b/erpnext/accounts/doctype/fiscal_year/fiscal_year_dashboard.py index 58480df1190..92e8a426cff 100644 --- a/erpnext/accounts/doctype/fiscal_year/fiscal_year_dashboard.py +++ b/erpnext/accounts/doctype/fiscal_year/fiscal_year_dashboard.py @@ -13,7 +13,7 @@ def get_data(): }, { 'label': _('References'), - 'items': ['Period Closing Voucher', 'Tax Withholding Category'] + 'items': ['Period Closing Voucher'] }, { 'label': _('Target Details'), diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index a7298d5e308..2d6ab7b2935 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -1151,10 +1151,11 @@ class TestPurchaseInvoice(unittest.TestCase): tax_withholding_category = 'TDS - 194 - Dividends - Individual') # Update tax withholding category with current fiscal year and rate details - update_tax_witholding_category('_Test Company', 'TDS Payable - _TC', nowdate()) + update_tax_witholding_category('_Test Company', 'TDS Payable - _TC') # Create Purchase Order with TDS applied - po = create_purchase_order(do_not_save=1, supplier=supplier.name, rate=3000, item='_Test Non Stock Item') + po = create_purchase_order(do_not_save=1, supplier=supplier.name, rate=3000, item='_Test Non Stock Item', + posting_date='2021-09-15') po.apply_tds = 1 po.tax_withholding_category = 'TDS - 194 - Dividends - Individual' po.save() @@ -1226,11 +1227,10 @@ def check_gl_entries(doc, voucher_no, expected_gle, posting_date): doc.assertEqual(expected_gle[i][2], gle.credit) doc.assertEqual(getdate(expected_gle[i][3]), gle.posting_date) -def update_tax_witholding_category(company, account, date): +def update_tax_witholding_category(company, account): from erpnext.accounts.utils import get_fiscal_year - fiscal_year = get_fiscal_year(date=date, company=company) - print(fiscal_year[0], fiscal_year[1], fiscal_year[2], "$#$#$#") + fiscal_year = get_fiscal_year(fiscal_year='_Test Fiscal Year 2021') if not frappe.db.get_value('Tax Withholding Rate', {'parent': 'TDS - 194 - Dividends - Individual', 'from_date': ('>=', fiscal_year[1]), From b0d970001a9e581d21fef1e66d07b8dfb97a7ee9 Mon Sep 17 00:00:00 2001 From: Anupam Kumar Date: Mon, 6 Sep 2021 16:54:28 +0530 Subject: [PATCH 007/116] feat: multi-currency in Opportunity (#26590) * feat: multi-currency * refactor: lead form * fix: test case for opportunity item * fix: removing local changes * fix: test cases * fix: test cases * fix: review changes * fix: reverting lead.json chnages * fix: toggle display for currency fields * review changes * fix: test case * fix: linter issues * fix: unused import * feat: grand total in opportunity * fix: patch * fix: sort imports * fix: reload opportunity item doctype Co-authored-by: Rucha Mahabal --- .../crm/doctype/opportunity/opportunity.js | 90 +++ .../crm/doctype/opportunity/opportunity.json | 65 +- .../crm/doctype/opportunity/opportunity.py | 29 +- .../doctype/opportunity/test_opportunity.py | 6 + .../opportunity_item/opportunity_item.json | 610 +++++------------- erpnext/patches.txt | 1 + .../update_opportunity_currency_fields.py | 36 ++ 7 files changed, 374 insertions(+), 463 deletions(-) create mode 100644 erpnext/patches/v14_0/update_opportunity_currency_fields.py diff --git a/erpnext/crm/doctype/opportunity/opportunity.js b/erpnext/crm/doctype/opportunity/opportunity.js index 3866fc263e6..f8376e6ca94 100644 --- a/erpnext/crm/doctype/opportunity/opportunity.js +++ b/erpnext/crm/doctype/opportunity/opportunity.js @@ -132,10 +132,43 @@ frappe.ui.form.on("Opportunity", { } }, + currency: function(frm) { + let company_currency = erpnext.get_currency(frm.doc.company); + if (company_currency != frm.doc.company) { + frappe.call({ + method: "erpnext.setup.utils.get_exchange_rate", + args: { + from_currency: frm.doc.currency, + to_currency: company_currency + }, + callback: function(r) { + if (r.message) { + frm.set_value('conversion_rate', flt(r.message)); + frm.set_df_property('conversion_rate', 'description', '1 ' + frm.doc.currency + + ' = [?] ' + company_currency); + } + } + }); + } else { + frm.set_value('conversion_rate', 1.0); + frm.set_df_property('conversion_rate', 'hidden', 1); + frm.set_df_property('conversion_rate', 'description', ''); + } + + frm.trigger('opportunity_amount'); + frm.trigger('set_dynamic_field_label'); + }, + + opportunity_amount: function(frm) { + frm.set_value('base_opportunity_amount', flt(frm.doc.opportunity_amount) * flt(frm.doc.conversion_rate)); + }, + set_dynamic_field_label: function(frm){ if (frm.doc.opportunity_from) { frm.set_df_property("party_name", "label", frm.doc.opportunity_from); } + frm.trigger('change_grid_labels'); + frm.trigger('change_form_labels'); }, make_supplier_quotation: function(frm) { @@ -152,6 +185,62 @@ frappe.ui.form.on("Opportunity", { }) }, + change_form_labels: function(frm) { + let company_currency = erpnext.get_currency(frm.doc.company); + frm.set_currency_labels(["base_opportunity_amount", "base_total", "base_grand_total"], company_currency); + frm.set_currency_labels(["opportunity_amount", "total", "grand_total"], frm.doc.currency); + + // toggle fields + frm.toggle_display(["conversion_rate", "base_opportunity_amount", "base_total", "base_grand_total"], + frm.doc.currency != company_currency); + }, + + change_grid_labels: function(frm) { + let company_currency = erpnext.get_currency(frm.doc.company); + frm.set_currency_labels(["base_rate", "base_amount"], company_currency, "items"); + frm.set_currency_labels(["rate", "amount"], frm.doc.currency, "items"); + + let item_grid = frm.fields_dict.items.grid; + $.each(["base_rate", "base_amount"], function(i, fname) { + if(frappe.meta.get_docfield(item_grid.doctype, fname)) + item_grid.set_column_disp(fname, frm.doc.currency != company_currency); + }); + frm.refresh_fields(); + }, + + calculate_total: function(frm) { + let total = 0, base_total = 0, grand_total = 0, base_grand_total = 0; + frm.doc.items.forEach(item => { + total += item.amount; + base_total += item.base_amount; + }) + + base_grand_total = base_total + frm.doc.base_opportunity_amount; + grand_total = total + frm.doc.opportunity_amount; + + frm.set_value({ + 'total': flt(total), + 'base_total': flt(base_total), + 'grand_total': flt(grand_total), + 'base_grand_total': flt(base_grand_total) + }); + } + +}); +frappe.ui.form.on("Opportunity Item", { + calculate: function(frm, cdt, cdn) { + let row = frappe.get_doc(cdt, cdn); + frappe.model.set_value(cdt, cdn, "amount", flt(row.qty) * flt(row.rate)); + frappe.model.set_value(cdt, cdn, "base_rate", flt(frm.doc.conversion_rate) * flt(row.rate)); + frappe.model.set_value(cdt, cdn, "base_amount", flt(frm.doc.conversion_rate) * flt(row.amount)); + frm.trigger("calculate_total"); + }, + qty: function(frm, cdt, cdn) { + frm.trigger("calculate", cdt, cdn); + }, + rate: function(frm, cdt, cdn) { + frm.trigger("calculate", cdt, cdn); + } }) // TODO commonify this code @@ -169,6 +258,7 @@ erpnext.crm.Opportunity = class Opportunity extends frappe.ui.form.Controller { } this.setup_queries(); + this.frm.trigger('currency'); } setup_queries() { diff --git a/erpnext/crm/doctype/opportunity/opportunity.json b/erpnext/crm/doctype/opportunity/opportunity.json index 12a564a9cb3..dc886b51b40 100644 --- a/erpnext/crm/doctype/opportunity/opportunity.json +++ b/erpnext/crm/doctype/opportunity/opportunity.json @@ -33,12 +33,20 @@ "to_discuss", "section_break_14", "currency", - "opportunity_amount", + "conversion_rate", + "base_opportunity_amount", "with_items", "column_break_17", "probability", + "opportunity_amount", "items_section", "items", + "section_break_32", + "base_total", + "base_grand_total", + "column_break_33", + "total", + "grand_total", "contact_info", "customer_address", "address_display", @@ -425,12 +433,65 @@ "fieldtype": "Link", "label": "Print Language", "options": "Language" + }, + { + "fieldname": "base_opportunity_amount", + "fieldtype": "Currency", + "label": "Opportunity Amount (Company Currency)", + "options": "Company:company:default_currency", + "print_hide": 1, + "read_only": 1 + }, + { + "depends_on": "with_items", + "fieldname": "section_break_32", + "fieldtype": "Section Break", + "hide_border": 1 + }, + { + "fieldname": "base_total", + "fieldtype": "Currency", + "label": "Total (Company Currency)", + "options": "Company:company:default_currency", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "total", + "fieldtype": "Currency", + "label": "Total", + "options": "currency", + "read_only": 1 + }, + { + "fieldname": "conversion_rate", + "fieldtype": "Float", + "label": "Exchange Rate" + }, + { + "fieldname": "column_break_33", + "fieldtype": "Column Break" + }, + { + "fieldname": "base_grand_total", + "fieldtype": "Currency", + "label": "Grand Total (Company Currency)", + "options": "Company:company:default_currency", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "grand_total", + "fieldtype": "Currency", + "label": "Grand Total", + "options": "currency", + "read_only": 1 } ], "icon": "fa fa-info-sign", "idx": 195, "links": [], - "modified": "2021-08-25 10:28:24.923543", + "modified": "2021-09-06 10:02:18.609136", "modified_by": "Administrator", "module": "CRM", "name": "Opportunity", diff --git a/erpnext/crm/doctype/opportunity/opportunity.py b/erpnext/crm/doctype/opportunity/opportunity.py index 0b3f50897ab..be843a3386c 100644 --- a/erpnext/crm/doctype/opportunity/opportunity.py +++ b/erpnext/crm/doctype/opportunity/opportunity.py @@ -9,9 +9,8 @@ import frappe from frappe import _ from frappe.email.inbox import link_communication_to_document from frappe.model.mapper import get_mapped_doc -from frappe.utils import cint, cstr, get_fullname +from frappe.utils import cint, cstr, flt, get_fullname -from erpnext.accounts.party import get_party_account_currency from erpnext.setup.utils import get_exchange_rate from erpnext.utilities.transaction_base import TransactionBase @@ -41,6 +40,23 @@ class Opportunity(TransactionBase): if not self.with_items: self.items = [] + else: + self.calculate_totals() + + def calculate_totals(self): + total = base_total = 0 + for item in self.get('items'): + item.amount = flt(item.rate) * flt(item.qty) + item.base_rate = flt(self.conversion_rate * item.rate) + item.base_amount = flt(self.conversion_rate * item.amount) + total += item.amount + base_total += item.base_amount + + self.total = flt(total) + self.base_total = flt(base_total) + self.grand_total = flt(self.total) + flt(self.opportunity_amount) + self.base_grand_total = flt(self.base_total) + flt(self.base_opportunity_amount) + def make_new_lead_if_required(self): """Set lead against new opportunity""" if (not self.get("party_name")) and self.contact_email: @@ -224,13 +240,6 @@ def make_quotation(source_name, target_doc=None): company_currency = frappe.get_cached_value('Company', quotation.company, "default_currency") - if quotation.quotation_to == 'Customer' and quotation.party_name: - party_account_currency = get_party_account_currency("Customer", quotation.party_name, quotation.company) - else: - party_account_currency = company_currency - - quotation.currency = party_account_currency or company_currency - if company_currency == quotation.currency: exchange_rate = 1 else: @@ -254,7 +263,7 @@ def make_quotation(source_name, target_doc=None): "doctype": "Quotation", "field_map": { "opportunity_from": "quotation_to", - "name": "enq_no", + "name": "enq_no" } }, "Opportunity Item": { diff --git a/erpnext/crm/doctype/opportunity/test_opportunity.py b/erpnext/crm/doctype/opportunity/test_opportunity.py index 347bf6366d2..65d6cb308dd 100644 --- a/erpnext/crm/doctype/opportunity/test_opportunity.py +++ b/erpnext/crm/doctype/opportunity/test_opportunity.py @@ -63,6 +63,10 @@ class TestOpportunity(unittest.TestCase): self.assertEqual(opp_doc.opportunity_from, "Customer") self.assertEqual(opp_doc.party_name, customer.name) + def test_opportunity_item(self): + opportunity_doc = make_opportunity(with_items=1, rate=1100, qty=2) + self.assertEqual(opportunity_doc.total, 2200) + def make_opportunity(**args): args = frappe._dict(args) @@ -71,6 +75,7 @@ def make_opportunity(**args): "company": args.company or "_Test Company", "opportunity_from": args.opportunity_from or "Customer", "opportunity_type": "Sales", + "conversion_rate": 1.0, "with_items": args.with_items or 0, "transaction_date": today() }) @@ -85,6 +90,7 @@ def make_opportunity(**args): opp_doc.append('items', { "item_code": args.item_code or "_Test Item", "qty": args.qty or 1, + "rate": args.rate or 1000, "uom": "_Test UOM" }) diff --git a/erpnext/crm/doctype/opportunity_item/opportunity_item.json b/erpnext/crm/doctype/opportunity_item/opportunity_item.json index 65e8433583b..1b4973c1b2b 100644 --- a/erpnext/crm/doctype/opportunity_item/opportunity_item.json +++ b/erpnext/crm/doctype/opportunity_item/opportunity_item.json @@ -1,469 +1,177 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2013-02-22 01:27:51", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", + "actions": [], + "creation": "2013-02-22 01:27:51", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "item_code", + "item_name", + "col_break1", + "uom", + "qty", + "section_break_6", + "brand", + "item_group", + "description", + "column_break_8", + "image", + "image_view", + "quantity_and_rate_section", + "base_rate", + "base_amount", + "column_break_16", + "rate", + "amount" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "item_code", - "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": "Item Code", - "length": 0, - "no_copy": 0, - "oldfieldname": "item_code", - "oldfieldtype": "Link", - "options": "Item", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "item_code", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Item Code", + "oldfieldname": "item_code", + "oldfieldtype": "Link", + "options": "Item" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "col_break1", - "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, - "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": "col_break1", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "qty", - "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": "Qty", - "length": 0, - "no_copy": 0, - "oldfieldname": "qty", - "oldfieldtype": "Currency", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "1", + "fieldname": "qty", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Qty", + "oldfieldname": "qty", + "oldfieldtype": "Currency" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "", - "fieldname": "item_group", - "fieldtype": "Link", - "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": "Item Group", - "length": 0, - "no_copy": 0, - "oldfieldname": "item_group", - "oldfieldtype": "Link", - "options": "Item Group", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "item_group", + "fieldtype": "Link", + "hidden": 1, + "label": "Item Group", + "oldfieldname": "item_group", + "oldfieldtype": "Link", + "options": "Item Group", + "print_hide": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "brand", - "fieldtype": "Link", - "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": "Brand", - "length": 0, - "no_copy": 0, - "oldfieldname": "brand", - "oldfieldtype": "Link", - "options": "Brand", - "permlevel": 0, - "print_hide": 1, - "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": "brand", + "fieldtype": "Link", + "hidden": 1, + "label": "Brand", + "oldfieldname": "brand", + "oldfieldtype": "Link", + "options": "Brand", + "print_hide": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_6", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "collapsible": 1, + "fieldname": "section_break_6", + "fieldtype": "Section Break", + "label": "Description" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "uom", - "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": "UOM", - "length": 0, - "no_copy": 0, - "oldfieldname": "uom", - "oldfieldtype": "Link", - "options": "UOM", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "uom", + "fieldtype": "Link", + "label": "UOM", + "oldfieldname": "uom", + "oldfieldtype": "Link", + "options": "UOM" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "item_name", - "fieldtype": "Data", - "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": "Item Name", - "length": 0, - "no_copy": 0, - "oldfieldname": "item_name", - "oldfieldtype": "Data", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "item_name", + "fieldtype": "Data", + "in_global_search": 1, + "label": "Item Name", + "oldfieldname": "item_name", + "oldfieldtype": "Data" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "description", - "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": "Description", - "length": 0, - "no_copy": 0, - "oldfieldname": "description", - "oldfieldtype": "Text", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "300px", - "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": "description", + "fieldtype": "Text Editor", + "label": "Description", + "oldfieldname": "description", + "oldfieldtype": "Text", + "print_width": "300px", "width": "300px" - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_8", - "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_8", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "image", - "fieldtype": "Attach", - "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": "Image", - "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 - }, + "fieldname": "image", + "fieldtype": "Attach", + "hidden": 1, + "label": "Image" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "image_view", - "fieldtype": "Image", - "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": "Image View", - "length": 0, - "no_copy": 0, - "options": "image", - "permlevel": 0, - "precision": "", - "print_hide": 1, - "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": "image_view", + "fieldtype": "Image", + "label": "Image View", + "options": "image", + "print_hide": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "basic_rate", - "fieldtype": "Currency", - "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": "Basic Rate", - "length": 0, - "no_copy": 0, - "oldfieldname": "basic_rate", - "oldfieldtype": "Currency", - "options": "Company:company:default_currency", - "permlevel": 0, - "print_hide": 1, - "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": "rate", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Rate", + "options": "currency", + "reqd": 1 + }, + { + "fieldname": "quantity_and_rate_section", + "fieldtype": "Section Break", + "label": "Quantity and Rate" + }, + { + "fieldname": "base_amount", + "fieldtype": "Currency", + "label": "Amount (Company Currency)", + "options": "Company:company:default_currency", + "print_hide": 1, + "read_only": 1, + "reqd": 1 + }, + { + "fieldname": "column_break_16", + "fieldtype": "Column Break" + }, + { + "fieldname": "amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Amount", + "options": "currency", + "read_only": 1, + "reqd": 1 + }, + { + "fieldname": "base_rate", + "fieldtype": "Currency", + "label": "Rate (Company Currency)", + "oldfieldname": "basic_rate", + "oldfieldtype": "Currency", + "options": "Company:company:default_currency", + "print_hide": 1, + "read_only": 1, + "reqd": 1 } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 1, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2018-12-28 15:43:09.382012", - "modified_by": "Administrator", - "module": "CRM", - "name": "Opportunity Item", - "owner": "Administrator", - "permissions": [], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "track_changes": 1, - "track_seen": 0, - "track_views": 0 + ], + "idx": 1, + "istable": 1, + "links": [], + "modified": "2021-07-30 16:39:09.775720", + "modified_by": "Administrator", + "module": "CRM", + "name": "Opportunity Item", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/patches.txt b/erpnext/patches.txt index a03f90ea5b0..0f57b50307a 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -304,3 +304,4 @@ erpnext.patches.v13_0.set_operation_time_based_on_operating_cost erpnext.patches.v13_0.validate_options_for_data_field erpnext.patches.v13_0.create_gst_payment_entry_fields erpnext.patches.v14_0.delete_shopify_doctypes +erpnext.patches.v14_0.update_opportunity_currency_fields \ No newline at end of file diff --git a/erpnext/patches/v14_0/update_opportunity_currency_fields.py b/erpnext/patches/v14_0/update_opportunity_currency_fields.py new file mode 100644 index 00000000000..223be7f6262 --- /dev/null +++ b/erpnext/patches/v14_0/update_opportunity_currency_fields.py @@ -0,0 +1,36 @@ +from __future__ import unicode_literals + +import frappe +from frappe.utils import flt + +import erpnext +from erpnext.setup.utils import get_exchange_rate + + +def execute(): + frappe.reload_doc('crm', 'doctype', 'opportunity') + frappe.reload_doc('crm', 'doctype', 'opportunity_item') + + opportunities = frappe.db.get_list('Opportunity', filters={ + 'opportunity_amount': ['>', 0] + }, fields=['name', 'company', 'currency', 'opportunity_amount']) + + for opportunity in opportunities: + company_currency = erpnext.get_company_currency(opportunity.company) + + # base total and total will be 0 only since item table did not have amount field earlier + if opportunity.currency != company_currency: + conversion_rate = get_exchange_rate(opportunity.currency, company_currency) + base_opportunity_amount = flt(conversion_rate) * flt(opportunity.opportunity_amount) + grand_total = flt(opportunity.opportunity_amount) + base_grand_total = flt(conversion_rate) * flt(opportunity.opportunity_amount) + else: + conversion_rate = 1 + base_opportunity_amount = grand_total = base_grand_total = flt(opportunity.opportunity_amount) + + frappe.db.set_value('Opportunity', opportunity.name, { + 'conversion_rate': conversion_rate, + 'base_opportunity_amount': base_opportunity_amount, + 'grand_total': grand_total, + 'base_grand_total': base_grand_total + }, update_modified=False) From 88c9fe35bd152cde0d350ba859e2692e954f141a Mon Sep 17 00:00:00 2001 From: Anupam Kumar Date: Tue, 7 Sep 2021 00:15:58 +0530 Subject: [PATCH 008/116] fix: employee remider settings (#27365) --- erpnext/hr/doctype/employee/employee_reminders.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/hr/doctype/employee/employee_reminders.py b/erpnext/hr/doctype/employee/employee_reminders.py index fc018664b46..ba086dc0602 100644 --- a/erpnext/hr/doctype/employee/employee_reminders.py +++ b/erpnext/hr/doctype/employee/employee_reminders.py @@ -13,7 +13,7 @@ from erpnext.hr.utils import get_holidays_for_employee # HOLIDAY REMINDERS # ----------------- def send_reminders_in_advance_weekly(): - to_send_in_advance = int(frappe.db.get_single_value("HR Settings", "send_holiday_reminders") or 1) + to_send_in_advance = int(frappe.db.get_single_value("HR Settings", "send_holiday_reminders")) frequency = frappe.db.get_single_value("HR Settings", "frequency") if not (to_send_in_advance and frequency == "Weekly"): return @@ -21,7 +21,7 @@ def send_reminders_in_advance_weekly(): send_advance_holiday_reminders("Weekly") def send_reminders_in_advance_monthly(): - to_send_in_advance = int(frappe.db.get_single_value("HR Settings", "send_holiday_reminders") or 1) + to_send_in_advance = int(frappe.db.get_single_value("HR Settings", "send_holiday_reminders")) frequency = frappe.db.get_single_value("HR Settings", "frequency") if not (to_send_in_advance and frequency == "Monthly"): return @@ -79,7 +79,7 @@ def send_holidays_reminder_in_advance(employee, holidays): # ------------------ def send_birthday_reminders(): """Send Employee birthday reminders if no 'Stop Birthday Reminders' is not set.""" - to_send = int(frappe.db.get_single_value("HR Settings", "send_birthday_reminders") or 1) + to_send = int(frappe.db.get_single_value("HR Settings", "send_birthday_reminders")) if not to_send: return From d795e5569470f3c35612bd59754bae0501929972 Mon Sep 17 00:00:00 2001 From: Mohammed Yusuf Shaikh <49878143+mohammedyusufshaikh@users.noreply.github.com> Date: Tue, 7 Sep 2021 10:49:34 +0530 Subject: [PATCH 009/116] feat(CRM): Sales Pipeline Analytics Report and Opportunity Summary by Sales Stage Report (#26639) * feat: Sales Pipeline Analytics Report * fix: sider Issues and added tests * fix: Semgrep Issue * feat: Opportunity Summary by Sales Stage Report * fix: add some checks and tests * fix: sider issues and test * fix: additional checks for error handling and minor changes * fix: remove unused conditions * fix: Changes mentioned on PR * fix: currency conversions and other changes * fix: remove unused imports * fix: correction for failing test case * fix: recorrected failing test case * fix: sider issues and resolve test case errors * fix: rewrite query using query builder * fix: test case changes * fix: sider fixes and other changes * fix: clear data before running test * fix: test case fixed * refactor: code formatting - smaller functions - variable and function naming * refactor: improve code formatting * fix: linter issues * fix: linter issues * fix: change indentation to tabs * fix: linter issues * fix: naming, code formatting * fix: quarterly values not showing up in Sales Pipeline Analytics * fix: typo in tests Co-authored-by: Rucha Mahabal --- .../__init__.py | 0 .../opportunity_summary_by_sales_stage.js | 65 ++++ .../opportunity_summary_by_sales_stage.json | 29 ++ .../opportunity_summary_by_sales_stage.py | 254 +++++++++++++ ...test_opportunity_summary_by_sales_stage.py | 94 +++++ .../sales_pipeline_analytics/__init__.py | 0 .../sales_pipeline_analytics.js | 70 ++++ .../sales_pipeline_analytics.json | 29 ++ .../sales_pipeline_analytics.py | 333 ++++++++++++++++++ .../test_sales_pipeline_analytics.py | 238 +++++++++++++ erpnext/crm/workspace/crm/crm.json | 20 +- 11 files changed, 1131 insertions(+), 1 deletion(-) create mode 100644 erpnext/crm/report/opportunity_summary_by_sales_stage/__init__.py create mode 100644 erpnext/crm/report/opportunity_summary_by_sales_stage/opportunity_summary_by_sales_stage.js create mode 100644 erpnext/crm/report/opportunity_summary_by_sales_stage/opportunity_summary_by_sales_stage.json create mode 100644 erpnext/crm/report/opportunity_summary_by_sales_stage/opportunity_summary_by_sales_stage.py create mode 100644 erpnext/crm/report/opportunity_summary_by_sales_stage/test_opportunity_summary_by_sales_stage.py create mode 100644 erpnext/crm/report/sales_pipeline_analytics/__init__.py create mode 100644 erpnext/crm/report/sales_pipeline_analytics/sales_pipeline_analytics.js create mode 100644 erpnext/crm/report/sales_pipeline_analytics/sales_pipeline_analytics.json create mode 100644 erpnext/crm/report/sales_pipeline_analytics/sales_pipeline_analytics.py create mode 100644 erpnext/crm/report/sales_pipeline_analytics/test_sales_pipeline_analytics.py diff --git a/erpnext/crm/report/opportunity_summary_by_sales_stage/__init__.py b/erpnext/crm/report/opportunity_summary_by_sales_stage/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/crm/report/opportunity_summary_by_sales_stage/opportunity_summary_by_sales_stage.js b/erpnext/crm/report/opportunity_summary_by_sales_stage/opportunity_summary_by_sales_stage.js new file mode 100644 index 00000000000..116db2f5a27 --- /dev/null +++ b/erpnext/crm/report/opportunity_summary_by_sales_stage/opportunity_summary_by_sales_stage.js @@ -0,0 +1,65 @@ +// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports["Opportunity Summary by Sales Stage"] = { + "filters": [ + { + fieldname: "based_on", + label: __("Based On"), + fieldtype: "Select", + options: "Opportunity Owner\nSource\nOpportunity Type", + default: "Opportunity Owner" + }, + { + fieldname: "data_based_on", + label: __("Data Based On"), + fieldtype: "Select", + options: "Number\nAmount", + default: "Number" + }, + { + fieldname: "from_date", + label: __("From Date"), + fieldtype: "Date", + + }, + { + fieldname: "to_date", + label: __("To Date"), + fieldtype: "Date", + }, + { + fieldname: "status", + label: __("Status"), + fieldtype: "MultiSelectList", + get_data: function() { + return [ + {value: "Open", description: "Status"}, + {value: "Converted", description: "Status"}, + {value: "Quotation", description: "Status"}, + {value: "Replied", description: "Status"} + ] + } + }, + { + fieldname: "opportunity_source", + label: __("Oppoturnity Source"), + fieldtype: "Link", + options: "Lead Source", + }, + { + fieldname: "opportunity_type", + label: __("Opportunity Type"), + fieldtype: "Link", + options: "Opportunity Type", + }, + { + fieldname: "company", + label: __("Company"), + fieldtype: "Link", + options: "Company", + default: frappe.defaults.get_user_default("Company") + } + ] +}; \ No newline at end of file diff --git a/erpnext/crm/report/opportunity_summary_by_sales_stage/opportunity_summary_by_sales_stage.json b/erpnext/crm/report/opportunity_summary_by_sales_stage/opportunity_summary_by_sales_stage.json new file mode 100644 index 00000000000..3605aecacd9 --- /dev/null +++ b/erpnext/crm/report/opportunity_summary_by_sales_stage/opportunity_summary_by_sales_stage.json @@ -0,0 +1,29 @@ +{ + "add_total_row": 0, + "columns": [], + "creation": "2021-07-28 12:18:24.028737", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 0, + "is_standard": "Yes", + "modified": "2021-07-28 12:18:24.028737", + "modified_by": "Administrator", + "module": "CRM", + "name": "Opportunity Summary by Sales Stage", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Opportunity", + "report_name": "Opportunity Summary by Sales Stage ", + "report_type": "Script Report", + "roles": [ + { + "role": "Sales User" + }, + { + "role": "Sales Manager" + } + ] +} \ No newline at end of file diff --git a/erpnext/crm/report/opportunity_summary_by_sales_stage/opportunity_summary_by_sales_stage.py b/erpnext/crm/report/opportunity_summary_by_sales_stage/opportunity_summary_by_sales_stage.py new file mode 100644 index 00000000000..4cff13f2321 --- /dev/null +++ b/erpnext/crm/report/opportunity_summary_by_sales_stage/opportunity_summary_by_sales_stage.py @@ -0,0 +1,254 @@ +# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt +import json + +import frappe +import pandas +from frappe import _ +from frappe.utils import flt +from six import iteritems + +from erpnext.setup.utils import get_exchange_rate + + +def execute(filters=None): + return OpportunitySummaryBySalesStage(filters).run() + +class OpportunitySummaryBySalesStage(object): + def __init__(self,filters=None): + self.filters = frappe._dict(filters or {}) + + def run(self): + self.get_columns() + self.get_data() + self.get_chart_data() + return self.columns, self.data, None, self.chart + + def get_columns(self): + self.columns = [] + + if self.filters.get('based_on') == 'Opportunity Owner': + self.columns.append({ + 'label': _('Opportunity Owner'), + 'fieldname': 'opportunity_owner', + 'width': 200 + }) + + if self.filters.get('based_on') == 'Source': + self.columns.append({ + 'label': _('Source'), + 'fieldname': 'source', + 'fieldtype': 'Link', + 'options': 'Lead Source', + 'width': 200 + }) + + if self.filters.get('based_on') == 'Opportunity Type': + self.columns.append({ + 'label': _('Opportunity Type'), + 'fieldname': 'opportunity_type', + 'width': 200 + }) + + self.set_sales_stage_columns() + + def set_sales_stage_columns(self): + self.sales_stage_list = frappe.db.get_list('Sales Stage', pluck='name') + + for sales_stage in self.sales_stage_list: + if self.filters.get('data_based_on') == 'Number': + self.columns.append({ + 'label': _(sales_stage), + 'fieldname': sales_stage, + 'fieldtype': 'Int', + 'width': 150 + }) + + elif self.filters.get('data_based_on') == 'Amount': + self.columns.append({ + 'label': _(sales_stage), + 'fieldname': sales_stage, + 'fieldtype': 'Currency', + 'width': 150 + }) + + def get_data(self): + self.data = [] + + based_on = { + 'Opportunity Owner': '_assign', + 'Source': 'source', + 'Opportunity Type': 'opportunity_type' + }[self.filters.get('based_on')] + + data_based_on = { + 'Number': 'count(name) as count', + 'Amount': 'opportunity_amount as amount', + }[self.filters.get('data_based_on')] + + self.get_data_query(based_on, data_based_on) + + self.get_rows() + + def get_data_query(self, based_on, data_based_on): + if self.filters.get('data_based_on') == 'Number': + group_by = '{},{}'.format('sales_stage', based_on) + self.query_result = frappe.db.get_list('Opportunity', + filters=self.get_conditions(), + fields=['sales_stage', data_based_on, based_on], + group_by=group_by + ) + + elif self.filters.get('data_based_on') == 'Amount': + self.query_result = frappe.db.get_list('Opportunity', + filters=self.get_conditions(), + fields=['sales_stage', based_on, data_based_on, 'currency'] + ) + + self.convert_to_base_currency() + + dataframe = pandas.DataFrame.from_records(self.query_result) + dataframe.replace(to_replace=[None], value='Not Assigned', inplace=True) + result = dataframe.groupby(['sales_stage', based_on], as_index=False)['amount'].sum() + + self.grouped_data = [] + + for i in range(len(result['amount'])): + self.grouped_data.append({ + 'sales_stage': result['sales_stage'][i], + based_on : result[based_on][i], + 'amount': result['amount'][i] + }) + + self.query_result = self.grouped_data + + def get_rows(self): + self.data = [] + self.get_formatted_data() + + for based_on,data in iteritems(self.formatted_data): + row_based_on={ + 'Opportunity Owner': 'opportunity_owner', + 'Source': 'source', + 'Opportunity Type': 'opportunity_type' + }[self.filters.get('based_on')] + + row = {row_based_on: based_on} + + for d in self.query_result: + sales_stage = d.get('sales_stage') + row[sales_stage] = data.get(sales_stage) + + self.data.append(row) + + def get_formatted_data(self): + self.formatted_data = frappe._dict() + + for d in self.query_result: + data_based_on ={ + 'Number': 'count', + 'Amount': 'amount' + }[self.filters.get('data_based_on')] + + based_on ={ + 'Opportunity Owner': '_assign', + 'Source': 'source', + 'Opportunity Type': 'opportunity_type' + }[self.filters.get('based_on')] + + if self.filters.get('based_on') == 'Opportunity Owner': + if d.get(based_on) == '[]' or d.get(based_on) is None or d.get(based_on) == 'Not Assigned': + assignments = ['Not Assigned'] + else: + assignments = json.loads(d.get(based_on)) + + sales_stage = d.get('sales_stage') + count = d.get(data_based_on) + + if assignments: + if len(assignments) > 1: + for assigned_to in assignments: + self.set_formatted_data_based_on_sales_stage(assigned_to, sales_stage, count) + else: + assigned_to = assignments[0] + self.set_formatted_data_based_on_sales_stage(assigned_to, sales_stage, count) + else: + value = d.get(based_on) + sales_stage = d.get('sales_stage') + count = d.get(data_based_on) + self.set_formatted_data_based_on_sales_stage(value, sales_stage, count) + + def set_formatted_data_based_on_sales_stage(self, based_on, sales_stage, count): + self.formatted_data.setdefault(based_on, frappe._dict()).setdefault(sales_stage, 0) + self.formatted_data[based_on][sales_stage] += count + + def get_conditions(self): + filters = [] + + if self.filters.get('company'): + filters.append({'company': self.filters.get('company')}) + + if self.filters.get('opportunity_type'): + filters.append({'opportunity_type': self.filters.get('opportunity_type')}) + + if self.filters.get('opportunity_source'): + filters.append({'source': self.filters.get('opportunity_source')}) + + if self.filters.get('status'): + filters.append({'status': ('in',self.filters.get('status'))}) + + if self.filters.get('from_date') and self.filters.get('to_date'): + filters.append(['transaction_date', 'between', [self.filters.get('from_date'), self.filters.get('to_date')]]) + + return filters + + def get_chart_data(self): + labels = [] + datasets = [] + values = [0] * 8 + + for sales_stage in self.sales_stage_list: + labels.append(sales_stage) + + options = { + 'Number': 'count', + 'Amount': 'amount' + }[self.filters.get('data_based_on')] + + for data in self.query_result: + for count in range(len(values)): + if data['sales_stage'] == labels[count]: + values[count] = values[count] + data[options] + + datasets.append({'name':options, 'values':values}) + + self.chart = { + 'data':{ + 'labels': labels, + 'datasets': datasets + }, + 'type':'line' + } + + def currency_conversion(self,from_currency,to_currency): + cacheobj = frappe.cache() + + if cacheobj.get(from_currency): + return flt(str(cacheobj.get(from_currency),'UTF-8')) + + else: + value = get_exchange_rate(from_currency,to_currency) + cacheobj.set(from_currency,value) + return flt(str(cacheobj.get(from_currency),'UTF-8')) + + def get_default_currency(self): + company = self.filters.get('company') + return frappe.db.get_value('Company', company, 'default_currency') + + def convert_to_base_currency(self): + default_currency = self.get_default_currency() + for data in self.query_result: + if data.get('currency') != default_currency: + opportunity_currency = data.get('currency') + value = self.currency_conversion(opportunity_currency,default_currency) + data['amount'] = data['amount'] * value \ No newline at end of file diff --git a/erpnext/crm/report/opportunity_summary_by_sales_stage/test_opportunity_summary_by_sales_stage.py b/erpnext/crm/report/opportunity_summary_by_sales_stage/test_opportunity_summary_by_sales_stage.py new file mode 100644 index 00000000000..13859d9e0b4 --- /dev/null +++ b/erpnext/crm/report/opportunity_summary_by_sales_stage/test_opportunity_summary_by_sales_stage.py @@ -0,0 +1,94 @@ +import unittest + +import frappe + +from erpnext.crm.report.opportunity_summary_by_sales_stage.opportunity_summary_by_sales_stage import ( + execute, +) +from erpnext.crm.report.sales_pipeline_analytics.test_sales_pipeline_analytics import ( + create_company, + create_customer, + create_opportunity, +) + + +class TestOpportunitySummaryBySalesStage(unittest.TestCase): + @classmethod + def setUpClass(self): + frappe.db.delete("Opportunity") + create_company() + create_customer() + create_opportunity() + + def test_opportunity_summary_by_sales_stage(self): + self.check_for_opportunity_owner() + self.check_for_source() + self.check_for_opportunity_type() + self.check_all_filters() + + def check_for_opportunity_owner(self): + filters = { + 'based_on': "Opportunity Owner", + 'data_based_on': "Number", + 'company': "Best Test" + } + + report = execute(filters) + + expected_data = [{ + 'opportunity_owner': "Not Assigned", + 'Prospecting': 1 + }] + + self.assertEqual(expected_data, report[1]) + + def check_for_source(self): + filters = { + 'based_on': "Source", + 'data_based_on': "Number", + 'company': "Best Test" + } + + report = execute(filters) + + expected_data = [{ + 'source': 'Cold Calling', + 'Prospecting': 1 + }] + + self.assertEqual(expected_data, report[1]) + + def check_for_opportunity_type(self): + filters = { + 'based_on': "Opportunity Type", + 'data_based_on': "Number", + 'company': "Best Test" + } + + report = execute(filters) + + expected_data = [{ + 'opportunity_type': 'Sales', + 'Prospecting': 1 + }] + + self.assertEqual(expected_data, report[1]) + + def check_all_filters(self): + filters = { + 'based_on': "Opportunity Type", + 'data_based_on': "Number", + 'company': "Best Test", + 'opportunity_source': "Cold Calling", + 'opportunity_type': "Sales", + 'status': ["Open"] + } + + report = execute(filters) + + expected_data = [{ + 'opportunity_type': 'Sales', + 'Prospecting': 1 + }] + + self.assertEqual(expected_data, report[1]) \ No newline at end of file diff --git a/erpnext/crm/report/sales_pipeline_analytics/__init__.py b/erpnext/crm/report/sales_pipeline_analytics/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/crm/report/sales_pipeline_analytics/sales_pipeline_analytics.js b/erpnext/crm/report/sales_pipeline_analytics/sales_pipeline_analytics.js new file mode 100644 index 00000000000..1426f4b6fd2 --- /dev/null +++ b/erpnext/crm/report/sales_pipeline_analytics/sales_pipeline_analytics.js @@ -0,0 +1,70 @@ +// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports["Sales Pipeline Analytics"] = { + "filters": [ + { + fieldname: "pipeline_by", + label: __("Pipeline By"), + fieldtype: "Select", + options: "Owner\nSales Stage", + default: "Owner" + }, + { + fieldname: "from_date", + label: __("From Date"), + fieldtype: "Date" + }, + { + fieldname: "to_date", + label: __("To Date"), + fieldtype: "Date" + }, + { + fieldname: "range", + label: __("Range"), + fieldtype: "Select", + options: "Monthly\nQuarterly", + default: "Monthly" + }, + { + fieldname: "assigned_to", + label: __("Assigned To"), + fieldtype: "Link", + options: "User" + }, + { + fieldname: "status", + label: __("Status"), + fieldtype: "Select", + options: "Open\nQuotation\nConverted\nReplied" + }, + { + fieldname: "based_on", + label: __("Based On"), + fieldtype: "Select", + options: "Number\nAmount", + default: "Number" + }, + { + fieldname: "company", + label: __("Company"), + fieldtype: "Link", + options: "Company", + default: frappe.defaults.get_user_default("Company") + }, + { + fieldname: "opportunity_source", + label: __("Opportunity Source"), + fieldtype: "Link", + options: "Lead Source" + }, + { + fieldname: "opportunity_type", + label: __("Opportunity Type"), + fieldtype: "Link", + options: "Opportunity Type" + }, + ] +}; diff --git a/erpnext/crm/report/sales_pipeline_analytics/sales_pipeline_analytics.json b/erpnext/crm/report/sales_pipeline_analytics/sales_pipeline_analytics.json new file mode 100644 index 00000000000..cffdddfd23f --- /dev/null +++ b/erpnext/crm/report/sales_pipeline_analytics/sales_pipeline_analytics.json @@ -0,0 +1,29 @@ +{ + "add_total_row": 0, + "columns": [], + "creation": "2021-07-01 17:29:09.530787", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 0, + "is_standard": "Yes", + "modified": "2021-07-01 17:45:17.612861", + "modified_by": "Administrator", + "module": "CRM", + "name": "Sales Pipeline Analytics", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Opportunity", + "report_name": "Sales Pipeline Analytics", + "report_type": "Script Report", + "roles": [ + { + "role": "Sales User" + }, + { + "role": "Sales Manager" + } + ] +} \ No newline at end of file diff --git a/erpnext/crm/report/sales_pipeline_analytics/sales_pipeline_analytics.py b/erpnext/crm/report/sales_pipeline_analytics/sales_pipeline_analytics.py new file mode 100644 index 00000000000..7466982d924 --- /dev/null +++ b/erpnext/crm/report/sales_pipeline_analytics/sales_pipeline_analytics.py @@ -0,0 +1,333 @@ +# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +import json +from datetime import date + +import frappe +import pandas +from dateutil.relativedelta import relativedelta +from frappe import _ +from frappe.utils import cint, flt +from six import iteritems + +from erpnext.setup.utils import get_exchange_rate + + +def execute(filters=None): + return SalesPipelineAnalytics(filters).run() + +class SalesPipelineAnalytics(object): + def __init__(self, filters=None): + self.filters = frappe._dict(filters or {}) + + def run(self): + self.get_columns() + self.get_data() + self.get_chart_data() + + return self.columns, self.data, None, self.chart + + def get_columns(self): + self.columns = [] + + self.set_range_columns() + self.set_pipeline_based_on_column() + + def set_range_columns(self): + based_on = { + 'Number': 'Int', + 'Amount': 'Currency' + }[self.filters.get('based_on')] + + if self.filters.get('range') == 'Monthly': + month_list = self.get_month_list() + + for month in month_list: + self.columns.append({ + 'fieldname': month, + 'fieldtype': based_on, + 'label': month, + 'width': 200 + }) + + elif self.filters.get('range') == 'Quarterly': + for quarter in range(1, 5): + self.columns.append({ + 'fieldname': f'Q{quarter}', + 'fieldtype': based_on, + 'label': f'Q{quarter}', + 'width': 200 + }) + + def set_pipeline_based_on_column(self): + if self.filters.get('pipeline_by') == 'Owner': + self.columns.insert(0, { + 'fieldname': 'opportunity_owner', + 'label': _('Opportunity Owner'), + 'width': 200 + }) + + elif self.filters.get('pipeline_by') == 'Sales Stage': + self.columns.insert(0, { + 'fieldname': 'sales_stage', + 'label': _('Sales Stage'), + 'width': 200 + }) + + def get_fields(self): + self.based_on ={ + 'Owner': '_assign as opportunity_owner', + 'Sales Stage': 'sales_stage' + }[self.filters.get('pipeline_by')] + + self.data_based_on ={ + 'Number': 'count(name) as count', + 'Amount': 'opportunity_amount as amount' + }[self.filters.get('based_on')] + + self.group_by_based_on = { + 'Owner': '_assign', + 'Sales Stage': 'sales_stage' + }[self.filters.get('pipeline_by')] + + self.group_by_period = { + 'Monthly': 'month(expected_closing)', + 'Quarterly': 'QUARTER(expected_closing)' + }[self.filters.get('range')] + + self.pipeline_by = { + 'Owner': 'opportunity_owner', + 'Sales Stage': 'sales_stage' + }[self.filters.get('pipeline_by')] + + self.duration = { + 'Monthly': 'monthname(expected_closing) as month', + 'Quarterly': 'QUARTER(expected_closing) as quarter' + }[self.filters.get('range')] + + self.period_by = { + 'Monthly': 'month', + 'Quarterly': 'quarter' + }[self.filters.get('range')] + + def get_data(self): + self.get_fields() + + if self.filters.get('based_on') == 'Number': + self.query_result = frappe.db.get_list('Opportunity', + filters=self.get_conditions(), + fields=[self.based_on, self.data_based_on, self.duration], + group_by='{},{}'.format(self.group_by_based_on, self.group_by_period), + order_by=self.group_by_period + ) + + if self.filters.get('based_on') == 'Amount': + self.query_result = frappe.db.get_list('Opportunity', + filters=self.get_conditions(), + fields=[self.based_on, self.data_based_on, self.duration, 'currency'] + ) + + self.convert_to_base_currency() + + dataframe = pandas.DataFrame.from_records(self.query_result) + dataframe.replace(to_replace=[None], value='Not Assigned', inplace=True) + result = dataframe.groupby([self.pipeline_by, self.period_by], as_index=False)['amount'].sum() + + self.grouped_data = [] + + for i in range(len(result['amount'])): + self.grouped_data.append({ + self.pipeline_by : result[self.pipeline_by][i], + self.period_by : result[self.period_by][i], + 'amount': result['amount'][i] + }) + + self.query_result = self.grouped_data + + self.get_periodic_data() + self.append_data(self.pipeline_by, self.period_by) + + def get_conditions(self): + conditions = [] + + if self.filters.get('opportunity_source'): + conditions.append({'source': self.filters.get('opportunity_source')}) + + if self.filters.get('opportunity_type'): + conditions.append({'opportunity_type': self.filters.get('opportunity_type')}) + + if self.filters.get('status'): + conditions.append({'status': self.filters.get('status')}) + + if self.filters.get('company'): + conditions.append({'company': self.filters.get('company')}) + + if self.filters.get('from_date') and self.filters.get('to_date'): + conditions.append(['expected_closing', 'between', + [self.filters.get('from_date'), self.filters.get('to_date')]]) + + return conditions + + def get_chart_data(self): + labels = [] + datasets = [] + + self.append_to_dataset(datasets) + + for column in self.columns: + if column['fieldname'] != 'opportunity_owner' and column['fieldname'] != 'sales_stage': + labels.append(column['fieldname']) + + self.chart = { + 'data':{ + 'labels': labels, + 'datasets': datasets + }, + 'type':'line' + } + + return self.chart + + def get_periodic_data(self): + self.periodic_data = frappe._dict() + + based_on = { + 'Number': 'count', + 'Amount': 'amount' + }[self.filters.get('based_on')] + + pipeline_by = { + 'Owner': 'opportunity_owner', + 'Sales Stage': 'sales_stage' + }[self.filters.get('pipeline_by')] + + frequency = { + 'Monthly': 'month', + 'Quarterly': 'quarter' + }[self.filters.get('range')] + + for info in self.query_result: + if self.filters.get('range') == 'Monthly': + period = info.get(frequency) + if self.filters.get('range') == 'Quarterly': + period = f'Q{cint(info.get("quarter"))}' + + value = info.get(pipeline_by) + count_or_amount = info.get(based_on) + + if self.filters.get('pipeline_by') == 'Owner': + if value == 'Not Assigned' or value == '[]' or value is None: + assigned_to = ['Not Assigned'] + else: + assigned_to = json.loads(value) + self.check_for_assigned_to(period, value, count_or_amount, assigned_to, info) + + else: + self.set_formatted_data(period, value, count_or_amount, None) + + def set_formatted_data(self, period, value, count_or_amount, assigned_to): + if assigned_to: + if len(assigned_to) > 1: + if self.filters.get('assigned_to'): + for user in assigned_to: + if self.filters.get('assigned_to') == user: + value = user + self.periodic_data.setdefault(value, frappe._dict()).setdefault(period, 0) + self.periodic_data[value][period] += count_or_amount + else: + for user in assigned_to: + value = user + self.periodic_data.setdefault(value, frappe._dict()).setdefault(period, 0) + self.periodic_data[value][period] += count_or_amount + else: + value = assigned_to[0] + self.periodic_data.setdefault(value, frappe._dict()).setdefault(period, 0) + self.periodic_data[value][period] += count_or_amount + + else: + self.periodic_data.setdefault(value, frappe._dict()).setdefault(period, 0) + self.periodic_data[value][period] += count_or_amount + + def check_for_assigned_to(self, period, value, count_or_amount, assigned_to, info): + if self.filters.get('assigned_to'): + for data in json.loads(info.get('opportunity_owner')): + if data == self.filters.get('assigned_to'): + self.set_formatted_data(period, data, count_or_amount, assigned_to) + else: + self.set_formatted_data(period, value, count_or_amount, assigned_to) + + def get_month_list(self): + month_list= [] + current_date = date.today() + month_number = date.today().month + + for month in range(month_number,13): + month_list.append(current_date.strftime('%B')) + current_date = current_date + relativedelta(months=1) + + return month_list + + def append_to_dataset(self, datasets): + range_by = { + 'Monthly': 'month', + 'Quarterly': 'quarter' + }[self.filters.get('range')] + + based_on = { + 'Amount': 'amount', + 'Number': 'count' + }[self.filters.get('based_on')] + + if self.filters.get('range') == 'Quarterly': + frequency_list = [1,2,3,4] + count = [0] * 4 + + if self.filters.get('range') == 'Monthly': + frequency_list = self.get_month_list() + count = [0] * 12 + + for info in self.query_result: + for i in range(len(frequency_list)): + if info[range_by] == frequency_list[i]: + count[i] = count[i] + info[based_on] + datasets.append({'name': based_on, 'values': count}) + + def append_data(self, pipeline_by, period_by): + self.data = [] + for pipeline,period_data in iteritems(self.periodic_data): + row = {pipeline_by : pipeline} + for info in self.query_result: + if self.filters.get('range') == 'Monthly': + period = info.get(period_by) + + if self.filters.get('range') == 'Quarterly': + period = f'Q{cint(info.get(period_by))}' + + count = period_data.get(period,0.0) + row[period] = count + + self.data.append(row) + + def get_default_currency(self): + company = self.filters.get('company') + return frappe.db.get_value('Company',company,['default_currency']) + + def get_currency_rate(self, from_currency, to_currency): + cacheobj = frappe.cache() + + if cacheobj.get(from_currency): + return flt(str(cacheobj.get(from_currency),'UTF-8')) + + else: + value = get_exchange_rate(from_currency, to_currency) + cacheobj.set(from_currency, value) + return flt(str(cacheobj.get(from_currency),'UTF-8')) + + def convert_to_base_currency(self): + default_currency = self.get_default_currency() + for data in self.query_result: + if data.get('currency') != default_currency: + opportunity_currency = data.get('currency') + value = self.get_currency_rate(opportunity_currency,default_currency) + data['amount'] = data['amount'] * value \ No newline at end of file diff --git a/erpnext/crm/report/sales_pipeline_analytics/test_sales_pipeline_analytics.py b/erpnext/crm/report/sales_pipeline_analytics/test_sales_pipeline_analytics.py new file mode 100644 index 00000000000..24c3839d2d9 --- /dev/null +++ b/erpnext/crm/report/sales_pipeline_analytics/test_sales_pipeline_analytics.py @@ -0,0 +1,238 @@ +import unittest + +import frappe + +from erpnext.crm.report.sales_pipeline_analytics.sales_pipeline_analytics import execute + + +class TestSalesPipelineAnalytics(unittest.TestCase): + @classmethod + def setUpClass(self): + frappe.db.delete("Opportunity") + create_company() + create_customer() + create_opportunity() + + def test_sales_pipeline_analytics(self): + self.check_for_monthly_and_number() + self.check_for_monthly_and_amount() + self.check_for_quarterly_and_number() + self.check_for_quarterly_and_amount() + self.check_for_all_filters() + + def check_for_monthly_and_number(self): + filters = { + 'pipeline_by':"Owner", + 'range':"Monthly", + 'based_on':"Number", + 'status':"Open", + 'opportunity_type':"Sales", + 'company':"Best Test" + } + + report = execute(filters) + + expected_data = [ + { + 'opportunity_owner':'Not Assigned', + 'August':1 + } + ] + + self.assertEqual(expected_data,report[1]) + + filters = { + 'pipeline_by':"Sales Stage", + 'range':"Monthly", + 'based_on':"Number", + 'status':"Open", + 'opportunity_type':"Sales", + 'company':"Best Test" + } + + report = execute(filters) + + expected_data = [ + { + 'sales_stage':'Prospecting', + 'August':1 + } + ] + + self.assertEqual(expected_data,report[1]) + + def check_for_monthly_and_amount(self): + filters = { + 'pipeline_by':"Owner", + 'range':"Monthly", + 'based_on':"Amount", + 'status':"Open", + 'opportunity_type':"Sales", + 'company':"Best Test" + } + + report = execute(filters) + + expected_data = [ + { + 'opportunity_owner':'Not Assigned', + 'August':150000 + } + ] + + self.assertEqual(expected_data,report[1]) + + filters = { + 'pipeline_by':"Sales Stage", + 'range':"Monthly", + 'based_on':"Amount", + 'status':"Open", + 'opportunity_type':"Sales", + 'company':"Best Test" + } + + report = execute(filters) + + expected_data = [ + { + 'sales_stage':'Prospecting', + 'August':150000 + } + ] + + self.assertEqual(expected_data,report[1]) + + def check_for_quarterly_and_number(self): + filters = { + 'pipeline_by':"Owner", + 'range':"Quarterly", + 'based_on':"Number", + 'status':"Open", + 'opportunity_type':"Sales", + 'company':"Best Test" + } + + report = execute(filters) + + expected_data = [ + { + 'opportunity_owner':'Not Assigned', + 'Q3':1 + } + ] + + self.assertEqual(expected_data,report[1]) + + filters = { + 'pipeline_by':"Sales Stage", + 'range':"Quarterly", + 'based_on':"Number", + 'status':"Open", + 'opportunity_type':"Sales", + 'company':"Best Test" + } + + report = execute(filters) + + expected_data = [ + { + 'sales_stage':'Prospecting', + 'Q3':1 + } + ] + + self.assertEqual(expected_data,report[1]) + + def check_for_quarterly_and_amount(self): + filters = { + 'pipeline_by':"Owner", + 'range':"Quarterly", + 'based_on':"Amount", + 'status':"Open", + 'opportunity_type':"Sales", + 'company':"Best Test" + } + + report = execute(filters) + + expected_data = [ + { + 'opportunity_owner':'Not Assigned', + 'Q3':150000 + } + ] + + self.assertEqual(expected_data,report[1]) + + filters = { + 'pipeline_by':"Sales Stage", + 'range':"Quarterly", + 'based_on':"Amount", + 'status':"Open", + 'opportunity_type':"Sales", + 'company':"Best Test" + } + + report = execute(filters) + + expected_data = [ + { + 'sales_stage':'Prospecting', + 'Q3':150000 + } + ] + + self.assertEqual(expected_data,report[1]) + + def check_for_all_filters(self): + filters = { + 'pipeline_by':"Owner", + 'range':"Monthly", + 'based_on':"Number", + 'status':"Open", + 'opportunity_type':"Sales", + 'company':"Best Test", + 'opportunity_source':'Cold Calling', + 'from_date': '2021-08-01', + 'to_date':'2021-08-31' + } + + report = execute(filters) + + expected_data = [ + { + 'opportunity_owner':'Not Assigned', + 'August': 1 + } + ] + + self.assertEqual(expected_data,report[1]) + +def create_company(): + doc = frappe.db.exists('Company','Best Test') + if not doc: + doc = frappe.new_doc('Company') + doc.company_name = 'Best Test' + doc.default_currency = "INR" + doc.insert() + +def create_customer(): + doc = frappe.db.exists("Customer","_Test NC") + if not doc: + doc = frappe.new_doc("Customer") + doc.customer_name = '_Test NC' + doc.insert() + +def create_opportunity(): + doc = frappe.db.exists({"doctype":"Opportunity","party_name":"_Test NC"}) + if not doc: + doc = frappe.new_doc("Opportunity") + doc.opportunity_from = "Customer" + customer_name = frappe.db.get_value("Customer",{"customer_name":'_Test NC'},['customer_name']) + doc.party_name = customer_name + doc.opportunity_amount = 150000 + doc.source = "Cold Calling" + doc.currency = "INR" + doc.expected_closing = "2021-08-31" + doc.company = 'Best Test' + doc.insert() \ No newline at end of file diff --git a/erpnext/crm/workspace/crm/crm.json b/erpnext/crm/workspace/crm/crm.json index c363395452b..a661b623792 100644 --- a/erpnext/crm/workspace/crm/crm.json +++ b/erpnext/crm/workspace/crm/crm.json @@ -147,6 +147,24 @@ "onboard": 1, "type": "Link" }, + { + "hidden": 0, + "is_query_report": 1, + "label": "Sales Pipeline Analytics", + "link_to": "Sales Pipeline Analytics", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 1, + "label": "Opportunity Summary by Sales Stage", + "link_to": "Opportunity Summary by Sales Stage", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, { "dependencies": "", "hidden": 0, @@ -403,7 +421,7 @@ "type": "Link" } ], - "modified": "2021-08-05 12:15:56.913091", + "modified": "2021-08-19 19:08:08.728876", "modified_by": "Administrator", "module": "CRM", "name": "CRM", From e1f55df4d76de7a2125f7608fe1c7a40823efa51 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 7 Sep 2021 11:07:22 +0530 Subject: [PATCH 010/116] fix: GSTR-1 Reports not showing any data --- erpnext/regional/report/gstr_1/gstr_1.py | 64 +++++++++++++----------- 1 file changed, 34 insertions(+), 30 deletions(-) diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py index ca0defa648a..16346f07069 100644 --- a/erpnext/regional/report/gstr_1/gstr_1.py +++ b/erpnext/regional/report/gstr_1/gstr_1.py @@ -50,6 +50,7 @@ class Gstr1Report(object): self.gst_accounts = get_gst_accounts(self.filters.company, only_non_reverse_charge=1) self.get_invoice_data() + print(self.invoices, "$#$#$#$#$#") if self.invoices: self.get_invoice_items() self.get_items_based_on_tax_rate() @@ -96,35 +97,36 @@ class Gstr1Report(object): def get_b2c_data(self): b2cs_output = {} - for inv, items_based_on_rate in self.items_based_on_tax_rate.items(): - invoice_details = self.invoices.get(inv) - for rate, items in items_based_on_rate.items(): - place_of_supply = invoice_details.get("place_of_supply") - ecommerce_gstin = invoice_details.get("ecommerce_gstin") + if self.invoices: + for inv, items_based_on_rate in self.items_based_on_tax_rate.items(): + invoice_details = self.invoices.get(inv) + for rate, items in items_based_on_rate.items(): + place_of_supply = invoice_details.get("place_of_supply") + ecommerce_gstin = invoice_details.get("ecommerce_gstin") - b2cs_output.setdefault((rate, place_of_supply, ecommerce_gstin),{ - "place_of_supply": "", - "ecommerce_gstin": "", - "rate": "", - "taxable_value": 0, - "cess_amount": 0, - "type": "", - "invoice_number": invoice_details.get("invoice_number"), - "posting_date": invoice_details.get("posting_date"), - "invoice_value": invoice_details.get("base_grand_total"), - }) + b2cs_output.setdefault((rate, place_of_supply, ecommerce_gstin), { + "place_of_supply": "", + "ecommerce_gstin": "", + "rate": "", + "taxable_value": 0, + "cess_amount": 0, + "type": "", + "invoice_number": invoice_details.get("invoice_number"), + "posting_date": invoice_details.get("posting_date"), + "invoice_value": invoice_details.get("base_grand_total"), + }) - row = b2cs_output.get((rate, place_of_supply, ecommerce_gstin)) - row["place_of_supply"] = place_of_supply - row["ecommerce_gstin"] = ecommerce_gstin - row["rate"] = rate - row["taxable_value"] += sum([abs(net_amount) - for item_code, net_amount in self.invoice_items.get(inv).items() if item_code in items]) - row["cess_amount"] += flt(self.invoice_cess.get(inv), 2) - row["type"] = "E" if ecommerce_gstin else "OE" + row = b2cs_output.get((rate, place_of_supply, ecommerce_gstin)) + row["place_of_supply"] = place_of_supply + row["ecommerce_gstin"] = ecommerce_gstin + row["rate"] = rate + row["taxable_value"] += sum([abs(net_amount) + for item_code, net_amount in self.invoice_items.get(inv).items() if item_code in items]) + row["cess_amount"] += flt(self.invoice_cess.get(inv), 2) + row["type"] = "E" if ecommerce_gstin else "OE" - for key, value in iteritems(b2cs_output): - self.data.append(value) + for key, value in iteritems(b2cs_output): + self.data.append(value) def get_row_data_for_invoice(self, invoice, invoice_details, tax_rate, items): row = [] @@ -173,9 +175,10 @@ class Gstr1Report(object): company_gstins = get_company_gstin_number(self.filters.get('company'), all_gstins=True) - self.filters.update({ - 'company_gstins': company_gstins - }) + if company_gstins: + self.filters.update({ + 'company_gstins': company_gstins + }) invoice_data = frappe.db.sql(""" select @@ -185,7 +188,7 @@ class Gstr1Report(object): and is_opening = 'No' order by posting_date desc """.format(select_columns=self.select_columns, doctype=self.doctype, - where_conditions=conditions), self.filters, as_dict=1) + where_conditions=conditions), self.filters, as_dict=1, debug=1) for d in invoice_data: self.invoices.setdefault(d.invoice_number, d) @@ -1050,6 +1053,7 @@ def get_company_gstin_number(company, address=None, all_gstins=False): ["Dynamic Link", "link_doctype", "=", "Company"], ["Dynamic Link", "link_name", "=", company], ["Dynamic Link", "parenttype", "=", "Address"], + ["gstin", "!=", ''] ] gstin = frappe.get_all("Address", filters=filters, pluck="gstin", order_by="is_primary_address desc") if gstin and not all_gstins: From 6dfcc1e2ae408ffb6a468be36cd1e7f2f957d95a Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 7 Sep 2021 11:08:30 +0530 Subject: [PATCH 011/116] fix: Remove print and debug --- erpnext/regional/report/gstr_1/gstr_1.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py index 16346f07069..cf4850e2781 100644 --- a/erpnext/regional/report/gstr_1/gstr_1.py +++ b/erpnext/regional/report/gstr_1/gstr_1.py @@ -50,7 +50,6 @@ class Gstr1Report(object): self.gst_accounts = get_gst_accounts(self.filters.company, only_non_reverse_charge=1) self.get_invoice_data() - print(self.invoices, "$#$#$#$#$#") if self.invoices: self.get_invoice_items() self.get_items_based_on_tax_rate() @@ -188,7 +187,7 @@ class Gstr1Report(object): and is_opening = 'No' order by posting_date desc """.format(select_columns=self.select_columns, doctype=self.doctype, - where_conditions=conditions), self.filters, as_dict=1, debug=1) + where_conditions=conditions), self.filters, as_dict=1) for d in invoice_data: self.invoices.setdefault(d.invoice_number, d) From 058d98342adcef0438a6fdad061af19dfe1fe702 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Tue, 7 Sep 2021 12:14:40 +0530 Subject: [PATCH 012/116] fix: missed to add voucher_type, voucher_no to get GL Entries (#27368) * fix: missed to add voucher_type, voucher_no to get gl entries * test: get voucherwise details utilities Co-authored-by: Ankush Menat --- erpnext/accounts/test/test_utils.py | 71 +++++++++++++++++------------ erpnext/accounts/utils.py | 5 +- 2 files changed, 45 insertions(+), 31 deletions(-) diff --git a/erpnext/accounts/test/test_utils.py b/erpnext/accounts/test/test_utils.py index d7b60daa37b..c3f6d274437 100644 --- a/erpnext/accounts/test/test_utils.py +++ b/erpnext/accounts/test/test_utils.py @@ -5,21 +5,48 @@ import unittest from frappe.test_runner import make_test_objects from erpnext.accounts.party import get_party_shipping_address +from erpnext.accounts.utils import get_future_stock_vouchers, get_voucherwise_gl_entries +from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt class TestUtils(unittest.TestCase): @classmethod def setUpClass(cls): super(TestUtils, cls).setUpClass() - make_test_objects('Address', ADDRESS_RECORDS) + make_test_objects("Address", ADDRESS_RECORDS) def test_get_party_shipping_address(self): - address = get_party_shipping_address('Customer', '_Test Customer 1') - self.assertEqual(address, '_Test Billing Address 2 Title-Billing') + address = get_party_shipping_address("Customer", "_Test Customer 1") + self.assertEqual(address, "_Test Billing Address 2 Title-Billing") def test_get_party_shipping_address2(self): - address = get_party_shipping_address('Customer', '_Test Customer 2') - self.assertEqual(address, '_Test Shipping Address 2 Title-Shipping') + address = get_party_shipping_address("Customer", "_Test Customer 2") + self.assertEqual(address, "_Test Shipping Address 2 Title-Shipping") + + def test_get_voucher_wise_gl_entry(self): + + pr = make_purchase_receipt( + item_code="_Test Item", + posting_date="2021-02-01", + rate=100, + qty=1, + warehouse="Stores - TCP1", + company="_Test Company with perpetual inventory", + ) + + future_vouchers = get_future_stock_vouchers("2021-01-01", "00:00:00", for_items=["_Test Item"]) + + voucher_type_and_no = ("Purchase Receipt", pr.name) + self.assertTrue( + voucher_type_and_no in future_vouchers, + msg="get_future_stock_vouchers not returning correct value", + ) + + posting_date = "2021-01-01" + gl_entries = get_voucherwise_gl_entries(future_vouchers, posting_date) + self.assertTrue( + voucher_type_and_no in gl_entries, msg="get_voucherwise_gl_entries not returning expected GLes", + ) ADDRESS_RECORDS = [ @@ -31,12 +58,8 @@ ADDRESS_RECORDS = [ "city": "Lagos", "country": "Nigeria", "links": [ - { - "link_doctype": "Customer", - "link_name": "_Test Customer 2", - "doctype": "Dynamic Link" - } - ] + {"link_doctype": "Customer", "link_name": "_Test Customer 2", "doctype": "Dynamic Link"} + ], }, { "doctype": "Address", @@ -46,12 +69,8 @@ ADDRESS_RECORDS = [ "city": "Lagos", "country": "Nigeria", "links": [ - { - "link_doctype": "Customer", - "link_name": "_Test Customer 2", - "doctype": "Dynamic Link" - } - ] + {"link_doctype": "Customer", "link_name": "_Test Customer 2", "doctype": "Dynamic Link"} + ], }, { "doctype": "Address", @@ -62,12 +81,8 @@ ADDRESS_RECORDS = [ "country": "Nigeria", "is_shipping_address": "1", "links": [ - { - "link_doctype": "Customer", - "link_name": "_Test Customer 2", - "doctype": "Dynamic Link" - } - ] + {"link_doctype": "Customer", "link_name": "_Test Customer 2", "doctype": "Dynamic Link"} + ], }, { "doctype": "Address", @@ -78,11 +93,7 @@ ADDRESS_RECORDS = [ "country": "Nigeria", "is_shipping_address": "1", "links": [ - { - "link_doctype": "Customer", - "link_name": "_Test Customer 1", - "doctype": "Dynamic Link" - } - ] - } + {"link_doctype": "Customer", "link_name": "_Test Customer 1", "doctype": "Dynamic Link"} + ], + }, ] diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 4692869343f..fbad171b787 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -963,6 +963,9 @@ def get_voucherwise_gl_entries(future_stock_vouchers, posting_date): Only fetches GLE fields required for comparing with new GLE. Check compare_existing_and_expected_gle function below. + + returns: + Dict[Tuple[voucher_type, voucher_no], List[GL Entries]] """ gl_entries = {} if not future_stock_vouchers: @@ -971,7 +974,7 @@ def get_voucherwise_gl_entries(future_stock_vouchers, posting_date): voucher_nos = [d[1] for d in future_stock_vouchers] gles = frappe.db.sql(""" - select name, account, credit, debit, cost_center, project + select name, account, credit, debit, cost_center, project, voucher_type, voucher_no from `tabGL Entry` where posting_date >= %s and voucher_no in (%s)""" % From 61a1356109ae35fcdec438e09df4f784f76d24b8 Mon Sep 17 00:00:00 2001 From: Marica Date: Tue, 7 Sep 2021 14:42:17 +0530 Subject: [PATCH 013/116] fix: Check if Item is serialised before trying to fetch SN and set SO in it (#27358) --- erpnext/stock/doctype/stock_entry/stock_entry.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 41ca8300a8e..4ccfa62b5a0 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -1503,7 +1503,8 @@ class StockEntry(StockController): qty_to_reserve -= reserved_qty[0][0] if qty_to_reserve > 0: for item in self.items: - if item.item_code == item_code: + has_serial_no = frappe.get_cached_value("Item", item.item_code, "has_serial_no") + if item.item_code == item_code and has_serial_no: serial_nos = (item.serial_no).split("\n") for serial_no in serial_nos: if qty_to_reserve > 0: From 5596988c94527509a847e9df055d1cfc996181c4 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 7 Sep 2021 14:53:30 +0530 Subject: [PATCH 014/116] ci: fix docs checker for wiki based docs (#27380) --- .github/helper/documentation.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/helper/documentation.py b/.github/helper/documentation.py index 91983d3eae5..378983e95f2 100644 --- a/.github/helper/documentation.py +++ b/.github/helper/documentation.py @@ -24,6 +24,8 @@ def docs_link_exists(body): parts = parsed_url.path.split('/') if len(parts) == 5 and parts[1] == "frappe" and parts[2] in docs_repos: return True + elif parsed_url.netloc == "docs.erpnext.com": + return True if __name__ == "__main__": From df3e4ce1c09675f941f7622333b555eee16e0981 Mon Sep 17 00:00:00 2001 From: Saqib Date: Wed, 8 Sep 2021 16:04:13 +0530 Subject: [PATCH 015/116] fix: scan barcode fields input length (#27389) * fix: undo unintentional changes * fix: scan barcode fields input length --- .../doctype/sales_invoice/sales_invoice.json | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index b5620ae6a96..e476439c7a2 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -247,7 +247,7 @@ "depends_on": "customer", "fetch_from": "customer.customer_name", "fieldname": "customer_name", - "fieldtype": "Data", + "fieldtype": "Small Text", "hide_days": 1, "hide_seconds": 1, "in_global_search": 1, @@ -1061,6 +1061,7 @@ "hide_days": 1, "hide_seconds": 1, "label": "Apply Additional Discount On", + "length": 15, "options": "\nGrand Total\nNet Total", "print_hide": 1 }, @@ -1147,7 +1148,7 @@ { "description": "In Words will be visible once you save the Sales Invoice.", "fieldname": "base_in_words", - "fieldtype": "Data", + "fieldtype": "Small Text", "hide_days": 1, "hide_seconds": 1, "label": "In Words (Company Currency)", @@ -1207,7 +1208,7 @@ }, { "fieldname": "in_words", - "fieldtype": "Data", + "fieldtype": "Small Text", "hide_days": 1, "hide_seconds": 1, "label": "In Words", @@ -1560,6 +1561,7 @@ "hide_days": 1, "hide_seconds": 1, "label": "Print Language", + "length": 6, "print_hide": 1, "read_only": 1 }, @@ -1647,6 +1649,7 @@ "hide_seconds": 1, "in_standard_filter": 1, "label": "Status", + "length": 30, "no_copy": 1, "options": "\nDraft\nReturn\nCredit Note Issued\nSubmitted\nPaid\nUnpaid\nUnpaid and Discounted\nOverdue and Discounted\nOverdue\nCancelled\nInternal Transfer", "print_hide": 1, @@ -1706,6 +1709,7 @@ "hide_days": 1, "hide_seconds": 1, "label": "Is Opening Entry", + "length": 4, "oldfieldname": "is_opening", "oldfieldtype": "Select", "options": "No\nYes", @@ -1717,6 +1721,7 @@ "hide_days": 1, "hide_seconds": 1, "label": "C-Form Applicable", + "length": 4, "no_copy": 1, "options": "No\nYes", "print_hide": 1 @@ -2017,7 +2022,7 @@ "link_fieldname": "consolidated_invoice" } ], - "modified": "2021-08-27 20:13:40.456462", + "modified": "2021-09-08 15:24:25.486499", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", From fa819f2fb0523276f64ed80c885088ac0596933d Mon Sep 17 00:00:00 2001 From: Dany Robert Date: Wed, 8 Sep 2021 16:28:05 +0530 Subject: [PATCH 016/116] fix: General Ledger translation issues (#27298) * fix: remove translations from GL report options Options need not be translated, their display label gets translated client side. * fix: make group by options translatable * ci: semgrep rule for translated options in report Co-authored-by: Ankush Menat --- .github/helper/semgrep_rules/report.yml | 13 +++++++++++ .../report/general_ledger/general_ledger.js | 23 ++++++++++++++++--- .../report/general_ledger/general_ledger.py | 20 ++++++++-------- 3 files changed, 43 insertions(+), 13 deletions(-) diff --git a/.github/helper/semgrep_rules/report.yml b/.github/helper/semgrep_rules/report.yml index 7f3dd011dc1..f2a9b167399 100644 --- a/.github/helper/semgrep_rules/report.yml +++ b/.github/helper/semgrep_rules/report.yml @@ -19,3 +19,16 @@ rules: languages: [python] severity: ERROR +- id: frappe-translated-values-in-business-logic + paths: + include: + - "**/report" + patterns: + - pattern-inside: | + {..., filters: [...], ...} + - pattern: | + {..., options: [..., __("..."), ...], ...} + message: | + Using translated values in options field will require you to translate the values while comparing in business logic. Instead of passing translated labels provide objects that contain both label and value. e.g. { label: __("Option value"), value: "Option value"} + languages: [javascript] + severity: ERROR diff --git a/erpnext/accounts/report/general_ledger/general_ledger.js b/erpnext/accounts/report/general_ledger/general_ledger.js index 095f5eda66a..b2968761c63 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.js +++ b/erpnext/accounts/report/general_ledger/general_ledger.js @@ -110,9 +110,26 @@ frappe.query_reports["General Ledger"] = { "fieldname":"group_by", "label": __("Group by"), "fieldtype": "Select", - "options": ["", __("Group by Voucher"), __("Group by Voucher (Consolidated)"), - __("Group by Account"), __("Group by Party")], - "default": __("Group by Voucher (Consolidated)") + "options": [ + "", + { + label: __("Group by Voucher"), + value: "Group by Voucher", + }, + { + label: __("Group by Voucher (Consolidated)"), + value: "Group by Voucher (Consolidated)", + }, + { + label: __("Group by Account"), + value: "Group by Account", + }, + { + label: __("Group by Party"), + value: "Group by Party", + }, + ], + "default": "Group by Voucher (Consolidated)" }, { "fieldname":"tax_id", diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index a0445187499..5bd6e583dbb 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -62,14 +62,14 @@ def validate_filters(filters, account_details): if not account_details.get(account): frappe.throw(_("Account {0} does not exists").format(account)) - if (filters.get("account") and filters.get("group_by") == _('Group by Account')): + if (filters.get("account") and filters.get("group_by") == 'Group by Account'): filters.account = frappe.parse_json(filters.get('account')) for account in filters.account: if account_details[account].is_group == 0: frappe.throw(_("Can not filter based on Child Account, if grouped by Account")) if (filters.get("voucher_no") - and filters.get("group_by") in [_('Group by Voucher')]): + and filters.get("group_by") in ['Group by Voucher']): frappe.throw(_("Can not filter based on Voucher No, if grouped by Voucher")) if filters.from_date > filters.to_date: @@ -153,7 +153,7 @@ def get_gl_entries(filters, accounting_dimensions): if filters.get("include_dimensions"): order_by_statement = "order by posting_date, creation" - if filters.get("group_by") == _("Group by Voucher"): + if filters.get("group_by") == "Group by Voucher": order_by_statement = "order by posting_date, voucher_type, voucher_no" if filters.get("include_default_book_entries"): @@ -312,13 +312,13 @@ def get_data_with_opening_closing(filters, account_details, accounting_dimension # Opening for filtered account data.append(totals.opening) - if filters.get("group_by") != _('Group by Voucher (Consolidated)'): + if filters.get("group_by") != 'Group by Voucher (Consolidated)': for acc, acc_dict in iteritems(gle_map): # acc if acc_dict.entries: # opening data.append({}) - if filters.get("group_by") != _("Group by Voucher"): + if filters.get("group_by") != "Group by Voucher": data.append(acc_dict.totals.opening) data += acc_dict.entries @@ -327,7 +327,7 @@ def get_data_with_opening_closing(filters, account_details, accounting_dimension data.append(acc_dict.totals.total) # closing - if filters.get("group_by") != _("Group by Voucher"): + if filters.get("group_by") != "Group by Voucher": data.append(acc_dict.totals.closing) data.append({}) else: @@ -357,9 +357,9 @@ def get_totals_dict(): ) def group_by_field(group_by): - if group_by == _('Group by Party'): + if group_by == 'Group by Party': return 'party' - elif group_by in [_('Group by Voucher (Consolidated)'), _('Group by Account')]: + elif group_by in ['Group by Voucher (Consolidated)', 'Group by Account']: return 'account' else: return 'voucher_no' @@ -423,9 +423,9 @@ def get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map): elif gle.posting_date <= to_date: update_value_in_dict(gle_map[gle.get(group_by)].totals, 'total', gle) update_value_in_dict(totals, 'total', gle) - if filters.get("group_by") != _('Group by Voucher (Consolidated)'): + if filters.get("group_by") != 'Group by Voucher (Consolidated)': gle_map[gle.get(group_by)].entries.append(gle) - elif filters.get("group_by") == _('Group by Voucher (Consolidated)'): + elif filters.get("group_by") == 'Group by Voucher (Consolidated)': keylist = [gle.get("voucher_type"), gle.get("voucher_no"), gle.get("account")] for dim in accounting_dimensions: keylist.append(gle.get(dim)) From 9c27f9be1e0fb26166b65c96ceb3f56f82312819 Mon Sep 17 00:00:00 2001 From: Saqib Date: Wed, 8 Sep 2021 16:47:04 +0530 Subject: [PATCH 017/116] fix: document naming rule not working for subscription invoices (#27386) --- erpnext/accounts/doctype/subscription/subscription.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/accounts/doctype/subscription/subscription.py b/erpnext/accounts/doctype/subscription/subscription.py index 445eb3c7096..8171b3b019d 100644 --- a/erpnext/accounts/doctype/subscription/subscription.py +++ b/erpnext/accounts/doctype/subscription/subscription.py @@ -400,6 +400,7 @@ class Subscription(Document): invoice.flags.ignore_mandatory = True + invoice.set_missing_values() invoice.save() if self.submit_invoice: From 81d3524d2747e2e3f2227053f6b0e48cb94919cc Mon Sep 17 00:00:00 2001 From: Alan <2.alan.tom@gmail.com> Date: Wed, 8 Sep 2021 16:48:42 +0530 Subject: [PATCH 018/116] fix: auto complete sales order rows in production plan (#27352) * fix: auto complete sales order rows in production plan * fix: sider --- .../production_plan/production_plan.js | 19 +++++++++++++++++++ .../production_plan/production_plan.py | 6 ++++++ 2 files changed, 25 insertions(+) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.js b/erpnext/manufacturing/doctype/production_plan/production_plan.js index 7b4b7c3ca38..db0f2c5dbd8 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.js +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.js @@ -434,6 +434,25 @@ frappe.ui.form.on("Material Request Plan Item", { } }); +frappe.ui.form.on("Production Plan Sales Order", { + sales_order(frm, cdt, cdn) { + const { sales_order } = locals[cdt][cdn]; + if (!sales_order) { + return; + } + frappe.call({ + method: "erpnext.manufacturing.doctype.production_plan.production_plan.get_so_details", + args: { sales_order }, + callback(r) { + const {transaction_date, customer, grand_total} = r.message; + frappe.model.set_value(cdt, cdn, 'sales_order_date', transaction_date); + frappe.model.set_value(cdt, cdn, 'customer', customer); + frappe.model.set_value(cdt, cdn, 'grand_total', grand_total); + } + }); + } +}); + cur_frm.fields_dict['sales_orders'].grid.get_field("sales_order").get_query = function() { return{ filters: [ diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 91e57489644..73db29030ef 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -800,6 +800,12 @@ def get_bin_details(row, company, for_warehouse=None, all_warehouse=False): group by item_code, warehouse """.format(conditions=conditions), { "item_code": row['item_code'] }, as_dict=1) +@frappe.whitelist() +def get_so_details(sales_order): + return frappe.db.get_value("Sales Order", sales_order, + ['transaction_date', 'customer', 'grand_total'], as_dict=1 + ) + def get_warehouse_list(warehouses): warehouse_list = [] From 295020451f54c83e711d598a67f72ecbc94fcae2 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Wed, 8 Sep 2021 19:28:30 +0530 Subject: [PATCH 019/116] fix: added delivery date filters to get sales orders in production plan (#27367) --- .../production_plan/production_plan.json | 16 ++++++- .../production_plan/production_plan.py | 45 +++++++++---------- 2 files changed, 36 insertions(+), 25 deletions(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.json b/erpnext/manufacturing/doctype/production_plan/production_plan.json index b5ed28802c8..56cf2b4f08a 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.json +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.json @@ -16,10 +16,12 @@ "customer", "warehouse", "project", + "sales_order_status", "column_break2", "from_date", "to_date", - "sales_order_status", + "from_delivery_date", + "to_delivery_date", "sales_orders_detail", "get_sales_orders", "sales_orders", @@ -358,13 +360,23 @@ "fieldname": "get_sub_assembly_items", "fieldtype": "Button", "label": "Get Sub Assembly Items" + }, + { + "fieldname": "from_delivery_date", + "fieldtype": "Date", + "label": "From Delivery Date" + }, + { + "fieldname": "to_delivery_date", + "fieldtype": "Date", + "label": "To Delivery Date" } ], "icon": "fa fa-calendar", "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2021-08-23 17:26:03.799876", + "modified": "2021-09-06 18:35:59.642232", "modified_by": "Administrator", "module": "Manufacturing", "name": "Production Plan", diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 73db29030ef..a28fc7abf0e 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -735,43 +735,42 @@ def get_material_request_items(row, sales_order, company, def get_sales_orders(self): so_filter = item_filter = "" bom_item = "bom.item = so_item.item_code" - if self.from_date: - so_filter += " and so.transaction_date >= %(from_date)s" - if self.to_date: - so_filter += " and so.transaction_date <= %(to_date)s" - if self.customer: - so_filter += " and so.customer = %(customer)s" - if self.project: - so_filter += " and so.project = %(project)s" - if self.sales_order_status: - so_filter += "and so.status = %(sales_order_status)s" + + date_field_mapper = { + 'from_date': ('>=', 'so.transaction_date'), + 'to_date': ('<=', 'so.transaction_date'), + 'from_delivery_date': ('>=', 'so_item.delivery_date'), + 'to_delivery_date': ('<=', 'so_item.delivery_date') + } + + for field, value in date_field_mapper.items(): + if self.get(field): + so_filter += f" and {value[1]} {value[0]} %({field})s" + + for field in ['customer', 'project', 'sales_order_status']: + if self.get(field): + so_field = 'status' if field == 'sales_order_status' else field + so_filter += f" and so.{so_field} = %({field})s" if self.item_code and frappe.db.exists('Item', self.item_code): bom_item = self.get_bom_item() or bom_item - item_filter += " and so_item.item_code = %(item)s" + item_filter += " and so_item.item_code = %(item_code)s" - open_so = frappe.db.sql(""" + open_so = frappe.db.sql(f""" select distinct so.name, so.transaction_date, so.customer, so.base_grand_total from `tabSales Order` so, `tabSales Order Item` so_item where so_item.parent = so.name and so.docstatus = 1 and so.status not in ("Stopped", "Closed") and so.company = %(company)s - and so_item.qty > so_item.work_order_qty {0} {1} - and (exists (select name from `tabBOM` bom where {2} + and so_item.qty > so_item.work_order_qty {so_filter} {item_filter} + and (exists (select name from `tabBOM` bom where {bom_item} and bom.is_active = 1) or exists (select name from `tabPacked Item` pi where pi.parent = so.name and pi.parent_item = so_item.item_code and exists (select name from `tabBOM` bom where bom.item=pi.item_code and bom.is_active = 1))) - """.format(so_filter, item_filter, bom_item), { - "from_date": self.from_date, - "to_date": self.to_date, - "customer": self.customer, - "project": self.project, - "item": self.item_code, - "company": self.company, - "sales_order_status": self.sales_order_status - }, as_dict=1) + """, self.as_dict(), as_dict=1) + return open_so @frappe.whitelist() From c33bbd4f399b95ab23cc1a621086a366d7be7435 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 9 Sep 2021 11:13:29 +0530 Subject: [PATCH 020/116] fix: Test Case --- .../doctype/purchase_invoice/test_purchase_invoice.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 2d6ab7b2935..5cbeb562326 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -1230,12 +1230,14 @@ def check_gl_entries(doc, voucher_no, expected_gle, posting_date): def update_tax_witholding_category(company, account): from erpnext.accounts.utils import get_fiscal_year - fiscal_year = get_fiscal_year(fiscal_year='_Test Fiscal Year 2021') + fiscal_year = get_fiscal_year(fiscal_year='2021') if not frappe.db.get_value('Tax Withholding Rate', {'parent': 'TDS - 194 - Dividends - Individual', 'from_date': ('>=', fiscal_year[1]), 'to_date': ('<=', fiscal_year[2])}): tds_category = frappe.get_doc('Tax Withholding Category', 'TDS - 194 - Dividends - Individual') + tds_category.set('rates', []) + tds_category.append('rates', { 'from_date': fiscal_year[1], 'to_date': fiscal_year[2], From e7e2ce127116a9b0152d497ee9d11fbf9fff19f9 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 9 Sep 2021 11:36:57 +0530 Subject: [PATCH 021/116] fix: Linting Issues --- .../tax_withholding_category/tax_withholding_category.py | 6 ++---- .../v13_0/update_dates_in_tax_withholding_category.py | 2 ++ 2 files changed, 4 insertions(+), 4 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 33b7e475e51..fa4ea218e90 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -9,8 +9,6 @@ from frappe import _ from frappe.model.document import Document from frappe.utils import cint, getdate -from erpnext.accounts.utils import get_fiscal_year - class TaxWithholdingCategory(Document): def validate(self): @@ -163,9 +161,9 @@ def get_tax_row_for_tds(tax_details, tax_amount): } def get_lower_deduction_certificate(tax_details, pan_no): - ldc_name = frappe.db.get_value('Lower Deduction Certificate', + ldc_name = frappe.db.get_value('Lower Deduction Certificate', { - 'pan_no': pan_no, + 'pan_no': pan_no, 'valid_from': ('>=', tax_details.from_date), 'valid_upto': ('<=', tax_details.to_date) }, 'name') diff --git a/erpnext/patches/v13_0/update_dates_in_tax_withholding_category.py b/erpnext/patches/v13_0/update_dates_in_tax_withholding_category.py index 33c49428533..2af7f954128 100644 --- a/erpnext/patches/v13_0/update_dates_in_tax_withholding_category.py +++ b/erpnext/patches/v13_0/update_dates_in_tax_withholding_category.py @@ -2,8 +2,10 @@ # License: GNU General Public License v3. See license.txt import frappe + from erpnext.accounts.utils import get_fiscal_year + def execute(): frappe.reload_doc('accounts', 'doctype', 'Tax Withholding Rate') From 3576668638c4f571e66e12b762b519abd602dde7 Mon Sep 17 00:00:00 2001 From: Anuja Pawar <60467153+Anuja-pawar@users.noreply.github.com> Date: Thu, 9 Sep 2021 11:57:29 +0530 Subject: [PATCH 022/116] fix: added Show Remarks checkbox in AR & AP reports (#27374) --- .../accounts_payable/accounts_payable.js | 33 +++++++----- .../accounts_receivable.js | 51 ++++++++++--------- .../accounts_receivable.py | 11 +++- 3 files changed, 56 insertions(+), 39 deletions(-) diff --git a/erpnext/accounts/report/accounts_payable/accounts_payable.js b/erpnext/accounts/report/accounts_payable/accounts_payable.js index b6c6689be0b..81c60bb337d 100644 --- a/erpnext/accounts/report/accounts_payable/accounts_payable.js +++ b/erpnext/accounts/report/accounts_payable/accounts_payable.js @@ -4,7 +4,7 @@ frappe.query_reports["Accounts Payable"] = { "filters": [ { - "fieldname":"company", + "fieldname": "company", "label": __("Company"), "fieldtype": "Link", "options": "Company", @@ -12,19 +12,19 @@ frappe.query_reports["Accounts Payable"] = { "default": frappe.defaults.get_user_default("Company") }, { - "fieldname":"report_date", + "fieldname": "report_date", "label": __("Posting Date"), "fieldtype": "Date", "default": frappe.datetime.get_today() }, { - "fieldname":"finance_book", + "fieldname": "finance_book", "label": __("Finance Book"), "fieldtype": "Link", "options": "Finance Book" }, { - "fieldname":"cost_center", + "fieldname": "cost_center", "label": __("Cost Center"), "fieldtype": "Link", "options": "Cost Center", @@ -38,7 +38,7 @@ frappe.query_reports["Accounts Payable"] = { } }, { - "fieldname":"supplier", + "fieldname": "supplier", "label": __("Supplier"), "fieldtype": "Link", "options": "Supplier", @@ -54,48 +54,48 @@ frappe.query_reports["Accounts Payable"] = { } }, { - "fieldname":"ageing_based_on", + "fieldname": "ageing_based_on", "label": __("Ageing Based On"), "fieldtype": "Select", "options": 'Posting Date\nDue Date\nSupplier Invoice Date', "default": "Due Date" }, { - "fieldname":"range1", + "fieldname": "range1", "label": __("Ageing Range 1"), "fieldtype": "Int", "default": "30", "reqd": 1 }, { - "fieldname":"range2", + "fieldname": "range2", "label": __("Ageing Range 2"), "fieldtype": "Int", "default": "60", "reqd": 1 }, { - "fieldname":"range3", + "fieldname": "range3", "label": __("Ageing Range 3"), "fieldtype": "Int", "default": "90", "reqd": 1 }, { - "fieldname":"range4", + "fieldname": "range4", "label": __("Ageing Range 4"), "fieldtype": "Int", "default": "120", "reqd": 1 }, { - "fieldname":"payment_terms_template", + "fieldname": "payment_terms_template", "label": __("Payment Terms Template"), "fieldtype": "Link", "options": "Payment Terms Template" }, { - "fieldname":"supplier_group", + "fieldname": "supplier_group", "label": __("Supplier Group"), "fieldtype": "Link", "options": "Supplier Group" @@ -106,12 +106,17 @@ frappe.query_reports["Accounts Payable"] = { "fieldtype": "Check" }, { - "fieldname":"based_on_payment_terms", + "fieldname": "based_on_payment_terms", "label": __("Based On Payment Terms"), "fieldtype": "Check", }, { - "fieldname":"tax_id", + "fieldname": "show_remarks", + "label": __("Show Remarks"), + "fieldtype": "Check", + }, + { + "fieldname": "tax_id", "label": __("Tax Id"), "fieldtype": "Data", "hidden": 1 diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js index 1a32e2a8e06..570029851e8 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js @@ -4,7 +4,7 @@ frappe.query_reports["Accounts Receivable"] = { "filters": [ { - "fieldname":"company", + "fieldname": "company", "label": __("Company"), "fieldtype": "Link", "options": "Company", @@ -12,19 +12,19 @@ frappe.query_reports["Accounts Receivable"] = { "default": frappe.defaults.get_user_default("Company") }, { - "fieldname":"report_date", + "fieldname": "report_date", "label": __("Posting Date"), "fieldtype": "Date", "default": frappe.datetime.get_today() }, { - "fieldname":"finance_book", + "fieldname": "finance_book", "label": __("Finance Book"), "fieldtype": "Link", "options": "Finance Book" }, { - "fieldname":"cost_center", + "fieldname": "cost_center", "label": __("Cost Center"), "fieldtype": "Link", "options": "Cost Center", @@ -38,7 +38,7 @@ frappe.query_reports["Accounts Receivable"] = { } }, { - "fieldname":"customer", + "fieldname": "customer", "label": __("Customer"), "fieldtype": "Link", "options": "Customer", @@ -67,66 +67,66 @@ frappe.query_reports["Accounts Receivable"] = { } }, { - "fieldname":"ageing_based_on", + "fieldname": "ageing_based_on", "label": __("Ageing Based On"), "fieldtype": "Select", "options": 'Posting Date\nDue Date', "default": "Due Date" }, { - "fieldname":"range1", + "fieldname": "range1", "label": __("Ageing Range 1"), "fieldtype": "Int", "default": "30", "reqd": 1 }, { - "fieldname":"range2", + "fieldname": "range2", "label": __("Ageing Range 2"), "fieldtype": "Int", "default": "60", "reqd": 1 }, { - "fieldname":"range3", + "fieldname": "range3", "label": __("Ageing Range 3"), "fieldtype": "Int", "default": "90", "reqd": 1 }, { - "fieldname":"range4", + "fieldname": "range4", "label": __("Ageing Range 4"), "fieldtype": "Int", "default": "120", "reqd": 1 }, { - "fieldname":"customer_group", + "fieldname": "customer_group", "label": __("Customer Group"), "fieldtype": "Link", "options": "Customer Group" }, { - "fieldname":"payment_terms_template", + "fieldname": "payment_terms_template", "label": __("Payment Terms Template"), "fieldtype": "Link", "options": "Payment Terms Template" }, { - "fieldname":"sales_partner", + "fieldname": "sales_partner", "label": __("Sales Partner"), "fieldtype": "Link", "options": "Sales Partner" }, { - "fieldname":"sales_person", + "fieldname": "sales_person", "label": __("Sales Person"), "fieldtype": "Link", "options": "Sales Person" }, { - "fieldname":"territory", + "fieldname": "territory", "label": __("Territory"), "fieldtype": "Link", "options": "Territory" @@ -137,45 +137,50 @@ frappe.query_reports["Accounts Receivable"] = { "fieldtype": "Check" }, { - "fieldname":"based_on_payment_terms", + "fieldname": "based_on_payment_terms", "label": __("Based On Payment Terms"), "fieldtype": "Check", }, { - "fieldname":"show_future_payments", + "fieldname": "show_future_payments", "label": __("Show Future Payments"), "fieldtype": "Check", }, { - "fieldname":"show_delivery_notes", + "fieldname": "show_delivery_notes", "label": __("Show Linked Delivery Notes"), "fieldtype": "Check", }, { - "fieldname":"show_sales_person", + "fieldname": "show_sales_person", "label": __("Show Sales Person"), "fieldtype": "Check", }, { - "fieldname":"tax_id", + "fieldname": "show_remarks", + "label": __("Show Remarks"), + "fieldtype": "Check", + }, + { + "fieldname": "tax_id", "label": __("Tax Id"), "fieldtype": "Data", "hidden": 1 }, { - "fieldname":"customer_name", + "fieldname": "customer_name", "label": __("Customer Name"), "fieldtype": "Data", "hidden": 1 }, { - "fieldname":"payment_terms", + "fieldname": "payment_terms", "label": __("Payment Tems"), "fieldtype": "Data", "hidden": 1 }, { - "fieldname":"credit_limit", + "fieldname": "credit_limit", "label": __("Credit Limit"), "fieldtype": "Currency", "hidden": 1 diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index e91fdf27cdd..7f8eadea16d 100755 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -106,6 +106,7 @@ class ReceivablePayableReport(object): party = gle.party, posting_date = gle.posting_date, account_currency = gle.account_currency, + remarks = gle.remarks if self.filters.get("show_remarks") else None, invoiced = 0.0, paid = 0.0, credit_note = 0.0, @@ -583,10 +584,12 @@ class ReceivablePayableReport(object): else: select_fields = "debit, credit" + remarks = ", remarks" if self.filters.get("show_remarks") else "" + self.gl_entries = frappe.db.sql(""" select name, posting_date, account, party_type, party, voucher_type, voucher_no, cost_center, - against_voucher_type, against_voucher, account_currency, {0} + against_voucher_type, against_voucher, account_currency, {0} {remarks} from `tabGL Entry` where @@ -595,7 +598,7 @@ class ReceivablePayableReport(object): and party_type=%s and (party is not null and party != '') {1} {2} {3}""" - .format(select_fields, date_condition, conditions, order_by), values, as_dict=True) + .format(select_fields, date_condition, conditions, order_by, remarks=remarks), values, as_dict=True) def get_sales_invoices_or_customers_based_on_sales_person(self): if self.filters.get("sales_person"): @@ -754,6 +757,10 @@ class ReceivablePayableReport(object): self.add_column(label=_('Voucher Type'), fieldname='voucher_type', fieldtype='Data') self.add_column(label=_('Voucher No'), fieldname='voucher_no', fieldtype='Dynamic Link', options='voucher_type', width=180) + + if self.filters.show_remarks: + self.add_column(label=_('Remarks'), fieldname='remarks', fieldtype='Text', width=200), + self.add_column(label='Due Date', fieldtype='Date') if self.party_type == "Supplier": From 9670490a1d4ab282fb4293f5dc19a380e11b9f0a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 Sep 2021 12:21:47 +0530 Subject: [PATCH 023/116] chore(deps): bump axios from 0.21.1 to 0.21.4 (#27402) Bumps [axios](https://github.com/axios/axios) from 0.21.1 to 0.21.4. - [Release notes](https://github.com/axios/axios/releases) - [Changelog](https://github.com/axios/axios/blob/master/CHANGELOG.md) - [Commits](https://github.com/axios/axios/compare/v0.21.1...v0.21.4) --- updated-dependencies: - dependency-name: axios dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> [skip ci] --- yarn.lock | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/yarn.lock b/yarn.lock index 0a49c52f0e8..75b281509d7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -677,11 +677,11 @@ async@^3.2.0: 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== + version "0.21.4" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575" + integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg== dependencies: - follow-redirects "^1.10.0" + follow-redirects "^1.14.0" balanced-match@^1.0.0: version "1.0.0" @@ -1269,10 +1269,10 @@ fill-range@^7.0.1: 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== +follow-redirects@^1.14.0: + version "1.14.3" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.3.tgz#6ada78118d8d24caee595595accdc0ac6abd022e" + integrity sha512-3MkHxknWMUtb23apkgz/83fDoe+y+qr0TdgacGIA7bew+QLBo3vdgEN2xEsuXNivpFy4CyDhBBZnNZOtalmenw== fs-constants@^1.0.0: version "1.0.0" From 678335f8ac290c9e519d8483bd70baa46285cb8a Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 9 Sep 2021 12:24:23 +0530 Subject: [PATCH 024/116] fix: job card overlap unknown column `jc.employee` (#27403) * fix: incorrect query for job card overlap * test: employee overlap in job cards * test: simplify/refactor job card tests --- .../doctype/job_card/job_card.py | 2 +- .../doctype/job_card/test_job_card.py | 114 ++++++++++-------- 2 files changed, 68 insertions(+), 48 deletions(-) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index 3bf9de2708c..ceae63cb940 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -91,7 +91,7 @@ class JobCard(Document): if args.get("employee"): # override capacity for employee production_capacity = 1 - validate_overlap_for = " and jc.employee = %(employee)s " + validate_overlap_for = " and jctl.employee = %(employee)s " extra_cond = '' if check_next_available_slot: diff --git a/erpnext/manufacturing/doctype/job_card/test_job_card.py b/erpnext/manufacturing/doctype/job_card/test_job_card.py index db0e08fcd0a..80295bba635 100644 --- a/erpnext/manufacturing/doctype/job_card/test_job_card.py +++ b/erpnext/manufacturing/doctype/job_card/test_job_card.py @@ -8,71 +8,91 @@ import unittest import frappe from frappe.utils import random_string -from erpnext.manufacturing.doctype.job_card.job_card import OperationMismatchError +from erpnext.manufacturing.doctype.job_card.job_card import OperationMismatchError, OverlapError from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record from erpnext.manufacturing.doctype.workstation.test_workstation import make_workstation class TestJobCard(unittest.TestCase): + + def setUp(self): + self.work_order = make_wo_order_test_record(item="_Test FG Item 2", qty=2) + + def tearDown(self): + frappe.db.rollback() + def test_job_card(self): - data = frappe.get_cached_value('BOM', - {'docstatus': 1, 'with_operations': 1, 'company': '_Test Company'}, ['name', 'item']) - if data: - bom, bom_item = data + job_cards = frappe.get_all('Job Card', + filters = {'work_order': self.work_order.name}, fields = ["operation_id", "name"]) - work_order = make_wo_order_test_record(item=bom_item, qty=1, bom_no=bom) + if job_cards: + job_card = job_cards[0] + frappe.db.set_value("Job Card", job_card.name, "operation_row_number", job_card.operation_id) - job_cards = frappe.get_all('Job Card', - filters = {'work_order': work_order.name}, fields = ["operation_id", "name"]) + doc = frappe.get_doc("Job Card", job_card.name) + doc.operation_id = "Test Data" + self.assertRaises(OperationMismatchError, doc.save) - if job_cards: - job_card = job_cards[0] - frappe.db.set_value("Job Card", job_card.name, "operation_row_number", job_card.operation_id) - - doc = frappe.get_doc("Job Card", job_card.name) - doc.operation_id = "Test Data" - self.assertRaises(OperationMismatchError, doc.save) - - for d in job_cards: - frappe.delete_doc("Job Card", d.name) + for d in job_cards: + frappe.delete_doc("Job Card", d.name) def test_job_card_with_different_work_station(self): - data = frappe.get_cached_value('BOM', - {'docstatus': 1, 'with_operations': 1, 'company': '_Test Company'}, ['name', 'item']) + job_cards = frappe.get_all('Job Card', + filters = {'work_order': self.work_order.name}, + fields = ["operation_id", "workstation", "name", "for_quantity"]) - if data: - bom, bom_item = data + job_card = job_cards[0] - work_order = make_wo_order_test_record(item=bom_item, qty=1, bom_no=bom) + if job_card: + workstation = frappe.db.get_value("Workstation", + {"name": ("not in", [job_card.workstation])}, "name") - job_cards = frappe.get_all('Job Card', - filters = {'work_order': work_order.name}, - fields = ["operation_id", "workstation", "name", "for_quantity"]) + if not workstation or job_card.workstation == workstation: + workstation = make_workstation(workstation_name=random_string(5)).name - job_card = job_cards[0] + doc = frappe.get_doc("Job Card", job_card.name) + doc.workstation = workstation + doc.append("time_logs", { + "from_time": "2009-01-01 12:06:25", + "to_time": "2009-01-01 12:37:25", + "time_in_mins": "31.00002", + "completed_qty": job_card.for_quantity + }) + doc.submit() - if job_card: - workstation = frappe.db.get_value("Workstation", - {"name": ("not in", [job_card.workstation])}, "name") + completed_qty = frappe.db.get_value("Work Order Operation", job_card.operation_id, "completed_qty") + self.assertEqual(completed_qty, job_card.for_quantity) - if not workstation or job_card.workstation == workstation: - workstation = make_workstation(workstation_name=random_string(5)).name - - doc = frappe.get_doc("Job Card", job_card.name) - doc.workstation = workstation - doc.append("time_logs", { - "from_time": "2009-01-01 12:06:25", - "to_time": "2009-01-01 12:37:25", - "time_in_mins": "31.00002", - "completed_qty": job_card.for_quantity - }) - doc.submit() - - completed_qty = frappe.db.get_value("Work Order Operation", job_card.operation_id, "completed_qty") - self.assertEqual(completed_qty, job_card.for_quantity) - - doc.cancel() + doc.cancel() for d in job_cards: frappe.delete_doc("Job Card", d.name) + + def test_job_card_overlap(self): + wo2 = make_wo_order_test_record(item="_Test FG Item 2", qty=2) + + jc1_name = frappe.db.get_value("Job Card", {'work_order': self.work_order.name}) + jc2_name = frappe.db.get_value("Job Card", {'work_order': wo2.name}) + + jc1 = frappe.get_doc("Job Card", jc1_name) + jc2 = frappe.get_doc("Job Card", jc2_name) + + employee = "_T-Employee-00001" # from test records + + jc1.append("time_logs", { + "from_time": "2021-01-01 00:00:00", + "to_time": "2021-01-01 08:00:00", + "completed_qty": 1, + "employee": employee, + }) + jc1.save() + + # add a new entry in same time slice + jc2.append("time_logs", { + "from_time": "2021-01-01 00:01:00", + "to_time": "2021-01-01 06:00:00", + "completed_qty": 1, + "employee": employee, + }) + self.assertRaises(OverlapError, jc2.save) From 518f827d8721a67cf0ea00455435b03e35ca1b30 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 9 Sep 2021 12:52:45 +0530 Subject: [PATCH 025/116] chore: remove snyk from devdependencies --- package.json | 15 +- yarn.lock | 3549 -------------------------------------------------- 2 files changed, 4 insertions(+), 3560 deletions(-) diff --git a/package.json b/package.json index 5bc1e56a210..6c11e9dddc7 100644 --- a/package.json +++ b/package.json @@ -11,16 +11,9 @@ "bugs": { "url": "https://github.com/frappe/erpnext/issues" }, - "devDependencies": { - "snyk": "^1.518.0" - }, + "devDependencies": {}, "dependencies": { - "onscan.js": "^1.5.2", - "html2canvas": "^1.1.4" - }, - "scripts": { - "snyk-protect": "snyk protect", - "prepare": "yarn run snyk-protect" - }, - "snyk": true + "html2canvas": "^1.1.4", + "onscan.js": "^1.5.2" + } } diff --git a/yarn.lock b/yarn.lock index 75b281509d7..8e5d1bd1c17 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,1006 +2,11 @@ # yarn lockfile v1 -"@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: - grapheme-splitter "^1.0.4" - -"@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: - "@nodelib/fs.stat" "2.0.4" - run-parallel "^1.1.9" - -"@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: - "@nodelib/fs.scandir" "2.1.4" - fastq "^1.6.0" - -"@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/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: - 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/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: - is-glob "^4.0.1" - -"@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: - 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/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/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/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/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@^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/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/node" "*" - -"@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/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: - "@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/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== - -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: - clean-stack "^2.0.0" - indent-string "^4.0.0" - -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: - string-width "^3.0.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-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" - integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== - 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" - integrity sha1-+cjBN1fMHde8N5rHeyxipcKGjEA= - -argparse@^1.0.7: - version "1.0.10" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" - integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== - 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= - -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@^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.4" - resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575" - integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg== - dependencies: - follow-redirects "^1.14.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-arraybuffer@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.2.0.tgz#4b944fac0191aa5907afe2d8c999ccc57ce80f45" integrity sha512-7emyCsu1/xiBXgQZrscw/8KPRT44I4Yq9Pe6EGs3aPRTsWuggML1/1DTuZUuIaJPIm1FTDUVXl4x/yW8s0kQDQ== -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" - integrity sha512-jrCW5ZhfQ/Vt07WX1Ngs+yn9BDqPL/gw28S7s9H6QK/gupnizNzJAss5akW20ISgOrbLTlXOOCTJeNUQqruAWQ== - dependencies: - readable-stream "^3.0.1" - -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: - 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" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" - integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== - dependencies: - 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== - -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: - base64-js "^1.3.1" - ieee754 "^1.1.13" - -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== - dependencies: - ansi-styles "^3.2.1" - 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== - -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= - -chownr@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" - integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== - -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 "^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-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== - -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: - mimic-response "^1.0.0" - -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" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" - integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== - 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= - -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= - -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: - path-key "^3.1.0" - shebang-command "^2.0.0" - which "^2.0.1" - -cross-spawn@^6.0.0: - version "6.0.5" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" - integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== - dependencies: - nice-try "^1.0.4" - path-key "^2.0.1" - semver "^5.5.0" - shebang-command "^1.2.0" - which "^1.2.9" - -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== - css-line-break@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/css-line-break/-/css-line-break-1.1.1.tgz#d5e9bdd297840099eb0503c7310fd34927a026ef" @@ -1009,481 +14,6 @@ css-line-break@1.1.1: dependencies: base64-arraybuffer "^0.2.0" -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" - -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== - -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: - clone "^1.0.2" - -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== - -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: - 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" - resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.2.0.tgz#c34ecc29556dc45f1f4c22697b6f4904e0cc4fcb" - integrity sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A== - dependencies: - is-obj "^2.0.0" - -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: - 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.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" - integrity sha512-gYCwo7kh5S3IDyZPLZf6hSS0MnZT8QmJFqYvbqlDZSbwdZlY6QZWxJ4i/6UhITOJ4XzyI647Bm2MXKCLqnJ4nQ== - -emoji-regex@^7.0.1: - version "7.0.3" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" - integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== - -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" - -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: - 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= - -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== - -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 "^2.1.0" - -execa@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8" - integrity sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA== - dependencies: - cross-spawn "^6.0.0" - get-stream "^4.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" - -external-editor@^3.0.3: - version "3.1.0" - resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" - integrity sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew== - dependencies: - chardet "^0.7.0" - iconv-lite "^0.4.24" - tmp "^0.0.33" - -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" - -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" - -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.14.0: - version "1.14.3" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.3.tgz#6ada78118d8d24caee595595accdc0ac6abd022e" - integrity sha512-3MkHxknWMUtb23apkgz/83fDoe+y+qr0TdgacGIA7bew+QLBo3vdgEN2xEsuXNivpFy4CyDhBBZnNZOtalmenw== - -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= - -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: - function-bind "^1.1.1" - has "^1.0.3" - has-symbols "^1.0.1" - -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-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: - pump "^3.0.0" - -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-glob "^4.0.1" - -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== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - -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: - 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" - -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: - 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 "^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== - -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: - 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= - -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== - -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== - -has-yarn@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/has-yarn/-/has-yarn-2.1.0.tgz#137e11354a7b5bf11aa5cb649cf0c6f3ff2b2e77" - integrity sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw== - -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: - function-bind "^1.1.1" - -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" - html2canvas@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/html2canvas/-/html2canvas-1.1.4.tgz#53ae91cd26e9e9e623c56533cccb2e3f57c8124c" @@ -1491,2086 +21,7 @@ html2canvas@^1.1.4: dependencies: css-line-break "1.1.1" -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" - integrity sha1-nbHb0Pr43m++D13V5Wu2BigN5ps= - -import-lazy@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43" - integrity sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM= - -imurmurhash@^0.1.4: - version "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" - integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= - dependencies: - once "^1.3.0" - wrappy "1" - -inherits@2, inherits@~2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" - integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= - -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.7, ini@~1.3.0: - version "1.3.7" - resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.7.tgz#a09363e1911972ea16d7a8851005d84cf09a9a84" - integrity sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ== - -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== - -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/is-deflate/-/is-deflate-1.0.0.tgz#c862901c3c161fb09dac7cdc7e784f80e98f2f14" - integrity sha1-yGKQHDwWH7CdrHzcfnhPgOmPLxQ= - -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-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-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-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-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@^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-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-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= - -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" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= - -isexe@^2.0.0: - version "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" - integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw== - dependencies: - argparse "^1.0.7" - esprima "^4.0.0" - -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" - -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/keyv/-/keyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9" - integrity sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA== - dependencies: - json-buffer "3.0.0" - -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: - json-buffer "3.0.1" - -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: - package-json "^6.3.0" - -lie@~3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/lie/-/lie-3.3.0.tgz#dcf82dee545f46074daf200c7c1c5a08e0f40f6a" - integrity sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ== - dependencies: - immediate "~3.0.5" - -lodash.assign@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7" - integrity sha1-DZnzzNem0mHRm9rrkkUAXShYCOc= - -lodash.assignin@^4.2.0: - version "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" - integrity sha1-GVhwRQ9aExkkeN9Lw9I9LeoZB7Y= - -lodash.clonedeep@^4.3.0, lodash.clonedeep@^4.5.0: - version "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.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.21" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" - integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== - -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== - -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== - dependencies: - pseudomap "^1.0.2" - yallist "^2.1.2" - -lru-cache@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" - integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== - 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@^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: - semver "^6.0.0" - -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" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" - integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== - dependencies: - brace-expansion "^1.1.7" - -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== - -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.8: - version "0.0.8" - resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" - integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== - -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" - -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== - -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" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" - integrity sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8= - dependencies: - path-key "^2.0.0" - -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-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" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= - dependencies: - wrappy "1" - -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: - 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== - -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-docker "^2.0.0" - is-wsl "^2.1.1" - -ora@5.3.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/ora/-/ora-5.3.0.tgz#fb832899d3a1372fe71c8b2c534bbfe74961bb6f" - integrity sha512-zAKMgGXUim0Jyd6CXK9lraBnD3H5yPGBPPOkC23a2BG6hsm4Zu6OQSjQuEtV0BHDf4aKHcUFvJiGRrFuW3MG8g== - dependencies: - 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" - resolved "https://registry.yarnpkg.com/os-name/-/os-name-3.1.0.tgz#dec19d966296e1cd62d701a5a66ee1ddeae70801" - integrity sha512-h8L+8aNjNcMpo/mAIBPn5PXCM16iyPGjHNWo6U1YO8sJTMHtEtyczI6QJnLoplswm6goopQkqc7OAnjhWcugVg== - dependencies: - macos-release "^2.2.0" - windows-release "^3.1.0" - -os-tmpdir@~1.0.2: - version "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== - -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: - aggregate-error "^3.0.0" - -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@^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 "^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-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: - 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-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= - -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== - -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== - -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== - -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" - -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: - is-callable "^1.1.5" - promise-deferred "^2.0.3" - -proxy-from-env@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.0.0.tgz#33c50398f70ea7eb96d21f7b817630a55791c7ee" - integrity sha1-M8UDmPcOp+uW0h97gXYwpVeRx+4= - -pseudomap@^1.0.2: - version "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" - integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== - dependencies: - end-of-stream "^1.1.0" - once "^1.3.1" - -pumpify@^1.3.3: - version "1.5.1" - resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.5.1.tgz#36513be246ab27570b1a374a5ce278bfd74370ce" - integrity sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ== - dependencies: - duplexify "^3.6.0" - inherits "^2.0.3" - pump "^2.0.0" - -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== - dependencies: - deep-extend "^0.6.0" - ini "~1.3.0" - minimist "^1.2.0" - strip-json-comments "~2.0.1" - -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== - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.3" - isarray "~1.0.0" - process-nextick-args "~2.0.0" - safe-buffer "~5.1.1" - string_decoder "~1.1.1" - util-deprecate "~1.0.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== - dependencies: - inherits "^2.0.3" - string_decoder "^1.1.1" - util-deprecate "^1.0.1" - -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.2.8" - -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.2.8" - -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/responselike/-/responselike-2.0.0.tgz#26391bcc3174f750f9a79eacc40a12a5c42d7723" - integrity sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw== - dependencies: - 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" - integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== - dependencies: - glob "^7.1.3" - -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: - glob "^7.1.3" - -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.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== - -safe-buffer@~5.2.0: - version "5.2.1" - 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.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== - -sax@>=0.6.0, sax@^1.2.4: - version "1.2.4" - resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" - integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== - -semver-compare@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc" - integrity sha1-De4hahyUGrN+nvsXiPavxf9VN/w= - -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 "^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@^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= - -shebang-command@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" - integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= - 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= - -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@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" - lodash.merge "^4.6.2" - minimist "^1.2.5" - -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" - hosted-git-info "^3.0.7" - tslib "^2.0.0" - -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/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" - 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-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: - toml "^3.0.0" - tslib "^1.10.0" - -snyk-go-plugin@1.17.0: - version "1.17.0" - resolved "https://registry.yarnpkg.com/snyk-go-plugin/-/snyk-go-plugin-1.17.0.tgz#56d0c92d7def29ba4c3c2030c5830093e3b0dd26" - integrity sha512-1jAYPRgMapO2BYL+HWsUq5gsAiDGmI0Pn7omc0lk24tcUOMhUB+1hb0u9WBMNzHvXBjevBkjOctjpnt2hMKN6Q== - dependencies: - "@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-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: - "@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.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.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.9.1" - "@snyk/composer-lockfile-parser" "^1.4.1" - tslib "1.11.1" - -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: - "@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 "^3.0.0" - snyk-resolve "^1.1.0" - snyk-try-require "^2.0.0" - -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.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: - ansicolors "^0.3.2" - debug "^4.1.1" - lodash.assign "^4.2.0" - lodash.assignin "^4.2.0" - lodash.clone "^4.5.0" - lodash.flatten "^4.4.0" - lodash.get "^4.4.2" - lodash.set "^4.3.2" - lru-cache "^4.0.0" - semver "^5.5.1" - 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.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== - dependencies: - debug "^3.1.0" - then-fs "^2.0.0" - -snyk-sbt-plugin@2.11.0: - version "2.11.0" - resolved "https://registry.yarnpkg.com/snyk-sbt-plugin/-/snyk-sbt-plugin-2.11.0.tgz#f5469dcf5589e34575fc901e2064475582cc3e48" - integrity sha512-wUqHLAa3MzV6sVO+05MnV+lwc+T6o87FZZaY+43tQPytBI2Wq23O3j4POREM4fa2iFfiQJoEYD6c7xmhiEUsSA== - dependencies: - debug "^4.1.1" - semver "^6.1.2" - tmp "^0.1.0" - tree-kill "^1.2.2" - tslib "^1.10.0" - -snyk-tree@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/snyk-tree/-/snyk-tree-1.0.0.tgz#0fb73176dbf32e782f19100294160448f9111cc8" - integrity sha1-D7cxdtvzLngvGRAClBYESPkRHMg= - dependencies: - archy "^1.0.0" - -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= - dependencies: - debug "^3.1.0" - lodash.clonedeep "^4.3.0" - lru-cache "^4.0.0" - then-fs "^2.0.0" - -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: - 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/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" - configstore "^5.0.1" - debug "^4.1.1" - diff "^4.0.1" - 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" - promise-queue "^2.2.5" - proxy-from-env "^1.0.0" - rimraf "^2.6.3" - semver "^6.0.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" - update-notifier "^4.1.0" - uuid "^3.3.2" - wrap-ansi "^5.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" - integrity sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ== - dependencies: - buffer-from "^1.0.0" - source-map "^0.6.0" - -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= - -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: - asn1 "~0.2.0" - bcrypt-pbkdf "^1.0.2" - streamsearch "~0.1.2" - -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: - 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" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" - integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== - dependencies: - emoji-regex "^7.0.1" - 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" - integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== - dependencies: - safe-buffer "~5.2.0" - -string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" - integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== - dependencies: - safe-buffer "~5.1.0" - -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 "^5.0.0" - -strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" - integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== - dependencies: - ansi-regex "^4.1.0" - -strip-eof@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" - integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8= - -strip-json-comments@~2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" - integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= - -supports-color@^5.3.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" - integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== - 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" - integrity sha512-+DAn4Nb4+gz6WZigRzKEZl1QuJVOLtAwwF+WUxy1fJ6X63CaGaUAxJRD2KEn1OMfcbCjySTYpNC6WmfQoIEOdw== - dependencies: - bl "^3.0.0" - end-of-stream "^1.4.1" - fs-constants "^1.0.0" - inherits "^2.0.3" - readable-stream "^3.1.1" - -tar@^6.1.0: - version "6.1.11" - resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.11.tgz#6760a38f003afa1b2ffd0ffe9e9abbd0eab3d621" - integrity sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA== - 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" - integrity sha1-awRGhWqbERTRhW/8vlCczLCXcmU= - dependencies: - temp-dir "^1.0.0" - uuid "^3.0.1" - -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" - resolved "https://registry.yarnpkg.com/then-fs/-/then-fs-2.0.0.tgz#72f792dd9d31705a91ae19ebfcf8b3f968c81da2" - integrity sha1-cveS3Z0xcFqRrhnr/Piz+WjIHaI= - 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= - -tmp@0.0.33, tmp@^0.0.33: - version "0.0.33" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" - integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== - 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" - integrity sha512-J7Z2K08jbGcdA1kkQpJSqLF6T0tdQqpR2pnSUXsIchbPdTI9v3e85cLW0d6WDhwuAleOV71j2xWs8qMPfK7nKw== - dependencies: - rimraf "^2.6.3" - -to-readable-stream@^1.0.0: - version "1.0.0" - 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" - resolved "https://registry.yarnpkg.com/toml/-/toml-3.0.0.tgz#342160f1af1904ec9d204d03a5d61222d762c5ee" - integrity sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w== - -tree-kill@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc" - integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A== - -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== - -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: - is-typedarray "^1.0.0" - -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 "^2.0.0" - -upath@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/upath/-/upath-2.0.1.tgz#50c73dea68d6f6b990f51d279ce6081665d61a8b" - integrity sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w== - -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: - 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" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= - -uuid@^3.0.1, uuid@^3.3.2: - version "3.4.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" - integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== - -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" - resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" - integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== - dependencies: - isexe "^2.0.0" - -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: - isexe "^2.0.0" - -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" - resolved "https://registry.yarnpkg.com/windows-release/-/windows-release-3.2.0.tgz#8122dad5afc303d833422380680a79cdfa91785f" - integrity sha512-QTlz2hKLrdqukrsapKsINzqMgOUpQW268eJ0OaOpJN32h272waxR9fkB9VoWRtK7uKHG5EHJcTXQBD8XZVJkFA== - dependencies: - execa "^1.0.0" - -wrap-ansi@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09" - integrity sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q== - dependencies: - ansi-styles "^3.2.0" - string-width "^3.0.0" - strip-ansi "^5.0.0" - -wrappy@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= - -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: - imurmurhash "^0.1.4" - is-typedarray "^1.0.0" - signal-exit "^3.0.2" - typedarray-to-buffer "^3.1.5" - -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== - -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 "^1.2.4" - -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== - dependencies: - sax ">=0.6.0" - xmlbuilder "~11.0.0" - -xmlbuilder@~11.0.0: - version "11.0.1" - resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3" - integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA== - -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" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" - integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI= - -yallist@^3.0.2: - version "3.1.1" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" - integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== - -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 a40709a673a383004db4b82bf6132bd6e6bc6978 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 9 Sep 2021 13:55:56 +0530 Subject: [PATCH 026/116] chore: remove obsolete file snyk has been removed, this file is not required anymore. --- .snyk | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 .snyk diff --git a/.snyk b/.snyk deleted file mode 100644 index 140f3edd846..00000000000 --- a/.snyk +++ /dev/null @@ -1,8 +0,0 @@ -# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities. -version: v1.14.0 -ignore: {} -# patches apply the minimum changes required to fix a vulnerability -patch: - SNYK-JS-LODASH-450202: - - cypress > getos > async > lodash: - patched: '2020-01-31T01:35:12.802Z' From acdb10377f66a3c980d822a1f74e954fa10544b4 Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Thu, 9 Sep 2021 18:54:00 +0530 Subject: [PATCH 027/116] refactor: .doc missing and empty row on new doc (#27408) * refactor: .doc missing and empty row on new doc * fix: let -> const --- .../doctype/maintenance_visit/maintenance_visit.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js index 6b3f18484ad..7f983541f6d 100644 --- a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js +++ b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js @@ -31,8 +31,8 @@ frappe.ui.form.on('Maintenance Visit', { }, onload: function (frm, cdt, cdn) { let item = locals[cdt][cdn]; - if (frm.maintenance_type == 'Scheduled') { - let schedule_id = item.purposes[0].prevdoc_detail_docname; + if (frm.doc.maintenance_type === "Scheduled") { + const schedule_id = item.purposes[0].prevdoc_detail_docname || frm.doc.maintenance_schedule_detail; frappe.call({ method: "erpnext.maintenance.doctype.maintenance_schedule.maintenance_schedule.update_serial_nos", args: { @@ -43,6 +43,9 @@ frappe.ui.form.on('Maintenance Visit', { } }); } + else { + frm.clear_table("purposes"); + } if (!frm.doc.status) { frm.set_value({ status: 'Draft' }); From 7edac5a5d7acbea0b1a627bde3a20bfbdb44876d Mon Sep 17 00:00:00 2001 From: Anupam Kumar Date: Thu, 9 Sep 2021 19:17:01 +0530 Subject: [PATCH 028/116] fix: removing duplicate currency() from sales_invoice.js (#27410) --- erpnext/accounts/doctype/sales_invoice/sales_invoice.js | 9 --------- 1 file changed, 9 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index 2cb9acfa2a9..34ac343bedb 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -445,15 +445,6 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e this.frm.refresh_field("base_paid_amount"); } - currency() { - this._super(); - $.each(cur_frm.doc.timesheets, function(i, d) { - let row = frappe.get_doc(d.doctype, d.name) - set_timesheet_detail_rate(row.doctype, row.name, cur_frm.doc.currency, row.timesheet_detail) - }); - calculate_total_billing_amount(cur_frm) - } - currency() { var me = this; super.currency(); From 8b4c57ee09166c689dfca7d67a65d5376d27b501 Mon Sep 17 00:00:00 2001 From: Subin Tom <36098155+nemesis189@users.noreply.github.com> Date: Thu, 9 Sep 2021 19:33:34 +0530 Subject: [PATCH 029/116] fix: pos payment mode selection issue (#27409) --- .../selling/page/point_of_sale/pos_payment.js | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/erpnext/selling/page/point_of_sale/pos_payment.js b/erpnext/selling/page/point_of_sale/pos_payment.js index 8e69851213f..7ddbf45fdb8 100644 --- a/erpnext/selling/page/point_of_sale/pos_payment.js +++ b/erpnext/selling/page/point_of_sale/pos_payment.js @@ -297,6 +297,7 @@ erpnext.PointOfSale.Payment = class { this.render_payment_mode_dom(); this.make_invoice_fields_control(); this.update_totals_section(); + this.focus_on_default_mop(); } edit_cart() { @@ -378,17 +379,24 @@ erpnext.PointOfSale.Payment = class { }); this[`${mode}_control`].toggle_label(false); this[`${mode}_control`].set_value(p.amount); + }); + this.render_loyalty_points_payment_mode(); + + this.attach_cash_shortcuts(doc); + } + + focus_on_default_mop() { + const doc = this.events.get_frm().doc; + const payments = doc.payments; + payments.forEach(p => { + const mode = p.mode_of_payment.replace(/ +/g, "_").toLowerCase(); if (p.default) { setTimeout(() => { this.$payment_modes.find(`.${mode}.mode-of-payment-control`).parent().click(); }, 500); } }); - - this.render_loyalty_points_payment_mode(); - - this.attach_cash_shortcuts(doc); } attach_cash_shortcuts(doc) { From a58e309297964a2509b2e372b5440db85502e7d7 Mon Sep 17 00:00:00 2001 From: Anuja Pawar <60467153+Anuja-pawar@users.noreply.github.com> Date: Thu, 9 Sep 2021 19:37:52 +0530 Subject: [PATCH 030/116] fix: fix to fetch customers and billing email in PSOA (#27363) --- .../process_statement_of_accounts.json | 4 ++- .../process_statement_of_accounts.py | 30 ++++++++++++++++--- 2 files changed, 29 insertions(+), 5 deletions(-) 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 295a3b86a9e..a26267ba5e8 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 @@ -219,6 +219,7 @@ }, { "default": "1", + "description": "A customer must have primary contact email.", "fieldname": "primary_mandatory", "fieldtype": "Check", "label": "Send To Primary Contact" @@ -286,10 +287,11 @@ } ], "links": [], - "modified": "2021-05-21 11:14:22.426672", + "modified": "2021-09-06 21:00:45.732505", "modified_by": "Administrator", "module": "Accounts", "name": "Process Statement Of Accounts", + "naming_rule": "Set by user", "owner": "Administrator", "permissions": [ { 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 73f30385120..503fd0d6f80 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 @@ -196,7 +196,10 @@ def fetch_customers(customer_collection, collection_name, primary_mandatory): primary_email = customer.get('email_id') or '' billing_email = get_customer_emails(customer.name, 1, billing_and_primary=False) - if billing_email == '' or (primary_email == '' and int(primary_mandatory)): + if int(primary_mandatory): + if (primary_email == ''): + continue + elif (billing_email == '') and (primary_email == ''): continue customer_list.append({ @@ -208,10 +211,29 @@ def fetch_customers(customer_collection, collection_name, primary_mandatory): @frappe.whitelist() def get_customer_emails(customer_name, primary_mandatory, billing_and_primary=True): + """ Returns first email from Contact Email table as a Billing email + when Is Billing Contact checked + and Primary email- email with Is Primary checked """ + billing_email = frappe.db.sql(""" - SELECT c.email_id FROM `tabContact` AS c JOIN `tabDynamic Link` AS l ON c.name=l.parent - WHERE l.link_doctype='Customer' and l.link_name=%s and c.is_billing_contact=1 - order by c.creation desc""", customer_name) + SELECT + email.email_id + FROM + `tabContact Email` AS email + JOIN + `tabDynamic Link` AS link + ON + email.parent=link.parent + JOIN + `tabContact` AS contact + ON + contact.name=link.parent + WHERE + link.link_doctype='Customer' + and link.link_name=%s + and contact.is_billing_contact=1 + ORDER BY + contact.creation desc""", customer_name) if len(billing_email) == 0 or (billing_email[0][0] is None): if billing_and_primary: From 1fc8fcc2587000749eb2647563aa9cbac839a54a Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Fri, 10 Sep 2021 08:08:00 +0530 Subject: [PATCH 031/116] ci: Upload coverage report to codecov --- .github/workflows/server-tests.yml | 38 ++++++------------------------ 1 file changed, 7 insertions(+), 31 deletions(-) diff --git a/.github/workflows/server-tests.yml b/.github/workflows/server-tests.yml index 71e9c2cd138..4f84b860af8 100644 --- a/.github/workflows/server-tests.yml +++ b/.github/workflows/server-tests.yml @@ -99,34 +99,10 @@ jobs: CI_BUILD_ID: ${{ github.run_id }} ORCHESTRATOR_URL: http://test-orchestrator.frappe.io - - name: Upload Coverage Data - run: | - cp ~/frappe-bench/sites/.coverage ${GITHUB_WORKSPACE} - cd ${GITHUB_WORKSPACE} - pip3 install coverage==5.5 - pip3 install coveralls==3.0.1 - coveralls - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_TOKEN }} - COVERALLS_FLAG_NAME: run-${{ matrix.container }} - COVERALLS_SERVICE_NAME: ${{ github.event_name == 'pull_request' && 'github' || 'github-actions' }} - COVERALLS_PARALLEL: true - - coveralls: - name: Coverage Wrap Up - needs: test - container: python:3-slim - runs-on: ubuntu-latest - steps: - - name: Clone - uses: actions/checkout@v2 - - - name: Coveralls Finished - run: | - cd ${GITHUB_WORKSPACE} - pip3 install coverage==5.5 - pip3 install coveralls==3.0.1 - coveralls --finish - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Upload coverage data + uses: codecov/codecov-action@v2 + with: + name: MariaDB + fail_ci_if_error: true + files: /home/runner/frappe-bench/sites/coverage.xml + verbose: true From 86afece6ad774e2d6a444030830a56c79109f854 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Fri, 10 Sep 2021 08:14:43 +0530 Subject: [PATCH 032/116] ci: Add codecov configuration file --- codecov.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 codecov.yml diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 00000000000..217b52c21f8 --- /dev/null +++ b/codecov.yml @@ -0,0 +1,13 @@ +codecov: + require_ci_to_pass: yes + +coverage: + status: + project: + default: + target: auto + threshold: 0.5% + +comment: + layout: "diff, files" + require_changes: true From 629f5e1fdf6191a47622afe081e20c94f1eb4f58 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Fri, 10 Sep 2021 08:21:01 +0530 Subject: [PATCH 033/116] chore: Add codecov badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c6fc2512445..847904d1dd2 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ [![CI](https://github.com/frappe/erpnext/actions/workflows/server-tests.yml/badge.svg?branch=develop)](https://github.com/frappe/erpnext/actions/workflows/server-tests.yml) [![Open Source Helpers](https://www.codetriage.com/frappe/erpnext/badges/users.svg)](https://www.codetriage.com/frappe/erpnext) -[![Coverage Status](https://coveralls.io/repos/github/frappe/erpnext/badge.svg?branch=develop)](https://coveralls.io/github/frappe/erpnext?branch=develop) +[![codecov](https://codecov.io/gh/frappe/erpnext/branch/develop/graph/badge.svg?token=0TwvyUg3I5)](https://codecov.io/gh/frappe/erpnext) [https://erpnext.com](https://erpnext.com) From 333b83b583f81b9b8555cc32034fd92b697b42f1 Mon Sep 17 00:00:00 2001 From: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> Date: Fri, 10 Sep 2021 09:58:48 +0530 Subject: [PATCH 034/116] ci: Ignore demo folder Coverage for the demo folder is not relevant... can be ignored --- codecov.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/codecov.yml b/codecov.yml index 217b52c21f8..7af239924c7 100644 --- a/codecov.yml +++ b/codecov.yml @@ -11,3 +11,6 @@ coverage: comment: layout: "diff, files" require_changes: true + + ignore: + - "erpnext/demo" From 3da34382b5792902b5dbc4bea8635a7eb92ab7ad Mon Sep 17 00:00:00 2001 From: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> Date: Fri, 10 Sep 2021 10:18:41 +0530 Subject: [PATCH 035/116] ci: Remove extra space MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤦🏻‍♂️ --- codecov.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codecov.yml b/codecov.yml index 7af239924c7..a8da340d087 100644 --- a/codecov.yml +++ b/codecov.yml @@ -12,5 +12,5 @@ comment: layout: "diff, files" require_changes: true - ignore: +ignore: - "erpnext/demo" From 62fc5442801e09dfa8e93b7855a0de3e487ba458 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 10 Sep 2021 12:46:35 +0530 Subject: [PATCH 036/116] test: basic tests for controllers/queries (#27422) --- erpnext/controllers/queries.py | 2 +- erpnext/controllers/tests/test_queries.py | 87 +++++++++++++++++++++++ 2 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 erpnext/controllers/tests/test_queries.py diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index aafaf5b9e08..ccd417be04f 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -307,7 +307,7 @@ def bom(doctype, txt, searchfield, start, page_len, filters): @frappe.validate_and_sanitize_search_inputs def get_project_name(doctype, txt, searchfield, start, page_len, filters): cond = '' - if filters.get('customer'): + if filters and filters.get('customer'): cond = """(`tabProject`.customer = %s or ifnull(`tabProject`.customer,"")="") and""" %(frappe.db.escape(filters.get("customer"))) diff --git a/erpnext/controllers/tests/test_queries.py b/erpnext/controllers/tests/test_queries.py new file mode 100644 index 00000000000..05541d16887 --- /dev/null +++ b/erpnext/controllers/tests/test_queries.py @@ -0,0 +1,87 @@ +import unittest +from functools import partial + +from erpnext.controllers import queries + + +def add_default_params(func, doctype): + return partial( + func, doctype=doctype, txt="", searchfield="name", start=0, page_len=20, filters=None + ) + + +class TestQueries(unittest.TestCase): + + # All tests are based on doctype/test_records.json + + def assert_nested_in(self, item, container): + self.assertIn(item, [vals for tuples in container for vals in tuples]) + + def test_employee_query(self): + query = add_default_params(queries.employee_query, "Employee") + + self.assertGreaterEqual(len(query(txt="_Test Employee")), 3) + self.assertGreaterEqual(len(query(txt="_Test Employee 1")), 1) + + def test_lead_query(self): + query = add_default_params(queries.lead_query, "Lead") + + self.assertGreaterEqual(len(query(txt="_Test Lead")), 4) + self.assertEqual(len(query(txt="_Test Lead 4")), 1) + + def test_customer_query(self): + query = add_default_params(queries.customer_query, "Customer") + + self.assertGreaterEqual(len(query(txt="_Test Customer")), 7) + self.assertGreaterEqual(len(query(txt="_Test Customer USD")), 1) + + def test_supplier_query(self): + query = add_default_params(queries.supplier_query, "Supplier") + + self.assertGreaterEqual(len(query(txt="_Test Supplier")), 7) + self.assertGreaterEqual(len(query(txt="_Test Supplier USD")), 1) + + def test_item_query(self): + query = add_default_params(queries.item_query, "Item") + + self.assertGreaterEqual(len(query(txt="_Test Item")), 7) + self.assertEqual(len(query(txt="_Test Item Home Desktop 100 3")), 1) + + fg_item = "_Test FG Item" + stock_items = query(txt=fg_item, filters={"is_stock_item": 1}) + self.assert_nested_in("_Test FG Item", stock_items) + + bundled_stock_items = query(txt="_test product bundle item 5", filters={"is_stock_item": 1}) + self.assertEqual(len(bundled_stock_items), 0) + + def test_bom_qury(self): + query = add_default_params(queries.bom, "BOM") + + self.assertGreaterEqual(len(query(txt="_Test Item Home Desktop Manufactured")), 1) + + def test_project_query(self): + query = add_default_params(queries.get_project_name, "BOM") + + self.assertGreaterEqual(len(query(txt="_Test Project")), 1) + + def test_account_query(self): + query = add_default_params(queries.get_account_list, "Account") + + debtor_accounts = query(txt="Debtors", filters={"company": "_Test Company"}) + self.assert_nested_in("Debtors - _TC", debtor_accounts) + + def test_income_account_query(self): + query = add_default_params(queries.get_income_account, "Account") + + self.assertGreaterEqual(len(query(filters={"company": "_Test Company"})), 1) + + def test_expense_account_query(self): + query = add_default_params(queries.get_expense_account, "Account") + + self.assertGreaterEqual(len(query(filters={"company": "_Test Company"})), 1) + + def test_warehouse_query(self): + query = add_default_params(queries.warehouse_query, "Account") + + wh = query(filters=[["Bin", "item_code", "=", "_Test Item"]]) + self.assertGreaterEqual(len(wh), 1) From dae0a1c1d672f44aa50967c23cf60bc03cf438f1 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Sat, 11 Sep 2021 17:54:21 +0530 Subject: [PATCH 037/116] fix: Template Error due to use of single quote (#27433) --- erpnext/public/js/bank_reconciliation_tool/dialog_manager.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/public/js/bank_reconciliation_tool/dialog_manager.js b/erpnext/public/js/bank_reconciliation_tool/dialog_manager.js index 239fbb92b11..ca73393c546 100644 --- a/erpnext/public/js/bank_reconciliation_tool/dialog_manager.js +++ b/erpnext/public/js/bank_reconciliation_tool/dialog_manager.js @@ -227,7 +227,7 @@ erpnext.accounts.bank_reconciliation.DialogManager = class DialogManager { { fieldtype: "HTML", fieldname: "no_matching_vouchers", - options: "
No Matching Vouchers Found
" + options: "
No Matching Vouchers Found
" }, { fieldtype: "Section Break", From a52d4c25fffbfbb2465b955fddafe8a47f531523 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Sat, 11 Sep 2021 17:54:55 +0530 Subject: [PATCH 038/116] fix: fail migration due to None type during v13_0.update_returned_qty_in_pr_dn (#27430) (#27436) * fix: fail migration due to None type * fix: incorrect key: value pair in filter. Co-authored-by: Ankush Menat (cherry picked from commit becf471a3a238cfaa20a8940e0106c7e0e0386c2) Co-authored-by: Devin Slauenwhite --- erpnext/patches/v13_0/update_returned_qty_in_pr_dn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/patches/v13_0/update_returned_qty_in_pr_dn.py b/erpnext/patches/v13_0/update_returned_qty_in_pr_dn.py index efb3a5961f9..dd64e05ec16 100644 --- a/erpnext/patches/v13_0/update_returned_qty_in_pr_dn.py +++ b/erpnext/patches/v13_0/update_returned_qty_in_pr_dn.py @@ -13,7 +13,7 @@ def execute(): frappe.reload_doc('stock', 'doctype', 'stock_settings') def update_from_return_docs(doctype): - for return_doc in frappe.get_all(doctype, filters={'is_return' : 1, 'docstatus' : 1}): + for return_doc in frappe.get_all(doctype, filters={'is_return' : 1, 'docstatus' : 1, 'return_against': ('!=', '')}): # Update original receipt/delivery document from return return_doc = frappe.get_cached_doc(doctype, return_doc.name) try: From 6ef879fca28b5e7239f025f64b7e7c248284a1a7 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sun, 12 Sep 2021 16:36:51 +0530 Subject: [PATCH 039/116] fix(ux): clean invalid fields from variant setting (#27412) --- .../item_variant_settings.js | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/erpnext/stock/doctype/item_variant_settings/item_variant_settings.js b/erpnext/stock/doctype/item_variant_settings/item_variant_settings.js index e8fb34732fc..488920aadbc 100644 --- a/erpnext/stock/doctype/item_variant_settings/item_variant_settings.js +++ b/erpnext/stock/doctype/item_variant_settings/item_variant_settings.js @@ -2,19 +2,32 @@ // For license information, please see license.txt frappe.ui.form.on('Item Variant Settings', { - setup: function(frm) { + refresh: function(frm) { const allow_fields = []; - const exclude_fields = ["naming_series", "item_code", "item_name", "show_in_website", - "show_variant_in_website", "opening_stock", "variant_of", "valuation_rate"]; + + const existing_fields = frm.doc.fields.map(row => row.field_name); + const exclude_fields = [...existing_fields, "naming_series", "item_code", "item_name", + "show_in_website", "show_variant_in_website", "standard_rate", "opening_stock", "image", + "variant_of", "valuation_rate", "barcodes", "website_image", "thumbnail", + "website_specifiations", "web_long_description", "has_variants", "attributes"]; + + const exclude_field_types = ['HTML', 'Section Break', 'Column Break', 'Button', 'Read Only']; frappe.model.with_doctype('Item', () => { frappe.get_meta('Item').fields.forEach(d => { - if(!in_list(['HTML', 'Section Break', 'Column Break', 'Button', 'Read Only'], d.fieldtype) + if (!in_list(exclude_field_types, d.fieldtype) && !d.no_copy && !in_list(exclude_fields, d.fieldname)) { allow_fields.push(d.fieldname); } }); + if (allow_fields.length == 0) { + allow_fields.push({ + label: __("No additional fields available"), + value: "", + }); + } + frm.fields_dict.fields.grid.update_docfield_property( 'field_name', 'options', allow_fields ); From d743c41b54c6ff72eb5dd6f0386bfc1f37e4d288 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sun, 12 Sep 2021 16:38:18 +0530 Subject: [PATCH 040/116] fix(ux): apply proper filtering in stock reports (#27411) * fix(ux): apply proper filtering in stock reports Stock Balance: apply company filter to warehouse field Stock Ageing: apply company filter to warehouse field * fix: unnecessary parens Co-authored-by: Alan <2.alan.tom@gmail.com> --- erpnext/stock/report/stock_ageing/stock_ageing.js | 10 +++++++++- .../stock/report/stock_balance/stock_balance.js | 15 ++++++++------- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/erpnext/stock/report/stock_ageing/stock_ageing.js b/erpnext/stock/report/stock_ageing/stock_ageing.js index b22788f7a29..db463b7ca09 100644 --- a/erpnext/stock/report/stock_ageing/stock_ageing.js +++ b/erpnext/stock/report/stock_ageing/stock_ageing.js @@ -22,7 +22,15 @@ frappe.query_reports["Stock Ageing"] = { "fieldname":"warehouse", "label": __("Warehouse"), "fieldtype": "Link", - "options": "Warehouse" + "options": "Warehouse", + get_query: () => { + const company = frappe.query_report.get_filter_value("company"); + return { + filters: { + ...company && {company}, + } + }; + } }, { "fieldname":"item_code", diff --git a/erpnext/stock/report/stock_balance/stock_balance.js b/erpnext/stock/report/stock_balance/stock_balance.js index 7d22823eb80..ce6ffa0b914 100644 --- a/erpnext/stock/report/stock_balance/stock_balance.js +++ b/erpnext/stock/report/stock_balance/stock_balance.js @@ -53,13 +53,14 @@ frappe.query_reports["Stock Balance"] = { "width": "80", "options": "Warehouse", get_query: () => { - var warehouse_type = frappe.query_report.get_filter_value('warehouse_type'); - if(warehouse_type){ - return { - filters: { - 'warehouse_type': warehouse_type - } - }; + let warehouse_type = frappe.query_report.get_filter_value("warehouse_type"); + let company = frappe.query_report.get_filter_value("company"); + + return { + filters: { + ...warehouse_type && {warehouse_type}, + ...company && {company} + } } } }, From 7292f5476d7f0c3a9a5d78da7ad664dcc1f7d51a Mon Sep 17 00:00:00 2001 From: Saqib Date: Mon, 13 Sep 2021 12:13:43 +0530 Subject: [PATCH 041/116] feat: (get_items_from) filter material request item in purchase order (#27449) --- .../buying/doctype/purchase_order/purchase_order.js | 5 ++++- erpnext/public/js/utils.js | 5 ++++- .../doctype/material_request/material_request.py | 13 +++++++++++-- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js index 521432d296b..2005dac37d7 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.js +++ b/erpnext/buying/doctype/purchase_order/purchase_order.js @@ -425,7 +425,10 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends e status: ["!=", "Stopped"], per_ordered: ["<", 100], company: me.frm.doc.company - } + }, + allow_child_item_selection: true, + child_fielname: "items", + child_columns: ["item_code", "qty"] }) }, __("Get Items From")); diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index e0610eb8c01..2a74d6015f4 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -709,6 +709,9 @@ erpnext.utils.map_current_doc = function(opts) { setters: opts.setters, get_query: opts.get_query, add_filters_group: 1, + allow_child_item_selection: opts.allow_child_item_selection, + child_fieldname: opts.child_fielname, + child_columns: opts.child_columns, action: function(selections, args) { let values = selections; if(values.length === 0){ @@ -716,7 +719,7 @@ erpnext.utils.map_current_doc = function(opts) { return; } opts.source_name = values; - opts.setters = args; + opts.args = args; d.dialog.hide(); _map(); }, diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py index 9eb47216266..2569c04251c 100644 --- a/erpnext/stock/doctype/material_request/material_request.py +++ b/erpnext/stock/doctype/material_request/material_request.py @@ -6,10 +6,13 @@ from __future__ import unicode_literals +import json + import frappe from frappe import _, msgprint from frappe.model.mapper import get_mapped_doc from frappe.utils import cstr, flt, get_link_to_form, getdate, new_line_sep, nowdate +from six import string_types from erpnext.buying.utils import check_on_hold_or_closed_status, validate_for_items from erpnext.controllers.buying_controller import BuyingController @@ -269,7 +272,10 @@ def update_status(name, status): material_request.update_status(status) @frappe.whitelist() -def make_purchase_order(source_name, target_doc=None): +def make_purchase_order(source_name, target_doc=None, args={}): + + if isinstance(args, string_types): + args = json.loads(args) def postprocess(source, target_doc): if frappe.flags.args and frappe.flags.args.default_supplier: @@ -284,7 +290,10 @@ def make_purchase_order(source_name, target_doc=None): set_missing_values(source, target_doc) def select_item(d): - return d.ordered_qty < d.stock_qty + filtered_items = args.get('filtered_children', []) + child_filter = d.name in filtered_items if filtered_items else True + + return d.ordered_qty < d.stock_qty and child_filter doclist = get_mapped_doc("Material Request", source_name, { "Material Request": { From bab644a249de4355d6700f53a7bfbf0114ebb30c Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 13 Sep 2021 13:24:27 +0530 Subject: [PATCH 042/116] fix(Payroll): incorrect component amount calculation if dependent on another payment days based component (#27349) * fix(Payroll): incorrect component amount calculation if dependent on another payment days based component * fix: set component amount precision at the end * fix: consider default amount during taxt calculations * test: component amount dependent on another payment days based component * fix: test --- .../doctype/salary_slip/salary_slip.py | 33 ++-- .../doctype/salary_slip/test_salary_slip.py | 148 ++++++++++++++++++ 2 files changed, 167 insertions(+), 14 deletions(-) diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py index 8c48345d8fd..d113e7e5697 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py @@ -487,7 +487,7 @@ class SalarySlip(TransactionBase): self.calculate_component_amounts("deductions") self.set_loan_repayment() - self.set_component_amounts_based_on_payment_days() + self.set_precision_for_component_amounts() self.set_net_pay() def set_net_pay(self): @@ -709,6 +709,17 @@ class SalarySlip(TransactionBase): component_row.amount = amount + self.update_component_amount_based_on_payment_days(component_row) + + def update_component_amount_based_on_payment_days(self, component_row): + joining_date, relieving_date = self.get_joining_and_relieving_dates() + component_row.amount = self.get_amount_based_on_payment_days(component_row, joining_date, relieving_date)[0] + + def set_precision_for_component_amounts(self): + for component_type in ("earnings", "deductions"): + for component_row in self.get(component_type): + component_row.amount = flt(component_row.amount, component_row.precision("amount")) + def calculate_variable_based_on_taxable_salary(self, tax_component, payroll_period): if not payroll_period: frappe.msgprint(_("Start and end dates not in a valid Payroll Period, cannot calculate {0}.") @@ -866,14 +877,7 @@ class SalarySlip(TransactionBase): return total_tax_paid def get_taxable_earnings(self, allow_tax_exemption=False, based_on_payment_days=0): - joining_date, relieving_date = frappe.get_cached_value("Employee", self.employee, - ["date_of_joining", "relieving_date"]) - - if not relieving_date: - relieving_date = getdate(self.end_date) - - if not joining_date: - frappe.throw(_("Please set the Date Of Joining for employee {0}").format(frappe.bold(self.employee_name))) + joining_date, relieving_date = self.get_joining_and_relieving_dates() taxable_earnings = 0 additional_income = 0 @@ -884,7 +888,10 @@ class SalarySlip(TransactionBase): if based_on_payment_days: amount, additional_amount = self.get_amount_based_on_payment_days(earning, joining_date, relieving_date) else: - amount, additional_amount = earning.amount, earning.additional_amount + if earning.additional_amount: + amount, additional_amount = earning.amount, earning.additional_amount + else: + amount, additional_amount = earning.default_amount, earning.additional_amount if earning.is_tax_applicable: if additional_amount: @@ -1055,7 +1062,7 @@ class SalarySlip(TransactionBase): total += amount return total - def set_component_amounts_based_on_payment_days(self): + def get_joining_and_relieving_dates(self): joining_date, relieving_date = frappe.get_cached_value("Employee", self.employee, ["date_of_joining", "relieving_date"]) @@ -1065,9 +1072,7 @@ class SalarySlip(TransactionBase): if not joining_date: frappe.throw(_("Please set the Date Of Joining for employee {0}").format(frappe.bold(self.employee_name))) - for component_type in ("earnings", "deductions"): - for d in self.get(component_type): - d.amount = flt(self.get_amount_based_on_payment_days(d, joining_date, relieving_date)[0], d.precision("amount")) + return joining_date, relieving_date def set_loan_repayment(self): self.total_loan_repayment = 0 diff --git a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py index 480daa25954..bff36a41490 100644 --- a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py @@ -17,6 +17,7 @@ from frappe.utils import ( getdate, nowdate, ) +from frappe.utils.make_random import get_random import erpnext from erpnext.accounts.utils import get_fiscal_year @@ -134,6 +135,65 @@ class TestSalarySlip(unittest.TestCase): frappe.db.set_value("Payroll Settings", None, "payroll_based_on", "Leave") + def test_component_amount_dependent_on_another_payment_days_based_component(self): + from erpnext.hr.doctype.attendance.attendance import mark_attendance + from erpnext.payroll.doctype.salary_structure.test_salary_structure import ( + create_salary_structure_assignment, + ) + + no_of_days = self.get_no_of_days() + # Payroll based on attendance + frappe.db.set_value("Payroll Settings", None, "payroll_based_on", "Attendance") + + salary_structure = make_salary_structure_for_payment_days_based_component_dependency() + employee = make_employee("test_payment_days_based_component@salary.com", company="_Test Company") + + # base = 50000 + create_salary_structure_assignment(employee, salary_structure.name, company="_Test Company", currency="INR") + + # mark employee absent for a day since this case works fine if payment days are equal to working days + month_start_date = get_first_day(nowdate()) + month_end_date = get_last_day(nowdate()) + + first_sunday = frappe.db.sql(""" + select holiday_date from `tabHoliday` + where parent = 'Salary Slip Test Holiday List' + and holiday_date between %s and %s + order by holiday_date + """, (month_start_date, month_end_date))[0][0] + + mark_attendance(employee, add_days(first_sunday, 1), 'Absent', ignore_validate=True) # counted as absent + + # make salary slip and assert payment days + ss = make_salary_slip_for_payment_days_dependency_test("test_payment_days_based_component@salary.com", salary_structure.name) + self.assertEqual(ss.absent_days, 1) + + days_in_month = no_of_days[0] + no_of_holidays = no_of_days[1] + + self.assertEqual(ss.payment_days, days_in_month - no_of_holidays - 1) + + ss.reload() + payment_days_based_comp_amount = 0 + for component in ss.earnings: + if component.salary_component == "HRA - Payment Days": + payment_days_based_comp_amount = flt(component.amount, component.precision("amount")) + break + + # check if the dependent component is calculated using the amount updated after payment days + actual_amount = 0 + precision = 0 + for component in ss.deductions: + if component.salary_component == "P - Employee Provident Fund": + precision = component.precision("amount") + actual_amount = flt(component.amount, precision) + break + + expected_amount = flt((flt(ss.gross_pay) - payment_days_based_comp_amount) * 0.12, precision) + + self.assertEqual(actual_amount, expected_amount) + frappe.db.set_value("Payroll Settings", None, "payroll_based_on", "Leave") + def test_salary_slip_with_holidays_included(self): no_of_days = self.get_no_of_days() frappe.db.set_value("Payroll Settings", None, "include_holidays_in_total_working_days", 1) @@ -864,3 +924,91 @@ def make_holiday_list(): holiday_list = holiday_list.name return holiday_list + +def make_salary_structure_for_payment_days_based_component_dependency(): + earnings = [ + { + "salary_component": "Basic Salary - Payment Days", + "abbr": "P_BS", + "type": "Earning", + "formula": "base", + "amount_based_on_formula": 1 + }, + { + "salary_component": "HRA - Payment Days", + "abbr": "P_HRA", + "type": "Earning", + "depends_on_payment_days": 1, + "amount_based_on_formula": 1, + "formula": "base * 0.20" + } + ] + + make_salary_component(earnings, False, company_list=["_Test Company"]) + + deductions = [ + { + "salary_component": "P - Professional Tax", + "abbr": "P_PT", + "type": "Deduction", + "depends_on_payment_days": 1, + "amount": 200.00 + }, + { + "salary_component": "P - Employee Provident Fund", + "abbr": "P_EPF", + "type": "Deduction", + "exempted_from_income_tax": 1, + "amount_based_on_formula": 1, + "depends_on_payment_days": 0, + "formula": "(gross_pay - P_HRA) * 0.12" + } + ] + + make_salary_component(deductions, False, company_list=["_Test Company"]) + + salary_structure = "Salary Structure with PF" + if frappe.db.exists("Salary Structure", salary_structure): + frappe.db.delete("Salary Structure", salary_structure) + + details = { + "doctype": "Salary Structure", + "name": salary_structure, + "company": "_Test Company", + "payroll_frequency": "Monthly", + "payment_account": get_random("Account", filters={"account_currency": "INR"}), + "currency": "INR" + } + + salary_structure_doc = frappe.get_doc(details) + + for entry in earnings: + salary_structure_doc.append("earnings", entry) + + for entry in deductions: + salary_structure_doc.append("deductions", entry) + + salary_structure_doc.insert() + salary_structure_doc.submit() + + return salary_structure_doc + +def make_salary_slip_for_payment_days_dependency_test(employee, salary_structure): + employee = frappe.db.get_value("Employee", { + "user_id": employee + }, + ["name", "company", "employee_name"], + as_dict=True) + + salary_slip_name = frappe.db.get_value("Salary Slip", {"employee": frappe.db.get_value("Employee", {"user_id": employee})}) + + if not salary_slip_name: + salary_slip = make_salary_slip(salary_structure, employee=employee.name) + salary_slip.employee_name = employee.employee_name + salary_slip.payroll_frequency = "Monthly" + salary_slip.posting_date = nowdate() + salary_slip.insert() + else: + salary_slip = frappe.get_doc("Salary Slip", salary_slip_name) + + return salary_slip \ No newline at end of file From a5baf909b7b9defd046b23e11d2c0c3a32737e2c Mon Sep 17 00:00:00 2001 From: Saqib Date: Mon, 13 Sep 2021 15:50:20 +0530 Subject: [PATCH 043/116] fix: editable price list rate field in sales transactions (#27455) --- erpnext/selling/sales_common.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/erpnext/selling/sales_common.js b/erpnext/selling/sales_common.js index 955ef5e9396..6a09109bfd4 100644 --- a/erpnext/selling/sales_common.js +++ b/erpnext/selling/sales_common.js @@ -247,7 +247,12 @@ erpnext.selling.SellingController = class SellingController extends erpnext.Tran var editable_price_list_rate = cint(frappe.defaults.get_default("editable_price_list_rate")); if(df && editable_price_list_rate) { - df.read_only = 0; + const parent_field = frappe.meta.get_parentfield(this.frm.doc.doctype, this.frm.doc.doctype + " Item"); + if (!this.frm.fields_dict[parent_field]) return; + + this.frm.fields_dict[parent_field].grid.update_docfield_property( + 'price_list_rate', 'read_only', 0 + ); } } From 44139fbac5ed73fa2be82fb8ae029c3bdb6023ad Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Mon, 13 Sep 2021 16:50:11 +0530 Subject: [PATCH 044/116] fix: cancelled sales invoices are considered in billed quantity calculation --- .../selling/report/sales_order_analysis/sales_order_analysis.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py b/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py index 805c3d804fa..5c4d8b601f4 100644 --- a/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py +++ b/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py @@ -73,7 +73,7 @@ def get_data(conditions, filters): `tabSales Order` so, `tabSales Order Item` soi LEFT JOIN `tabSales Invoice Item` sii - ON sii.so_detail = soi.name + ON sii.so_detail = soi.name and sii.docstatus = 1 WHERE soi.parent = so.name and so.status not in ('Stopped', 'Closed', 'On Hold') From b98740b44a73b5fda7e7e11f7f5f85f0fb5d0bf9 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 14 Sep 2021 11:20:15 +0530 Subject: [PATCH 045/116] fix: employee advance return through multiple additional salaries (#27438) * fix: employee advance return through multiple additional salaries * test: test repay unclaimed amount from salary * fix: sorting in imports --- .../employee_advance/employee_advance.js | 2 +- .../employee_advance/employee_advance.json | 5 +- .../employee_advance/employee_advance.py | 5 +- .../employee_advance/test_employee_advance.py | 49 ++++++++++++++++++- .../additional_salary/additional_salary.py | 16 ++++-- 5 files changed, 69 insertions(+), 8 deletions(-) diff --git a/erpnext/hr/doctype/employee_advance/employee_advance.js b/erpnext/hr/doctype/employee_advance/employee_advance.js index fa4b06aad37..7d1c7cbf4a8 100644 --- a/erpnext/hr/doctype/employee_advance/employee_advance.js +++ b/erpnext/hr/doctype/employee_advance/employee_advance.js @@ -73,7 +73,7 @@ frappe.ui.form.on('Employee Advance', { 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() { + frm.add_custom_button(__("Deduction from Salary"), function() { frm.events.make_deduction_via_additional_salary(frm); }, __('Create')); } diff --git a/erpnext/hr/doctype/employee_advance/employee_advance.json b/erpnext/hr/doctype/employee_advance/employee_advance.json index ea25aa720ad..04754530c3a 100644 --- a/erpnext/hr/doctype/employee_advance/employee_advance.json +++ b/erpnext/hr/doctype/employee_advance/employee_advance.json @@ -170,7 +170,7 @@ "default": "0", "fieldname": "repay_unclaimed_amount_from_salary", "fieldtype": "Check", - "label": "Repay unclaimed amount from salary" + "label": "Repay Unclaimed Amount from Salary" }, { "depends_on": "eval:cur_frm.doc.employee", @@ -200,10 +200,11 @@ ], "is_submittable": 1, "links": [], - "modified": "2021-03-31 22:31:53.746659", + "modified": "2021-09-11 18:38:38.617478", "modified_by": "Administrator", "module": "HR", "name": "Employee Advance", + "naming_rule": "By \"Naming Series\" field", "owner": "Administrator", "permissions": [ { diff --git a/erpnext/hr/doctype/employee_advance/employee_advance.py b/erpnext/hr/doctype/employee_advance/employee_advance.py index 87d42d34e39..8d90bccd2da 100644 --- a/erpnext/hr/doctype/employee_advance/employee_advance.py +++ b/erpnext/hr/doctype/employee_advance/employee_advance.py @@ -172,7 +172,10 @@ def get_paying_amount_paying_exchange_rate(payment_account, doc): @frappe.whitelist() def create_return_through_additional_salary(doc): import json - doc = frappe._dict(json.loads(doc)) + + if isinstance(doc, str): + doc = frappe._dict(json.loads(doc)) + additional_salary = frappe.new_doc('Additional Salary') additional_salary.employee = doc.employee additional_salary.currency = doc.currency diff --git a/erpnext/hr/doctype/employee_advance/test_employee_advance.py b/erpnext/hr/doctype/employee_advance/test_employee_advance.py index f8e5f535cb5..c439d45b55c 100644 --- a/erpnext/hr/doctype/employee_advance/test_employee_advance.py +++ b/erpnext/hr/doctype/employee_advance/test_employee_advance.py @@ -12,8 +12,11 @@ import erpnext from erpnext.hr.doctype.employee.test_employee import make_employee from erpnext.hr.doctype.employee_advance.employee_advance import ( EmployeeAdvanceOverPayment, + create_return_through_additional_salary, make_bank_entry, ) +from erpnext.payroll.doctype.salary_component.test_salary_component import create_salary_component +from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure class TestEmployeeAdvance(unittest.TestCase): @@ -33,6 +36,46 @@ class TestEmployeeAdvance(unittest.TestCase): journal_entry1 = make_payment_entry(advance) self.assertRaises(EmployeeAdvanceOverPayment, journal_entry1.submit) + def test_repay_unclaimed_amount_from_salary(self): + employee_name = make_employee("_T@employe.advance") + advance = make_employee_advance(employee_name, {"repay_unclaimed_amount_from_salary": 1}) + + args = {"type": "Deduction"} + create_salary_component("Advance Salary - Deduction", **args) + make_salary_structure("Test Additional Salary for Advance Return", "Monthly", employee=employee_name) + + # additional salary for 700 first + advance.reload() + additional_salary = create_return_through_additional_salary(advance) + additional_salary.salary_component = "Advance Salary - Deduction" + additional_salary.payroll_date = nowdate() + additional_salary.amount = 700 + additional_salary.insert() + additional_salary.submit() + + advance.reload() + self.assertEqual(advance.return_amount, 700) + + # additional salary for remaining 300 + additional_salary = create_return_through_additional_salary(advance) + additional_salary.salary_component = "Advance Salary - Deduction" + additional_salary.payroll_date = nowdate() + additional_salary.amount = 300 + additional_salary.insert() + additional_salary.submit() + + advance.reload() + self.assertEqual(advance.return_amount, 1000) + + # update advance return amount on additional salary cancellation + additional_salary.cancel() + advance.reload() + self.assertEqual(advance.return_amount, 700) + + def tearDown(self): + frappe.db.rollback() + + def make_payment_entry(advance): journal_entry = frappe.get_doc(make_bank_entry("Employee Advance", advance.name)) journal_entry.cheque_no = "123123" @@ -41,7 +84,7 @@ def make_payment_entry(advance): return journal_entry -def make_employee_advance(employee_name): +def make_employee_advance(employee_name, args=None): doc = frappe.new_doc("Employee Advance") doc.employee = employee_name doc.company = "_Test company" @@ -51,6 +94,10 @@ def make_employee_advance(employee_name): doc.advance_amount = 1000 doc.posting_date = nowdate() doc.advance_account = "_Test Employee Advance - _TC" + + if args: + doc.update(args) + doc.insert() doc.submit() diff --git a/erpnext/payroll/doctype/additional_salary/additional_salary.py b/erpnext/payroll/doctype/additional_salary/additional_salary.py index ed10f2bc67a..7c0a8eac99c 100644 --- a/erpnext/payroll/doctype/additional_salary/additional_salary.py +++ b/erpnext/payroll/doctype/additional_salary/additional_salary.py @@ -14,12 +14,11 @@ from erpnext.hr.utils import validate_active_employee class AdditionalSalary(Document): def on_submit(self): - 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_return_amount_in_employee_advance() self.update_employee_referral() def on_cancel(self): + self.update_return_amount_in_employee_advance() self.update_employee_referral(cancel=True) def validate(self): @@ -98,6 +97,17 @@ class AdditionalSalary(Document): frappe.throw(_("Additional Salary for referral bonus can only be created against Employee Referral with status {0}").format( frappe.bold("Accepted"))) + def update_return_amount_in_employee_advance(self): + if self.ref_doctype == "Employee Advance" and self.ref_docname: + return_amount = frappe.db.get_value("Employee Advance", self.ref_docname, "return_amount") + + if self.docstatus == 2: + return_amount -= self.amount + else: + return_amount += self.amount + + frappe.db.set_value("Employee Advance", self.ref_docname, "return_amount", return_amount) + def update_employee_referral(self, cancel=False): if self.ref_doctype == "Employee Referral": status = "Unpaid" if cancel else "Paid" From 1c1b476d67dd7d84ed99578f8159cd6a1cd92c27 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Tue, 14 Sep 2021 11:41:19 +0530 Subject: [PATCH 046/116] perf: Optimize get_attribute_filters (#26729) * perf: Optimize get_attribute_filters * fix: handle when filter attributes are undefined * chore: unused imports Co-authored-by: Ankush Menat --- .../setup/doctype/item_group/item_group.py | 2 +- erpnext/shopping_cart/filters.py | 53 ++++++++----------- erpnext/www/all-products/index.html | 8 +-- erpnext/www/all-products/index.py | 2 +- 4 files changed, 29 insertions(+), 36 deletions(-) diff --git a/erpnext/setup/doctype/item_group/item_group.py b/erpnext/setup/doctype/item_group/item_group.py index ddf3e662e0c..b26c6a49b4c 100644 --- a/erpnext/setup/doctype/item_group/item_group.py +++ b/erpnext/setup/doctype/item_group/item_group.py @@ -99,7 +99,7 @@ class ItemGroup(NestedSet, WebsiteGenerator): filter_engine = ProductFiltersBuilder(self.name) context.field_filters = filter_engine.get_field_filters() - context.attribute_filters = filter_engine.get_attribute_fitlers() + context.attribute_filters = filter_engine.get_attribute_filters() context.update({ "parents": get_parent_item_groups(self.parent_item_group), diff --git a/erpnext/shopping_cart/filters.py b/erpnext/shopping_cart/filters.py index aaeff0fe073..4787ae534cf 100644 --- a/erpnext/shopping_cart/filters.py +++ b/erpnext/shopping_cart/filters.py @@ -4,7 +4,6 @@ from __future__ import unicode_literals import frappe -from frappe import _dict class ProductFiltersBuilder: @@ -57,37 +56,31 @@ class ProductFiltersBuilder: return filter_data - def get_attribute_fitlers(self): + def get_attribute_filters(self): attributes = [row.attribute for row in self.doc.filter_attributes] - attribute_docs = [ - frappe.get_doc('Item Attribute', attribute) for attribute in attributes - ] - valid_attributes = [] + if not attributes: + return [] - for attr_doc in attribute_docs: - selected_attributes = [] - for attr in attr_doc.item_attribute_values: - or_filters = [] - filters= [ - ["Item Variant Attribute", "attribute", "=", attr.parent], - ["Item Variant Attribute", "attribute_value", "=", attr.attribute_value] - ] - if self.item_group: - or_filters.extend([ - ["item_group", "=", self.item_group], - ["Website Item Group", "item_group", "=", self.item_group] - ]) + result = frappe.db.sql( + """ + select + distinct attribute, attribute_value + from + `tabItem Variant Attribute` + where + attribute in %(attributes)s + and attribute_value is not null + """, + {"attributes": attributes}, + as_dict=1, + ) - if frappe.db.get_all("Item", filters, or_filters=or_filters, limit=1): - selected_attributes.append(attr) + attribute_value_map = {} + for d in result: + attribute_value_map.setdefault(d.attribute, []).append(d.attribute_value) - if selected_attributes: - valid_attributes.append( - _dict( - item_attribute_values=selected_attributes, - name=attr_doc.name - ) - ) - - return valid_attributes + out = [] + for name, values in attribute_value_map.items(): + out.append(frappe._dict(name=name, item_attribute_values=values)) + return out diff --git a/erpnext/www/all-products/index.html b/erpnext/www/all-products/index.html index 7c18ecc41fe..a7838eebc14 100644 --- a/erpnext/www/all-products/index.html +++ b/erpnext/www/all-products/index.html @@ -98,14 +98,14 @@
{% for attr_value in attribute.item_attribute_values %}
-
{% endfor %} diff --git a/erpnext/www/all-products/index.py b/erpnext/www/all-products/index.py index 335c10443a7..df5258b238c 100644 --- a/erpnext/www/all-products/index.py +++ b/erpnext/www/all-products/index.py @@ -27,7 +27,7 @@ def get_context(context): filter_engine = ProductFiltersBuilder() context.field_filters = filter_engine.get_field_filters() - context.attribute_filters = filter_engine.get_attribute_fitlers() + context.attribute_filters = filter_engine.get_attribute_filters() context.product_settings = product_settings context.body_class = "product-page" From 95460d98186ea62873487faf3c04af19f1d2fb11 Mon Sep 17 00:00:00 2001 From: Chillar Anand Date: Tue, 14 Sep 2021 12:00:34 +0530 Subject: [PATCH 047/116] fix(HR): Ignore invalid fields when updating employee details (#27456) --- erpnext/hr/utils.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/hr/utils.py b/erpnext/hr/utils.py index deec6442093..b6f4cadcc9c 100644 --- a/erpnext/hr/utils.py +++ b/erpnext/hr/utils.py @@ -32,7 +32,10 @@ def set_employee_name(doc): def update_employee(employee, details, date=None, cancel=False): internal_work_history = {} for item in details: - fieldtype = frappe.get_meta("Employee").get_field(item.fieldname).fieldtype + field = frappe.get_meta("Employee").get_field(item.fieldname) + if not field: + continue + fieldtype = field.fieldtype new_data = item.new if not cancel else item.current if fieldtype == "Date" and new_data: new_data = getdate(new_data) From 9520215e2b093f287ffba83762e2a90c9757d9d1 Mon Sep 17 00:00:00 2001 From: marination Date: Tue, 14 Sep 2021 12:33:21 +0530 Subject: [PATCH 048/116] feat: Handle Excess/Multiple Item Transfer against Job Card MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Hide MR/Material Transfer buttons in JC if cancelled - Show MR/Material transfer buttons if pending to transfer or excess transfer allowed - Renamed ‘Transferred Qty’ to ‘FG Qty from Transferred Raw Materials’ in JC - Set status to Completed in JC in case of excess transfer too - During excess transfer against JC, avoid negative ‘For Quantity’. Set to 0 instead - Job card section and excess transfer allowance checkbox in Manufacturing Settings - Renamed ’Allow Multiple Material Consumption’ to ‘Allow Continuous Material Consumption’ (fiedname is same) - Secured denominator variables in `get_transfered_raw_materials` to avoid ZeroDivisionError --- .../doctype/job_card/job_card.js | 13 ++++++--- .../doctype/job_card/job_card.json | 5 ++-- .../doctype/job_card/job_card.py | 16 +++++++++-- .../manufacturing_settings.json | 27 ++++++++++++++++--- .../stock/doctype/stock_entry/stock_entry.py | 4 +-- 5 files changed, 52 insertions(+), 13 deletions(-) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.js b/erpnext/manufacturing/doctype/job_card/job_card.js index 91eb4a0fa90..9b8f81bc244 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.js +++ b/erpnext/manufacturing/doctype/job_card/job_card.js @@ -27,14 +27,21 @@ frappe.ui.form.on('Job Card', { frappe.flags.pause_job = 0; frappe.flags.resume_job = 0; - if(!frm.doc.__islocal && frm.doc.items && frm.doc.items.length) { - if (frm.doc.for_quantity != frm.doc.transferred_qty) { + if(!frm.doc.__islocal && frm.doc.items && frm.doc.items.length && frm.doc.docstatus < 2) { + let to_request = frm.doc.for_quantity > frm.doc.transferred_qty; + let excess_transfer_allowed = frm.doc.__onload.job_card_excess_transfer; + + if (to_request || excess_transfer_allowed) { frm.add_custom_button(__("Material Request"), () => { frm.trigger("make_material_request"); }); } - if (frm.doc.for_quantity != frm.doc.transferred_qty) { + // check if any row has untransferred materials + // in case of multiple items in JC + let to_transfer = frm.doc.items.some((row) => row.transferred_qty < row.required_qty); + + if (to_transfer || excess_transfer_allowed) { frm.add_custom_button(__("Material Transfer"), () => { frm.trigger("make_stock_entry"); }).addClass("btn-primary"); diff --git a/erpnext/manufacturing/doctype/job_card/job_card.json b/erpnext/manufacturing/doctype/job_card/job_card.json index 046e2fd1825..f5bbac33b81 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.json +++ b/erpnext/manufacturing/doctype/job_card/job_card.json @@ -185,7 +185,7 @@ "default": "0", "fieldname": "transferred_qty", "fieldtype": "Float", - "label": "Transferred Qty", + "label": "FG Qty from Transferred Raw Materials", "read_only": 1 }, { @@ -396,10 +396,11 @@ ], "is_submittable": 1, "links": [], - "modified": "2021-03-16 15:59:32.766484", + "modified": "2021-09-13 21:34:15.177928", "modified_by": "Administrator", "module": "Manufacturing", "name": "Job Card", + "naming_rule": "By \"Naming Series\" field", "owner": "Administrator", "permissions": [ { diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index ceae63cb940..1906bf6a9db 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -37,6 +37,10 @@ class OperationSequenceError(frappe.ValidationError): pass class JobCardCancelError(frappe.ValidationError): pass class JobCard(Document): + def onload(self): + excess_transfer = frappe.db.get_single_value("Manufacturing Settings", "job_card_excess_transfer") + self.set_onload("job_card_excess_transfer", excess_transfer) + def validate(self): self.validate_time_logs() self.set_status() @@ -449,6 +453,7 @@ class JobCard(Document): frappe.db.set_value('Job Card Item', row.job_card_item, 'transferred_qty', flt(qty)) def set_transferred_qty(self, update_status=False): + "Set total FG Qty for which RM was transferred." if not self.items: self.transferred_qty = self.for_quantity if self.docstatus == 1 else 0 @@ -457,6 +462,7 @@ class JobCard(Document): return if self.items: + # sum of 'For Quantity' of Stock Entries against JC self.transferred_qty = frappe.db.get_value('Stock Entry', { 'job_card': self.name, 'work_order': self.work_order, @@ -500,7 +506,9 @@ class JobCard(Document): self.status = 'Work In Progress' if (self.docstatus == 1 and - (self.for_quantity == self.transferred_qty or not self.items)): + (self.for_quantity <= self.transferred_qty or not self.items)): + # consider excess transfer + # completed qty is checked via separate validation self.status = 'Completed' if self.status != 'Completed': @@ -618,7 +626,11 @@ def make_stock_entry(source_name, target_doc=None): def set_missing_values(source, target): target.purpose = "Material Transfer for Manufacture" target.from_bom = 1 - target.fg_completed_qty = source.get('for_quantity', 0) - source.get('transferred_qty', 0) + + # avoid negative 'For Quantity' + pending_fg_qty = source.get('for_quantity', 0) - source.get('transferred_qty', 0) + target.fg_completed_qty = pending_fg_qty if pending_fg_qty > 0 else 0 + target.set_transfer_qty() target.calculate_rate_and_amount() target.set_missing_values() diff --git a/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json b/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json index 024f7847259..01647d56c91 100644 --- a/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json +++ b/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json @@ -25,9 +25,12 @@ "overproduction_percentage_for_sales_order", "column_break_16", "overproduction_percentage_for_work_order", + "job_card_section", + "add_corrective_operation_cost_in_finished_good_valuation", + "column_break_24", + "job_card_excess_transfer", "other_settings_section", "update_bom_costs_automatically", - "add_corrective_operation_cost_in_finished_good_valuation", "column_break_23", "make_serial_no_batch_from_work_order" ], @@ -96,10 +99,10 @@ }, { "default": "0", - "description": "Allow multiple material consumptions against a Work Order", + "description": "Allow material consumptions without immediately manufacturing finished goods against a Work Order", "fieldname": "material_consumption", "fieldtype": "Check", - "label": "Allow Multiple Material Consumption" + "label": "Allow Continuous Material Consumption" }, { "default": "0", @@ -175,13 +178,29 @@ "fieldname": "add_corrective_operation_cost_in_finished_good_valuation", "fieldtype": "Check", "label": "Add Corrective Operation Cost in Finished Good Valuation" + }, + { + "fieldname": "job_card_section", + "fieldtype": "Section Break", + "label": "Job Card" + }, + { + "fieldname": "column_break_24", + "fieldtype": "Column Break" + }, + { + "default": "0", + "description": "Allow transferring raw materials even after the Required Quantity is fulfilled", + "fieldname": "job_card_excess_transfer", + "fieldtype": "Check", + "label": "Allow Excess Material Transfer" } ], "icon": "icon-wrench", "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2021-03-16 15:54:38.967341", + "modified": "2021-09-13 22:09:09.401559", "modified_by": "Administrator", "module": "Manufacturing", "name": "Manufacturing Settings", diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 4ccfa62b5a0..0459489185e 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -1264,9 +1264,9 @@ class StockEntry(StockController): po_qty = frappe.db.sql("""select qty, produced_qty, material_transferred_for_manufacturing from `tabWork Order` where name=%s""", self.work_order, as_dict=1)[0] - manufacturing_qty = flt(po_qty.qty) + manufacturing_qty = flt(po_qty.qty) or 1 produced_qty = flt(po_qty.produced_qty) - trans_qty = flt(po_qty.material_transferred_for_manufacturing) + trans_qty = flt(po_qty.material_transferred_for_manufacturing) or 1 for item in transferred_materials: qty= item.qty From 9aa6f52edd1ceecedd7bcd890d99572e2fb1cec9 Mon Sep 17 00:00:00 2001 From: Shariq Ansari <30859809+shariquerik@users.noreply.github.com> Date: Tue, 14 Sep 2021 12:49:08 +0530 Subject: [PATCH 049/116] fix: Webform Permission for custom doctype (#26232) * fix: Webform Permission for custom doctype * fix: sider fix * fix: app check condition for getting correct list_context * chore: Better naming convention * test: Added test case to check permission for webform of custom doctype * chore: linting issue * chore: linting issue * fix: sider fix * test: minor changes * chore: linter and better naming method * chore: linter fix Co-authored-by: Nabin Hait --- .../controllers/website_list_for_contact.py | 36 ++++- erpnext/hooks.py | 1 + erpnext/tests/test_webform.py | 138 ++++++++++++++++++ 3 files changed, 173 insertions(+), 2 deletions(-) create mode 100644 erpnext/tests/test_webform.py diff --git a/erpnext/controllers/website_list_for_contact.py b/erpnext/controllers/website_list_for_contact.py index ff2ed45bd24..8e5952c4a38 100644 --- a/erpnext/controllers/website_list_for_contact.py +++ b/erpnext/controllers/website_list_for_contact.py @@ -7,6 +7,7 @@ import json import frappe from frappe import _ +from frappe.modules.utils import get_module_app from frappe.utils import flt, has_common from frappe.utils.user import is_website_user @@ -21,8 +22,32 @@ def get_list_context(context=None): "get_list": get_transaction_list } +def get_webform_list_context(module): + if get_module_app(module) != 'erpnext': + return + return { + "get_list": get_webform_transaction_list + } -def get_transaction_list(doctype, txt=None, filters=None, limit_start=0, limit_page_length=20, order_by="modified"): +def get_webform_transaction_list(doctype, txt=None, filters=None, limit_start=0, limit_page_length=20, order_by="modified"): + """ Get List of transactions for custom doctypes """ + from frappe.www.list import get_list + + if not filters: + filters = [] + + meta = frappe.get_meta(doctype) + + for d in meta.fields: + if d.fieldtype == 'Link' and d.fieldname != 'amended_from': + allowed_docs = [d.name for d in get_transaction_list(doctype=d.options, custom=True)] + allowed_docs.append('') + filters.append((d.fieldname, 'in', allowed_docs)) + + return get_list(doctype, txt, filters, limit_start, limit_page_length, ignore_permissions=False, + fields=None, order_by="modified") + +def get_transaction_list(doctype, txt=None, filters=None, limit_start=0, limit_page_length=20, order_by="modified", custom=False): user = frappe.session.user ignore_permissions = False @@ -46,7 +71,7 @@ def get_transaction_list(doctype, txt=None, filters=None, limit_start=0, limit_p filters.append(('customer', 'in', customers)) elif suppliers: filters.append(('supplier', 'in', suppliers)) - else: + elif not custom: return [] if doctype == 'Request for Quotation': @@ -56,9 +81,16 @@ def get_transaction_list(doctype, txt=None, filters=None, limit_start=0, limit_p # Since customers and supplier do not have direct access to internal doctypes ignore_permissions = True + if not customers and not suppliers and custom: + ignore_permissions = False + filters = [] + transactions = get_list_for_transactions(doctype, txt, filters, limit_start, limit_page_length, fields='name', ignore_permissions=ignore_permissions, order_by='modified desc') + if custom: + return transactions + return post_process(doctype, transactions) def get_list_for_transactions(doctype, txt, filters, limit_start, limit_page_length=20, diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 5b6e1eeafcd..be05e35113e 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -62,6 +62,7 @@ treeviews = ['Account', 'Cost Center', 'Warehouse', 'Item Group', 'Customer Grou # website update_website_context = ["erpnext.shopping_cart.utils.update_website_context", "erpnext.education.doctype.education_settings.education_settings.update_website_context"] my_account_context = "erpnext.shopping_cart.utils.update_my_account_context" +webform_list_context = "erpnext.controllers.website_list_for_contact.get_webform_list_context" calendars = ["Task", "Work Order", "Leave Application", "Sales Order", "Holiday List", "Course Schedule"] diff --git a/erpnext/tests/test_webform.py b/erpnext/tests/test_webform.py new file mode 100644 index 00000000000..19255db33c5 --- /dev/null +++ b/erpnext/tests/test_webform.py @@ -0,0 +1,138 @@ +import unittest + +import frappe + +from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order + + +class TestWebsite(unittest.TestCase): + def test_permission_for_custom_doctype(self): + create_user('Supplier 1', 'supplier1@gmail.com') + create_user('Supplier 2', 'supplier2@gmail.com') + create_supplier_with_contact('Supplier1', 'All Supplier Groups', 'Supplier 1', 'supplier1@gmail.com') + create_supplier_with_contact('Supplier2', 'All Supplier Groups', 'Supplier 2', 'supplier2@gmail.com') + po1 = create_purchase_order(supplier='Supplier1') + po2 = create_purchase_order(supplier='Supplier2') + + create_custom_doctype() + create_webform() + create_order_assignment(supplier='Supplier1', po = po1.name) + create_order_assignment(supplier='Supplier2', po = po2.name) + + frappe.set_user("Administrator") + # checking if data consist of all order assignment of Supplier1 and Supplier2 + self.assertTrue('Supplier1' and 'Supplier2' in [data.supplier for data in get_data()]) + + frappe.set_user("supplier1@gmail.com") + # checking if data only consist of order assignment of Supplier1 + self.assertTrue('Supplier1' in [data.supplier for data in get_data()]) + self.assertFalse([data.supplier for data in get_data() if data.supplier != 'Supplier1']) + + frappe.set_user("supplier2@gmail.com") + # checking if data only consist of order assignment of Supplier2 + self.assertTrue('Supplier2' in [data.supplier for data in get_data()]) + self.assertFalse([data.supplier for data in get_data() if data.supplier != 'Supplier2']) + + frappe.set_user("Administrator") + +def get_data(): + webform_list_contexts = frappe.get_hooks('webform_list_context') + if webform_list_contexts: + context = frappe._dict(frappe.get_attr(webform_list_contexts[0])('Buying') or {}) + kwargs = dict(doctype='Order Assignment', order_by = 'modified desc') + return context.get_list(**kwargs) + +def create_user(name, email): + frappe.get_doc({ + 'doctype': 'User', + 'send_welcome_email': 0, + 'user_type': 'Website User', + 'first_name': name, + 'email': email, + 'roles': [{"doctype": "Has Role", "role": "Supplier"}] + }).insert(ignore_if_duplicate = True) + +def create_supplier_with_contact(name, group, contact_name, contact_email): + supplier = frappe.get_doc({ + 'doctype': 'Supplier', + 'supplier_name': name, + 'supplier_group': group + }).insert(ignore_if_duplicate = True) + + if not frappe.db.exists('Contact', contact_name+'-1-'+name): + new_contact = frappe.new_doc("Contact") + new_contact.first_name = contact_name + new_contact.is_primary_contact = True, + new_contact.append('links', { + "link_doctype": "Supplier", + "link_name": supplier.name + }) + new_contact.append('email_ids', { + "email_id": contact_email, + "is_primary": 1 + }) + + new_contact.insert(ignore_mandatory=True) + +def create_custom_doctype(): + frappe.get_doc({ + 'doctype': 'DocType', + 'name': 'Order Assignment', + 'module': 'Buying', + 'custom': 1, + 'autoname': 'field:po', + 'fields': [ + {'label': 'PO', 'fieldname': 'po', 'fieldtype': 'Link', 'options': 'Purchase Order'}, + {'label': 'Supplier', 'fieldname': 'supplier', 'fieldtype': 'Data', "fetch_from": "po.supplier"} + ], + 'permissions': [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + }, + { + "read": 1, + "role": "Supplier" + } + ] + }).insert(ignore_if_duplicate = True) + +def create_webform(): + frappe.get_doc({ + 'doctype': 'Web Form', + 'module': 'Buying', + 'title': 'SO Schedule', + 'route': 'so-schedule', + 'doc_type': 'Order Assignment', + 'web_form_fields': [ + { + 'doctype': 'Web Form Field', + 'fieldname': 'po', + 'fieldtype': 'Link', + 'options': 'Purchase Order', + 'label': 'PO' + }, + { + 'doctype': 'Web Form Field', + 'fieldname': 'supplier', + 'fieldtype': 'Data', + 'label': 'Supplier' + } + ] + + }).insert(ignore_if_duplicate = True) + +def create_order_assignment(supplier, po): + frappe.get_doc({ + 'doctype': 'Order Assignment', + 'po': po, + 'supplier': supplier, + }).insert(ignore_if_duplicate = True) \ No newline at end of file From aa82624f31059dc03a17472c8aa2c88c075ae34a Mon Sep 17 00:00:00 2001 From: DeeMysterio Date: Tue, 14 Sep 2021 13:58:18 +0530 Subject: [PATCH 050/116] Merge pull request #27281 from DeeMysterio/party-specific-items feat: link items to supplier / customer --- erpnext/buying/doctype/supplier/supplier.json | 8 +- .../supplier_item_group.json | 77 ------------------- .../supplier_item_group.py | 20 ----- .../test_supplier_item_group.py | 11 --- erpnext/controllers/queries.py | 30 +++++--- erpnext/patches.txt | 1 + ...ier_item_group_with_party_specific_item.py | 17 ++++ .../selling/doctype/customer/customer.json | 22 +++--- .../doctype/party_specific_item}/__init__.py | 0 .../party_specific_item.js} | 2 +- .../party_specific_item.json | 77 +++++++++++++++++++ .../party_specific_item.py | 19 +++++ .../test_party_specific_item.py | 38 +++++++++ erpnext/selling/sales_common.js | 2 +- 14 files changed, 189 insertions(+), 135 deletions(-) delete mode 100644 erpnext/buying/doctype/supplier_item_group/supplier_item_group.json delete mode 100644 erpnext/buying/doctype/supplier_item_group/supplier_item_group.py delete mode 100644 erpnext/buying/doctype/supplier_item_group/test_supplier_item_group.py create mode 100644 erpnext/patches/v13_0/replace_supplier_item_group_with_party_specific_item.py rename erpnext/{buying/doctype/supplier_item_group => selling/doctype/party_specific_item}/__init__.py (100%) rename erpnext/{buying/doctype/supplier_item_group/supplier_item_group.js => selling/doctype/party_specific_item/party_specific_item.js} (79%) create mode 100644 erpnext/selling/doctype/party_specific_item/party_specific_item.json create mode 100644 erpnext/selling/doctype/party_specific_item/party_specific_item.py create mode 100644 erpnext/selling/doctype/party_specific_item/test_party_specific_item.py diff --git a/erpnext/buying/doctype/supplier/supplier.json b/erpnext/buying/doctype/supplier/supplier.json index c7a5db59941..12a09cdd0ec 100644 --- a/erpnext/buying/doctype/supplier/supplier.json +++ b/erpnext/buying/doctype/supplier/supplier.json @@ -433,12 +433,12 @@ "image_field": "image", "links": [ { - "group": "Item Group", - "link_doctype": "Supplier Item Group", - "link_fieldname": "supplier" + "group": "Allowed Items", + "link_doctype": "Party Specific Item", + "link_fieldname": "party" } ], - "modified": "2021-08-27 18:02:44.314077", + "modified": "2021-09-06 17:37:56.522233", "modified_by": "Administrator", "module": "Buying", "name": "Supplier", diff --git a/erpnext/buying/doctype/supplier_item_group/supplier_item_group.json b/erpnext/buying/doctype/supplier_item_group/supplier_item_group.json deleted file mode 100644 index 1971458f61e..00000000000 --- a/erpnext/buying/doctype/supplier_item_group/supplier_item_group.json +++ /dev/null @@ -1,77 +0,0 @@ -{ - "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", - "reqd": 1 - }, - { - "fieldname": "item_group", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Item Group", - "options": "Item Group", - "reqd": 1 - } - ], - "index_web_pages_for_search": 1, - "links": [], - "modified": "2021-05-19 13:48:16.742303", - "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 - }, - { - "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", - "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 deleted file mode 100644 index 6d71f7d5160..00000000000 --- a/erpnext/buying/doctype/supplier_item_group/supplier_item_group.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- 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.model.document import Document - - -class SupplierItemGroup(Document): - 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.")) 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 deleted file mode 100644 index 55ba85ef2d6..00000000000 --- a/erpnext/buying/doctype/supplier_item_group/test_supplier_item_group.py +++ /dev/null @@ -1,11 +0,0 @@ -# -*- 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 diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index ccd417be04f..9f28646a0b9 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -7,6 +7,7 @@ import json from collections import defaultdict import frappe +from frappe import scrub from frappe.desk.reportview import get_filters_cond, get_match_cond from frappe.utils import nowdate, unique @@ -223,18 +224,29 @@ 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 and isinstance(filters, dict) and filters.get('supplier'): - item_group_list = frappe.get_all('Supplier Item Group', - filters = {'supplier': filters.get('supplier')}, fields = ['item_group']) + if filters and isinstance(filters, dict): + if filters.get('customer') or filters.get('supplier'): + party = filters.get('customer') or filters.get('supplier') + item_rules_list = frappe.get_all('Party Specific Item', + filters = {'party': party}, fields = ['restrict_based_on', 'based_on_value']) - item_groups = [] - for i in item_group_list: - item_groups.append(i.item_group) + filters_dict = {} + for rule in item_rules_list: + if rule['restrict_based_on'] == 'Item': + rule['restrict_based_on'] = 'name' + filters_dict[rule.restrict_based_on] = [] - del filters['supplier'] + for rule in item_rules_list: + filters_dict[rule.restrict_based_on].append(rule.based_on_value) + + for filter in filters_dict: + filters[scrub(filter)] = ['in', filters_dict[filter]] + + if filters.get('customer'): + del filters['customer'] + else: + del filters['supplier'] - if item_groups: - filters['item_group'] = ['in', item_groups] description_cond = '' if frappe.db.count('Item', cache=True) < 50000: diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 15b196fd780..3bc40a17e06 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -304,5 +304,6 @@ erpnext.patches.v13_0.set_operation_time_based_on_operating_cost erpnext.patches.v13_0.validate_options_for_data_field erpnext.patches.v13_0.create_gst_payment_entry_fields erpnext.patches.v14_0.delete_shopify_doctypes +erpnext.patches.v13_0.replace_supplier_item_group_with_party_specific_item erpnext.patches.v13_0.update_dates_in_tax_withholding_category erpnext.patches.v14_0.update_opportunity_currency_fields diff --git a/erpnext/patches/v13_0/replace_supplier_item_group_with_party_specific_item.py b/erpnext/patches/v13_0/replace_supplier_item_group_with_party_specific_item.py new file mode 100644 index 00000000000..ba96fdd2266 --- /dev/null +++ b/erpnext/patches/v13_0/replace_supplier_item_group_with_party_specific_item.py @@ -0,0 +1,17 @@ +# Copyright (c) 2019, Frappe and Contributors +# License: GNU General Public License v3. See license.txt + +import frappe + + +def execute(): + if frappe.db.table_exists('Supplier Item Group'): + frappe.reload_doc("selling", "doctype", "party_specific_item") + sig = frappe.db.get_all("Supplier Item Group", fields=["name", "supplier", "item_group"]) + for item in sig: + psi = frappe.new_doc("Party Specific Item") + psi.party_type = "Supplier" + psi.party = item.supplier + psi.restrict_based_on = "Item Group" + psi.based_on_value = item.item_group + psi.insert() diff --git a/erpnext/selling/doctype/customer/customer.json b/erpnext/selling/doctype/customer/customer.json index 5913b849eb7..e811435e669 100644 --- a/erpnext/selling/doctype/customer/customer.json +++ b/erpnext/selling/doctype/customer/customer.json @@ -20,7 +20,6 @@ "tax_withholding_category", "default_bank_account", "lead_name", - "prospect", "opportunity_name", "image", "column_break0", @@ -214,7 +213,8 @@ "fieldtype": "Link", "ignore_user_permissions": 1, "label": "Represents Company", - "options": "Company" + "options": "Company", + "unique": 1 }, { "depends_on": "represents_company", @@ -497,14 +497,6 @@ "label": "Tax Withholding Category", "options": "Tax Withholding Category" }, - { - "fieldname": "prospect", - "fieldtype": "Link", - "label": "Prospect", - "no_copy": 1, - "options": "Prospect", - "print_hide": 1 - }, { "fieldname": "opportunity_name", "fieldtype": "Link", @@ -518,8 +510,14 @@ "idx": 363, "image_field": "image", "index_web_pages_for_search": 1, - "links": [], - "modified": "2021-08-25 18:56:09.929905", + "links": [ + { + "group": "Allowed Items", + "link_doctype": "Party Specific Item", + "link_fieldname": "party" + } + ], + "modified": "2021-09-06 17:38:54.196663", "modified_by": "Administrator", "module": "Selling", "name": "Customer", diff --git a/erpnext/buying/doctype/supplier_item_group/__init__.py b/erpnext/selling/doctype/party_specific_item/__init__.py similarity index 100% rename from erpnext/buying/doctype/supplier_item_group/__init__.py rename to erpnext/selling/doctype/party_specific_item/__init__.py diff --git a/erpnext/buying/doctype/supplier_item_group/supplier_item_group.js b/erpnext/selling/doctype/party_specific_item/party_specific_item.js similarity index 79% rename from erpnext/buying/doctype/supplier_item_group/supplier_item_group.js rename to erpnext/selling/doctype/party_specific_item/party_specific_item.js index f7da90d98d6..077b93631ec 100644 --- a/erpnext/buying/doctype/supplier_item_group/supplier_item_group.js +++ b/erpnext/selling/doctype/party_specific_item/party_specific_item.js @@ -1,7 +1,7 @@ // Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -frappe.ui.form.on('Supplier Item Group', { +frappe.ui.form.on('Party Specific Item', { // refresh: function(frm) { // } diff --git a/erpnext/selling/doctype/party_specific_item/party_specific_item.json b/erpnext/selling/doctype/party_specific_item/party_specific_item.json new file mode 100644 index 00000000000..32b5d478bb5 --- /dev/null +++ b/erpnext/selling/doctype/party_specific_item/party_specific_item.json @@ -0,0 +1,77 @@ +{ + "actions": [], + "creation": "2021-08-27 19:28:07.559978", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "party_type", + "party", + "column_break_3", + "restrict_based_on", + "based_on_value" + ], + "fields": [ + { + "fieldname": "party_type", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Party Type", + "options": "Customer\nSupplier", + "reqd": 1 + }, + { + "fieldname": "party", + "fieldtype": "Dynamic Link", + "in_list_view": 1, + "label": "Party Name", + "options": "party_type", + "reqd": 1 + }, + { + "fieldname": "restrict_based_on", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Restrict Items Based On", + "options": "Item\nItem Group\nBrand", + "reqd": 1 + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "fieldname": "based_on_value", + "fieldtype": "Dynamic Link", + "in_list_view": 1, + "label": "Based On Value", + "options": "restrict_based_on", + "reqd": 1 + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2021-09-14 13:27:58.612334", + "modified_by": "Administrator", + "module": "Selling", + "name": "Party Specific Item", + "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", + "title_field": "party", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/selling/doctype/party_specific_item/party_specific_item.py b/erpnext/selling/doctype/party_specific_item/party_specific_item.py new file mode 100644 index 00000000000..a408af56420 --- /dev/null +++ b/erpnext/selling/doctype/party_specific_item/party_specific_item.py @@ -0,0 +1,19 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +import frappe +from frappe import _ +from frappe.model.document import Document + + +class PartySpecificItem(Document): + def validate(self): + exists = frappe.db.exists({ + 'doctype': 'Party Specific Item', + 'party_type': self.party_type, + 'party': self.party, + 'restrict_based_on': self.restrict_based_on, + 'based_on': self.based_on_value, + }) + if exists: + frappe.throw(_("This item filter has already been applied for the {0}").format(self.party_type)) diff --git a/erpnext/selling/doctype/party_specific_item/test_party_specific_item.py b/erpnext/selling/doctype/party_specific_item/test_party_specific_item.py new file mode 100644 index 00000000000..874a3645929 --- /dev/null +++ b/erpnext/selling/doctype/party_specific_item/test_party_specific_item.py @@ -0,0 +1,38 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt + +import unittest + +import frappe + +from erpnext.controllers.queries import item_query + +test_dependencies = ['Item', 'Customer', 'Supplier'] + +def create_party_specific_item(**args): + psi = frappe.new_doc("Party Specific Item") + psi.party_type = args.get('party_type') + psi.party = args.get('party') + psi.restrict_based_on = args.get('restrict_based_on') + psi.based_on_value = args.get('based_on_value') + psi.insert() + +class TestPartySpecificItem(unittest.TestCase): + def setUp(self): + self.customer = frappe.get_last_doc("Customer") + self.supplier = frappe.get_last_doc("Supplier") + self.item = frappe.get_last_doc("Item") + + def test_item_query_for_customer(self): + create_party_specific_item(party_type='Customer', party=self.customer.name, restrict_based_on='Item', based_on_value=self.item.name) + filters = {'is_sales_item': 1, 'customer': self.customer.name} + items = item_query(doctype= 'Item', txt= '', searchfield= 'name', start= 0, page_len= 20,filters=filters, as_dict= False) + for item in items: + self.assertEqual(item[0], self.item.name) + + def test_item_query_for_supplier(self): + create_party_specific_item(party_type='Supplier', party=self.supplier.name, restrict_based_on='Item Group', based_on_value=self.item.item_group) + filters = {'supplier': self.supplier.name, 'is_purchase_item': 1} + items = item_query(doctype= 'Item', txt= '', searchfield= 'name', start= 0, page_len= 20,filters=filters, as_dict= False) + for item in items: + self.assertEqual(item[2], self.item.item_group) diff --git a/erpnext/selling/sales_common.js b/erpnext/selling/sales_common.js index 6a09109bfd4..ddd4c4e6a5a 100644 --- a/erpnext/selling/sales_common.js +++ b/erpnext/selling/sales_common.js @@ -63,7 +63,7 @@ erpnext.selling.SellingController = class SellingController extends erpnext.Tran this.frm.set_query("item_code", "items", function() { return { query: "erpnext.controllers.queries.item_query", - filters: {'is_sales_item': 1} + filters: {'is_sales_item': 1, 'customer': cur_frm.doc.customer} } }); } From 469bb8d977f135532a4d4afe4352ece58b2de4b4 Mon Sep 17 00:00:00 2001 From: marination Date: Tue, 14 Sep 2021 16:27:39 +0530 Subject: [PATCH 051/116] test: Work Order Material Transfer against Job Card - Tests for multiple items transfer and excess transfer against JC - Remove unused __future__ imports - Changed Copyright year - Sider: (js) Added space before if --- .../doctype/job_card/job_card.js | 3 +- .../doctype/job_card/job_card.py | 5 +- .../doctype/job_card/test_job_card.py | 107 +++++++++++++++++- .../doctype/work_order/test_work_order.py | 7 +- 4 files changed, 107 insertions(+), 15 deletions(-) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.js b/erpnext/manufacturing/doctype/job_card/job_card.js index 9b8f81bc244..35be38813e5 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.js +++ b/erpnext/manufacturing/doctype/job_card/job_card.js @@ -26,8 +26,9 @@ frappe.ui.form.on('Job Card', { refresh: function(frm) { frappe.flags.pause_job = 0; frappe.flags.resume_job = 0; + let has_items = frm.doc.items && frm.doc.items.length; - if(!frm.doc.__islocal && frm.doc.items && frm.doc.items.length && frm.doc.docstatus < 2) { + if (!frm.doc.__islocal && has_items && frm.doc.docstatus < 2) { let to_request = frm.doc.for_quantity > frm.doc.transferred_qty; let excess_transfer_allowed = frm.doc.__onload.job_card_excess_transfer; diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index 1906bf6a9db..3209546a12c 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -1,9 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt - -from __future__ import unicode_literals - import datetime import json diff --git a/erpnext/manufacturing/doctype/job_card/test_job_card.py b/erpnext/manufacturing/doctype/job_card/test_job_card.py index 80295bba635..57336e1b330 100644 --- a/erpnext/manufacturing/doctype/job_card/test_job_card.py +++ b/erpnext/manufacturing/doctype/job_card/test_job_card.py @@ -1,22 +1,38 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals - import unittest import frappe from frappe.utils import random_string -from erpnext.manufacturing.doctype.job_card.job_card import OperationMismatchError, OverlapError +from erpnext.manufacturing.doctype.job_card.job_card import ( + make_stock_entry as make_stock_entry_from_jc, + OperationMismatchError, + OverlapError +) from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record from erpnext.manufacturing.doctype.workstation.test_workstation import make_workstation +from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry class TestJobCard(unittest.TestCase): def setUp(self): - self.work_order = make_wo_order_test_record(item="_Test FG Item 2", qty=2) + transfer_material_against, source_warehouse = None, None + tests_that_transfer_against_jc = ("test_job_card_multiple_materials_transfer", + "test_job_card_excess_material_transfer") + + if self._testMethodName in tests_that_transfer_against_jc: + transfer_material_against = "Job Card" + source_warehouse = "Stores - _TC" + + self.work_order = make_wo_order_test_record( + item="_Test FG Item 2", + qty=2, + transfer_material_against=transfer_material_against, + source_warehouse=source_warehouse + ) def tearDown(self): frappe.db.rollback() @@ -96,3 +112,84 @@ class TestJobCard(unittest.TestCase): "employee": employee, }) self.assertRaises(OverlapError, jc2.save) + + def test_job_card_multiple_materials_transfer(self): + "Test transferring RMs separately against Job Card with multiple RMs." + make_stock_entry( + item_code="_Test Item", + target="Stores - _TC", + qty=10, + basic_rate=100 + ) + make_stock_entry( + item_code="_Test Item Home Desktop Manufactured", + target="Stores - _TC", + qty=6, + basic_rate=100 + ) + + job_card_name = frappe.db.get_value("Job Card", {'work_order': self.work_order.name}) + job_card = frappe.get_doc("Job Card", job_card_name) + + transfer_entry_1 = make_stock_entry_from_jc(job_card_name) + del transfer_entry_1.items[1] # transfer only 1 of 2 RMs + transfer_entry_1.insert() + transfer_entry_1.submit() + + job_card.reload() + + self.assertEqual(transfer_entry_1.fg_completed_qty, 2) + self.assertEqual(job_card.transferred_qty, 2) + + # transfer second RM + transfer_entry_2 = make_stock_entry_from_jc(job_card_name) + del transfer_entry_2.items[0] + transfer_entry_2.insert() + transfer_entry_2.submit() + + # 'For Quantity' here will be 0 since + # transfer was made for 2 fg qty in first transfer Stock Entry + self.assertEqual(transfer_entry_2.fg_completed_qty, 0) + + def test_job_card_excess_material_transfer(self): + "Test transferring more than required RM against Job Card." + make_stock_entry(item_code="_Test Item", target="Stores - _TC", + qty=25, basic_rate=100) + make_stock_entry(item_code="_Test Item Home Desktop Manufactured", + target="Stores - _TC", qty=15, basic_rate=100) + + job_card_name = frappe.db.get_value("Job Card", {'work_order': self.work_order.name}) + job_card = frappe.get_doc("Job Card", job_card_name) + + # fully transfer both RMs + transfer_entry_1 = make_stock_entry_from_jc(job_card_name) + transfer_entry_1.insert() + transfer_entry_1.submit() + + # transfer extra qty of both RM due to previously damaged RM + transfer_entry_2 = make_stock_entry_from_jc(job_card_name) + # deliberately change 'For Quantity' + transfer_entry_2.fg_completed_qty = 1 + transfer_entry_2.items[0].qty = 5 + transfer_entry_2.items[1].qty = 3 + transfer_entry_2.insert() + transfer_entry_2.submit() + + job_card.reload() + self.assertGreater(job_card.transferred_qty, job_card.for_quantity) + + # Check if 'For Quantity' is negative + # as 'transferred_qty' > Qty to Manufacture + transfer_entry_3 = make_stock_entry_from_jc(job_card_name) + self.assertEqual(transfer_entry_3.fg_completed_qty, 0) + + job_card.append("time_logs", { + "from_time": "2021-01-01 00:01:00", + "to_time": "2021-01-01 06:00:00", + "completed_qty": 2 + }) + job_card.save() + job_card.submit() + + # JC is Completed with excess transfer + self.assertEqual(job_card.status, "Completed") \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index bb431498636..d87b5ec654f 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -1,9 +1,5 @@ -# 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 unittest import frappe @@ -814,6 +810,7 @@ def make_wo_order_test_record(**args): wo_order.get_items_and_operations_from_bom() wo_order.sales_order = args.sales_order or None wo_order.planned_start_date = args.planned_start_date or now() + wo_order.transfer_material_against = args.transfer_material_against or "Work Order" if args.source_warehouse: for item in wo_order.get("required_items"): From fea51f40825fa281e1a331a42d77648abaf909b1 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 14 Sep 2021 19:04:32 +0530 Subject: [PATCH 052/116] chore: isort [skip ci] --- erpnext/manufacturing/doctype/job_card/test_job_card.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/manufacturing/doctype/job_card/test_job_card.py b/erpnext/manufacturing/doctype/job_card/test_job_card.py index 57336e1b330..ea5d364a9ce 100644 --- a/erpnext/manufacturing/doctype/job_card/test_job_card.py +++ b/erpnext/manufacturing/doctype/job_card/test_job_card.py @@ -6,10 +6,9 @@ import unittest import frappe from frappe.utils import random_string +from erpnext.manufacturing.doctype.job_card.job_card import OperationMismatchError, OverlapError from erpnext.manufacturing.doctype.job_card.job_card import ( make_stock_entry as make_stock_entry_from_jc, - OperationMismatchError, - OverlapError ) from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record from erpnext.manufacturing.doctype.workstation.test_workstation import make_workstation From c53b78e712428f9b83ecd1825891e7287eb325e4 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Tue, 14 Sep 2021 20:28:48 +0530 Subject: [PATCH 053/116] fix: Patch for updating tax withholding category dates (#27489) --- ...pdate_dates_in_tax_withholding_category.py | 14 +- erpnext/regional/india/setup.py | 123 +++++++++--------- 2 files changed, 69 insertions(+), 68 deletions(-) diff --git a/erpnext/patches/v13_0/update_dates_in_tax_withholding_category.py b/erpnext/patches/v13_0/update_dates_in_tax_withholding_category.py index 2af7f954128..90fb50fb42c 100644 --- a/erpnext/patches/v13_0/update_dates_in_tax_withholding_category.py +++ b/erpnext/patches/v13_0/update_dates_in_tax_withholding_category.py @@ -3,8 +3,6 @@ import frappe -from erpnext.accounts.utils import get_fiscal_year - def execute(): frappe.reload_doc('accounts', 'doctype', 'Tax Withholding Rate') @@ -13,12 +11,14 @@ def execute(): tds_category_rates = frappe.get_all('Tax Withholding Rate', fields=['name', 'fiscal_year']) fiscal_year_map = {} - for rate in tds_category_rates: - if not fiscal_year_map.get(rate.fiscal_year): - fiscal_year_map[rate.fiscal_year] = get_fiscal_year(fiscal_year=rate.fiscal_year) + fiscal_year_details = frappe.get_all('Fiscal Year', fields=['name', 'year_start_date', 'year_end_date']) - from_date = fiscal_year_map.get(rate.fiscal_year)[1] - to_date = fiscal_year_map.get(rate.fiscal_year)[2] + for d in fiscal_year_details: + fiscal_year_map.setdefault(d.name, d) + + for rate in tds_category_rates: + from_date = fiscal_year_map.get(rate.fiscal_year).get('year_start_date') + to_date = fiscal_year_map.get(rate.fiscal_year).get('year_end_date') frappe.db.set_value('Tax Withholding Rate', rate.name, { 'from_date': from_date, diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index 963c4075cd2..79dc4b8fc97 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -758,11 +758,11 @@ def set_tax_withholding_category(company): accounts = [dict(company=company, account=tds_account)] try: - fiscal_year = get_fiscal_year(today(), verbose=0, company=company)[0] + fiscal_year_details = get_fiscal_year(today(), verbose=0, company=company) except FiscalYearError: pass - docs = get_tds_details(accounts, fiscal_year) + docs = get_tds_details(accounts, fiscal_year_details) for d in docs: if not frappe.db.exists("Tax Withholding Category", d.get("name")): @@ -777,9 +777,10 @@ def set_tax_withholding_category(company): if accounts: doc.append("accounts", accounts[0]) - if fiscal_year: + if fiscal_year_details: # if fiscal year don't match with any of the already entered data, append rate row - fy_exist = [k for k in doc.get('rates') if k.get('fiscal_year')==fiscal_year] + fy_exist = [k for k in doc.get('rates') if k.get('from_date') <= fiscal_year_details[1] \ + and k.get('to_date') >= fiscal_year_details[2]] if not fy_exist: doc.append("rates", d.get('rates')[0]) @@ -802,149 +803,149 @@ def set_tds_account(docs, company): } ]) -def get_tds_details(accounts, fiscal_year): +def get_tds_details(accounts, fiscal_year_details): # bootstrap default tax withholding sections return [ dict(name="TDS - 194C - Company", category_name="Payment to Contractors (Single / Aggregate)", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 2, - "single_threshold": 30000, "cumulative_threshold": 100000}]), + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 2, "single_threshold": 30000, "cumulative_threshold": 100000}]), dict(name="TDS - 194C - Individual", category_name="Payment to Contractors (Single / Aggregate)", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 1, - "single_threshold": 30000, "cumulative_threshold": 100000}]), + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 1, "single_threshold": 30000, "cumulative_threshold": 100000}]), dict(name="TDS - 194C - No PAN / Invalid PAN", category_name="Payment to Contractors (Single / Aggregate)", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 20, - "single_threshold": 30000, "cumulative_threshold": 100000}]), + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 20, "single_threshold": 30000, "cumulative_threshold": 100000}]), dict(name="TDS - 194D - Company", category_name="Insurance Commission", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 5, - "single_threshold": 15000, "cumulative_threshold": 0}]), + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 5, "single_threshold": 15000, "cumulative_threshold": 0}]), dict(name="TDS - 194D - Company Assessee", category_name="Insurance Commission", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 10, - "single_threshold": 15000, "cumulative_threshold": 0}]), + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 10, "single_threshold": 15000, "cumulative_threshold": 0}]), dict(name="TDS - 194D - Individual", category_name="Insurance Commission", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 5, - "single_threshold": 15000, "cumulative_threshold": 0}]), + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 5, "single_threshold": 15000, "cumulative_threshold": 0}]), dict(name="TDS - 194D - No PAN / Invalid PAN", category_name="Insurance Commission", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 20, - "single_threshold": 15000, "cumulative_threshold": 0}]), + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 20, "single_threshold": 15000, "cumulative_threshold": 0}]), dict(name="TDS - 194DA - Company", category_name="Non-exempt payments made under a life insurance policy", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 1, - "single_threshold": 100000, "cumulative_threshold": 0}]), + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 1, "single_threshold": 100000, "cumulative_threshold": 0}]), dict(name="TDS - 194DA - Individual", category_name="Non-exempt payments made under a life insurance policy", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 1, - "single_threshold": 100000, "cumulative_threshold": 0}]), + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 1, "single_threshold": 100000, "cumulative_threshold": 0}]), dict(name="TDS - 194DA - No PAN / Invalid PAN", category_name="Non-exempt payments made under a life insurance policy", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 20, - "single_threshold": 100000, "cumulative_threshold": 0}]), + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 20, "single_threshold": 100000, "cumulative_threshold": 0}]), dict(name="TDS - 194H - Company", category_name="Commission / Brokerage", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 5, - "single_threshold": 15000, "cumulative_threshold": 0}]), + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 5, "single_threshold": 15000, "cumulative_threshold": 0}]), dict(name="TDS - 194H - Individual", category_name="Commission / Brokerage", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 5, - "single_threshold": 15000, "cumulative_threshold": 0}]), + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 5, "single_threshold": 15000, "cumulative_threshold": 0}]), dict(name="TDS - 194H - No PAN / Invalid PAN", category_name="Commission / Brokerage", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 20, - "single_threshold": 15000, "cumulative_threshold": 0}]), + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 20, "single_threshold": 15000, "cumulative_threshold": 0}]), dict(name="TDS - 194I - Rent - Company", category_name="Rent", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 10, - "single_threshold": 180000, "cumulative_threshold": 0}]), + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 10, "single_threshold": 180000, "cumulative_threshold": 0}]), dict(name="TDS - 194I - Rent - Individual", category_name="Rent", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 10, - "single_threshold": 180000, "cumulative_threshold": 0}]), + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 10, "single_threshold": 180000, "cumulative_threshold": 0}]), dict(name="TDS - 194I - Rent - No PAN / Invalid PAN", category_name="Rent", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 20, - "single_threshold": 180000, "cumulative_threshold": 0}]), + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 20, "single_threshold": 180000, "cumulative_threshold": 0}]), dict(name="TDS - 194I - Rent/Machinery - Company", category_name="Rent-Plant / Machinery", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 2, - "single_threshold": 180000, "cumulative_threshold": 0}]), + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 2, "single_threshold": 180000, "cumulative_threshold": 0}]), dict(name="TDS - 194I - Rent/Machinery - Individual", category_name="Rent-Plant / Machinery", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 2, - "single_threshold": 180000, "cumulative_threshold": 0}]), + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 2, "single_threshold": 180000, "cumulative_threshold": 0}]), dict(name="TDS - 194I - Rent/Machinery - No PAN / Invalid PAN", category_name="Rent-Plant / Machinery", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 20, - "single_threshold": 180000, "cumulative_threshold": 0}]), + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 20, "single_threshold": 180000, "cumulative_threshold": 0}]), dict(name="TDS - 194J - Professional Fees - Company", category_name="Professional Fees", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 10, - "single_threshold": 30000, "cumulative_threshold": 0}]), + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 10, "single_threshold": 30000, "cumulative_threshold": 0}]), dict(name="TDS - 194J - Professional Fees - Individual", category_name="Professional Fees", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 10, - "single_threshold": 30000, "cumulative_threshold": 0}]), + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 10, "single_threshold": 30000, "cumulative_threshold": 0}]), dict(name="TDS - 194J - Professional Fees - No PAN / Invalid PAN", category_name="Professional Fees", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 20, - "single_threshold": 30000, "cumulative_threshold": 0}]), + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 20, "single_threshold": 30000, "cumulative_threshold": 0}]), dict(name="TDS - 194J - Director Fees - Company", category_name="Director Fees", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 10, - "single_threshold": 0, "cumulative_threshold": 0}]), + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 10, "single_threshold": 0, "cumulative_threshold": 0}]), dict(name="TDS - 194J - Director Fees - Individual", category_name="Director Fees", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 10, - "single_threshold": 0, "cumulative_threshold": 0}]), + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 10, "single_threshold": 0, "cumulative_threshold": 0}]), dict(name="TDS - 194J - Director Fees - No PAN / Invalid PAN", category_name="Director Fees", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 20, - "single_threshold": 0, "cumulative_threshold": 0}]), + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 20, "single_threshold": 0, "cumulative_threshold": 0}]), dict(name="TDS - 194 - Dividends - Company", category_name="Dividends", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 10, - "single_threshold": 2500, "cumulative_threshold": 0}]), + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 10, "single_threshold": 2500, "cumulative_threshold": 0}]), dict(name="TDS - 194 - Dividends - Individual", category_name="Dividends", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 10, - "single_threshold": 2500, "cumulative_threshold": 0}]), + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 10, "single_threshold": 2500, "cumulative_threshold": 0}]), dict(name="TDS - 194 - Dividends - No PAN / Invalid PAN", category_name="Dividends", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 20, - "single_threshold": 2500, "cumulative_threshold": 0}]) + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 20, "single_threshold": 2500, "cumulative_threshold": 0}]) ] def create_gratuity_rule(): From 2e2985e4f14ddc063a9f481a4ac5c40b18e1f633 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Wed, 15 Sep 2021 00:37:41 +0530 Subject: [PATCH 054/116] fix: calculate operating cost based on BOM Quantity (#27464) * fix: calculate operating cost based on BOM Quantity * fix: added test cases --- erpnext/manufacturing/doctype/bom/bom.py | 12 +++- erpnext/manufacturing/doctype/bom/test_bom.py | 18 ++++++ .../doctype/bom_operation/bom_operation.json | 64 +++++++++++++++++-- 3 files changed, 87 insertions(+), 7 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 28a84b25062..232e3a0b0ff 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -510,8 +510,14 @@ class BOM(WebsiteGenerator): if d.workstation: self.update_rate_and_time(d, update_hour_rate) - self.operating_cost += flt(d.operating_cost) - self.base_operating_cost += flt(d.base_operating_cost) + operating_cost = d.operating_cost + base_operating_cost = d.base_operating_cost + if d.set_cost_based_on_bom_qty: + operating_cost = flt(d.cost_per_unit) * flt(self.quantity) + base_operating_cost = flt(d.base_cost_per_unit) * flt(self.quantity) + + self.operating_cost += flt(operating_cost) + self.base_operating_cost += flt(base_operating_cost) def update_rate_and_time(self, row, update_hour_rate = False): if not row.hour_rate or update_hour_rate: @@ -535,6 +541,8 @@ class BOM(WebsiteGenerator): row.base_hour_rate = flt(row.hour_rate) * flt(self.conversion_rate) row.operating_cost = flt(row.hour_rate) * flt(row.time_in_mins) / 60.0 row.base_operating_cost = flt(row.operating_cost) * flt(self.conversion_rate) + row.cost_per_unit = row.operating_cost / (row.batch_size or 1.0) + row.base_cost_per_unit = row.base_operating_cost / (row.batch_size or 1.0) if update_hour_rate: row.db_update() diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py index 7950dd9d972..706ea268c6e 100644 --- a/erpnext/manufacturing/doctype/bom/test_bom.py +++ b/erpnext/manufacturing/doctype/bom/test_bom.py @@ -108,6 +108,24 @@ class TestBOM(unittest.TestCase): self.assertAlmostEqual(bom.base_raw_material_cost, base_raw_material_cost) self.assertAlmostEqual(bom.base_total_cost, base_raw_material_cost + base_op_cost) + def test_bom_cost_with_batch_size(self): + bom = frappe.copy_doc(test_records[2]) + bom.docstatus = 0 + op_cost = 0.0 + for op_row in bom.operations: + op_row.docstatus = 0 + op_row.batch_size = 2 + op_row.set_cost_based_on_bom_qty = 1 + op_cost += op_row.operating_cost + + bom.save() + + for op_row in bom.operations: + self.assertAlmostEqual(op_row.cost_per_unit, op_row.operating_cost / 2) + + self.assertAlmostEqual(bom.operating_cost, op_cost/2) + bom.delete() + def test_bom_cost_multi_uom_multi_currency_based_on_price_list(self): frappe.db.set_value("Price List", "_Test Price List", "price_not_uom_dependent", 1) for item_code, rate in (("_Test Item", 3600), ("_Test Item Home Desktop Manufactured", 3000)): diff --git a/erpnext/manufacturing/doctype/bom_operation/bom_operation.json b/erpnext/manufacturing/doctype/bom_operation/bom_operation.json index 4458e6db234..ec617f3aaa9 100644 --- a/erpnext/manufacturing/doctype/bom_operation/bom_operation.json +++ b/erpnext/manufacturing/doctype/bom_operation/bom_operation.json @@ -8,15 +8,23 @@ "field_order": [ "sequence_id", "operation", - "workstation", - "description", "col_break1", - "hour_rate", + "workstation", "time_in_mins", - "operating_cost", + "costing_section", + "hour_rate", "base_hour_rate", + "column_break_9", + "operating_cost", "base_operating_cost", + "column_break_11", "batch_size", + "set_cost_based_on_bom_qty", + "cost_per_unit", + "base_cost_per_unit", + "more_information_section", + "description", + "column_break_18", "image" ], "fields": [ @@ -117,13 +125,59 @@ "fieldname": "sequence_id", "fieldtype": "Int", "label": "Sequence ID" + }, + { + "depends_on": "eval:doc.batch_size > 0 && doc.set_cost_based_on_bom_qty", + "fieldname": "cost_per_unit", + "fieldtype": "Float", + "label": "Cost Per Unit", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "base_cost_per_unit", + "fieldtype": "Float", + "hidden": 1, + "label": "Base Cost Per Unit", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "costing_section", + "fieldtype": "Section Break", + "label": "Costing" + }, + { + "fieldname": "column_break_11", + "fieldtype": "Column Break" + }, + { + "fieldname": "more_information_section", + "fieldtype": "Section Break", + "label": "More Information" + }, + { + "fieldname": "column_break_18", + "fieldtype": "Column Break" + }, + { + "fieldname": "column_break_9", + "fieldtype": "Column Break" + }, + { + "default": "0", + "fieldname": "set_cost_based_on_bom_qty", + "fieldtype": "Check", + "label": "Set Operating Cost Based On BOM Quantity" } ], "idx": 1, "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-01-12 14:48:09.596843", + "modified": "2021-09-13 16:45:01.092868", "modified_by": "Administrator", "module": "Manufacturing", "name": "BOM Operation", From 625626b973b399ccc963370edb940e2e9f84d948 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Wed, 15 Sep 2021 10:10:54 +0530 Subject: [PATCH 055/116] fix: Values with same account and different account number in consolidated balance sheet report (#27493) --- .../consolidated_financial_statement.py | 29 ++++++++++++++++--- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py index e419727c2d1..b0cfbac9cb1 100644 --- a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py +++ b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py @@ -260,7 +260,12 @@ def get_company_currency(filters=None): def calculate_values(accounts_by_name, gl_entries_by_account, companies, start_date, filters): for entries in gl_entries_by_account.values(): for entry in entries: - d = accounts_by_name.get(entry.account_name) + if entry.account_number: + account_name = entry.account_number + ' - ' + entry.account_name + else: + account_name = entry.account_name + + d = accounts_by_name.get(account_name) if d: for company in companies: # check if posting date is within the period @@ -307,7 +312,14 @@ def update_parent_account_names(accounts): of account_number and suffix of company abbr. This function adds key called `parent_account_name` which does not have such prefix/suffix. """ - name_to_account_map = { d.name : d.account_name for d in accounts } + name_to_account_map = {} + + for d in accounts: + if d.account_number: + account_name = d.account_number + ' - ' + d.account_name + else: + account_name = d.account_name + name_to_account_map[d.name] = account_name for account in accounts: if account.parent_account: @@ -420,7 +432,11 @@ def set_gl_entries_by_account(from_date, to_date, root_lft, root_rgt, filters, g convert_to_presentation_currency(gl_entries, currency_info, filters.get('company')) for entry in gl_entries: - account_name = entry.account_name + if entry.account_number: + account_name = entry.account_number + ' - ' + entry.account_name + else: + account_name = entry.account_name + validate_entries(account_name, entry, accounts_by_name, accounts) gl_entries_by_account.setdefault(account_name, []).append(entry) @@ -491,7 +507,12 @@ def filter_accounts(accounts, depth=10): parent_children_map = {} accounts_by_name = {} for d in accounts: - accounts_by_name[d.account_name] = d + if d.account_number: + account_name = d.account_number + ' - ' + d.account_name + else: + account_name = d.account_name + accounts_by_name[account_name] = d + parent_children_map.setdefault(d.parent_account or None, []).append(d) filtered_accounts = [] From 759f2b7920f6bf017df555bfaba9d06fccc66d40 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Wed, 15 Sep 2021 10:24:26 +0530 Subject: [PATCH 056/116] fix: Autoname for customer and supplier (#27398) --- .../buying/doctype/buying_settings/buying_settings.json | 4 ++-- erpnext/buying/doctype/supplier/supplier.py | 6 ++++-- erpnext/selling/doctype/customer/customer.py | 6 ++++-- .../doctype/selling_settings/selling_settings.json | 8 ++++---- 4 files changed, 14 insertions(+), 10 deletions(-) diff --git a/erpnext/buying/doctype/buying_settings/buying_settings.json b/erpnext/buying/doctype/buying_settings/buying_settings.json index b9c77d59b18..b828a43d3cf 100644 --- a/erpnext/buying/doctype/buying_settings/buying_settings.json +++ b/erpnext/buying/doctype/buying_settings/buying_settings.json @@ -28,7 +28,7 @@ "fieldname": "supp_master_name", "fieldtype": "Select", "label": "Supplier Naming By", - "options": "Supplier Name\nNaming Series" + "options": "Supplier Name\nNaming Series\nAuto Name" }, { "fieldname": "supplier_group", @@ -123,7 +123,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2021-06-24 10:38:28.934525", + "modified": "2021-09-08 19:26:23.548837", "modified_by": "Administrator", "module": "Buying", "name": "Buying Settings", diff --git a/erpnext/buying/doctype/supplier/supplier.py b/erpnext/buying/doctype/supplier/supplier.py index 2a9f784ec6e..0ab01712e32 100644 --- a/erpnext/buying/doctype/supplier/supplier.py +++ b/erpnext/buying/doctype/supplier/supplier.py @@ -10,7 +10,7 @@ from frappe.contacts.address_and_contact import ( delete_contact_and_address, load_address_and_contact, ) -from frappe.model.naming import set_name_by_naming_series +from frappe.model.naming import set_name_by_naming_series, set_name_from_naming_options from erpnext.accounts.party import get_dashboard_info, validate_party_accounts from erpnext.utilities.transaction_base import TransactionBase @@ -40,8 +40,10 @@ class Supplier(TransactionBase): supp_master_name = frappe.defaults.get_global_default('supp_master_name') if supp_master_name == 'Supplier Name': self.name = self.supplier_name - else: + elif supp_master_name == 'Naming Series': set_name_by_naming_series(self) + else: + self.name = set_name_from_naming_options(frappe.get_meta(self.doctype).autoname, self) def on_update(self): if not self.naming_series: diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index 4be8139d575..7adf2cd909f 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -14,7 +14,7 @@ from frappe.contacts.address_and_contact import ( ) from frappe.desk.reportview import build_match_conditions, get_filters_cond from frappe.model.mapper import get_mapped_doc -from frappe.model.naming import set_name_by_naming_series +from frappe.model.naming import set_name_by_naming_series, set_name_from_naming_options from frappe.model.rename_doc import update_linked_doctypes from frappe.utils import cint, cstr, flt, get_formatted_email, today from frappe.utils.user import get_users_with_role @@ -40,8 +40,10 @@ class Customer(TransactionBase): cust_master_name = frappe.defaults.get_global_default('cust_master_name') if cust_master_name == 'Customer Name': self.name = self.get_customer_name() - else: + elif cust_master_name == 'Naming Series': set_name_by_naming_series(self) + else: + self.name = set_name_from_naming_options(frappe.get_meta(self.doctype).autoname, self) def get_customer_name(self): diff --git a/erpnext/selling/doctype/selling_settings/selling_settings.json b/erpnext/selling/doctype/selling_settings/selling_settings.json index 59fcb982819..c27f1ea81ad 100644 --- a/erpnext/selling/doctype/selling_settings/selling_settings.json +++ b/erpnext/selling/doctype/selling_settings/selling_settings.json @@ -41,14 +41,14 @@ "fieldtype": "Select", "in_list_view": 1, "label": "Customer Naming By", - "options": "Customer Name\nNaming Series" + "options": "Customer Name\nNaming Series\nAuto Name" }, { "fieldname": "campaign_naming_by", "fieldtype": "Select", "in_list_view": 1, "label": "Campaign Naming By", - "options": "Campaign Name\nNaming Series" + "options": "Campaign Name\nNaming Series\nAuto Name" }, { "fieldname": "customer_group", @@ -204,7 +204,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2021-09-01 22:55:33.803624", + "modified": "2021-09-08 19:38:10.175989", "modified_by": "Administrator", "module": "Selling", "name": "Selling Settings", @@ -223,4 +223,4 @@ "sort_field": "modified", "sort_order": "DESC", "track_changes": 1 -} +} \ No newline at end of file From 6e7945fbb7bafa560277841c912c3b1a46d1c416 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Wed, 15 Sep 2021 11:10:11 +0530 Subject: [PATCH 057/116] fix: Tags getting fetched correctly in Get Supplier in RFQ (Request For Quotation) (#27499) (#27506) * fix: Tags getting fetched correctly in Get Supplier in RFQ( Request For Quotation ) #26343 * fix: Linting issues * fix: remove unnecessary caching [skip ci] Co-authored-by: Vama Mehta Co-authored-by: Ankush Menat (cherry picked from commit 50fe23308acad58ab2db2056ea6dd163913725ef) --- .../request_for_quotation/request_for_quotation.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) 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 af1a9a907a9..5aa2d1374e2 100644 --- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py +++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py @@ -394,12 +394,10 @@ def get_item_from_material_requests_based_on_supplier(source_name, target_doc = @frappe.whitelist() def get_supplier_tag(): - if not frappe.cache().hget("Supplier", "Tags"): - filters = {"document_type": "Supplier"} - tags = list(set(tag.tag for tag in frappe.get_all("Tag Link", filters=filters, fields=["tag"]) if tag)) - frappe.cache().hset("Supplier", "Tags", tags) + filters = {"document_type": "Supplier"} + tags = list(set(tag.tag for tag in frappe.get_all("Tag Link", filters=filters, fields=["tag"]) if tag)) - return frappe.cache().hget("Supplier", "Tags") + return tags @frappe.whitelist() @frappe.validate_and_sanitize_search_inputs From c5a77f60ed362ece3dd7ecd4568c82809f15bf28 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Wed, 15 Sep 2021 16:45:57 +0530 Subject: [PATCH 058/116] feat: provision to add scrap item in job card (#27483) --- .../doctype/job_card/job_card.json | 17 +++- .../doctype/job_card_scrap_item/__init__.py | 0 .../job_card_scrap_item.json | 82 ++++++++++++++++++ .../job_card_scrap_item.py | 8 ++ .../production_plan/test_production_plan.py | 1 + .../doctype/work_order/test_work_order.py | 56 ++++++++++++- .../stock/doctype/stock_entry/stock_entry.py | 84 ++++++++++++++++++- 7 files changed, 242 insertions(+), 6 deletions(-) create mode 100644 erpnext/manufacturing/doctype/job_card_scrap_item/__init__.py create mode 100644 erpnext/manufacturing/doctype/job_card_scrap_item/job_card_scrap_item.json create mode 100644 erpnext/manufacturing/doctype/job_card_scrap_item/job_card_scrap_item.py diff --git a/erpnext/manufacturing/doctype/job_card/job_card.json b/erpnext/manufacturing/doctype/job_card/job_card.json index f5bbac33b81..7dd38f4673d 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.json +++ b/erpnext/manufacturing/doctype/job_card/job_card.json @@ -38,6 +38,8 @@ "total_time_in_mins", "section_break_8", "items", + "scrap_items_section", + "scrap_items", "corrective_operation_section", "for_job_card", "is_corrective_job_card", @@ -392,11 +394,24 @@ "fieldtype": "Link", "label": "Batch No", "options": "Batch" + }, + { + "fieldname": "scrap_items_section", + "fieldtype": "Section Break", + "label": "Scrap Items" + }, + { + "fieldname": "scrap_items", + "fieldtype": "Table", + "label": "Scrap Items", + "no_copy": 1, + "options": "Job Card Scrap Item", + "print_hide": 1 } ], "is_submittable": 1, "links": [], - "modified": "2021-09-13 21:34:15.177928", + "modified": "2021-09-14 00:38:46.873105", "modified_by": "Administrator", "module": "Manufacturing", "name": "Job Card", diff --git a/erpnext/manufacturing/doctype/job_card_scrap_item/__init__.py b/erpnext/manufacturing/doctype/job_card_scrap_item/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/manufacturing/doctype/job_card_scrap_item/job_card_scrap_item.json b/erpnext/manufacturing/doctype/job_card_scrap_item/job_card_scrap_item.json new file mode 100644 index 00000000000..9e9f1c4c89f --- /dev/null +++ b/erpnext/manufacturing/doctype/job_card_scrap_item/job_card_scrap_item.json @@ -0,0 +1,82 @@ +{ + "actions": [], + "creation": "2021-09-14 00:30:28.533884", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "item_code", + "item_name", + "column_break_3", + "description", + "quantity_and_rate", + "stock_qty", + "column_break_6", + "stock_uom" + ], + "fields": [ + { + "fieldname": "item_code", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Scrap Item Code", + "options": "Item", + "reqd": 1 + }, + { + "fetch_from": "item_code.item_name", + "fieldname": "item_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Scrap Item Name" + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "fetch_from": "item_code.description", + "fieldname": "description", + "fieldtype": "Small Text", + "label": "Description", + "read_only": 1 + }, + { + "fieldname": "quantity_and_rate", + "fieldtype": "Section Break", + "label": "Quantity and Rate" + }, + { + "fieldname": "stock_qty", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Qty", + "reqd": 1 + }, + { + "fieldname": "column_break_6", + "fieldtype": "Column Break" + }, + { + "fetch_from": "item_code.stock_uom", + "fieldname": "stock_uom", + "fieldtype": "Link", + "label": "Stock UOM", + "options": "UOM", + "read_only": 1 + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-09-14 01:20:48.588052", + "modified_by": "Administrator", + "module": "Manufacturing", + "name": "Job Card Scrap Item", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/job_card_scrap_item/job_card_scrap_item.py b/erpnext/manufacturing/doctype/job_card_scrap_item/job_card_scrap_item.py new file mode 100644 index 00000000000..372df1b0fad --- /dev/null +++ b/erpnext/manufacturing/doctype/job_card_scrap_item/job_card_scrap_item.py @@ -0,0 +1,8 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from frappe.model.document import Document + + +class JobCardScrapItem(Document): + pass diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py index 6a942d54335..707b3f62d4e 100644 --- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py @@ -404,6 +404,7 @@ def make_bom(**args): 'uom': item_doc.stock_uom, 'stock_uom': item_doc.stock_uom, 'rate': item_doc.valuation_rate or args.rate, + 'source_warehouse': args.source_warehouse }) if not args.do_not_save: diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index d87b5ec654f..85b5bfb9bfc 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -16,7 +16,7 @@ from erpnext.manufacturing.doctype.work_order.work_order import ( stop_unstop, ) from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order -from erpnext.stock.doctype.item.test_item import make_item +from erpnext.stock.doctype.item.test_item import create_item, make_item from erpnext.stock.doctype.stock_entry import test_stock_entry from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse from erpnext.stock.utils import get_bin @@ -768,6 +768,60 @@ class TestWorkOrder(unittest.TestCase): total_pl_qty ) + def test_job_card_scrap_item(self): + items = ['Test FG Item for Scrap Item Test', 'Test RM Item 1 for Scrap Item Test', + 'Test RM Item 2 for Scrap Item Test'] + + company = '_Test Company with perpetual inventory' + for item_code in items: + create_item(item_code = item_code, is_stock_item = 1, + is_purchase_item=1, opening_stock=100, valuation_rate=10, company=company, warehouse='Stores - TCP1') + + item = 'Test FG Item for Scrap Item Test' + raw_materials = ['Test RM Item 1 for Scrap Item Test', 'Test RM Item 2 for Scrap Item Test'] + if not frappe.db.get_value('BOM', {'item': item}): + bom = make_bom(item=item, source_warehouse='Stores - TCP1', raw_materials=raw_materials, do_not_save=True) + bom.with_operations = 1 + bom.append('operations', { + 'operation': '_Test Operation 1', + 'workstation': '_Test Workstation 1', + 'hour_rate': 20, + 'time_in_mins': 60 + }) + + bom.submit() + + wo_order = make_wo_order_test_record(item=item, company=company, planned_start_date=now(), qty=20, skip_transfer=1) + job_card = frappe.db.get_value('Job Card', {'work_order': wo_order.name}, 'name') + update_job_card(job_card) + + stock_entry = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 10)) + for row in stock_entry.items: + if row.is_scrap_item: + self.assertEqual(row.qty, 1) + +def update_job_card(job_card): + job_card_doc = frappe.get_doc('Job Card', job_card) + job_card_doc.set('scrap_items', [ + { + 'item_code': 'Test RM Item 1 for Scrap Item Test', + 'stock_qty': 2 + }, + { + 'item_code': 'Test RM Item 2 for Scrap Item Test', + 'stock_qty': 2 + }, + ]) + + job_card_doc.append('time_logs', { + 'from_time': now(), + 'time_in_mins': 60, + 'completed_qty': job_card_doc.for_quantity + }) + + job_card_doc.submit() + + def get_scrap_item_details(bom_no): scrap_items = {} for item in frappe.db.sql("""select item_code, stock_qty from `tabBOM Scrap Item` diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 0459489185e..1c9b9614f67 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -4,6 +4,7 @@ from __future__ import unicode_literals import json +from collections import defaultdict import frappe from frappe import _ @@ -684,7 +685,7 @@ class StockEntry(StockController): def validate_bom(self): for d in self.get('items'): - if d.bom_no and (d.t_warehouse != getattr(self, "pro_doc", frappe._dict()).scrap_warehouse): + if d.bom_no and d.is_finished_item: item_code = d.original_item or d.item_code validate_bom_no(item_code, d.bom_no) @@ -1191,13 +1192,88 @@ class StockEntry(StockController): # item dict = { item_code: {qty, description, stock_uom} } item_dict = get_bom_items_as_dict(self.bom_no, self.company, qty=qty, - fetch_exploded = 0, fetch_scrap_items = 1) + fetch_exploded = 0, fetch_scrap_items = 1) or {} for item in itervalues(item_dict): item.from_warehouse = "" item.is_scrap_item = 1 + + for row in self.get_scrap_items_from_job_card(): + if row.stock_qty <= 0: + continue + + item_row = item_dict.get(row.item_code) + if not item_row: + item_row = frappe._dict({}) + + item_row.update({ + 'uom': row.stock_uom, + 'from_warehouse': '', + 'qty': row.stock_qty + flt(item_row.stock_qty), + 'converison_factor': 1, + 'is_scrap_item': 1, + 'item_name': row.item_name, + 'description': row.description, + 'allow_zero_valuation_rate': 1 + }) + + item_dict[row.item_code] = item_row + return item_dict + def get_scrap_items_from_job_card(self): + if not self.pro_doc: + self.set_work_order_details() + + scrap_items = frappe.db.sql(''' + SELECT + JCSI.item_code, JCSI.item_name, SUM(JCSI.stock_qty) as stock_qty, JCSI.stock_uom, JCSI.description + FROM + `tabJob Card` JC, `tabJob Card Scrap Item` JCSI + WHERE + JCSI.parent = JC.name AND JC.docstatus = 1 + AND JCSI.item_code IS NOT NULL AND JC.work_order = %s + GROUP BY + JCSI.item_code + ''', self.work_order, as_dict=1) + + pending_qty = flt(self.pro_doc.qty) - flt(self.pro_doc.produced_qty) + if pending_qty <=0: + return [] + + used_scrap_items = self.get_used_scrap_items() + for row in scrap_items: + row.stock_qty -= flt(used_scrap_items.get(row.item_code)) + row.stock_qty = (row.stock_qty) * flt(self.fg_completed_qty) / flt(pending_qty) + + if used_scrap_items.get(row.item_code): + used_scrap_items[row.item_code] -= row.stock_qty + + if cint(frappe.get_cached_value('UOM', row.stock_uom, 'must_be_whole_number')): + row.stock_qty = frappe.utils.ceil(row.stock_qty) + + return scrap_items + + def get_used_scrap_items(self): + used_scrap_items = defaultdict(float) + data = frappe.get_all( + 'Stock Entry', + fields = [ + '`tabStock Entry Detail`.`item_code`', '`tabStock Entry Detail`.`qty`' + ], + filters = [ + ['Stock Entry', 'work_order', '=', self.work_order], + ['Stock Entry Detail', 'is_scrap_item', '=', 1], + ['Stock Entry', 'docstatus', '=', 1], + ['Stock Entry', 'purpose', 'in', ['Repack', 'Manufacture']] + ] + ) + + for row in data: + used_scrap_items[row.item_code] += row.qty + + return used_scrap_items + def get_unconsumed_raw_materials(self): wo = frappe.get_doc("Work Order", self.work_order) wo_items = frappe.get_all('Work Order Item', @@ -1417,8 +1493,8 @@ class StockEntry(StockController): se_child.is_scrap_item = item_dict[d].get("is_scrap_item", 0) se_child.is_process_loss = item_dict[d].get("is_process_loss", 0) - for field in ["idx", "po_detail", "original_item", - "expense_account", "description", "item_name", "serial_no", "batch_no"]: + for field in ["idx", "po_detail", "original_item", "expense_account", + "description", "item_name", "serial_no", "batch_no", "allow_zero_valuation_rate"]: if item_dict[d].get(field): se_child.set(field, item_dict[d].get(field)) From d4d5e2786f3e347491329d8882be5bee34fb8ee3 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Wed, 15 Sep 2021 17:07:58 +0530 Subject: [PATCH 059/116] fix: Maintain same rate in Stock Ledger until stock become positive (#27227) (#27478) * fix: Maintain same rate in Stock Ledger until stock become positive * fix: Maintain same rate in Stock Ledger until stock become positive (cherry picked from commit 10754831c33b3459d5a45c98f875afa48a444627) Co-authored-by: Nabin Hait --- erpnext/stock/stock_ledger.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 542124204b5..c33697d88a6 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -681,11 +681,15 @@ class update_entries_after(object): if self.wh_data.stock_queue[-1][1]==incoming_rate: self.wh_data.stock_queue[-1][0] += actual_qty else: + # Item has a positive balance qty, add new entry if self.wh_data.stock_queue[-1][0] > 0: self.wh_data.stock_queue.append([actual_qty, incoming_rate]) - else: + else: # negative balance qty qty = self.wh_data.stock_queue[-1][0] + actual_qty - self.wh_data.stock_queue[-1] = [qty, incoming_rate] + if qty > 0: # new balance qty is positive + self.wh_data.stock_queue[-1] = [qty, incoming_rate] + else: # new balance qty is still negative, maintain same rate + self.wh_data.stock_queue[-1][0] = qty else: qty_to_pop = abs(actual_qty) while qty_to_pop: From 5e0b21582acbc3b5629b2c8c29ea3035693a3227 Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Wed, 15 Sep 2021 17:37:52 +0530 Subject: [PATCH 060/116] fix: table data deleted on submitted maintenance schedule (#27513) --- .../doctype/maintenance_schedule/maintenance_schedule.js | 2 +- .../doctype/maintenance_schedule/maintenance_schedule.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js index d1a8c8de27c..035290d8f19 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js +++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js @@ -18,7 +18,7 @@ frappe.ui.form.on('Maintenance Schedule', { }, refresh: function (frm) { setTimeout(() => { - frm.toggle_display('generate_schedule', !(frm.is_new())); + frm.toggle_display('generate_schedule', !(frm.is_new() || frm.doc.docstatus)); frm.toggle_display('schedule', !(frm.is_new())); }, 10); }, diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py index 52e41c5863e..0bf5aeae711 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py +++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py @@ -16,9 +16,9 @@ from erpnext.utilities.transaction_base import TransactionBase, delete_events class MaintenanceSchedule(TransactionBase): @frappe.whitelist() def generate_schedule(self): + if self.docstatus != 0: + return self.set('schedules', []) - frappe.db.sql("""delete from `tabMaintenance Schedule Detail` - where parent=%s""", (self.name)) count = 1 for d in self.get('items'): self.validate_maintenance_detail() From 29dab0699c5a162cedc8a207099cf9e629530314 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Wed, 15 Sep 2021 19:12:02 +0530 Subject: [PATCH 061/116] fix(minor): Remove b2c limit check from CDNR Invoices (#27516) (#27520) * fix(minor): Remove b2c limit check from CDNR Invoices * fix: Remove unnecessary format (cherry picked from commit 978028c880445362afc0cfca9118424174541cc7) Co-authored-by: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> --- erpnext/regional/report/gstr_1/gstr_1.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py index cf4850e2781..23924c5fb66 100644 --- a/erpnext/regional/report/gstr_1/gstr_1.py +++ b/erpnext/regional/report/gstr_1/gstr_1.py @@ -214,7 +214,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 AND is_debit_note !=1" if self.filters.get("type_of_business") in ("B2C Large", "B2C Small"): b2c_limit = frappe.db.get_single_value('GST Settings', 'b2c_limit') @@ -223,7 +223,7 @@ class Gstr1Report(object): 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)) + AND grand_total > {0} AND is_return != 1 AND is_debit_note !=1 AND gst_category ='Unregistered' """.format(flt(b2c_limit)) elif self.filters.get("type_of_business") == "B2C Small": conditions += """ AND ( @@ -236,8 +236,8 @@ class Gstr1Report(object): elif self.filters.get("type_of_business") == "CDNR-UNREG": b2c_limit = frappe.db.get_single_value('GST Settings', 'b2c_limit') conditions += """ AND ifnull(SUBSTR(place_of_supply, 1, 2),'') != ifnull(SUBSTR(company_gstin, 1, 2),'') - AND ABS(grand_total) > {0} AND (is_return = 1 OR is_debit_note = 1) - AND IFNULL(gst_category, '') in ('Unregistered', 'Overseas')""".format(flt(b2c_limit)) + AND (is_return = 1 OR is_debit_note = 1) + AND IFNULL(gst_category, '') in ('Unregistered', 'Overseas')""" elif self.filters.get("type_of_business") == "EXPORT": conditions += """ AND is_return !=1 and gst_category = 'Overseas' """ From 70c203d19ed62fbd185809b1410ac45ff8fba8be Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 15 Sep 2021 19:24:35 +0530 Subject: [PATCH 062/116] test: automated test for running all stock reports (#27510) * test: automated test for running all stock reports These test do not assert correctness, they just check that "execute" function is working with sane filters. * test: make report execution test modular --- erpnext/stock/report/test_reports.py | 63 ++++++++++++++++++++++++++++ erpnext/tests/utils.py | 41 ++++++++++++++++++ 2 files changed, 104 insertions(+) create mode 100644 erpnext/stock/report/test_reports.py diff --git a/erpnext/stock/report/test_reports.py b/erpnext/stock/report/test_reports.py new file mode 100644 index 00000000000..d7fb5b2bf3f --- /dev/null +++ b/erpnext/stock/report/test_reports.py @@ -0,0 +1,63 @@ +import unittest +from typing import List, Tuple + +from erpnext.tests.utils import ReportFilters, ReportName, execute_script_report + +DEFAULT_FILTERS = { + "company": "_Test Company", + "from_date": "2010-01-01", + "to_date": "2030-01-01", +} + + +REPORT_FILTER_TEST_CASES: List[Tuple[ReportName, ReportFilters]] = [ + ("Stock Ledger", {"_optional": True}), + ("Stock Balance", {"_optional": True}), + ("Stock Projected Qty", {"_optional": True}), + ("Batch-Wise Balance History", {}), + ("Itemwise Recommended Reorder Level", {"item_group": "All Item Groups"}), + ("COGS By Item Group", {}), + ("Stock Qty vs Serial No Count", {"warehouse": "_Test Warehouse - _TC"}), + ( + "Stock and Account Value Comparison", + { + "company": "_Test Company with perpetual inventory", + "account": "Stock In Hand - TCP1", + "as_on_date": "2021-01-01", + }, + ), + ("Product Bundle Balance", {"date": "2022-01-01", "_optional": True}), + ( + "Stock Analytics", + { + "from_date": "2021-01-01", + "to_date": "2021-12-31", + "value_quantity": "Quantity", + "_optional": True, + }, + ), + ("Warehouse wise Item Balance Age and Value", {"_optional": True}), + ("Item Variant Details", {"item": "_Test Variant Item",}), + ("Total Stock Summary", {"group_by": "warehouse",}), + ("Batch Item Expiry Status", {}), + ("Stock Ageing", {"range1": 30, "range2": 60, "range3": 90, "_optional": True}), +] + +OPTIONAL_FILTERS = { + "warehouse": "_Test Warehouse - _TC", + "item": "_Test Item", + "item_group": "_Test Item Group", +} + + +class TestReports(unittest.TestCase): + def test_execute_all_stock_reports(self): + """Test that all script report in stock modules are executable with supported filters""" + for report, filter in REPORT_FILTER_TEST_CASES: + execute_script_report( + report_name=report, + module="Stock", + filters=filter, + default_filters=DEFAULT_FILTERS, + optional_filters=OPTIONAL_FILTERS if filter.get("_optional") else None, + ) diff --git a/erpnext/tests/utils.py b/erpnext/tests/utils.py index 2156bd51a4a..a3cab4b59da 100644 --- a/erpnext/tests/utils.py +++ b/erpnext/tests/utils.py @@ -3,8 +3,13 @@ import copy from contextlib import contextmanager +from typing import Any, Dict, NewType, Optional import frappe +from frappe.core.doctype.report.report import get_report_module_dotted_path + +ReportFilters = Dict[str, Any] +ReportName = NewType("ReportName", str) def create_test_contact_and_address(): @@ -78,3 +83,39 @@ def change_settings(doctype, settings_dict): for key, value in previous_settings.items(): setattr(settings, key, value) settings.save() + + +def execute_script_report( + report_name: ReportName, + module: str, + filters: ReportFilters, + default_filters: Optional[ReportFilters] = None, + optional_filters: Optional[ReportFilters] = None + ): + """Util for testing execution of a report with specified filters. + + Tests the execution of report with default_filters + filters. + Tests the execution using optional_filters one at a time. + + Args: + report_name: Human readable name of report (unscrubbed) + module: module to which report belongs to + filters: specific values for filters + default_filters: default values for filters such as company name. + optional_filters: filters which should be tested one at a time in addition to default filters. + """ + + if default_filters is None: + default_filters = {} + + report_execute_fn = frappe.get_attr(get_report_module_dotted_path(module, report_name) + ".execute") + report_filters = frappe._dict(default_filters).copy().update(filters) + + report_data = report_execute_fn(report_filters) + + if optional_filters: + for key, value in optional_filters.items(): + filter_with_optional_param = report_filters.copy().update({key: value}) + report_execute_fn(filter_with_optional_param) + + return report_data From e6a1ad8016b5e2aa425661978b99bc09c7ca08d1 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Wed, 15 Sep 2021 20:42:47 +0530 Subject: [PATCH 063/116] fix: not able to submit stock entry with 350 items (#27523) --- erpnext/stock/stock_ledger.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index c33697d88a6..1b5b792f946 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -407,7 +407,8 @@ class update_entries_after(object): return # Get dynamic incoming/outgoing rate - self.get_dynamic_incoming_outgoing_rate(sle) + if not self.args.get("sle_id"): + self.get_dynamic_incoming_outgoing_rate(sle) if sle.serial_no: self.get_serialized_values(sle) @@ -447,7 +448,8 @@ class update_entries_after(object): sle.doctype="Stock Ledger Entry" frappe.get_doc(sle).db_update() - self.update_outgoing_rate_on_transaction(sle) + if not self.args.get("sle_id"): + self.update_outgoing_rate_on_transaction(sle) def validate_negative_stock(self, sle): """ From 866763c16a9d39e1d0382c859437e4493f798fbc Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Thu, 16 Sep 2021 00:05:16 +0530 Subject: [PATCH 064/116] fix(minor): Employee filter in Unpaid Expense Claims report (#27530) --- .../report/unpaid_expense_claim/unpaid_expense_claim.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/report/unpaid_expense_claim/unpaid_expense_claim.js b/erpnext/accounts/report/unpaid_expense_claim/unpaid_expense_claim.js index 811414aaf07..f0ba78c9608 100644 --- a/erpnext/accounts/report/unpaid_expense_claim/unpaid_expense_claim.js +++ b/erpnext/accounts/report/unpaid_expense_claim/unpaid_expense_claim.js @@ -4,9 +4,10 @@ frappe.query_reports["Unpaid Expense Claim"] = { "filters": [ { - "fieldname":"employee", + "fieldname": "employee", "label": __("Employee"), - "fieldtype": "Link" + "fieldtype": "Link", + "options": "Employee" } ] } From 78fe92542cb3ee781ba8103f3a5f1e982f29ce10 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 16 Sep 2021 15:34:50 +0530 Subject: [PATCH 065/116] fix(ProdPlan): Get SubAssy Items does not work (#27537) * fix(ProdPlan): Get SubAssy Items does not work This button wasn't working unless the document was saved already. * fix: make form dirty when subassy item are fetched --- .../manufacturing/doctype/production_plan/production_plan.js | 2 ++ .../manufacturing/doctype/production_plan/production_plan.py | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.js b/erpnext/manufacturing/doctype/production_plan/production_plan.js index db0f2c5dbd8..2bd02dabd88 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.js +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.js @@ -242,6 +242,8 @@ frappe.ui.form.on('Production Plan', { }, get_sub_assembly_items: function(frm) { + frm.dirty(); + frappe.call({ method: "get_sub_assembly_items", freeze: true, diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index a28fc7abf0e..18284e0199e 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -561,8 +561,6 @@ class ProductionPlan(Document): get_sub_assembly_items(row.bom_no, bom_data, row.planned_qty) self.set_sub_assembly_items_based_on_level(row, bom_data, manufacturing_type) - self.save() - def set_sub_assembly_items_based_on_level(self, row, bom_data, manufacturing_type=None): bom_data = sorted(bom_data, key = lambda i: i.bom_level) From 9b388883d39d278ecc20ae3d99ee25a752714caa Mon Sep 17 00:00:00 2001 From: Marica Date: Thu, 16 Sep 2021 15:47:17 +0530 Subject: [PATCH 066/116] fix: Validate if item exists on uploading items in stock reco (#27538) * fix: Validate if item exists on uploading items in stock reco - Uploading non existent item in stock reco and then changing warehouse or batch gave an error - Check for non existent item * chore: translation Co-authored-by: Ankush Menat --- .../doctype/stock_reconciliation/stock_reconciliation.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index fa96c5a09b0..f59a4e6ff85 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -619,6 +619,11 @@ def get_stock_balance_for(item_code, warehouse, item_dict = frappe.db.get_value("Item", item_code, ["has_serial_no", "has_batch_no"], as_dict=1) + if not item_dict: + # In cases of data upload to Items table + msg = _("Item {} does not exist.").format(item_code) + frappe.throw(msg, title=_("Missing")) + serial_nos = "" with_serial_no = True if item_dict.get("has_serial_no") else False data = get_stock_balance(item_code, warehouse, posting_date, posting_time, From 557d9516ffe5308ce28df7a758bb4f59494b2412 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 16 Sep 2021 17:17:10 +0530 Subject: [PATCH 067/116] ci: comment about coverage after min. 3 builds (#27544) [skip ci] --- codecov.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/codecov.yml b/codecov.yml index a8da340d087..67bd4454ef1 100644 --- a/codecov.yml +++ b/codecov.yml @@ -11,6 +11,7 @@ coverage: comment: layout: "diff, files" require_changes: true - + after_n_builds: 3 + ignore: - "erpnext/demo" From 5eba1ccd51488a4b5d02382b94d659c71e73e36d Mon Sep 17 00:00:00 2001 From: Saqib Date: Thu, 16 Sep 2021 17:31:37 +0530 Subject: [PATCH 068/116] fix: no validation on item defaults (#27393) * fix: no validation on item defaults * fix: cache value while validating * test: item default company relation * fix: reorder validations * refactor: add guard conditions on update_defaults * test: add default warehouse for item group * fix: validate item defaults for item groups Co-authored-by: Ankush Menat --- .../setup/doctype/item_group/item_group.py | 5 + .../doctype/item_group/test_records.json | 91 ++++++++++--------- erpnext/stock/doctype/item/item.py | 82 +++++++++++------ erpnext/stock/doctype/item/test_item.py | 17 ++++ 4 files changed, 123 insertions(+), 72 deletions(-) diff --git a/erpnext/setup/doctype/item_group/item_group.py b/erpnext/setup/doctype/item_group/item_group.py index b26c6a49b4c..ab50a58c4fb 100644 --- a/erpnext/setup/doctype/item_group/item_group.py +++ b/erpnext/setup/doctype/item_group/item_group.py @@ -39,6 +39,7 @@ class ItemGroup(NestedSet, WebsiteGenerator): self.parent_item_group = _('All Item Groups') self.make_route() + self.validate_item_group_defaults() def on_update(self): NestedSet.on_update(self) @@ -134,6 +135,10 @@ class ItemGroup(NestedSet, WebsiteGenerator): def delete_child_item_groups_key(self): frappe.cache().hdel("child_item_groups", self.name) + def validate_item_group_defaults(self): + from erpnext.stock.doctype.item.item import validate_item_default_company_links + validate_item_default_company_links(self.item_group_defaults) + @frappe.whitelist(allow_guest=True) def get_product_list_for_group(product_group=None, start=0, limit=10, search=None): if product_group: diff --git a/erpnext/setup/doctype/item_group/test_records.json b/erpnext/setup/doctype/item_group/test_records.json index 146da87bddc..ce1d718375a 100644 --- a/erpnext/setup/doctype/item_group/test_records.json +++ b/erpnext/setup/doctype/item_group/test_records.json @@ -1,73 +1,74 @@ [ { - "doctype": "Item Group", - "is_group": 0, - "item_group_name": "_Test Item Group", + "doctype": "Item Group", + "is_group": 0, + "item_group_name": "_Test Item Group", "parent_item_group": "All Item Groups", "item_group_defaults": [{ "company": "_Test Company", "buying_cost_center": "_Test Cost Center 2 - _TC", - "selling_cost_center": "_Test Cost Center 2 - _TC" + "selling_cost_center": "_Test Cost Center 2 - _TC", + "default_warehouse": "_Test Warehouse - _TC" }] - }, + }, { - "doctype": "Item Group", - "is_group": 0, - "item_group_name": "_Test Item Group Desktops", + "doctype": "Item Group", + "is_group": 0, + "item_group_name": "_Test Item Group Desktops", "parent_item_group": "All Item Groups" - }, + }, { - "doctype": "Item Group", - "is_group": 1, - "item_group_name": "_Test Item Group A", + "doctype": "Item Group", + "is_group": 1, + "item_group_name": "_Test Item Group A", "parent_item_group": "All Item Groups" - }, + }, { - "doctype": "Item Group", - "is_group": 1, - "item_group_name": "_Test Item Group B", + "doctype": "Item Group", + "is_group": 1, + "item_group_name": "_Test Item Group B", "parent_item_group": "All Item Groups" - }, + }, { - "doctype": "Item Group", - "is_group": 1, - "item_group_name": "_Test Item Group B - 1", + "doctype": "Item Group", + "is_group": 1, + "item_group_name": "_Test Item Group B - 1", "parent_item_group": "_Test Item Group B" - }, + }, { - "doctype": "Item Group", - "is_group": 1, - "item_group_name": "_Test Item Group B - 2", + "doctype": "Item Group", + "is_group": 1, + "item_group_name": "_Test Item Group B - 2", "parent_item_group": "_Test Item Group B" - }, + }, { - "doctype": "Item Group", - "is_group": 0, - "item_group_name": "_Test Item Group B - 3", + "doctype": "Item Group", + "is_group": 0, + "item_group_name": "_Test Item Group B - 3", "parent_item_group": "_Test Item Group B" - }, + }, { - "doctype": "Item Group", - "is_group": 1, - "item_group_name": "_Test Item Group C", + "doctype": "Item Group", + "is_group": 1, + "item_group_name": "_Test Item Group C", "parent_item_group": "All Item Groups" - }, + }, { - "doctype": "Item Group", - "is_group": 1, - "item_group_name": "_Test Item Group C - 1", + "doctype": "Item Group", + "is_group": 1, + "item_group_name": "_Test Item Group C - 1", "parent_item_group": "_Test Item Group C" - }, + }, { - "doctype": "Item Group", - "is_group": 1, - "item_group_name": "_Test Item Group C - 2", + "doctype": "Item Group", + "is_group": 1, + "item_group_name": "_Test Item Group C - 2", "parent_item_group": "_Test Item Group C" - }, + }, { - "doctype": "Item Group", - "is_group": 1, - "item_group_name": "_Test Item Group D", + "doctype": "Item Group", + "is_group": 1, + "item_group_name": "_Test Item Group D", "parent_item_group": "All Item Groups" }, { @@ -104,4 +105,4 @@ } ] } -] \ No newline at end of file +] diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index 50fdd3845d0..86737f29b0c 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -4,6 +4,7 @@ import copy import itertools import json +from typing import List import frappe from frappe import _ @@ -36,6 +37,7 @@ from erpnext.setup.doctype.item_group.item_group import ( get_parent_item_groups, invalidate_cache_for, ) +from erpnext.stock.doctype.item_default.item_default import ItemDefault class DuplicateReorderRows(frappe.ValidationError): @@ -134,9 +136,9 @@ class Item(WebsiteGenerator): self.validate_fixed_asset() self.validate_retain_sample() self.validate_uom_conversion_factor() - self.validate_item_defaults() self.validate_customer_provided_part() self.update_defaults_from_item_group() + self.validate_item_defaults() self.validate_auto_reorder_enabled_in_stock_settings() self.cant_change() self.update_show_in_website() @@ -782,35 +784,39 @@ class Item(WebsiteGenerator): if len(companies) != len(self.item_defaults): frappe.throw(_("Cannot set multiple Item Defaults for a company.")) + validate_item_default_company_links(self.item_defaults) + + def update_defaults_from_item_group(self): """Get defaults from Item Group""" - if self.item_group and not self.item_defaults: - item_defaults = frappe.db.get_values("Item Default", {"parent": self.item_group}, - ['company', 'default_warehouse','default_price_list','buying_cost_center','default_supplier', - 'expense_account','selling_cost_center','income_account'], as_dict = 1) - if item_defaults: - for item in item_defaults: - self.append('item_defaults', { - 'company': item.company, - 'default_warehouse': item.default_warehouse, - 'default_price_list': item.default_price_list, - 'buying_cost_center': item.buying_cost_center, - 'default_supplier': item.default_supplier, - 'expense_account': item.expense_account, - 'selling_cost_center': item.selling_cost_center, - 'income_account': item.income_account - }) - else: - warehouse = '' - defaults = frappe.defaults.get_defaults() or {} + if self.item_defaults or not self.item_group: + return - # To check default warehouse is belong to the default company - if defaults.get("default_warehouse") and defaults.company and frappe.db.exists("Warehouse", - {'name': defaults.default_warehouse, 'company': defaults.company}): - self.append("item_defaults", { - "company": defaults.get("company"), - "default_warehouse": defaults.default_warehouse - }) + item_defaults = frappe.db.get_values("Item Default", {"parent": self.item_group}, + ['company', 'default_warehouse','default_price_list','buying_cost_center','default_supplier', + 'expense_account','selling_cost_center','income_account'], as_dict = 1) + if item_defaults: + for item in item_defaults: + self.append('item_defaults', { + 'company': item.company, + 'default_warehouse': item.default_warehouse, + 'default_price_list': item.default_price_list, + 'buying_cost_center': item.buying_cost_center, + 'default_supplier': item.default_supplier, + 'expense_account': item.expense_account, + 'selling_cost_center': item.selling_cost_center, + 'income_account': item.income_account + }) + else: + defaults = frappe.defaults.get_defaults() or {} + + # To check default warehouse is belong to the default company + if defaults.get("default_warehouse") and defaults.company and frappe.db.exists("Warehouse", + {'name': defaults.default_warehouse, 'company': defaults.company}): + self.append("item_defaults", { + "company": defaults.get("company"), + "default_warehouse": defaults.default_warehouse + }) def update_variants(self): if self.flags.dont_update_variants or \ @@ -1328,3 +1334,25 @@ def on_doctype_update(): @erpnext.allow_regional def set_item_tax_from_hsn_code(item): pass + + +def validate_item_default_company_links(item_defaults: List[ItemDefault]) -> None: + for item_default in item_defaults: + for doctype, field in [ + ['Warehouse', 'default_warehouse'], + ['Cost Center', 'buying_cost_center'], + ['Cost Center', 'selling_cost_center'], + ['Account', 'expense_account'], + ['Account', 'income_account'] + ]: + if item_default.get(field): + company = frappe.db.get_value(doctype, item_default.get(field), 'company', cache=True) + if company and company != item_default.company: + frappe.throw(_("Row #{}: {} {} doesn't belong to Company {}. Please select valid {}.") + .format( + item_default.idx, + doctype, + frappe.bold(item_default.get(field)), + frappe.bold(item_default.company), + frappe.bold(frappe.unscrub(field)) + ), title=_("Invalid Item Defaults")) diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py index 0ed27610200..e911d35db38 100644 --- a/erpnext/stock/doctype/item/test_item.py +++ b/erpnext/stock/doctype/item/test_item.py @@ -232,6 +232,23 @@ class TestItem(unittest.TestCase): for key, value in purchase_item_check.items(): self.assertEqual(value, purchase_item_details.get(key)) + def test_item_default_validations(self): + + with self.assertRaises(frappe.ValidationError) as ve: + make_item("Bad Item defaults", { + "item_group": "_Test Item Group", + "item_defaults": [{ + "company": "_Test Company 1", + "default_warehouse": "_Test Warehouse - _TC", + "expense_account": "Stock In Hand - _TC", + "buying_cost_center": "_Test Cost Center - _TC", + "selling_cost_center": "_Test Cost Center - _TC", + }] + }) + + self.assertTrue("belong to company" in str(ve.exception).lower(), + msg="Mismatching company entities in item defaults should not be allowed.") + def test_item_attribute_change_after_variant(self): frappe.delete_doc_if_exists("Item", "_Test Variant Item-L", force=1) From c9c89572502dab66595cbe0e0aa764143fc44b5a Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Thu, 16 Sep 2021 19:33:57 +0530 Subject: [PATCH 069/116] feat: Merge POS invoices based on customer group (#27471) * feat: Merge POS invoices based on customer group * fix: Linting Issues * fix: fieldname Co-authored-by: Saqib --- .../pos_invoice_merge_log.js | 7 +++- .../pos_invoice_merge_log.json | 21 +++++++++- .../pos_invoice_merge_log.py | 12 +++++- .../doctype/sales_invoice/sales_invoice.py | 2 +- erpnext/hooks.py | 2 +- erpnext/patches.txt | 1 + ...e_accounting_dimensions_in_pos_doctypes.py | 42 +++++++++++++++++++ 7 files changed, 80 insertions(+), 7 deletions(-) create mode 100644 erpnext/patches/v13_0/create_accounting_dimensions_in_pos_doctypes.py diff --git a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.js b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.js index 2f8081b95ce..73c6290d7b0 100644 --- a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.js +++ b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.js @@ -4,7 +4,7 @@ frappe.ui.form.on('POS Invoice Merge Log', { setup: function(frm) { frm.set_query("pos_invoice", "pos_invoices", doc => { - return{ + return { filters: { 'docstatus': 1, 'customer': doc.customer, @@ -12,5 +12,10 @@ frappe.ui.form.on('POS Invoice Merge Log', { } } }); + }, + + merge_invoices_based_on: function(frm) { + frm.set_value('customer', ''); + frm.set_value('customer_group', ''); } }); diff --git a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.json b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.json index da2984f05af..d7620870780 100644 --- a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.json +++ b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.json @@ -6,9 +6,11 @@ "engine": "InnoDB", "field_order": [ "posting_date", - "customer", + "merge_invoices_based_on", "column_break_3", "pos_closing_entry", + "customer", + "customer_group", "section_break_3", "pos_invoices", "references_section", @@ -88,12 +90,27 @@ "fieldtype": "Link", "label": "POS Closing Entry", "options": "POS Closing Entry" + }, + { + "fieldname": "merge_invoices_based_on", + "fieldtype": "Select", + "label": "Merge Invoices Based On", + "options": "Customer\nCustomer Group", + "reqd": 1 + }, + { + "depends_on": "eval:doc.merge_invoices_based_on == 'Customer Group'", + "fieldname": "customer_group", + "fieldtype": "Link", + "label": "Customer Group", + "mandatory_depends_on": "eval:doc.merge_invoices_based_on == 'Customer Group'", + "options": "Customer Group" } ], "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2020-12-01 11:53:57.267579", + "modified": "2021-09-14 11:17:19.001142", "modified_by": "Administrator", "module": "Accounts", "name": "POS Invoice Merge Log", 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 0be8ca7ee69..9dae3a7b75e 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 @@ -23,6 +23,9 @@ class POSInvoiceMergeLog(Document): self.validate_pos_invoice_status() def validate_customer(self): + if self.merge_invoices_based_on == 'Customer Group': + return + for d in self.pos_invoices: if d.customer != self.customer: frappe.throw(_("Row #{}: POS Invoice {} is not against customer {}").format(d.idx, d.pos_invoice, self.customer)) @@ -124,7 +127,7 @@ class POSInvoiceMergeLog(Document): found = False for i in items: if (i.item_code == item.item_code and not i.serial_no and not i.batch_no and - i.uom == item.uom and i.net_rate == item.net_rate): + i.uom == item.uom and i.net_rate == item.net_rate and i.warehouse == item.warehouse): found = True i.qty = i.qty + item.qty @@ -172,6 +175,11 @@ class POSInvoiceMergeLog(Document): invoice.discount_amount = 0.0 invoice.taxes_and_charges = None invoice.ignore_pricing_rule = 1 + invoice.customer = self.customer + + if self.merge_invoices_based_on == 'Customer Group': + invoice.flags.ignore_pos_profile = True + invoice.pos_profile = '' return invoice @@ -228,7 +236,7 @@ def get_all_unconsolidated_invoices(): return pos_invoices def get_invoice_customer_map(pos_invoices): - # pos_invoice_customer_map = { 'Customer 1': [{}, {}, {}], 'Custoemr 2' : [{}] } + # pos_invoice_customer_map = { 'Customer 1': [{}, {}, {}], 'Customer 2' : [{}] } pos_invoice_customer_map = {} for invoice in pos_invoices: customer = invoice.get('customer') diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index ec249c24194..ca6a77a2af7 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -499,7 +499,7 @@ class SalesInvoice(SellingController): self.account_for_change_amount = frappe.get_cached_value('Company', self.company, 'default_cash_account') from erpnext.stock.get_item_details import get_pos_profile, get_pos_profile_item_details - if not self.pos_profile: + if not self.pos_profile and not self.flags.ignore_pos_profile: pos_profile = get_pos_profile(self.company) or {} if not pos_profile: return diff --git a/erpnext/hooks.py b/erpnext/hooks.py index be05e35113e..a8f16171be0 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -442,7 +442,7 @@ accounting_dimension_doctypes = ["GL Entry", "Sales Invoice", "Purchase Invoice" "Purchase Receipt Item", "Stock Entry Detail", "Payment Entry Deduction", "Sales Taxes and Charges", "Purchase Taxes and Charges", "Shipping Rule", "Landed Cost Item", "Asset Value Adjustment", "Loyalty Program", "Fee Schedule", "Fee Structure", "Stock Reconciliation", "Travel Request", "Fees", "POS Profile", "Opening Invoice Creation Tool", "Opening Invoice Creation Tool Item", "Subscription", - "Subscription Plan" + "Subscription Plan", "POS Invoice", "POS Invoice Item" ] regional_overrides = { diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 3bc40a17e06..2148e6eec04 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -307,3 +307,4 @@ erpnext.patches.v14_0.delete_shopify_doctypes erpnext.patches.v13_0.replace_supplier_item_group_with_party_specific_item erpnext.patches.v13_0.update_dates_in_tax_withholding_category erpnext.patches.v14_0.update_opportunity_currency_fields +erpnext.patches.v13_0.create_accounting_dimensions_in_pos_doctypes diff --git a/erpnext/patches/v13_0/create_accounting_dimensions_in_pos_doctypes.py b/erpnext/patches/v13_0/create_accounting_dimensions_in_pos_doctypes.py new file mode 100644 index 00000000000..44501088102 --- /dev/null +++ b/erpnext/patches/v13_0/create_accounting_dimensions_in_pos_doctypes.py @@ -0,0 +1,42 @@ +import frappe +from frappe.custom.doctype.custom_field.custom_field import create_custom_field + + +def execute(): + frappe.reload_doc('accounts', 'doctype', 'accounting_dimension') + accounting_dimensions = frappe.db.sql("""select fieldname, label, document_type, disabled from + `tabAccounting Dimension`""", as_dict=1) + + if not accounting_dimensions: + return + + count = 1 + for d in accounting_dimensions: + + if count % 2 == 0: + insert_after_field = 'dimension_col_break' + else: + insert_after_field = 'accounting_dimensions_section' + + for doctype in ["POS Invoice", "POS Invoice Item"]: + + field = frappe.db.get_value("Custom Field", {"dt": doctype, "fieldname": d.fieldname}) + + if field: + continue + meta = frappe.get_meta(doctype, cached=False) + fieldnames = [d.fieldname for d in meta.get("fields")] + + df = { + "fieldname": d.fieldname, + "label": d.label, + "fieldtype": "Link", + "options": d.document_type, + "insert_after": insert_after_field + } + + if df['fieldname'] not in fieldnames: + create_custom_field(doctype, df) + frappe.clear_cache(doctype=doctype) + + count += 1 From 41f11eca7221e8534f8ad4cfd660057de276e484 Mon Sep 17 00:00:00 2001 From: Marica Date: Fri, 17 Sep 2021 10:27:29 +0530 Subject: [PATCH 070/116] fix: Remove duplicates from customer_code field (#27555) --- erpnext/stock/doctype/item/item.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index 86737f29b0c..768e5eae2da 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -563,8 +563,12 @@ class Item(WebsiteGenerator): _("Default BOM ({0}) must be active for this item or its template").format(bom_item)) def fill_customer_code(self): - """ Append all the customer codes and insert into "customer_code" field of item table """ - self.customer_code = ','.join(d.ref_code for d in self.get("customer_items", [])) + """ + Append all the customer codes and insert into "customer_code" field of item table. + Used to search Item by customer code. + """ + customer_codes = set(d.ref_code for d in self.get("customer_items", [])) + self.customer_code = ','.join(customer_codes) def check_item_tax(self): """Check whether Tax Rate is not entered twice for same Tax Type""" From d49346ac4561ab854dde6f97364b09af878ee877 Mon Sep 17 00:00:00 2001 From: Subin Tom <36098155+nemesis189@users.noreply.github.com> Date: Fri, 17 Sep 2021 10:39:03 +0530 Subject: [PATCH 071/116] fix: Tax breakup based on items, missing GST fields (#27524) * fix: Tax breakup based on items * fix: added gst fields,warehouse validation to pos inv,patch * fix: tax breakup test fix, eway bill hsn fix Co-authored-by: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> --- .../doctype/pos_invoice/pos_invoice.py | 1 + .../sales_invoice/test_sales_invoice.py | 13 ++++-- erpnext/patches.txt | 1 + .../v13_0/gst_fields_for_pos_invoice.py | 44 +++++++++++++++++++ erpnext/regional/india/setup.py | 2 + erpnext/regional/india/utils.py | 31 ++++++------- 6 files changed, 74 insertions(+), 18 deletions(-) create mode 100644 erpnext/patches/v13_0/gst_fields_for_pos_invoice.py diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py index d6e41e6f90d..27d678b212d 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py @@ -40,6 +40,7 @@ class POSInvoice(SalesInvoice): self.validate_change_amount() self.validate_change_account() self.validate_item_cost_centers() + self.validate_warehouse() self.validate_serialised_or_batched_item() self.validate_stock_availablility() self.validate_return_items_qty() diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index da0c3151933..3720ac33bbd 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -1420,15 +1420,22 @@ class TestSalesInvoice(unittest.TestCase): itemised_tax, itemised_taxable_amount = get_itemised_tax_breakup_data(si) expected_itemised_tax = { - "999800": { + "_Test Item": { "Service Tax": { "tax_rate": 10.0, - "tax_amount": 1500.0 + "tax_amount": 1000.0 + } + }, + "_Test Item 2": { + "Service Tax": { + "tax_rate": 10.0, + "tax_amount": 500.0 } } } expected_itemised_taxable_amount = { - "999800": 15000.0 + "_Test Item": 10000.0, + "_Test Item 2": 5000.0 } self.assertEqual(itemised_tax, expected_itemised_tax) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 2148e6eec04..cee796efbc2 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -307,4 +307,5 @@ erpnext.patches.v14_0.delete_shopify_doctypes erpnext.patches.v13_0.replace_supplier_item_group_with_party_specific_item erpnext.patches.v13_0.update_dates_in_tax_withholding_category erpnext.patches.v14_0.update_opportunity_currency_fields +erpnext.patches.v13_0.gst_fields_for_pos_invoice erpnext.patches.v13_0.create_accounting_dimensions_in_pos_doctypes diff --git a/erpnext/patches/v13_0/gst_fields_for_pos_invoice.py b/erpnext/patches/v13_0/gst_fields_for_pos_invoice.py new file mode 100644 index 00000000000..5b790d9f173 --- /dev/null +++ b/erpnext/patches/v13_0/gst_fields_for_pos_invoice.py @@ -0,0 +1,44 @@ +from __future__ import unicode_literals + +import frappe +from frappe.custom.doctype.custom_field.custom_field import create_custom_fields + + +def execute(): + company = frappe.get_all('Company', filters = {'country': 'India'}, fields=['name']) + if not company: + return + + hsn_sac_field = dict(fieldname='gst_hsn_code', label='HSN/SAC', + fieldtype='Data', fetch_from='item_code.gst_hsn_code', insert_after='description', + allow_on_submit=1, print_hide=1, fetch_if_empty=1) + nil_rated_exempt = dict(fieldname='is_nil_exempt', label='Is Nil Rated or Exempted', + fieldtype='Check', fetch_from='item_code.is_nil_exempt', insert_after='gst_hsn_code', + print_hide=1) + is_non_gst = dict(fieldname='is_non_gst', label='Is Non GST', + fieldtype='Check', fetch_from='item_code.is_non_gst', insert_after='is_nil_exempt', + print_hide=1) + taxable_value = dict(fieldname='taxable_value', label='Taxable Value', + fieldtype='Currency', insert_after='base_net_amount', hidden=1, options="Company:company:default_currency", + print_hide=1) + sales_invoice_gst_fields = [ + dict(fieldname='billing_address_gstin', label='Billing Address GSTIN', + fieldtype='Data', insert_after='customer_address', read_only=1, + fetch_from='customer_address.gstin', print_hide=1), + dict(fieldname='customer_gstin', label='Customer GSTIN', + fieldtype='Data', insert_after='shipping_address_name', + fetch_from='shipping_address_name.gstin', print_hide=1), + dict(fieldname='place_of_supply', label='Place of Supply', + fieldtype='Data', insert_after='customer_gstin', + print_hide=1, read_only=1), + dict(fieldname='company_gstin', label='Company GSTIN', + fieldtype='Data', insert_after='company_address', + fetch_from='company_address.gstin', print_hide=1, read_only=1), + ] + + custom_fields = { + 'POS Invoice': sales_invoice_gst_fields, + 'POS Invoice Item': [hsn_sac_field, nil_rated_exempt, is_non_gst, taxable_value], + } + + create_custom_fields(custom_fields, update=True) \ No newline at end of file diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index 79dc4b8fc97..ce346bcc5b1 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -483,6 +483,7 @@ def make_custom_fields(update=True): 'Purchase Order': purchase_invoice_gst_fields, 'Purchase Receipt': purchase_invoice_gst_fields, 'Sales Invoice': sales_invoice_gst_category + invoice_gst_fields + sales_invoice_shipping_fields + sales_invoice_gst_fields + si_ewaybill_fields, + 'POS Invoice': sales_invoice_gst_fields, 'Delivery Note': sales_invoice_gst_fields + ewaybill_fields + sales_invoice_shipping_fields + delivery_note_gst_category, 'Payment Entry': payment_entry_fields, 'Journal Entry': journal_entry_fields, @@ -501,6 +502,7 @@ def make_custom_fields(update=True): 'Sales Order Item': [hsn_sac_field, nil_rated_exempt, is_non_gst], 'Delivery Note Item': [hsn_sac_field, nil_rated_exempt, is_non_gst], 'Sales Invoice Item': [hsn_sac_field, nil_rated_exempt, is_non_gst, taxable_value], + 'POS Invoice Item': [hsn_sac_field, nil_rated_exempt, is_non_gst, taxable_value], 'Purchase Order Item': [hsn_sac_field, nil_rated_exempt, is_non_gst], 'Purchase Receipt Item': [hsn_sac_field, nil_rated_exempt, is_non_gst], 'Purchase Invoice Item': [hsn_sac_field, nil_rated_exempt, is_non_gst, taxable_value], diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index bf06d4a497d..903168d009f 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -117,7 +117,7 @@ def get_itemised_tax_breakup_header(item_doctype, tax_accounts): else: return [_("Item"), _("Taxable Amount")] + tax_accounts -def get_itemised_tax_breakup_data(doc, account_wise=False): +def get_itemised_tax_breakup_data(doc, account_wise=False, hsn_wise=False): itemised_tax = get_itemised_tax(doc.taxes, with_tax_account=account_wise) itemised_taxable_amount = get_itemised_taxable_amount(doc.items) @@ -125,28 +125,29 @@ def get_itemised_tax_breakup_data(doc, account_wise=False): if not frappe.get_meta(doc.doctype + " Item").has_field('gst_hsn_code'): return itemised_tax, itemised_taxable_amount - item_hsn_map = frappe._dict() - for d in doc.items: - item_hsn_map.setdefault(d.item_code or d.item_name, d.get("gst_hsn_code")) + if hsn_wise: + item_hsn_map = frappe._dict() + for d in doc.items: + item_hsn_map.setdefault(d.item_code or d.item_name, d.get("gst_hsn_code")) hsn_tax = {} for item, taxes in itemised_tax.items(): - hsn_code = item_hsn_map.get(item) - hsn_tax.setdefault(hsn_code, frappe._dict()) + item_or_hsn = item if not hsn_wise else item_hsn_map.get(item) + hsn_tax.setdefault(item_or_hsn, frappe._dict()) for tax_desc, tax_detail in taxes.items(): key = tax_desc if account_wise: key = tax_detail.get('tax_account') - hsn_tax[hsn_code].setdefault(key, {"tax_rate": 0, "tax_amount": 0}) - hsn_tax[hsn_code][key]["tax_rate"] = tax_detail.get("tax_rate") - hsn_tax[hsn_code][key]["tax_amount"] += tax_detail.get("tax_amount") + hsn_tax[item_or_hsn].setdefault(key, {"tax_rate": 0, "tax_amount": 0}) + hsn_tax[item_or_hsn][key]["tax_rate"] = tax_detail.get("tax_rate") + hsn_tax[item_or_hsn][key]["tax_amount"] += tax_detail.get("tax_amount") # set taxable amount hsn_taxable_amount = frappe._dict() for item in itemised_taxable_amount: - hsn_code = item_hsn_map.get(item) - hsn_taxable_amount.setdefault(hsn_code, 0) - hsn_taxable_amount[hsn_code] += itemised_taxable_amount.get(item) + item_or_hsn = item if not hsn_wise else item_hsn_map.get(item) + hsn_taxable_amount.setdefault(item_or_hsn, 0) + hsn_taxable_amount[item_or_hsn] += itemised_taxable_amount.get(item) return hsn_tax, hsn_taxable_amount @@ -440,7 +441,7 @@ def get_ewb_data(dt, dn): data.itemList = [] data.totalValue = doc.total - data = get_item_list(data, doc) + data = get_item_list(data, doc, hsn_wise=True) disable_rounded = frappe.db.get_single_value('Global Defaults', 'disable_rounded_total') data.totInvValue = doc.grand_total if disable_rounded else doc.rounded_total @@ -551,7 +552,7 @@ def get_address_details(data, doc, company_address, billing_address, dispatch_ad return data -def get_item_list(data, doc): +def get_item_list(data, doc, hsn_wise=False): for attr in ['cgstValue', 'sgstValue', 'igstValue', 'cessValue', 'OthValue']: data[attr] = 0 @@ -563,7 +564,7 @@ def get_item_list(data, doc): 'cess_account': ['cessRate', 'cessValue'] } item_data_attrs = ['sgstRate', 'cgstRate', 'igstRate', 'cessRate', 'cessNonAdvol'] - hsn_wise_charges, hsn_taxable_amount = get_itemised_tax_breakup_data(doc, account_wise=True) + hsn_wise_charges, hsn_taxable_amount = get_itemised_tax_breakup_data(doc, account_wise=True, hsn_wise=hsn_wise) for hsn_code, taxable_amount in hsn_taxable_amount.items(): item_data = frappe._dict() if not hsn_code: From e03d9aa8890680baefe0d335dafdbfc5d0445fd4 Mon Sep 17 00:00:00 2001 From: Saqib Date: Fri, 17 Sep 2021 13:03:27 +0530 Subject: [PATCH 072/116] fix: unecessary keyword args were passed in mapper functions (#27563) --- erpnext/public/js/utils.js | 7 +++++-- erpnext/stock/doctype/material_request/material_request.py | 5 +++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index 2a74d6015f4..7f39b990bf0 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -714,12 +714,15 @@ erpnext.utils.map_current_doc = function(opts) { child_columns: opts.child_columns, action: function(selections, args) { let values = selections; - if(values.length === 0){ + if (values.length === 0) { frappe.msgprint(__("Please select {0}", [opts.source_doctype])) return; } opts.source_name = values; - opts.args = args; + if (opts.allow_child_item_selection) { + // args contains filtered child docnames + opts.args = args; + } d.dialog.hide(); _map(); }, diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py index 2569c04251c..cf98b19e7a1 100644 --- a/erpnext/stock/doctype/material_request/material_request.py +++ b/erpnext/stock/doctype/material_request/material_request.py @@ -272,8 +272,9 @@ def update_status(name, status): material_request.update_status(status) @frappe.whitelist() -def make_purchase_order(source_name, target_doc=None, args={}): - +def make_purchase_order(source_name, target_doc=None, args=None): + if args is None: + args = {} if isinstance(args, string_types): args = json.loads(args) From bf2a590332100744b1641c3680e296f05f057dcb Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Fri, 17 Sep 2021 18:39:21 +0530 Subject: [PATCH 073/116] fix: View Stock / Accounting Ledger button not showing in the stock entry (#27570) --- erpnext/stock/doctype/stock_entry/stock_entry.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index c819d49f509..7cb9665e857 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -1100,4 +1100,4 @@ function check_should_not_attach_bom_items(bom_no) { ); } -$.extend(cur_frm.cscript, new erpnext.stock.StockEntry({frm: cur_frm})); +extend_cscript(cur_frm.cscript, new erpnext.stock.StockEntry({frm: cur_frm})); From 4f7af79c31720eb32defed0e5f1b60e9837af728 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Sat, 18 Sep 2021 13:34:27 +0530 Subject: [PATCH 074/116] fix: PO/PINV - Check if doctype has company_address field before setting the value (#27441) (#27576) Co-authored-by: Vama Mehta (cherry picked from commit 666eaae6ce976c5d820b3b9f91d23a0ed28a263a) Co-authored-by: vama --- erpnext/public/js/controllers/transaction.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index fd3d4721376..91b1247e6f4 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -884,7 +884,9 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe if (r.message) { me.frm.set_value("billing_address", r.message); } else { - me.frm.set_value("company_address", ""); + if (frappe.meta.get_docfield(me.frm.doctype, 'company_address')) { + me.frm.set_value("company_address", ""); + } } } }); From f07ff92a35c876e024703856cc2c0d31437b6dd5 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Mon, 20 Sep 2021 11:12:39 +0530 Subject: [PATCH 075/116] fix: Improvements in COA Importer (#27584) --- .../chart_of_accounts_importer.js | 56 +++++++------------ .../chart_of_accounts_importer.py | 27 +++++---- 2 files changed, 33 insertions(+), 50 deletions(-) diff --git a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.js b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.js index f795dfa83e6..f67c59c2549 100644 --- a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.js +++ b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.js @@ -79,7 +79,6 @@ frappe.ui.form.on('Chart of Accounts Importer', { $(frm.fields_dict['chart_tree'].wrapper).empty(); // empty wrapper on removing file } else { generate_tree_preview(frm); - validate_csv_data(frm); } }, @@ -104,23 +103,6 @@ frappe.ui.form.on('Chart of Accounts Importer', { } }); -var validate_csv_data = function(frm) { - frappe.call({ - method: "erpnext.accounts.doctype.chart_of_accounts_importer.chart_of_accounts_importer.validate_accounts", - args: {file_name: frm.doc.import_file}, - callback: function(r) { - if(r.message && r.message[0]===true) { - frm.page["show_import_button"] = true; - frm.page["total_accounts"] = r.message[1]; - frm.trigger("refresh"); - } else { - frm.page.set_indicator(__('Resolve error and upload again.'), 'orange'); - frappe.throw(__(r.message)); - } - } - }); -}; - var create_import_button = function(frm) { frm.page.set_primary_action(__("Import"), function () { frappe.call({ @@ -151,23 +133,25 @@ var create_reset_button = function(frm) { }; var generate_tree_preview = function(frm) { - let parent = __('All Accounts'); - $(frm.fields_dict['chart_tree'].wrapper).empty(); // empty wrapper to load new data + if (frm.doc.import_file) { + let parent = __('All Accounts'); + $(frm.fields_dict['chart_tree'].wrapper).empty(); // empty wrapper to load new data - // generate tree structure based on the csv data - new frappe.ui.Tree({ - parent: $(frm.fields_dict['chart_tree'].wrapper), - label: parent, - expandable: true, - method: 'erpnext.accounts.doctype.chart_of_accounts_importer.chart_of_accounts_importer.get_coa', - args: { - file_name: frm.doc.import_file, - parent: parent, - doctype: 'Chart of Accounts Importer', - file_type: frm.doc.file_type - }, - onclick: function(node) { - parent = node.value; - } - }); + // generate tree structure based on the csv data + new frappe.ui.Tree({ + parent: $(frm.fields_dict['chart_tree'].wrapper), + label: parent, + expandable: true, + method: 'erpnext.accounts.doctype.chart_of_accounts_importer.chart_of_accounts_importer.get_coa', + args: { + file_name: frm.doc.import_file, + parent: parent, + doctype: 'Chart of Accounts Importer', + file_type: frm.doc.file_type + }, + onclick: function(node) { + parent = node.value; + } + }); + } }; diff --git a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py index 61968cf627d..9a0234a91f9 100644 --- a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py +++ b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py @@ -25,8 +25,16 @@ from erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts import class ChartofAccountsImporter(Document): - def validate(self): - validate_accounts(self.import_file) + pass + +def validate_columns(data): + if not data: + frappe.throw(_('No data found. Seems like you uploaded a blank file')) + + no_of_columns = max([len(d) for d in data]) + + if no_of_columns > 7: + frappe.throw(_('More columns found than expected. Please compare the uploaded file with standard template')) @frappe.whitelist() def validate_company(company): @@ -131,6 +139,8 @@ def get_coa(doctype, parent, is_root=False, file_name=None): else: data = generate_data_from_excel(file_doc, extension) + validate_columns(data) + validate_accounts(data) forest = build_forest(data) accounts = build_tree_from_json("", chart_data=forest) # returns alist of dict in a tree render-able form @@ -322,9 +332,6 @@ def validate_accounts(file_name): def validate_root(accounts): roots = [accounts[d] for d in accounts if not accounts[d].get('parent_account')] - if len(roots) < 4: - frappe.throw(_("Number of root accounts cannot be less than 4")) - error_messages = [] for account in roots: @@ -364,20 +371,12 @@ def get_mandatory_account_types(): def validate_account_types(accounts): account_types_for_ledger = ["Cost of Goods Sold", "Depreciation", "Fixed Asset", "Payable", "Receivable", "Stock Adjustment"] - account_types = [accounts[d]["account_type"] for d in accounts if not accounts[d]['is_group'] == 1] + account_types = [accounts[d]["account_type"] for d in accounts if not cint(accounts[d]['is_group']) == 1] missing = list(set(account_types_for_ledger) - set(account_types)) if missing: frappe.throw(_("Please identify/create Account (Ledger) for type - {0}").format(' , '.join(missing))) - account_types_for_group = ["Bank", "Cash", "Stock"] - # fix logic bug - account_groups = [accounts[d]["account_type"] for d in accounts if accounts[d]['is_group'] == 1] - - missing = list(set(account_types_for_group) - set(account_groups)) - if missing: - frappe.throw(_("Please identify/create Account (Group) for type - {0}").format(' , '.join(missing))) - def unset_existing_data(company): linked = frappe.db.sql('''select fieldname from tabDocField where fieldtype="Link" and options="Account" and parent="Company"''', as_dict=True) From 648b2d72a547caa0703a334cd61111a7ba35e24b Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 20 Sep 2021 15:27:12 +0530 Subject: [PATCH 076/116] perf: extract loop invariant db calls --- erpnext/controllers/accounts_controller.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index b90db054b57..6034cced8db 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -980,6 +980,9 @@ class AccountsController(TransactionBase): item_allowance = {} global_qty_allowance, global_amount_allowance = None, None + role_allowed_to_over_bill = frappe.db.get_single_value('Accounts Settings', 'role_allowed_to_over_bill') + user_roles = frappe.get_roles() + for item in self.get("items"): if item.get(item_ref_dn): ref_amt = flt(frappe.db.get_value(ref_dt + " Item", @@ -1009,9 +1012,7 @@ class AccountsController(TransactionBase): total_billed_amt = abs(total_billed_amt) max_allowed_amt = abs(max_allowed_amt) - role_allowed_to_over_bill = frappe.db.get_single_value('Accounts Settings', 'role_allowed_to_over_bill') - - if total_billed_amt - max_allowed_amt > 0.01 and role_allowed_to_over_bill not in frappe.get_roles(): + if total_billed_amt - max_allowed_amt > 0.01 and role_allowed_to_over_bill not in user_roles: if self.doctype != "Purchase Invoice": self.throw_overbill_exception(item, max_allowed_amt) elif not cint(frappe.db.get_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice")): From ef66beec51dc98a12207b4c528fe0632413118b4 Mon Sep 17 00:00:00 2001 From: Saqib Date: Mon, 20 Sep 2021 15:33:29 +0530 Subject: [PATCH 077/116] fix(plaid): query to check if bank account exists --- .../doctype/plaid_settings/plaid_settings.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py index d2748c2faad..310afed4811 100644 --- a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py +++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py @@ -85,10 +85,8 @@ def add_bank_accounts(response, bank, company): if not acc_subtype: add_account_subtype(account["subtype"]) - existing_bank_account = frappe.db.exists("Bank Account", { - 'account_name': account["name"], - 'bank': bank["bank_name"] - }) + bank_account_name = "{} - {}".format(account["name"], bank["bank_name"]) + existing_bank_account = frappe.db.exists("Bank Account", bank_account_name) if not existing_bank_account: try: @@ -197,6 +195,7 @@ def get_transactions(bank, bank_account=None, start_date=None, end_date=None): plaid = PlaidConnector(access_token) + transactions = [] try: transactions = plaid.get_transactions(start_date=start_date, end_date=end_date, account_id=account_id) except ItemError as e: @@ -205,7 +204,7 @@ def get_transactions(bank, bank_account=None, start_date=None, end_date=None): msg += _("Please refresh or reset the Plaid linking of the Bank {}.").format(bank) + " " frappe.log_error(msg, title=_("Plaid Link Refresh Required")) - return transactions or [] + return transactions def new_bank_transaction(transaction): From 0ff7367f390682587346ebd54dbddf1e8eb5bb9e Mon Sep 17 00:00:00 2001 From: Subin Tom <36098155+nemesis189@users.noreply.github.com> Date: Mon, 20 Sep 2021 16:13:36 +0530 Subject: [PATCH 078/116] fix: Tax Breakup table headers fix (#27596) --- erpnext/regional/india/utils.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index 903168d009f..091cc8847cb 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -112,10 +112,7 @@ def validate_gstin_check_digit(gstin, label='GSTIN'): frappe.throw(_("""Invalid {0}! The check digit validation has failed. Please ensure you've typed the {0} correctly.""").format(label)) def get_itemised_tax_breakup_header(item_doctype, tax_accounts): - if frappe.get_meta(item_doctype).has_field('gst_hsn_code'): - return [_("HSN/SAC"), _("Taxable Amount")] + tax_accounts - else: - return [_("Item"), _("Taxable Amount")] + tax_accounts + return [_("Item"), _("Taxable Amount")] + tax_accounts def get_itemised_tax_breakup_data(doc, account_wise=False, hsn_wise=False): itemised_tax = get_itemised_tax(doc.taxes, with_tax_account=account_wise) From 43bf82b58beb0d057b10b0f98c398908acc8c00a Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 20 Sep 2021 16:31:20 +0530 Subject: [PATCH 079/116] fix: warn when overbilling checks are skipped. --- erpnext/controllers/accounts_controller.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 6034cced8db..22efcdf1cc2 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -983,6 +983,8 @@ class AccountsController(TransactionBase): role_allowed_to_over_bill = frappe.db.get_single_value('Accounts Settings', 'role_allowed_to_over_bill') user_roles = frappe.get_roles() + total_overbilled_amt = 0.0 + for item in self.get("items"): if item.get(item_ref_dn): ref_amt = flt(frappe.db.get_value(ref_dt + " Item", @@ -1012,12 +1014,19 @@ class AccountsController(TransactionBase): total_billed_amt = abs(total_billed_amt) max_allowed_amt = abs(max_allowed_amt) - if total_billed_amt - max_allowed_amt > 0.01 and role_allowed_to_over_bill not in user_roles: + overbill_amt = total_billed_amt - max_allowed_amt + total_overbilled_amt += overbill_amt + + if overbill_amt > 0.01 and role_allowed_to_over_bill not in user_roles: if self.doctype != "Purchase Invoice": self.throw_overbill_exception(item, max_allowed_amt) elif not cint(frappe.db.get_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice")): self.throw_overbill_exception(item, max_allowed_amt) + if role_allowed_to_over_bill in user_roles and total_overbilled_amt > 0.1: + frappe.msgprint(_("INFO: Overbilling of {} ignored because you have {} role.") + .format(total_overbilled_amt, role_allowed_to_over_bill)) + def throw_overbill_exception(self, item, max_allowed_amt): frappe.throw(_("Cannot overbill for Item {0} in row {1} more than {2}. To allow over-billing, please set allowance in Accounts Settings") .format(item.item_code, item.idx, max_allowed_amt)) From 5e4fbba753f9a968a03324773cbff65c04d67f0c Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 20 Sep 2021 16:36:27 +0530 Subject: [PATCH 080/116] refactor: add guard clause in for loop Reduce overly indented code/improve readability. --- erpnext/controllers/accounts_controller.py | 69 +++++++++++----------- 1 file changed, 36 insertions(+), 33 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 22efcdf1cc2..024a2978700 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -986,46 +986,49 @@ class AccountsController(TransactionBase): total_overbilled_amt = 0.0 for item in self.get("items"): - if item.get(item_ref_dn): - ref_amt = flt(frappe.db.get_value(ref_dt + " Item", - item.get(item_ref_dn), based_on), self.precision(based_on, item)) - if not ref_amt: - frappe.msgprint( - _("Warning: System will not check overbilling since amount for Item {0} in {1} is zero") - .format(item.item_code, ref_dt)) - else: - already_billed = frappe.db.sql(""" - select sum(%s) - from `tab%s` - where %s=%s and docstatus=1 and parent != %s - """ % (based_on, self.doctype + " Item", item_ref_dn, '%s', '%s'), - (item.get(item_ref_dn), self.name))[0][0] + if not item.get(item_ref_dn): + continue - total_billed_amt = flt(flt(already_billed) + flt(item.get(based_on)), - self.precision(based_on, item)) + ref_amt = flt(frappe.db.get_value(ref_dt + " Item", + item.get(item_ref_dn), based_on), self.precision(based_on, item)) + if not ref_amt: + frappe.msgprint( + _("Warning: System will not check overbilling since amount for Item {0} in {1} is zero") + .format(item.item_code, ref_dt)) + continue - allowance, item_allowance, global_qty_allowance, global_amount_allowance = \ - get_allowance_for(item.item_code, item_allowance, global_qty_allowance, global_amount_allowance, "amount") + already_billed = frappe.db.sql(""" + select sum(%s) + from `tab%s` + where %s=%s and docstatus=1 and parent != %s + """ % (based_on, self.doctype + " Item", item_ref_dn, '%s', '%s'), + (item.get(item_ref_dn), self.name))[0][0] - max_allowed_amt = flt(ref_amt * (100 + allowance) / 100) + total_billed_amt = flt(flt(already_billed) + flt(item.get(based_on)), + self.precision(based_on, item)) - if total_billed_amt < 0 and max_allowed_amt < 0: - # while making debit note against purchase return entry(purchase receipt) getting overbill error - total_billed_amt = abs(total_billed_amt) - max_allowed_amt = abs(max_allowed_amt) + allowance, item_allowance, global_qty_allowance, global_amount_allowance = \ + get_allowance_for(item.item_code, item_allowance, global_qty_allowance, global_amount_allowance, "amount") - overbill_amt = total_billed_amt - max_allowed_amt - total_overbilled_amt += overbill_amt + max_allowed_amt = flt(ref_amt * (100 + allowance) / 100) - if overbill_amt > 0.01 and role_allowed_to_over_bill not in user_roles: - if self.doctype != "Purchase Invoice": - self.throw_overbill_exception(item, max_allowed_amt) - elif not cint(frappe.db.get_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice")): - self.throw_overbill_exception(item, max_allowed_amt) + if total_billed_amt < 0 and max_allowed_amt < 0: + # while making debit note against purchase return entry(purchase receipt) getting overbill error + total_billed_amt = abs(total_billed_amt) + max_allowed_amt = abs(max_allowed_amt) - if role_allowed_to_over_bill in user_roles and total_overbilled_amt > 0.1: - frappe.msgprint(_("INFO: Overbilling of {} ignored because you have {} role.") - .format(total_overbilled_amt, role_allowed_to_over_bill)) + overbill_amt = total_billed_amt - max_allowed_amt + total_overbilled_amt += overbill_amt + + if overbill_amt > 0.01 and role_allowed_to_over_bill not in user_roles: + if self.doctype != "Purchase Invoice": + self.throw_overbill_exception(item, max_allowed_amt) + elif not cint(frappe.db.get_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice")): + self.throw_overbill_exception(item, max_allowed_amt) + + if role_allowed_to_over_bill in user_roles and total_overbilled_amt > 0.1: + frappe.msgprint(_("INFO: Overbilling of {} ignored because you have {} role.") + .format(total_overbilled_amt, role_allowed_to_over_bill)) def throw_overbill_exception(self, item, max_allowed_amt): frappe.throw(_("Cannot overbill for Item {0} in row {1} more than {2}. To allow over-billing, please set allowance in Accounts Settings") From 21a955d20b83f6c55aafab294be0f1f4d4b89153 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 20 Sep 2021 16:58:23 +0530 Subject: [PATCH 081/116] fix(ux): better error message Co-authored-by: Saqib --- erpnext/controllers/accounts_controller.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 024a2978700..5359089698a 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -993,8 +993,8 @@ class AccountsController(TransactionBase): item.get(item_ref_dn), based_on), self.precision(based_on, item)) if not ref_amt: frappe.msgprint( - _("Warning: System will not check overbilling since amount for Item {0} in {1} is zero") - .format(item.item_code, ref_dt)) + _("System will not check overbilling since amount for Item {0} in {1} is zero") + .format(item.item_code, ref_dt), title=_("Warning"), indicator="orange") continue already_billed = frappe.db.sql(""" @@ -1002,7 +1002,7 @@ class AccountsController(TransactionBase): from `tab%s` where %s=%s and docstatus=1 and parent != %s """ % (based_on, self.doctype + " Item", item_ref_dn, '%s', '%s'), - (item.get(item_ref_dn), self.name))[0][0] + (item.get(item_ref_dn), self.name))[0][0] total_billed_amt = flt(flt(already_billed) + flt(item.get(based_on)), self.precision(based_on, item)) @@ -1027,8 +1027,8 @@ class AccountsController(TransactionBase): self.throw_overbill_exception(item, max_allowed_amt) if role_allowed_to_over_bill in user_roles and total_overbilled_amt > 0.1: - frappe.msgprint(_("INFO: Overbilling of {} ignored because you have {} role.") - .format(total_overbilled_amt, role_allowed_to_over_bill)) + frappe.msgprint(_("Overbilling of {} ignored because you have {} role.") + .format(total_overbilled_amt, role_allowed_to_over_bill), title=_("Warning"), indicator="orange") def throw_overbill_exception(self, item, max_allowed_amt): frappe.throw(_("Cannot overbill for Item {0} in row {1} more than {2}. To allow over-billing, please set allowance in Accounts Settings") From 190c2d84da998344772fbba9675fada8f555b4fa Mon Sep 17 00:00:00 2001 From: Anuja Pawar <60467153+Anuja-pawar@users.noreply.github.com> Date: Mon, 20 Sep 2021 21:32:38 +0530 Subject: [PATCH 082/116] fix: reference row added in allocation table (#27602) --- .../payment_reconciliation_allocation.json | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.json b/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.json index 36535014320..b8c65eea847 100644 --- a/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.json +++ b/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.json @@ -7,6 +7,7 @@ "field_order": [ "reference_type", "reference_name", + "reference_row", "column_break_3", "invoice_type", "invoice_number", @@ -121,11 +122,17 @@ "label": "Amount", "options": "Currency", "read_only": 1 + }, + { + "fieldname": "reference_row", + "fieldtype": "Data", + "hidden": 1, + "label": "Reference Row" } ], "istable": 1, "links": [], - "modified": "2021-08-30 10:58:42.665107", + "modified": "2021-09-20 17:23:09.455803", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Reconciliation Allocation", From 6d8e869a92285a9be890b58b1754cb373cfcd6fd Mon Sep 17 00:00:00 2001 From: "Abdullah A. Zaqout" <26047413+zaqoutabed@users.noreply.github.com> Date: Mon, 20 Sep 2021 19:05:37 +0300 Subject: [PATCH 083/116] fix(pos): check if item exists before fetching available qty (#27581) --- erpnext/selling/page/point_of_sale/pos_item_details.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/selling/page/point_of_sale/pos_item_details.js b/erpnext/selling/page/point_of_sale/pos_item_details.js index d899c5c19b4..ec861d7c531 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_details.js +++ b/erpnext/selling/page/point_of_sale/pos_item_details.js @@ -236,7 +236,7 @@ erpnext.PointOfSale.ItemDetails = class { if (this.value) { me.events.form_updated(me.current_item, 'warehouse', this.value).then(() => { me.item_stock_map = me.events.get_item_stock_map(); - const available_qty = me.item_stock_map[me.item_row.item_code][this.value]; + const available_qty = me.item_stock_map[me.item_row.item_code] && me.item_stock_map[me.item_row.item_code][this.value]; if (available_qty === undefined) { me.events.get_available_stock(me.item_row.item_code, this.value).then(() => { // item stock map is updated now reset warehouse From 5c400da4e2c3a7831199a3a0d9e9e12b2c135aac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20de=20Ryckel?= Date: Tue, 21 Sep 2021 06:59:15 +0300 Subject: [PATCH 084/116] Update training_result.js (#27615) cur_frm is deprecated --- erpnext/hr/doctype/training_result/training_result.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/hr/doctype/training_result/training_result.js b/erpnext/hr/doctype/training_result/training_result.js index 5cdbcad8058..718b383e721 100644 --- a/erpnext/hr/doctype/training_result/training_result.js +++ b/erpnext/hr/doctype/training_result/training_result.js @@ -21,7 +21,7 @@ frappe.ui.form.on('Training Result', { frm.set_value("employees" ,""); if (r.message) { $.each(r.message, function(i, d) { - var row = frappe.model.add_child(cur_frm.doc, "Training Result Employee", "employees"); + var row = frappe.model.add_child(frm.doc, "Training Result Employee", "employees"); row.employee = d.employee; row.employee_name = d.employee_name; }); From abded895f3e47ac8ac24117eca998e202a5dba16 Mon Sep 17 00:00:00 2001 From: Kenneth Sequeira <33246109+kennethsequeira@users.noreply.github.com> Date: Tue, 21 Sep 2021 10:58:18 +0530 Subject: [PATCH 085/116] fix: update default KSA VAT rate for setup (#27614) --- erpnext/setup/setup_wizard/data/country_wise_tax.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/setup/setup_wizard/data/country_wise_tax.json b/erpnext/setup/setup_wizard/data/country_wise_tax.json index 34af093a231..b7e895db363 100644 --- a/erpnext/setup/setup_wizard/data/country_wise_tax.json +++ b/erpnext/setup/setup_wizard/data/country_wise_tax.json @@ -2116,9 +2116,9 @@ }, "Saudi Arabia": { - "KSA VAT 5%": { - "account_name": "VAT 5%", - "tax_rate": 5.00 + "KSA VAT 15%": { + "account_name": "VAT 15%", + "tax_rate": 15.00 }, "KSA VAT Zero": { "account_name": "VAT Zero", From 9110223341321946699931dc45467f96427d8643 Mon Sep 17 00:00:00 2001 From: Marica Date: Tue, 21 Sep 2021 16:50:14 +0530 Subject: [PATCH 086/116] fix: (ux) Use subassembly schedule date while making WO from Prod Plan (#27628) - Set subassemply WO's planned start date from Production Plan --- .../manufacturing/doctype/production_plan/production_plan.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 18284e0199e..b9efe9b41ea 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -457,7 +457,8 @@ class ProductionPlan(Document): def prepare_args_for_sub_assembly_items(self, row, args): for field in ["production_item", "item_name", "qty", "fg_warehouse", - "description", "bom_no", "stock_uom", "bom_level", "production_plan_item"]: + "description", "bom_no", "stock_uom", "bom_level", + "production_plan_item", "schedule_date"]: args[field] = row.get(field) args.update({ From c302c7ab4286e3547367beffc4feb6fb8c909f1f Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 21 Sep 2021 17:51:46 +0530 Subject: [PATCH 087/116] fix: remove bad default for anniversary reminders (#27632) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤦 --- erpnext/hr/doctype/employee/employee_reminders.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/hr/doctype/employee/employee_reminders.py b/erpnext/hr/doctype/employee/employee_reminders.py index ba086dc0602..216d8f6bb3a 100644 --- a/erpnext/hr/doctype/employee/employee_reminders.py +++ b/erpnext/hr/doctype/employee/employee_reminders.py @@ -184,7 +184,7 @@ def get_employees_having_an_event_today(event_type): # -------------------------- def send_work_anniversary_reminders(): """Send Employee Work Anniversary Reminders if 'Send Work Anniversary Reminders' is checked""" - to_send = int(frappe.db.get_single_value("HR Settings", "send_work_anniversary_reminders") or 1) + to_send = int(frappe.db.get_single_value("HR Settings", "send_work_anniversary_reminders")) if not to_send: return From 2af6ea68ae94fed5f363750b837eaa8a1e877e49 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Tue, 21 Sep 2021 20:33:09 +0530 Subject: [PATCH 088/116] fix: remove unknown field employee_name from query (bp #27634) * fix: remove unknown field employee_name from query * fix: remove unknown fieldname Co-authored-by: root Co-authored-by: Ankush Menat (cherry picked from commit 9ebabb86b3d28e2c94eb2761f1864c88b6eb1e59) Co-authored-by: Bhavesh Maheshwari <34086262+bhavesh95863@users.noreply.github.com> --- erpnext/manufacturing/doctype/job_card/job_card.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index 3209546a12c..e1d79be81c4 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -677,7 +677,7 @@ def get_job_details(start, end, filters=None): conditions = get_filters_cond("Job Card", filters, []) job_cards = frappe.db.sql(""" SELECT `tabJob Card`.name, `tabJob Card`.work_order, - `tabJob Card`.employee_name, `tabJob Card`.status, ifnull(`tabJob Card`.remarks, ''), + `tabJob Card`.status, ifnull(`tabJob Card`.remarks, ''), min(`tabJob Card Time Log`.from_time) as from_time, max(`tabJob Card Time Log`.to_time) as to_time FROM `tabJob Card` , `tabJob Card Time Log` @@ -687,7 +687,7 @@ def get_job_details(start, end, filters=None): for d in job_cards: subject_data = [] - for field in ["name", "work_order", "remarks", "employee_name"]: + for field in ["name", "work_order", "remarks"]: if not d.get(field): continue subject_data.append(d.get(field)) From c8b9a55e9620655850e406889788da15715300f8 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Wed, 22 Sep 2021 12:11:35 +0530 Subject: [PATCH 089/116] feat: add `Partly Paid` status in Invoices (#27625) --- .../purchase_invoice/purchase_invoice.json | 623 +++++------------- .../purchase_invoice/purchase_invoice.py | 12 +- .../purchase_invoice/purchase_invoice_list.js | 76 ++- .../doctype/sales_invoice/sales_invoice.json | 5 +- .../doctype/sales_invoice/sales_invoice.py | 51 +- .../sales_invoice/sales_invoice_list.js | 6 +- .../sales_invoice/test_sales_invoice.py | 51 ++ erpnext/controllers/accounts_controller.py | 18 +- 8 files changed, 319 insertions(+), 523 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index 7822f747f64..dde0328130f 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -177,9 +177,7 @@ "hidden": 1, "label": "Title", "no_copy": 1, - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "naming_series", @@ -191,9 +189,7 @@ "options": "ACC-PINV-.YYYY.-\nACC-PINV-RET-.YYYY.-", "print_hide": 1, "reqd": 1, - "set_only_once": 1, - "show_days": 1, - "show_seconds": 1 + "set_only_once": 1 }, { "fieldname": "supplier", @@ -205,9 +201,7 @@ "options": "Supplier", "print_hide": 1, "reqd": 1, - "search_index": 1, - "show_days": 1, - "show_seconds": 1 + "search_index": 1 }, { "bold": 1, @@ -219,9 +213,7 @@ "label": "Supplier Name", "oldfieldname": "supplier_name", "oldfieldtype": "Data", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fetch_from": "supplier.tax_id", @@ -229,27 +221,21 @@ "fieldtype": "Read Only", "label": "Tax Id", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "due_date", "fieldtype": "Date", "label": "Due Date", "oldfieldname": "due_date", - "oldfieldtype": "Date", - "show_days": 1, - "show_seconds": 1 + "oldfieldtype": "Date" }, { "default": "0", "fieldname": "is_paid", "fieldtype": "Check", "label": "Is Paid", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "default": "0", @@ -257,25 +243,19 @@ "fieldtype": "Check", "label": "Is Return (Debit Note)", "no_copy": 1, - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "default": "0", "fieldname": "apply_tds", "fieldtype": "Check", "label": "Apply Tax Withholding Amount", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "column_break1", "fieldtype": "Column Break", "oldfieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1, "width": "50%" }, { @@ -285,17 +265,13 @@ "label": "Company", "options": "Company", "print_hide": 1, - "remember_last_selected_value": 1, - "show_days": 1, - "show_seconds": 1 + "remember_last_selected_value": 1 }, { "fieldname": "cost_center", "fieldtype": "Link", "label": "Cost Center", - "options": "Cost Center", - "show_days": 1, - "show_seconds": 1 + "options": "Cost Center" }, { "default": "Today", @@ -307,9 +283,7 @@ "oldfieldtype": "Date", "print_hide": 1, "reqd": 1, - "search_index": 1, - "show_days": 1, - "show_seconds": 1 + "search_index": 1 }, { "fieldname": "posting_time", @@ -318,8 +292,6 @@ "no_copy": 1, "print_hide": 1, "print_width": "100px", - "show_days": 1, - "show_seconds": 1, "width": "100px" }, { @@ -328,9 +300,7 @@ "fieldname": "set_posting_time", "fieldtype": "Check", "label": "Edit Posting Date and Time", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "amended_from", @@ -342,58 +312,44 @@ "oldfieldtype": "Link", "options": "Purchase Invoice", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "collapsible": 1, "collapsible_depends_on": "eval:doc.on_hold", "fieldname": "sb_14", "fieldtype": "Section Break", - "label": "Hold Invoice", - "show_days": 1, - "show_seconds": 1 + "label": "Hold Invoice" }, { "default": "0", "fieldname": "on_hold", "fieldtype": "Check", - "label": "Hold Invoice", - "show_days": 1, - "show_seconds": 1 + "label": "Hold Invoice" }, { "depends_on": "eval:doc.on_hold", "description": "Once set, this invoice will be on hold till the set date", "fieldname": "release_date", "fieldtype": "Date", - "label": "Release Date", - "show_days": 1, - "show_seconds": 1 + "label": "Release Date" }, { "fieldname": "cb_17", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "depends_on": "eval:doc.on_hold", "fieldname": "hold_comment", "fieldtype": "Small Text", - "label": "Reason For Putting On Hold", - "show_days": 1, - "show_seconds": 1 + "label": "Reason For Putting On Hold" }, { "collapsible": 1, "collapsible_depends_on": "bill_no", "fieldname": "supplier_invoice_details", "fieldtype": "Section Break", - "label": "Supplier Invoice Details", - "show_days": 1, - "show_seconds": 1 + "label": "Supplier Invoice Details" }, { "fieldname": "bill_no", @@ -401,15 +357,11 @@ "label": "Supplier Invoice No", "oldfieldname": "bill_no", "oldfieldtype": "Data", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "column_break_15", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "fieldname": "bill_date", @@ -418,17 +370,13 @@ "no_copy": 1, "oldfieldname": "bill_date", "oldfieldtype": "Date", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "depends_on": "return_against", "fieldname": "returns", "fieldtype": "Section Break", - "label": "Returns", - "show_days": 1, - "show_seconds": 1 + "label": "Returns" }, { "depends_on": "return_against", @@ -438,34 +386,26 @@ "no_copy": 1, "options": "Purchase Invoice", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "collapsible": 1, "fieldname": "section_addresses", "fieldtype": "Section Break", - "label": "Address and Contact", - "show_days": 1, - "show_seconds": 1 + "label": "Address and Contact" }, { "fieldname": "supplier_address", "fieldtype": "Link", "label": "Select Supplier Address", "options": "Address", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "address_display", "fieldtype": "Small Text", "label": "Address", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "contact_person", @@ -473,67 +413,51 @@ "in_global_search": 1, "label": "Contact Person", "options": "Contact", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "contact_display", "fieldtype": "Small Text", "label": "Contact", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "contact_mobile", "fieldtype": "Small Text", "label": "Mobile No", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "contact_email", "fieldtype": "Small Text", "label": "Contact Email", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "col_break_address", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "fieldname": "shipping_address", "fieldtype": "Link", "label": "Select Shipping Address", "options": "Address", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "shipping_address_display", "fieldtype": "Small Text", "label": "Shipping Address", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "collapsible": 1, "fieldname": "currency_and_price_list", "fieldtype": "Section Break", "label": "Currency and Price List", - "options": "fa fa-tag", - "show_days": 1, - "show_seconds": 1 + "options": "fa fa-tag" }, { "fieldname": "currency", @@ -542,9 +466,7 @@ "oldfieldname": "currency", "oldfieldtype": "Select", "options": "Currency", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "conversion_rate", @@ -553,24 +475,18 @@ "oldfieldname": "conversion_rate", "oldfieldtype": "Currency", "precision": "9", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "column_break2", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "fieldname": "buying_price_list", "fieldtype": "Link", "label": "Price List", "options": "Price List", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "price_list_currency", @@ -578,18 +494,14 @@ "label": "Price List Currency", "options": "Currency", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "plc_conversion_rate", "fieldtype": "Float", "label": "Price List Exchange Rate", "precision": "9", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "default": "0", @@ -598,15 +510,11 @@ "label": "Ignore Pricing Rule", "no_copy": 1, "permlevel": 1, - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "sec_warehouse", - "fieldtype": "Section Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Section Break" }, { "depends_on": "update_stock", @@ -615,9 +523,7 @@ "fieldtype": "Link", "label": "Set Accepted Warehouse", "options": "Warehouse", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "depends_on": "update_stock", @@ -627,15 +533,11 @@ "label": "Rejected Warehouse", "no_copy": 1, "options": "Warehouse", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "col_break_warehouse", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "default": "No", @@ -643,26 +545,20 @@ "fieldtype": "Select", "label": "Raw Materials Supplied", "options": "No\nYes", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "items_section", "fieldtype": "Section Break", "oldfieldtype": "Section Break", - "options": "fa fa-shopping-cart", - "show_days": 1, - "show_seconds": 1 + "options": "fa fa-shopping-cart" }, { "default": "0", "fieldname": "update_stock", "fieldtype": "Check", "label": "Update Stock", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "scan_barcode", @@ -678,33 +574,25 @@ "oldfieldname": "entries", "oldfieldtype": "Table", "options": "Purchase Invoice Item", - "reqd": 1, - "show_days": 1, - "show_seconds": 1 + "reqd": 1 }, { "fieldname": "pricing_rule_details", "fieldtype": "Section Break", - "label": "Pricing Rules", - "show_days": 1, - "show_seconds": 1 + "label": "Pricing Rules" }, { "fieldname": "pricing_rules", "fieldtype": "Table", "label": "Pricing Rule Detail", "options": "Pricing Rule Detail", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "collapsible_depends_on": "supplied_items", "fieldname": "raw_materials_supplied", "fieldtype": "Section Break", - "label": "Raw Materials Supplied", - "show_days": 1, - "show_seconds": 1 + "label": "Raw Materials Supplied" }, { "depends_on": "update_stock", @@ -712,23 +600,17 @@ "fieldtype": "Table", "label": "Supplied Items", "no_copy": 1, - "options": "Purchase Receipt Item Supplied", - "show_days": 1, - "show_seconds": 1 + "options": "Purchase Receipt Item Supplied" }, { "fieldname": "section_break_26", - "fieldtype": "Section Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Section Break" }, { "fieldname": "total_qty", "fieldtype": "Float", "label": "Total Quantity", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "base_total", @@ -736,9 +618,7 @@ "label": "Total (Company Currency)", "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "base_net_total", @@ -748,24 +628,18 @@ "oldfieldtype": "Currency", "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "column_break_28", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "fieldname": "total", "fieldtype": "Currency", "label": "Total", "options": "currency", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "net_total", @@ -775,56 +649,42 @@ "oldfieldtype": "Currency", "options": "currency", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "total_net_weight", "fieldtype": "Float", "label": "Total Net Weight", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "taxes_section", "fieldtype": "Section Break", "oldfieldtype": "Section Break", - "options": "fa fa-money", - "show_days": 1, - "show_seconds": 1 + "options": "fa fa-money" }, { "fieldname": "tax_category", "fieldtype": "Link", "label": "Tax Category", "options": "Tax Category", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "column_break_49", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "fieldname": "shipping_rule", "fieldtype": "Link", "label": "Shipping Rule", "options": "Shipping Rule", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "section_break_51", - "fieldtype": "Section Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Section Break" }, { "fieldname": "taxes_and_charges", @@ -833,9 +693,7 @@ "oldfieldname": "purchase_other_charges", "oldfieldtype": "Link", "options": "Purchase Taxes and Charges Template", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "taxes", @@ -843,17 +701,13 @@ "label": "Purchase Taxes and Charges", "oldfieldname": "purchase_tax_details", "oldfieldtype": "Table", - "options": "Purchase Taxes and Charges", - "show_days": 1, - "show_seconds": 1 + "options": "Purchase Taxes and Charges" }, { "collapsible": 1, "fieldname": "sec_tax_breakup", "fieldtype": "Section Break", - "label": "Tax Breakup", - "show_days": 1, - "show_seconds": 1 + "label": "Tax Breakup" }, { "fieldname": "other_charges_calculation", @@ -862,17 +716,13 @@ "no_copy": 1, "oldfieldtype": "HTML", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "totals", "fieldtype": "Section Break", "oldfieldtype": "Section Break", - "options": "fa fa-money", - "show_days": 1, - "show_seconds": 1 + "options": "fa fa-money" }, { "fieldname": "base_taxes_and_charges_added", @@ -882,9 +732,7 @@ "oldfieldtype": "Currency", "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "base_taxes_and_charges_deducted", @@ -894,9 +742,7 @@ "oldfieldtype": "Currency", "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "base_total_taxes_and_charges", @@ -906,15 +752,11 @@ "oldfieldtype": "Currency", "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "column_break_40", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "fieldname": "taxes_and_charges_added", @@ -924,9 +766,7 @@ "oldfieldtype": "Currency", "options": "currency", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "taxes_and_charges_deducted", @@ -936,9 +776,7 @@ "oldfieldtype": "Currency", "options": "currency", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "total_taxes_and_charges", @@ -946,18 +784,14 @@ "label": "Total Taxes and Charges", "options": "currency", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "collapsible": 1, "collapsible_depends_on": "discount_amount", "fieldname": "section_break_44", "fieldtype": "Section Break", - "label": "Additional Discount", - "show_days": 1, - "show_seconds": 1 + "label": "Additional Discount" }, { "default": "Grand Total", @@ -965,9 +799,7 @@ "fieldtype": "Select", "label": "Apply Additional Discount On", "options": "\nGrand Total\nNet Total", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "base_discount_amount", @@ -975,38 +807,28 @@ "label": "Additional Discount Amount (Company Currency)", "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "column_break_46", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "fieldname": "additional_discount_percentage", "fieldtype": "Float", "label": "Additional Discount Percentage", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "discount_amount", "fieldtype": "Currency", "label": "Additional Discount Amount", "options": "currency", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "section_break_49", - "fieldtype": "Section Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Section Break" }, { "fieldname": "base_grand_total", @@ -1016,9 +838,7 @@ "oldfieldtype": "Currency", "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "depends_on": "eval:!doc.disable_rounded_total", @@ -1028,9 +848,7 @@ "no_copy": 1, "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "depends_on": "eval:!doc.disable_rounded_total", @@ -1040,9 +858,7 @@ "no_copy": 1, "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "base_in_words", @@ -1052,17 +868,13 @@ "oldfieldname": "in_words", "oldfieldtype": "Data", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "column_break8", "fieldtype": "Column Break", "oldfieldtype": "Column Break", "print_hide": 1, - "show_days": 1, - "show_seconds": 1, "width": "50%" }, { @@ -1073,9 +885,7 @@ "oldfieldname": "grand_total_import", "oldfieldtype": "Currency", "options": "currency", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "depends_on": "eval:!doc.disable_rounded_total", @@ -1085,9 +895,7 @@ "no_copy": 1, "options": "currency", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "depends_on": "eval:!doc.disable_rounded_total", @@ -1097,9 +905,7 @@ "no_copy": 1, "options": "currency", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "in_words", @@ -1109,9 +915,7 @@ "oldfieldname": "in_words_import", "oldfieldtype": "Data", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "total_advance", @@ -1122,9 +926,7 @@ "oldfieldtype": "Currency", "options": "party_account_currency", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "outstanding_amount", @@ -1135,18 +937,14 @@ "oldfieldtype": "Currency", "options": "party_account_currency", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "default": "0", "depends_on": "grand_total", "fieldname": "disable_rounded_total", "fieldtype": "Check", - "label": "Disable Rounded Total", - "show_days": 1, - "show_seconds": 1 + "label": "Disable Rounded Total" }, { "collapsible": 1, @@ -1154,26 +952,20 @@ "depends_on": "eval:doc.is_paid===1||(doc.advances && doc.advances.length>0)", "fieldname": "payments_section", "fieldtype": "Section Break", - "label": "Payments", - "show_days": 1, - "show_seconds": 1 + "label": "Payments" }, { "fieldname": "mode_of_payment", "fieldtype": "Link", "label": "Mode of Payment", "options": "Mode of Payment", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "cash_bank_account", "fieldtype": "Link", "label": "Cash/Bank Account", - "options": "Account", - "show_days": 1, - "show_seconds": 1 + "options": "Account" }, { "fieldname": "clearance_date", @@ -1181,15 +973,11 @@ "label": "Clearance Date", "no_copy": 1, "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "col_br_payments", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "depends_on": "is_paid", @@ -1198,9 +986,7 @@ "label": "Paid Amount", "no_copy": 1, "options": "currency", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "base_paid_amount", @@ -1209,9 +995,7 @@ "no_copy": 1, "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "collapsible": 1, @@ -1219,9 +1003,7 @@ "depends_on": "grand_total", "fieldname": "write_off", "fieldtype": "Section Break", - "label": "Write Off", - "show_days": 1, - "show_seconds": 1 + "label": "Write Off" }, { "fieldname": "write_off_amount", @@ -1229,9 +1011,7 @@ "label": "Write Off Amount", "no_copy": 1, "options": "currency", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "base_write_off_amount", @@ -1240,15 +1020,11 @@ "no_copy": 1, "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "column_break_61", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "depends_on": "eval:flt(doc.write_off_amount)!=0", @@ -1256,9 +1032,7 @@ "fieldtype": "Link", "label": "Write Off Account", "options": "Account", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "depends_on": "eval:flt(doc.write_off_amount)!=0", @@ -1266,9 +1040,7 @@ "fieldtype": "Link", "label": "Write Off Cost Center", "options": "Cost Center", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "collapsible": 1, @@ -1278,17 +1050,13 @@ "label": "Advance Payments", "oldfieldtype": "Section Break", "options": "fa fa-money", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "default": "0", "fieldname": "allocate_advances_automatically", "fieldtype": "Check", - "label": "Set Advances and Allocate (FIFO)", - "show_days": 1, - "show_seconds": 1 + "label": "Set Advances and Allocate (FIFO)" }, { "depends_on": "eval:!doc.allocate_advances_automatically", @@ -1296,9 +1064,7 @@ "fieldtype": "Button", "label": "Get Advances Paid", "oldfieldtype": "Button", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "advances", @@ -1308,26 +1074,20 @@ "oldfieldname": "advance_allocation_details", "oldfieldtype": "Table", "options": "Purchase Invoice Advance", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "collapsible": 1, "collapsible_depends_on": "eval:(!doc.is_return)", "fieldname": "payment_schedule_section", "fieldtype": "Section Break", - "label": "Payment Terms", - "show_days": 1, - "show_seconds": 1 + "label": "Payment Terms" }, { "fieldname": "payment_terms_template", "fieldtype": "Link", "label": "Payment Terms Template", - "options": "Payment Terms Template", - "show_days": 1, - "show_seconds": 1 + "options": "Payment Terms Template" }, { "fieldname": "payment_schedule", @@ -1335,9 +1095,7 @@ "label": "Payment Schedule", "no_copy": 1, "options": "Payment Schedule", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "collapsible": 1, @@ -1345,33 +1103,25 @@ "fieldname": "terms_section_break", "fieldtype": "Section Break", "label": "Terms and Conditions", - "options": "fa fa-legal", - "show_days": 1, - "show_seconds": 1 + "options": "fa fa-legal" }, { "fieldname": "tc_name", "fieldtype": "Link", "label": "Terms", "options": "Terms and Conditions", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "terms", "fieldtype": "Text Editor", - "label": "Terms and Conditions1", - "show_days": 1, - "show_seconds": 1 + "label": "Terms and Conditions1" }, { "collapsible": 1, "fieldname": "printing_settings", "fieldtype": "Section Break", - "label": "Printing Settings", - "show_days": 1, - "show_seconds": 1 + "label": "Printing Settings" }, { "allow_on_submit": 1, @@ -1379,9 +1129,7 @@ "fieldtype": "Link", "label": "Letter Head", "options": "Letter Head", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "allow_on_submit": 1, @@ -1389,15 +1137,11 @@ "fieldname": "group_same_items", "fieldtype": "Check", "label": "Group same items", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "column_break_112", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "allow_on_submit": 1, @@ -1409,18 +1153,14 @@ "oldfieldtype": "Link", "options": "Print Heading", "print_hide": 1, - "report_hide": 1, - "show_days": 1, - "show_seconds": 1 + "report_hide": 1 }, { "fieldname": "language", "fieldtype": "Data", "label": "Print Language", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "collapsible": 1, @@ -1429,9 +1169,7 @@ "label": "More Information", "oldfieldtype": "Section Break", "options": "fa fa-file-text", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "credit_to", @@ -1442,9 +1180,7 @@ "options": "Account", "print_hide": 1, "reqd": 1, - "search_index": 1, - "show_days": 1, - "show_seconds": 1 + "search_index": 1 }, { "fieldname": "party_account_currency", @@ -1454,9 +1190,7 @@ "no_copy": 1, "options": "Currency", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "default": "No", @@ -1466,9 +1200,7 @@ "oldfieldname": "is_opening", "oldfieldtype": "Select", "options": "No\nYes", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "against_expense_account", @@ -1478,15 +1210,11 @@ "no_copy": 1, "oldfieldname": "against_expense_account", "oldfieldtype": "Small Text", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "column_break_63", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "default": "Draft", @@ -1494,10 +1222,8 @@ "fieldtype": "Select", "in_standard_filter": 1, "label": "Status", - "options": "\nDraft\nReturn\nDebit Note Issued\nSubmitted\nPaid\nUnpaid\nOverdue\nCancelled\nInternal Transfer", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "options": "\nDraft\nReturn\nDebit Note Issued\nSubmitted\nPaid\nPartly Paid\nUnpaid\nOverdue\nCancelled\nInternal Transfer", + "print_hide": 1 }, { "fieldname": "inter_company_invoice_reference", @@ -1506,9 +1232,7 @@ "no_copy": 1, "options": "Sales Invoice", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "remarks", @@ -1517,18 +1241,14 @@ "no_copy": 1, "oldfieldname": "remarks", "oldfieldtype": "Text", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "collapsible": 1, "fieldname": "subscription_section", "fieldtype": "Section Break", "label": "Subscription Section", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "allow_on_submit": 1, @@ -1537,9 +1257,7 @@ "fieldtype": "Date", "label": "From Date", "no_copy": 1, - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "allow_on_submit": 1, @@ -1548,15 +1266,11 @@ "fieldtype": "Date", "label": "To Date", "no_copy": 1, - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "column_break_114", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "fieldname": "auto_repeat", @@ -1565,32 +1279,24 @@ "no_copy": 1, "options": "Auto Repeat", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "allow_on_submit": 1, "depends_on": "eval: doc.auto_repeat", "fieldname": "update_auto_repeat_reference", "fieldtype": "Button", - "label": "Update Auto Repeat Reference", - "show_days": 1, - "show_seconds": 1 + "label": "Update Auto Repeat Reference" }, { "collapsible": 1, "fieldname": "accounting_dimensions_section", "fieldtype": "Section Break", - "label": "Accounting Dimensions ", - "show_days": 1, - "show_seconds": 1 + "label": "Accounting Dimensions " }, { "fieldname": "dimension_col_break", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "default": "0", @@ -1598,9 +1304,7 @@ "fieldname": "is_internal_supplier", "fieldtype": "Check", "label": "Is Internal Supplier", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "tax_withholding_category", @@ -1608,33 +1312,25 @@ "hidden": 1, "label": "Tax Withholding Category", "options": "Tax Withholding Category", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "billing_address", "fieldtype": "Link", "label": "Select Billing Address", - "options": "Address", - "show_days": 1, - "show_seconds": 1 + "options": "Address" }, { "fieldname": "billing_address_display", "fieldtype": "Small Text", "label": "Billing Address", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "project", "fieldtype": "Link", "label": "Project", - "options": "Project", - "show_days": 1, - "show_seconds": 1 + "options": "Project" }, { "depends_on": "eval:doc.is_internal_supplier", @@ -1642,9 +1338,7 @@ "fieldname": "unrealized_profit_loss_account", "fieldtype": "Link", "label": "Unrealized Profit / Loss Account", - "options": "Account", - "show_days": 1, - "show_seconds": 1 + "options": "Account" }, { "depends_on": "eval:doc.is_internal_supplier", @@ -1653,9 +1347,7 @@ "fieldname": "represents_company", "fieldtype": "Link", "label": "Represents Company", - "options": "Company", - "show_days": 1, - "show_seconds": 1 + "options": "Company" }, { "depends_on": "eval:doc.update_stock && doc.is_internal_supplier", @@ -1667,8 +1359,6 @@ "options": "Warehouse", "print_hide": 1, "print_width": "50px", - "show_days": 1, - "show_seconds": 1, "width": "50px" }, { @@ -1680,8 +1370,6 @@ "options": "Warehouse", "print_hide": 1, "print_width": "50px", - "show_days": 1, - "show_seconds": 1, "width": "50px" }, { @@ -1705,20 +1393,19 @@ "fieldtype": "Check", "hidden": 1, "label": "Ignore Default Payment Terms Template", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 } ], "icon": "fa fa-file-text", "idx": 204, "is_submittable": 1, "links": [], - "modified": "2021-08-17 20:16:12.737743", + "modified": "2021-09-21 09:27:39.967811", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice", "name_case": "Title Case", + "naming_rule": "By \"Naming Series\" field", "owner": "Administrator", "permissions": [ { diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 62cd90ee9fc..dd4a0058938 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -15,6 +15,7 @@ from erpnext.accounts.deferred_revenue import validate_service_stop_date from erpnext.accounts.doctype.gl_entry.gl_entry import update_outstanding_amt from erpnext.accounts.doctype.sales_invoice.sales_invoice import ( check_if_return_invoice_linked_with_payment_entry, + is_overdue, unlink_inter_company_doc, update_linked_doc, validate_inter_company_party, @@ -1175,10 +1176,7 @@ class PurchaseInvoice(BuyingController): self.status = 'Draft' return - precision = self.precision("outstanding_amount") - outstanding_amount = flt(self.outstanding_amount, precision) - due_date = getdate(self.due_date) - nowdate = getdate() + outstanding_amount = flt(self.outstanding_amount, self.precision("outstanding_amount")) if not status: if self.docstatus == 2: @@ -1186,9 +1184,11 @@ class PurchaseInvoice(BuyingController): elif self.docstatus == 1: if self.is_internal_transfer(): self.status = 'Internal Transfer' - elif outstanding_amount > 0 and due_date < nowdate: + elif is_overdue(self): self.status = "Overdue" - elif outstanding_amount > 0 and due_date >= nowdate: + elif 0 < outstanding_amount < flt(self.grand_total, self.precision("grand_total")): + self.status = "Partly Paid" + elif outstanding_amount > 0 and getdate(self.due_date) >= getdate(): self.status = "Unpaid" #Check if outstanding amount is 0 due to debit note issued against invoice elif outstanding_amount <= 0 and self.is_return == 0 and frappe.db.get_value('Purchase Invoice', {'is_return': 1, 'return_against': self.name, 'docstatus': 1}): diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice_list.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice_list.js index 771b49ac629..f6ff83add8c 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice_list.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice_list.js @@ -2,28 +2,58 @@ // License: GNU General Public License v3. See license.txt // render -frappe.listview_settings['Purchase Invoice'] = { - add_fields: ["supplier", "supplier_name", "base_grand_total", "outstanding_amount", "due_date", "company", - "currency", "is_return", "release_date", "on_hold", "represents_company", "is_internal_supplier"], - get_indicator: function(doc) { - if ((flt(doc.outstanding_amount) <= 0) && doc.docstatus == 1 && doc.status == 'Debit Note Issued') { - return [__("Debit Note Issued"), "darkgrey", "outstanding_amount,<=,0"]; - } else if (flt(doc.outstanding_amount) > 0 && doc.docstatus==1) { - if(cint(doc.on_hold) && !doc.release_date) { - return [__("On Hold"), "darkgrey"]; - } else if (cint(doc.on_hold) && doc.release_date && frappe.datetime.get_diff(doc.release_date, frappe.datetime.nowdate()) > 0) { - return [__("Temporarily on Hold"), "darkgrey"]; - } else if (frappe.datetime.get_diff(doc.due_date) < 0) { - return [__("Overdue"), "red", "outstanding_amount,>,0|due_date,<,Today"]; - } else { - return [__("Unpaid"), "orange", "outstanding_amount,>,0|due_date,>=,Today"]; - } - } else if (cint(doc.is_return)) { - return [__("Return"), "gray", "is_return,=,Yes"]; - } else if (doc.company == doc.represents_company && doc.is_internal_supplier) { - return [__("Internal Transfer"), "darkgrey", "outstanding_amount,=,0"]; - } else if (flt(doc.outstanding_amount)==0 && doc.docstatus==1) { - return [__("Paid"), "green", "outstanding_amount,=,0"]; +frappe.listview_settings["Purchase Invoice"] = { + add_fields: [ + "supplier", + "supplier_name", + "base_grand_total", + "outstanding_amount", + "due_date", + "company", + "currency", + "is_return", + "release_date", + "on_hold", + "represents_company", + "is_internal_supplier", + ], + get_indicator(doc) { + if (doc.status == "Debit Note Issued") { + return [__(doc.status), "darkgrey", "status,=," + doc.status]; } - } + + if ( + flt(doc.outstanding_amount) > 0 && + doc.docstatus == 1 && + cint(doc.on_hold) + ) { + if (!doc.release_date) { + return [__("On Hold"), "darkgrey"]; + } else if ( + frappe.datetime.get_diff( + doc.release_date, + frappe.datetime.nowdate() + ) > 0 + ) { + return [__("Temporarily on Hold"), "darkgrey"]; + } + } + + const status_colors = { + "Unpaid": "orange", + "Paid": "green", + "Return": "gray", + "Overdue": "red", + "Partly Paid": "yellow", + "Internal Transfer": "darkgrey", + }; + + if (status_colors[doc.status]) { + return [ + __(doc.status), + status_colors[doc.status], + "status,=," + doc.status, + ]; + } + }, }; diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index e476439c7a2..f383b52b409 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -1651,7 +1651,7 @@ "label": "Status", "length": 30, "no_copy": 1, - "options": "\nDraft\nReturn\nCredit Note Issued\nSubmitted\nPaid\nUnpaid\nUnpaid and Discounted\nOverdue and Discounted\nOverdue\nCancelled\nInternal Transfer", + "options": "\nDraft\nReturn\nCredit Note Issued\nSubmitted\nPaid\nPartly Paid\nUnpaid\nUnpaid and Discounted\nPartly Paid and Discounted\nOverdue and Discounted\nOverdue\nCancelled\nInternal Transfer", "print_hide": 1, "read_only": 1 }, @@ -2022,11 +2022,12 @@ "link_fieldname": "consolidated_invoice" } ], - "modified": "2021-09-08 15:24:25.486499", + "modified": "2021-09-21 09:27:50.191854", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", "name_case": "Title Case", + "naming_rule": "By \"Naming Series\" field", "owner": "Administrator", "permissions": [ { diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index ca6a77a2af7..0ac4fc82792 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -1475,14 +1475,7 @@ class SalesInvoice(SellingController): self.status = 'Draft' return - precision = self.precision("outstanding_amount") - outstanding_amount = flt(self.outstanding_amount, precision) - due_date = getdate(self.due_date) - nowdate = getdate() - - discounting_status = None - if self.is_discounted: - discounting_status = get_discounting_status(self.name) + outstanding_amount = flt(self.outstanding_amount, self.precision("outstanding_amount")) if not status: if self.docstatus == 2: @@ -1490,15 +1483,13 @@ class SalesInvoice(SellingController): elif self.docstatus == 1: if self.is_internal_transfer(): self.status = 'Internal Transfer' - elif outstanding_amount > 0 and due_date < nowdate and self.is_discounted and discounting_status=='Disbursed': - self.status = "Overdue and Discounted" - elif outstanding_amount > 0 and due_date < nowdate: + elif is_overdue(self): self.status = "Overdue" - elif outstanding_amount > 0 and due_date >= nowdate and self.is_discounted and discounting_status=='Disbursed': - self.status = "Unpaid and Discounted" - elif outstanding_amount > 0 and due_date >= nowdate: + elif 0 < outstanding_amount < flt(self.grand_total, self.precision("grand_total")): + self.status = "Partly Paid" + elif outstanding_amount > 0 and getdate(self.due_date) >= getdate(): self.status = "Unpaid" - #Check if outstanding amount is 0 due to credit note issued against invoice + # Check if outstanding amount is 0 due to credit note issued against invoice elif outstanding_amount <= 0 and self.is_return == 0 and frappe.db.get_value('Sales Invoice', {'is_return': 1, 'return_against': self.name, 'docstatus': 1}): self.status = "Credit Note Issued" elif self.is_return == 1: @@ -1507,12 +1498,42 @@ class SalesInvoice(SellingController): self.status = "Paid" else: self.status = "Submitted" + + if ( + self.status in ("Unpaid", "Partly Paid", "Overdue") + and self.is_discounted + and get_discounting_status(self.name) == "Disbursed" + ): + self.status += " and Discounted" + else: self.status = "Draft" if update: self.db_set('status', self.status, update_modified = update_modified) +def is_overdue(doc): + outstanding_amount = flt(doc.outstanding_amount, doc.precision("outstanding_amount")) + + if outstanding_amount <= 0: + return + + grand_total = flt(doc.grand_total, doc.precision("grand_total")) + nowdate = getdate() + if doc.payment_schedule: + # calculate payable amount till date + payable_amount = sum( + payment.payment_amount + for payment in doc.payment_schedule + if getdate(payment.due_date) < nowdate + ) + + if (grand_total - outstanding_amount) < payable_amount: + return True + + elif getdate(doc.due_date) < nowdate: + return True + def get_discounting_status(sales_invoice): status = None diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice_list.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice_list.js index 1a01cb58f2a..06e6f511839 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice_list.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice_list.js @@ -6,18 +6,20 @@ frappe.listview_settings['Sales Invoice'] = { add_fields: ["customer", "customer_name", "base_grand_total", "outstanding_amount", "due_date", "company", "currency", "is_return"], get_indicator: function(doc) { - var status_color = { + const status_colors = { "Draft": "grey", "Unpaid": "orange", "Paid": "green", "Return": "gray", "Credit Note Issued": "gray", "Unpaid and Discounted": "orange", + "Partly Paid and Discounted": "yellow", "Overdue and Discounted": "red", "Overdue": "red", + "Partly Paid": "yellow", "Internal Transfer": "darkgrey" }; - return [__(doc.status), status_color[doc.status], "status,=,"+doc.status]; + return [__(doc.status), status_colors[doc.status], "status,=,"+doc.status]; }, right_column: "grand_total" }; diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 3720ac33bbd..619dc7b0ada 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -133,6 +133,7 @@ class TestSalesInvoice(unittest.TestCase): def test_payment_entry_unlink_against_invoice(self): from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry + si = frappe.copy_doc(test_records[0]) si.is_pos = 0 si.insert() @@ -156,6 +157,7 @@ class TestSalesInvoice(unittest.TestCase): def test_payment_entry_unlink_against_standalone_credit_note(self): from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry + si1 = create_sales_invoice(rate=1000) si2 = create_sales_invoice(rate=300) si3 = create_sales_invoice(qty=-1, rate=300, is_return=1) @@ -1646,6 +1648,7 @@ class TestSalesInvoice(unittest.TestCase): def test_credit_note(self): from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry + si = create_sales_invoice(item_code = "_Test Item", qty = (5 * -1), rate=500, is_return = 1) outstanding_amount = get_outstanding_amount(si.doctype, @@ -2269,6 +2272,54 @@ class TestSalesInvoice(unittest.TestCase): party_link.delete() frappe.db.set_value('Accounts Settings', None, 'enable_common_party_accounting', 0) + def test_payment_statuses(self): + from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry + + today = nowdate() + + # Test Overdue + si = create_sales_invoice(do_not_submit=True) + si.payment_schedule = [] + si.append("payment_schedule", { + "due_date": add_days(today, -5), + "invoice_portion": 50, + "payment_amount": si.grand_total / 2 + }) + si.append("payment_schedule", { + "due_date": add_days(today, 5), + "invoice_portion": 50, + "payment_amount": si.grand_total / 2 + }) + si.submit() + self.assertEqual(si.status, "Overdue") + + # Test payment less than due amount + pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Bank - _TC") + pe.reference_no = "1" + pe.reference_date = nowdate() + pe.paid_amount = 1 + pe.references[0].allocated_amount = pe.paid_amount + pe.submit() + si.reload() + self.assertEqual(si.status, "Overdue") + + # Test Partly Paid + pe = frappe.copy_doc(pe) + pe.paid_amount = si.grand_total / 2 + pe.references[0].allocated_amount = pe.paid_amount + pe.submit() + si.reload() + self.assertEqual(si.status, "Partly Paid") + + # Test Paid + pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Bank - _TC") + pe.reference_no = "1" + pe.reference_date = nowdate() + pe.paid_amount = si.outstanding_amount + pe.submit() + si.reload() + self.assertEqual(si.status, "Paid") + def get_sales_invoice_for_e_invoice(): si = make_sales_invoice_for_ewaybill() si.naming_series = 'INV-2020-.#####' diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 5359089698a..87d5648f6ed 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1681,14 +1681,18 @@ def get_advance_payment_entries(party_type, party, party_account, order_doctype, return list(payment_entries_against_order) + list(unallocated_payment_entries) def update_invoice_status(): - # Daily update the status of the invoices - - frappe.db.sql(""" update `tabSales Invoice` set status = 'Overdue' - where due_date < CURDATE() and docstatus = 1 and outstanding_amount > 0""") - - frappe.db.sql(""" update `tabPurchase Invoice` set status = 'Overdue' - where due_date < CURDATE() and docstatus = 1 and outstanding_amount > 0""") + """Updates status as Overdue for applicable invoices. Runs daily.""" + for doctype in ("Sales Invoice", "Purchase Invoice"): + frappe.db.sql(""" + update `tab{}` as dt set dt.status = 'Overdue' + where dt.docstatus = 1 + and dt.status != 'Overdue' + and dt.outstanding_amount > 0 + and (dt.grand_total - dt.outstanding_amount) < + (select sum(payment_amount) from `tabPayment Schedule` as ps + where ps.parent = dt.name and ps.due_date < %s) + """.format(doctype), getdate()) @frappe.whitelist() def get_payment_terms(terms_template, posting_date=None, grand_total=None, base_grand_total=None, bill_date=None): From dafe99b6e22620dcc52c8a9a26fbaf712c1ad4a4 Mon Sep 17 00:00:00 2001 From: escix Date: Sun, 26 Sep 2021 15:39:13 +1000 Subject: [PATCH 090/116] fix: holiday message reminder Minor grammatical change --- erpnext/templates/emails/holiday_reminder.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/templates/emails/holiday_reminder.html b/erpnext/templates/emails/holiday_reminder.html index e38d27bf8bc..bbef6be6728 100644 --- a/erpnext/templates/emails/holiday_reminder.html +++ b/erpnext/templates/emails/holiday_reminder.html @@ -11,6 +11,6 @@ {% endfor %} {% else %} -

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

+

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

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