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__": 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/.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 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' 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) diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 00000000000..67bd4454ef1 --- /dev/null +++ b/codecov.yml @@ -0,0 +1,17 @@ +codecov: + require_ci_to_pass: yes + +coverage: + status: + project: + default: + target: auto + threshold: 0.5% + +comment: + layout: "diff, files" + require_changes: true + after_n_builds: 3 + +ignore: + - "erpnext/demo" 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) 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) 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/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/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/payment_entry_reference/payment_entry_reference.json b/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json index 43eb0b6e2aa..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-04-21 13:30:11.605388", + "modified": "2021-09-26 17:06:55.597389", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Entry Reference", 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", 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/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/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: diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index 7822f747f64..55e288eeef9 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,42 +1279,33 @@ "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", "fetch_from": "supplier.is_internal_supplier", "fieldname": "is_internal_supplier", "fieldtype": "Check", + "ignore_user_permissions": 1, "label": "Is Internal Supplier", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "tax_withholding_category", @@ -1608,33 +1313,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 +1339,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 +1348,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 +1360,6 @@ "options": "Warehouse", "print_hide": 1, "print_width": "50px", - "show_days": 1, - "show_seconds": 1, "width": "50px" }, { @@ -1680,8 +1371,6 @@ "options": "Warehouse", "print_hide": 1, "print_width": "50px", - "show_days": 1, - "show_seconds": 1, "width": "50px" }, { @@ -1705,20 +1394,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-28 13:10:28.351810", "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..1c9943fd224 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, @@ -1145,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: @@ -1175,10 +1182,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 +1190,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/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 20d6c466e4c..5cbeb562326 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,16 +1227,20 @@ 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) + fiscal_year = get_fiscal_year(fiscal_year='2021') 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.set('rates', []) + 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 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/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(); diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index b5620ae6a96..2d6c04ebf9b 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,8 +1649,9 @@ "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", + "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 }, @@ -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 @@ -1948,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 @@ -2017,11 +2023,12 @@ "link_fieldname": "consolidated_invoice" } ], - "modified": "2021-08-27 20:13:40.456462", + "modified": "2021-09-28 13:09:34.391799", "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 ec249c24194..0ac4fc82792 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 @@ -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 da0c3151933..8a2e9450e97 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) @@ -1420,15 +1422,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) @@ -1639,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, @@ -1790,6 +1800,47 @@ 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") + + 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") @@ -2262,6 +2313,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-.#####' @@ -2294,6 +2393,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", @@ -2315,11 +2415,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", @@ -2339,9 +2440,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", @@ -2356,11 +2482,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(): @@ -2400,7 +2521,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" 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", 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: 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 b4bdd73c492..16ef5fc9745 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -9,11 +9,26 @@ 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): - 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(d.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 = '', '' @@ -52,8 +67,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}') @@ -67,7 +82,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': @@ -77,16 +92,19 @@ 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({ + "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, + "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, @@ -95,13 +113,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 = { @@ -143,38 +161,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: @@ -183,29 +201,47 @@ 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' + doctype = 'Purchase Invoice' if party_type == 'Supplier' else 'Sales Invoice' filters = { - dr_or_cr: ['>', 0], 'company': company, - 'party_type': party_type, - 'party': ['in', parties], - 'fiscal_year': fiscal_year, + 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') + }) -def get_advance_vouchers(parties, fiscal_year=None, company=None, from_date=None, to_date=None, party_type='Supplier'): + 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 dr_or_cr = 'debit' if party_type == 'Supplier' else 'credit' @@ -218,8 +254,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: @@ -227,20 +261,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), @@ -264,7 +299,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) @@ -292,9 +327,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', { @@ -313,14 +347,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) @@ -339,7 +373,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), @@ -356,14 +390,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 c4a5ba52fea..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 @@ -313,16 +337,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 @@ -339,7 +363,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 @@ -357,7 +382,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 @@ -377,7 +403,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 @@ -387,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/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/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): 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": 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 = [] 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)) 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 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" } ] } 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)""" % 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/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/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/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/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 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/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/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/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"): diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index b90db054b57..e9b531ecb86 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 @@ -710,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: @@ -729,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) ) @@ -980,42 +984,55 @@ 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() + + 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( + _("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 - 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") - role_allowed_to_over_bill = frappe.db.get_single_value('Accounts Settings', 'role_allowed_to_over_bill') + max_allowed_amt = flt(ref_amt * (100 + allowance) / 100) - if total_billed_amt - max_allowed_amt > 0.01 and role_allowed_to_over_bill not in frappe.get_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) + + 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(_("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") @@ -1668,14 +1685,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): diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index aafaf5b9e08..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: @@ -307,7 +319,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) 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/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/buying/doctype/supplier_item_group/__init__.py b/erpnext/crm/report/opportunity_summary_by_sales_stage/__init__.py similarity index 100% rename from erpnext/buying/doctype/supplier_item_group/__init__.py rename to erpnext/crm/report/opportunity_summary_by_sales_stage/__init__.py 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", 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): diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 5b6e1eeafcd..a8f16171be0 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"] @@ -441,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/hr/doctype/employee/employee_reminders.py b/erpnext/hr/doctype/employee/employee_reminders.py index fc018664b46..216d8f6bb3a 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 @@ -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 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/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 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; }); 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) 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() 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/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' }); diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 28a84b25062..7cfec974fc8 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() @@ -1125,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, 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", diff --git a/erpnext/manufacturing/doctype/job_card/job_card.js b/erpnext/manufacturing/doctype/job_card/job_card.js index 91eb4a0fa90..35be38813e5 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.js +++ b/erpnext/manufacturing/doctype/job_card/job_card.js @@ -26,15 +26,23 @@ 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) { - if (frm.doc.for_quantity != frm.doc.transferred_qty) { + 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; + + 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..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", @@ -185,7 +187,7 @@ "default": "0", "fieldname": "transferred_qty", "fieldtype": "Float", - "label": "Transferred Qty", + "label": "FG Qty from Transferred Raw Materials", "read_only": 1 }, { @@ -392,14 +394,28 @@ "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-03-16 15:59:32.766484", + "modified": "2021-09-14 00:38:46.873105", "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 3bf9de2708c..e1d79be81c4 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 @@ -37,6 +34,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() @@ -91,7 +92,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: @@ -449,6 +450,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 +459,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 +503,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 +623,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() @@ -668,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` @@ -678,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)) diff --git a/erpnext/manufacturing/doctype/job_card/test_job_card.py b/erpnext/manufacturing/doctype/job_card/test_job_card.py index db0e08fcd0a..ea5d364a9ce 100644 --- a/erpnext/manufacturing/doctype/job_card/test_job_card.py +++ b/erpnext/manufacturing/doctype/job_card/test_job_card.py @@ -1,78 +1,194 @@ # -*- 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 +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, +) 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): + 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() + 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) + + 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/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/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/manufacturing/doctype/production_plan/production_plan.js b/erpnext/manufacturing/doctype/production_plan/production_plan.js index 7b4b7c3ca38..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, @@ -434,6 +436,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.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 91e57489644..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({ @@ -561,8 +562,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) @@ -735,43 +734,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() @@ -800,6 +798,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 = [] 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 bb431498636..85b5bfb9bfc 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 @@ -20,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 @@ -772,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` @@ -814,6 +864,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"): diff --git a/erpnext/patches.txt b/erpnext/patches.txt index a03f90ea5b0..a4819965810 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -304,3 +304,11 @@ 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 +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/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 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/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/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/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..fa8a86437d0 --- /dev/null +++ b/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py @@ -0,0 +1,49 @@ +from __future__ import unicode_literals + +import json + +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 + 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) + + 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 + doc.make_gl_entries() + for advance in doc.advances: + 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 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/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 + """) 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..90fb50fb42c --- /dev/null +++ b/erpnext/patches/v13_0/update_dates_in_tax_withholding_category.py @@ -0,0 +1,26 @@ +# Copyright (c) 2021, Frappe and Contributors +# License: GNU General Public License v3. See license.txt + +import frappe + + +def execute(): + frappe.reload_doc('accounts', 'doctype', 'Tax Withholding Rate') + + if frappe.db.has_column('Tax Withholding Rate', 'fiscal_year'): + tds_category_rates = frappe.get_all('Tax Withholding Rate', fields=['name', 'fiscal_year']) + + fiscal_year_map = {} + fiscal_year_details = frappe.get_all('Fiscal Year', fields=['name', 'year_start_date', 'year_end_date']) + + 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, + 'to_date': to_date + }) \ No newline at end of file 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: 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) 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" 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 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( 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", diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index fd3d4721376..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); } }, @@ -884,7 +885,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", ""); + } } } }); diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index e0610eb8c01..7f39b990bf0 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -709,14 +709,20 @@ 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){ + if (values.length === 0) { frappe.msgprint(__("Please select {0}", [opts.source_doctype])) return; } opts.source_name = values; - opts.setters = args; + if (opts.allow_child_item_selection) { + // args contains filtered child docnames + opts.args = args; + } d.dialog.hide(); _map(); }, diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index 963c4075cd2..4a2c577e55b 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], @@ -661,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) @@ -750,7 +761,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') @@ -758,11 +769,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 +788,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 +814,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(): diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index bf06d4a497d..0faf80b0024 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -112,12 +112,9 @@ 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): +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 +122,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 +438,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 +549,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 +561,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: @@ -859,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)) diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py index ca0defa648a..23924c5fb66 100644 --- a/erpnext/regional/report/gstr_1/gstr_1.py +++ b/erpnext/regional/report/gstr_1/gstr_1.py @@ -96,35 +96,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 +174,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 @@ -212,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') @@ -221,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 ( @@ -234,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' """ @@ -1050,6 +1052,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: 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/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/party_specific_item/__init__.py b/erpnext/selling/doctype/party_specific_item/__init__.py new file mode 100644 index 00000000000..e69de29bb2d 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/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/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 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 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) { 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') diff --git a/erpnext/selling/sales_common.js b/erpnext/selling/sales_common.js index 955ef5e9396..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} } }); } @@ -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 + ); } } diff --git a/erpnext/setup/doctype/item_group/item_group.py b/erpnext/setup/doctype/item_group/item_group.py index ddf3e662e0c..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) @@ -99,7 +100,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), @@ -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/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", 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/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/item/item.py b/erpnext/stock/doctype/item/item.py index 50fdd3845d0..768e5eae2da 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() @@ -561,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""" @@ -782,35 +788,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 +1338,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) 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 ); diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py index 9eb47216266..cf98b19e7a1 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,11 @@ 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=None): + if args is 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 +291,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": { 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", 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", diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index c819d49f509..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) { @@ -1100,4 +1058,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})); diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 41ca8300a8e..bd7d22bcbc0 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 _ @@ -554,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"): @@ -684,7 +690,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) @@ -804,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: @@ -815,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"): @@ -1191,13 +1205,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', @@ -1264,9 +1353,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 @@ -1417,8 +1506,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)) @@ -1503,7 +1592,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: 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]) 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, 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} + } } } }, 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/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 542124204b5..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): """ @@ -681,11 +683,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: 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 %} 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) }}
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 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 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" 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 0a49c52f0e8..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.1" - resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8" - integrity sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA== - dependencies: - follow-redirects "^1.10.0" - -balanced-match@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" - integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= - base64-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.10.0: - version "1.14.0" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.0.tgz#f5d260f95c5f8c105894491feee5dc8993b402fe" - integrity sha512-0vRwd7RKQBTt+mgu87mtYeofLFZpTas2S9zY+jIeuLJMNvudIgF52nr19q40HOwH5RrhWIPuj9puybzSJiRrVg== - -fs-constants@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" - integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== - -fs-minipass@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" - integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== - dependencies: - minipass "^3.0.0" - -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= - -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==