diff --git a/.eslintrc b/.eslintrc index d6f0f493630..3b6ab7498d9 100644 --- a/.eslintrc +++ b/.eslintrc @@ -92,6 +92,7 @@ "cur_page": true, "cur_list": true, "cur_tree": true, + "cur_pos": true, "msg_dialog": true, "is_null": true, "in_list": true, @@ -149,6 +150,7 @@ "it": true, "context": true, "before": true, - "beforeEach": true + "beforeEach": true, + "onScan": true } } diff --git a/.github/helper/translation.py b/.github/helper/translation.py index 340f4f8772d..9146b3b32b8 100644 --- a/.github/helper/translation.py +++ b/.github/helper/translation.py @@ -2,7 +2,7 @@ import re import sys errors_encounter = 0 -pattern = re.compile(r"_\(([\"']{,3})(?P((?!\1).)*)\1(\s*,\s*context\s*=\s*([\"'])(?P((?!\5).)*)\5)*(\s*,\s*(.)*?\s*(,\s*([\"'])(?P((?!\11).)*)\11)*)*\)") +pattern = re.compile(r"_\(([\"']{,3})(?P((?!\1).)*)\1(\s*,\s*context\s*=\s*([\"'])(?P((?!\5).)*)\5)*(\s*,(\s*?.*?\n*?)*(,\s*([\"'])(?P((?!\11).)*)\11)*)*\)") words_pattern = re.compile(r"_{1,2}\([\"'`]{1,3}.*?[a-zA-Z]") start_pattern = re.compile(r"_{1,2}\([f\"'`]{1,3}") f_string_pattern = re.compile(r"_\(f[\"']") @@ -28,7 +28,7 @@ for _file in files_to_scan: has_f_string = f_string_pattern.search(line) if has_f_string: errors_encounter += 1 - print(f'\nF-strings are not supported for translations at line number {line_number + 1}\n{line.strip()[:100]}') + print(f'\nF-strings are not supported for translations at line number {line_number}\n{line.strip()[:100]}') continue else: continue @@ -36,7 +36,7 @@ for _file in files_to_scan: match = pattern.search(line) error_found = False - if not match and line.endswith(',\n'): + if not match and line.endswith((',\n', '[\n')): # concat remaining text to validate multiline pattern line = "".join(file_lines[line_number - 1:]) line = line[start_matches.start() + 1:] @@ -44,11 +44,11 @@ for _file in files_to_scan: if not match: error_found = True - print(f'\nTranslation syntax error at line number {line_number + 1}\n{line.strip()[:100]}') + print(f'\nTranslation syntax error at line number {line_number}\n{line.strip()[:100]}') if not error_found and not words_pattern.search(line): error_found = True - print(f'\nTranslation is useless because it has no words at line number {line_number + 1}\n{line.strip()[:100]}') + print(f'\nTranslation is useless because it has no words at line number {line_number}\n{line.strip()[:100]}') if error_found: errors_encounter += 1 diff --git a/erpnext/.stylelintrc b/erpnext/.stylelintrc new file mode 100644 index 00000000000..1e05d1fb41d --- /dev/null +++ b/erpnext/.stylelintrc @@ -0,0 +1,9 @@ +{ + "extends": ["stylelint-config-recommended"], + "plugins": ["stylelint-scss"], + "rules": { + "at-rule-no-unknown": null, + "scss/at-rule-no-unknown": true, + "no-descending-specificity": null + } +} \ No newline at end of file diff --git a/erpnext/__init__.py b/erpnext/__init__.py index b122e5fa11e..5ad34ea72aa 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -109,7 +109,7 @@ def get_region(company=None): ''' if company or frappe.flags.company: return frappe.get_cached_value('Company', - company or frappe.flags.company, 'country') + company or frappe.flags.company, 'country') elif frappe.flags.country: return frappe.flags.country else: diff --git a/erpnext/accounts/desk_page/accounting/accounting.json b/erpnext/accounts/desk_page/accounting/accounting.json deleted file mode 100644 index a18dbffd9ab..00000000000 --- a/erpnext/accounts/desk_page/accounting/accounting.json +++ /dev/null @@ -1,161 +0,0 @@ -{ - "cards": [ - { - "hidden": 0, - "label": "Accounting Masters", - "links": "[\n {\n \"description\": \"Company (not Customer or Supplier) master.\",\n \"label\": \"Company\",\n \"name\": \"Company\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Tree of financial accounts.\",\n \"icon\": \"fa fa-sitemap\",\n \"label\": \"Chart of Accounts\",\n \"name\": \"Account\",\n \"onboard\": 1,\n \"route\": \"#Tree/Account\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Accounts Settings\",\n \"name\": \"Accounts Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Financial / accounting year.\",\n \"label\": \"Fiscal Year\",\n \"name\": \"Fiscal Year\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Accounting Dimension\",\n \"name\": \"Accounting Dimension\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Finance Book\",\n \"name\": \"Finance Book\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Accounting Period\",\n \"name\": \"Accounting Period\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Payment Terms based on conditions\",\n \"label\": \"Payment Term\",\n \"name\": \"Payment Term\",\n \"type\": \"doctype\"\n }\n]" - }, - { - "hidden": 0, - "label": "General Ledger", - "links": "[\n {\n \"description\": \"Accounting journal entries.\",\n \"label\": \"Journal Entry\",\n \"name\": \"Journal Entry\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Make journal entries from a template.\",\n \"label\": \"Journal Entry Template\",\n \"name\": \"Journal Entry Template\",\n \"type\": \"doctype\"\n },\n \n {\n \"dependencies\": [\n \"GL Entry\"\n ],\n \"doctype\": \"GL Entry\",\n \"is_query_report\": true,\n \"label\": \"General Ledger\",\n \"name\": \"General Ledger\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Invoice\"\n ],\n \"doctype\": \"Sales Invoice\",\n \"is_query_report\": true,\n \"label\": \"Customer Ledger Summary\",\n \"name\": \"Customer Ledger Summary\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Invoice\"\n ],\n \"doctype\": \"Sales Invoice\",\n \"is_query_report\": true,\n \"label\": \"Supplier Ledger Summary\",\n \"name\": \"Supplier Ledger Summary\",\n \"type\": \"report\"\n }\n]" - }, - { - "hidden": 0, - "label": "Accounts Receivable", - "links": "[\n {\n \"description\": \"Bills raised to Customers.\",\n \"label\": \"Sales Invoice\",\n \"name\": \"Sales Invoice\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Customer database.\",\n \"label\": \"Customer\",\n \"name\": \"Customer\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Bank/Cash transactions against party or for internal transfer\",\n \"label\": \"Payment Entry\",\n \"name\": \"Payment Entry\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Payment Request\",\n \"label\": \"Payment Request\",\n \"name\": \"Payment Request\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Sales Invoice\"\n ],\n \"doctype\": \"Sales Invoice\",\n \"is_query_report\": true,\n \"label\": \"Accounts Receivable\",\n \"name\": \"Accounts Receivable\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Invoice\"\n ],\n \"doctype\": \"Sales Invoice\",\n \"is_query_report\": true,\n \"label\": \"Accounts Receivable Summary\",\n \"name\": \"Accounts Receivable Summary\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Invoice\"\n ],\n \"doctype\": \"Sales Invoice\",\n \"is_query_report\": true,\n \"label\": \"Sales Register\",\n \"name\": \"Sales Register\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Invoice\"\n ],\n \"doctype\": \"Sales Invoice\",\n \"is_query_report\": true,\n \"label\": \"Item-wise Sales Register\",\n \"name\": \"Item-wise Sales Register\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Invoice\"\n ],\n \"doctype\": \"Sales Order\",\n \"is_query_report\": true,\n \"label\": \"Sales Order Analysis\",\n \"name\": \"Sales Order Analysis\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Invoice\"\n ],\n \"doctype\": \"Sales Invoice\",\n \"is_query_report\": true,\n \"label\": \"Delivered Items To Be Billed\",\n \"name\": \"Delivered Items To Be Billed\",\n \"type\": \"report\"\n }\n]" - }, - { - "hidden": 0, - "label": "Accounts Payable", - "links": "[\n {\n \"description\": \"Bills raised by Suppliers.\",\n \"label\": \"Purchase Invoice\",\n \"name\": \"Purchase Invoice\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Supplier database.\",\n \"label\": \"Supplier\",\n \"name\": \"Supplier\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Bank/Cash transactions against party or for internal transfer\",\n \"label\": \"Payment Entry\",\n \"name\": \"Payment Entry\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Purchase Invoice\"\n ],\n \"doctype\": \"Purchase Invoice\",\n \"is_query_report\": true,\n \"label\": \"Accounts Payable\",\n \"name\": \"Accounts Payable\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Purchase Invoice\"\n ],\n \"doctype\": \"Purchase Invoice\",\n \"is_query_report\": true,\n \"label\": \"Accounts Payable Summary\",\n \"name\": \"Accounts Payable Summary\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Purchase Invoice\"\n ],\n \"doctype\": \"Purchase Invoice\",\n \"is_query_report\": true,\n \"label\": \"Purchase Register\",\n \"name\": \"Purchase Register\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Purchase Invoice\"\n ],\n \"doctype\": \"Purchase Invoice\",\n \"is_query_report\": true,\n \"label\": \"Item-wise Purchase Register\",\n \"name\": \"Item-wise Purchase Register\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Purchase Order\"\n ],\n \"doctype\": \"Purchase Order\",\n \"is_query_report\": true,\n \"label\": \"Purchase Order Analysis\",\n \"name\": \"Purchase Order Analysis\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Purchase Invoice\"\n ],\n \"doctype\": \"Purchase Invoice\",\n \"is_query_report\": true,\n \"label\": \"Received Items To Be Billed\",\n \"name\": \"Received Items To Be Billed\",\n \"type\": \"report\"\n }\n]" - }, - { - "hidden": 0, - "label": "Reports", - "links": "[\n {\n \"dependencies\": [\n \"GL Entry\"\n ],\n \"doctype\": \"GL Entry\",\n \"is_query_report\": true,\n \"label\": \"Trial Balance for Party\",\n \"name\": \"Trial Balance for Party\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Journal Entry\"\n ],\n \"doctype\": \"Journal Entry\",\n \"is_query_report\": true,\n \"label\": \"Payment Period Based On Invoice Date\",\n \"name\": \"Payment Period Based On Invoice Date\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Invoice\"\n ],\n \"doctype\": \"Sales Invoice\",\n \"is_query_report\": true,\n \"label\": \"Sales Partners Commission\",\n \"name\": \"Sales Partners Commission\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Customer\"\n ],\n \"doctype\": \"Customer\",\n \"is_query_report\": true,\n \"label\": \"Customer Credit Balance\",\n \"name\": \"Customer Credit Balance\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Invoice\"\n ],\n \"doctype\": \"Sales Invoice\",\n \"is_query_report\": true,\n \"label\": \"Sales Payment Summary\",\n \"name\": \"Sales Payment Summary\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Address\"\n ],\n \"doctype\": \"Address\",\n \"is_query_report\": true,\n \"label\": \"Address And Contacts\",\n \"name\": \"Address And Contacts\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"GL Entry\"\n ],\n \"doctype\": \"GL Entry\",\n \"is_query_report\": true,\n \"label\": \"DATEV Export\",\n \"name\": \"DATEV\",\n \"type\": \"report\"\n }\n]" - }, - { - "hidden": 0, - "label": "Financial Statements", - "links": "[\n {\n \"dependencies\": [\n \"GL Entry\"\n ],\n \"doctype\": \"GL Entry\",\n \"is_query_report\": true,\n \"label\": \"Trial Balance\",\n \"name\": \"Trial Balance\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"GL Entry\"\n ],\n \"doctype\": \"GL Entry\",\n \"is_query_report\": true,\n \"label\": \"Profit and Loss Statement\",\n \"name\": \"Profit and Loss Statement\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"GL Entry\"\n ],\n \"doctype\": \"GL Entry\",\n \"is_query_report\": true,\n \"label\": \"Balance Sheet\",\n \"name\": \"Balance Sheet\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"GL Entry\"\n ],\n \"doctype\": \"GL Entry\",\n \"is_query_report\": true,\n \"label\": \"Cash Flow\",\n \"name\": \"Cash Flow\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"GL Entry\"\n ],\n \"doctype\": \"GL Entry\",\n \"is_query_report\": true,\n \"label\": \"Consolidated Financial Statement\",\n \"name\": \"Consolidated Financial Statement\",\n \"type\": \"report\"\n }\n]" - }, - { - "hidden": 0, - "label": "Multi Currency", - "links": "[\n {\n \"description\": \"Enable / disable currencies.\",\n \"label\": \"Currency\",\n \"name\": \"Currency\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Currency exchange rate master.\",\n \"label\": \"Currency Exchange\",\n \"name\": \"Currency Exchange\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Exchange Rate Revaluation master.\",\n \"label\": \"Exchange Rate Revaluation\",\n \"name\": \"Exchange Rate Revaluation\",\n \"type\": \"doctype\"\n }\n]" - }, - { - "hidden": 0, - "label": "Settings", - "links": "[\n {\n \"description\": \"Setup Gateway accounts.\",\n \"label\": \"Payment Gateway Account\",\n \"name\": \"Payment Gateway Account\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Template of terms or contract.\",\n \"label\": \"Terms and Conditions Template\",\n \"name\": \"Terms and Conditions\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"e.g. Bank, Cash, Credit Card\",\n \"label\": \"Mode of Payment\",\n \"name\": \"Mode of Payment\",\n \"type\": \"doctype\"\n }\n]" - }, - { - "hidden": 0, - "label": "Bank Statement", - "links": "[\n {\n \"label\": \"Bank\",\n \"name\": \"Bank\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Account\",\n \"name\": \"Bank Account\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Clearance\",\n \"name\": \"Bank Clearance\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Reconciliation\",\n \"name\": \"bank-reconciliation\",\n \"type\": \"page\"\n },\n {\n \"dependencies\": [\n \"GL Entry\"\n ],\n \"doctype\": \"GL Entry\",\n \"is_query_report\": true,\n \"label\": \"Bank Reconciliation Statement\",\n \"name\": \"Bank Reconciliation Statement\",\n \"type\": \"report\"\n }\n]" - }, - { - "hidden": 0, - "label": "Subscription Management", - "links": "[\n {\n \"label\": \"Subscription Plan\",\n \"name\": \"Subscription Plan\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Subscription\",\n \"name\": \"Subscription\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Subscription Settings\",\n \"name\": \"Subscription Settings\",\n \"type\": \"doctype\"\n }\n]" - }, - { - "hidden": 0, - "label": "Goods and Services Tax (GST India)", - "links": "[\n {\n \"label\": \"GST Settings\",\n \"name\": \"GST Settings\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"GST HSN Code\",\n \"name\": \"GST HSN Code\",\n \"type\": \"doctype\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"GSTR-1\",\n \"name\": \"GSTR-1\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"GSTR-2\",\n \"name\": \"GSTR-2\",\n \"type\": \"report\"\n },\n {\n \"label\": \"GSTR 3B Report\",\n \"name\": \"GSTR 3B Report\",\n \"type\": \"doctype\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"GST Sales Register\",\n \"name\": \"GST Sales Register\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"GST Purchase Register\",\n \"name\": \"GST Purchase Register\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"GST Itemised Sales Register\",\n \"name\": \"GST Itemised Sales Register\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"GST Itemised Purchase Register\",\n \"name\": \"GST Itemised Purchase Register\",\n \"type\": \"report\"\n },\n {\n \"country\": \"India\",\n \"description\": \"C-Form records\",\n \"label\": \"C-Form\",\n \"name\": \"C-Form\",\n \"type\": \"doctype\"\n },\n {\n \"country\": \"India\",\n \"label\": \"Lower Deduction Certificate\",\n \"name\": \"Lower Deduction Certificate\",\n \"type\": \"doctype\"\n }\n]" - }, - { - "hidden": 0, - "label": "Share Management", - "links": "[\n {\n \"description\": \"List of available Shareholders with folio numbers\",\n \"label\": \"Shareholder\",\n \"name\": \"Shareholder\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"List of all share transactions\",\n \"label\": \"Share Transfer\",\n \"name\": \"Share Transfer\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Share Transfer\"\n ],\n \"doctype\": \"Share Transfer\",\n \"is_query_report\": true,\n \"label\": \"Share Ledger\",\n \"name\": \"Share Ledger\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Share Transfer\"\n ],\n \"doctype\": \"Share Transfer\",\n \"is_query_report\": true,\n \"label\": \"Share Balance\",\n \"name\": \"Share Balance\",\n \"type\": \"report\"\n }\n]" - }, - { - "hidden": 0, - "label": "Cost Center and Budgeting", - "links": "[\n {\n \"description\": \"Tree of financial Cost Centers.\",\n \"icon\": \"fa fa-sitemap\",\n \"label\": \"Chart of Cost Centers\",\n \"name\": \"Cost Center\",\n \"route\": \"#Tree/Cost Center\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Define budget for a financial year.\",\n \"label\": \"Budget\",\n \"name\": \"Budget\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Accounting Dimension\",\n \"name\": \"Accounting Dimension\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Cost Center\"\n ],\n \"doctype\": \"Cost Center\",\n \"is_query_report\": true,\n \"label\": \"Budget Variance Report\",\n \"name\": \"Budget Variance Report\",\n \"type\": \"report\"\n },\n {\n \"description\": \"Seasonality for setting budgets, targets etc.\",\n \"label\": \"Monthly Distribution\",\n \"name\": \"Monthly Distribution\",\n \"type\": \"doctype\"\n }\n]" - }, - { - "hidden": 0, - "label": "Opening and Closing", - "links": "[\n {\n \"label\": \"Opening Invoice Creation Tool\",\n \"name\": \"Opening Invoice Creation Tool\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Chart of Accounts Importer\",\n \"name\": \"Chart of Accounts Importer\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Close Balance Sheet and book Profit or Loss.\",\n \"label\": \"Period Closing Voucher\",\n \"name\": \"Period Closing Voucher\",\n \"type\": \"doctype\"\n }\n]" - }, - { - "hidden": 0, - "label": "Taxes", - "links": "[\n {\n \"description\": \"Tax template for selling transactions.\",\n \"label\": \"Sales Taxes and Charges Template\",\n \"name\": \"Sales Taxes and Charges Template\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Tax template for buying transactions.\",\n \"label\": \"Purchase Taxes and Charges Template\",\n \"name\": \"Purchase Taxes and Charges Template\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Tax template for item tax rates.\",\n \"label\": \"Item Tax Template\",\n \"name\": \"Item Tax Template\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Tax Category for overriding tax rates.\",\n \"label\": \"Tax Category\",\n \"name\": \"Tax Category\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Tax Rule for transactions.\",\n \"label\": \"Tax Rule\",\n \"name\": \"Tax Rule\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Tax Withholding rates to be applied on transactions.\",\n \"label\": \"Tax Withholding Category\",\n \"name\": \"Tax Withholding Category\",\n \"type\": \"doctype\"\n }\n]" - }, - { - "hidden": 0, - "label": "Profitability", - "links": "[\n {\n \"dependencies\": [\n \"Sales Invoice\"\n ],\n \"doctype\": \"Sales Invoice\",\n \"is_query_report\": true,\n \"label\": \"Gross Profit\",\n \"name\": \"Gross Profit\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"GL Entry\"\n ],\n \"doctype\": \"GL Entry\",\n \"is_query_report\": true,\n \"label\": \"Profitability Analysis\",\n \"name\": \"Profitability Analysis\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Invoice\"\n ],\n \"doctype\": \"Sales Invoice\",\n \"is_query_report\": true,\n \"label\": \"Sales Invoice Trends\",\n \"name\": \"Sales Invoice Trends\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Purchase Invoice\"\n ],\n \"doctype\": \"Purchase Invoice\",\n \"is_query_report\": true,\n \"label\": \"Purchase Invoice Trends\",\n \"name\": \"Purchase Invoice Trends\",\n \"type\": \"report\"\n }\n]" - }, - { - "hidden": 0, - "label": "Value-Added Tax (VAT UAE)", - "links": "[\n {\n \"country\": \"United Arab Emirates\",\n \"label\": \"UAE VAT Settings\",\n \"name\": \"UAE VAT Settings\",\n \"type\": \"doctype\"\n },\n {\n \"country\": \"United Arab Emirates\",\n \"is_query_report\": true,\n \"label\": \"UAE VAT 201\",\n \"name\": \"UAE VAT 201\",\n \"type\": \"report\"\n }\n\n]" - } - ], - "category": "Modules", - "charts": [ - { - "chart_name": "Profit and Loss", - "label": "Profit and Loss" - } - ], - "creation": "2020-03-02 15:41:59.515192", - "developer_mode_only": 0, - "disable_user_customization": 0, - "docstatus": 0, - "doctype": "Desk Page", - "extends_another_page": 0, - "hide_custom": 0, - "idx": 0, - "is_standard": 1, - "label": "Accounting", - "modified": "2020-11-11 18:35:11.542909", - "modified_by": "Administrator", - "module": "Accounts", - "name": "Accounting", - "onboarding": "Accounts", - "owner": "Administrator", - "pin_to_bottom": 0, - "pin_to_top": 0, - "shortcuts": [ - { - "label": "Chart Of Accounts", - "link_to": "Account", - "type": "DocType" - }, - { - "label": "Sales Invoice", - "link_to": "Sales Invoice", - "type": "DocType" - }, - { - "label": "Purchase Invoice", - "link_to": "Purchase Invoice", - "type": "DocType" - }, - { - "label": "Journal Entry", - "link_to": "Journal Entry", - "type": "DocType" - }, - { - "label": "Payment Entry", - "link_to": "Payment Entry", - "type": "DocType" - }, - { - "label": "Accounts Receivable", - "link_to": "Accounts Receivable", - "type": "Report" - }, - { - "label": "General Ledger", - "link_to": "General Ledger", - "type": "Report" - }, - { - "label": "Trial Balance", - "link_to": "Trial Balance", - "type": "Report" - }, - { - "label": "Dashboard", - "link_to": "Accounts", - "type": "Dashboard" - } - ] -} \ No newline at end of file diff --git a/erpnext/accounts/doctype/account/account_tree.js b/erpnext/accounts/doctype/account/account_tree.js index 7bbc1c93e6c..7516134baf5 100644 --- a/erpnext/accounts/doctype/account/account_tree.js +++ b/erpnext/accounts/doctype/account/account_tree.js @@ -120,17 +120,17 @@ frappe.treeview_settings["Account"] = { } else { treeview.new_node(); } - }, "octicon octicon-plus"); + }, "add"); }, onrender: function(node) { - if(frappe.boot.user.can_read.indexOf("GL Entry") !== -1){ + if (frappe.boot.user.can_read.indexOf("GL Entry") !== -1) { // show Dr if positive since balance is calculated as debit - credit else show Cr let balance = node.data.balance_in_account_currency || node.data.balance; let dr_or_cr = balance > 0 ? "Dr": "Cr"; if (node.data && node.data.balance!==undefined) { - $('' + $('' + (node.data.balance_in_account_currency ? (format_currency(Math.abs(node.data.balance_in_account_currency), node.data.account_currency) + " / ") : "") diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/verified/de_kontenplan_SKR03_gnucash.json b/erpnext/accounts/doctype/account/chart_of_accounts/verified/de_kontenplan_SKR03_gnucash.json index 89465eedf0e..ee501f664b6 100644 --- a/erpnext/accounts/doctype/account/chart_of_accounts/verified/de_kontenplan_SKR03_gnucash.json +++ b/erpnext/accounts/doctype/account/chart_of_accounts/verified/de_kontenplan_SKR03_gnucash.json @@ -63,17 +63,21 @@ "Gewinnermittlung \u00a74/3 nicht Ergebniswirksam": { "account_number": "1371" }, - "Abziehbare VSt. 7%": { - "account_number": "1571" - }, - "Abziehbare VSt. 19%": { - "account_number": "1576" - }, - "Abziehbare VStr. nach \u00a713b UStG 19%": { - "account_number": "1577" - }, - "Leistungen \u00a713b UStG 19% Vorsteuer, 19% Umsatzsteuer": { - "account_number": "3120" + "Abziehbare Vorsteuer": { + "account_type": "Tax", + "is_group": 1, + "Abziehbare Vorsteuer 7%": { + "account_number": "1571" + }, + "Abziehbare Vorsteuer 19%": { + "account_number": "1576" + }, + "Abziehbare Vorsteuer nach \u00a713b UStG 19%": { + "account_number": "1577" + }, + "Leistungen \u00a713b UStG 19% Vorsteuer, 19% Umsatzsteuer": { + "account_number": "3120" + } } }, "III. Wertpapiere": { @@ -196,6 +200,7 @@ }, "Umsatzsteuer": { "is_group": 1, + "account_type": "Tax", "Umsatzsteuer 7%": { "account_number": "1771" }, diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/verified/de_kontenplan_SKR04.json b/erpnext/accounts/doctype/account/chart_of_accounts/verified/de_kontenplan_SKR04.json index 7fa67081341..57e8bdd9dc7 100644 --- a/erpnext/accounts/doctype/account/chart_of_accounts/verified/de_kontenplan_SKR04.json +++ b/erpnext/accounts/doctype/account/chart_of_accounts/verified/de_kontenplan_SKR04.json @@ -292,18 +292,21 @@ "Umsatzsteuerforderungen fr\u00fchere Jahre": {} }, "Sonstige Verm\u00f6gensgegenst\u00e4nde oder sonstige Verbindlichkeiten": { - "Abziehbare Vorsteuer": {}, - "Abziehbare Vorsteuer 16%": {}, - "Abziehbare Vorsteuer 19%": {}, - "Abziehbare Vorsteuer 7%": {}, - "Abziehbare Vorsteuer aus der Auslagerung von Gegenst\u00e4nden aus einem Unsatzsteuerlager": {}, - "Abziehbare Vorsteuer aus innergemeinschaftlichem Erwerb": {}, - "Abziehbare Vorsteuer aus innergemeinschaftlichem Erwerb 16%": {}, - "Abziehbare Vorsteuer aus innergemeinschaftlichem Erwerb 19%": {}, - "Abziehbare Vorsteuer aus innergemeinschaftlichem Erwerb von Neufahrzeugen von Lieferanten ohne Ust-Identifikationsnummer": {}, - "Abziehbare Vorsteuer nach \u00a7 13b UStG ": {}, - "Abziehbare Vorsteuer nach \u00a7 13b UStG 16%": {}, - "Abziehbare Vorsteuer nach \u00a7 13b UStG 19%": {}, + "Abziehbare Vorsteuer": { + "account_type": "Tax", + "is_group": 1, + "Abziehbare Vorsteuer 16%": {}, + "Abziehbare Vorsteuer 19%": {}, + "Abziehbare Vorsteuer 7%": {}, + "Abziehbare Vorsteuer aus der Auslagerung von Gegenst\u00e4nden aus einem Unsatzsteuerlager": {}, + "Abziehbare Vorsteuer aus innergemeinschaftlichem Erwerb": {}, + "Abziehbare Vorsteuer aus innergemeinschaftlichem Erwerb 16%": {}, + "Abziehbare Vorsteuer aus innergemeinschaftlichem Erwerb 19%": {}, + "Abziehbare Vorsteuer aus innergemeinschaftlichem Erwerb von Neufahrzeugen von Lieferanten ohne Ust-Identifikationsnummer": {}, + "Abziehbare Vorsteuer nach \u00a7 13b UStG ": {}, + "Abziehbare Vorsteuer nach \u00a7 13b UStG 16%": {}, + "Abziehbare Vorsteuer nach \u00a7 13b UStG 19%": {} + }, "Aufl\u00f6sung Vorsteuer aus Vorjahr \u00a7 4/3 EStG": {}, "Aufzuteilende Vorsteuer": {}, "Aufzuteilende Vorsteuer 16%": {}, @@ -673,23 +676,26 @@ "Sonstige Verrechnungskonten (Interimskonto)": { "account_type": "Stock Received But Not Billed" }, - "Umsatzsteuer": {}, - "Umsatzsteuer 16%": {}, - "Umsatzsteuer 19%": {}, - "Umsatzsteuer 7%": {}, - "Umsatzsteuer Vorjahr": {}, - "Umsatzsteuer aus der Auslagerung von Gegenst\u00e4nden aus einem Umsatzsteuerlager": {}, - "Umsatzsteuer aus im Inland steuerpflichtigen EG-Lieferungen": {}, - "Umsatzsteuer aus im Inland steuerpflichtigen EG-Lieferungen 19%": {}, - "Umsatzsteuer aus innergemeinschaftlichem Erwerb ": {}, - "Umsatzsteuer aus innergemeinschaftlichem Erwerb 16%": {}, - "Umsatzsteuer aus innergemeinschaftlichem Erwerb 19%": {}, - "Umsatzsteuer aus innergemeinschaftlichem Erwerb ohne Vorsteuerabzug": {}, - "Umsatzsteuer fr\u00fchere Jahre": {}, - "Umsatzsteuer laufendes Jahr": {}, - "Umsatzsteuer nach \u00a713b UStG": {}, - "Umsatzsteuer nach \u00a713b UStG 16%": {}, - "Umsatzsteuer nach \u00a713b UStG 19%": {}, + "Umsatzsteuer": { + "account_type": "Tax", + "is_group": 1, + "Umsatzsteuer 16%": {}, + "Umsatzsteuer 19%": {}, + "Umsatzsteuer 7%": {}, + "Umsatzsteuer Vorjahr": {}, + "Umsatzsteuer aus der Auslagerung von Gegenst\u00e4nden aus einem Umsatzsteuerlager": {}, + "Umsatzsteuer aus im Inland steuerpflichtigen EG-Lieferungen": {}, + "Umsatzsteuer aus im Inland steuerpflichtigen EG-Lieferungen 19%": {}, + "Umsatzsteuer aus innergemeinschaftlichem Erwerb ": {}, + "Umsatzsteuer aus innergemeinschaftlichem Erwerb 16%": {}, + "Umsatzsteuer aus innergemeinschaftlichem Erwerb 19%": {}, + "Umsatzsteuer aus innergemeinschaftlichem Erwerb ohne Vorsteuerabzug": {}, + "Umsatzsteuer fr\u00fchere Jahre": {}, + "Umsatzsteuer laufendes Jahr": {}, + "Umsatzsteuer nach \u00a713b UStG": {}, + "Umsatzsteuer nach \u00a713b UStG 16%": {}, + "Umsatzsteuer nach \u00a713b UStG 19%": {} + }, "Umsatzsteuer- Vorauszahlungen": {}, "Umsatzsteuer- Vorauszahlungen 1/11": {}, "Verbindlichkeiten aus Lohn- und Kirchensteuer": {} diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/verified/de_kontenplan_SKR04_with_account_number.json b/erpnext/accounts/doctype/account/chart_of_accounts/verified/de_kontenplan_SKR04_with_account_number.json index 849df18c6f9..2bf55cfcd04 100644 --- a/erpnext/accounts/doctype/account/chart_of_accounts/verified/de_kontenplan_SKR04_with_account_number.json +++ b/erpnext/accounts/doctype/account/chart_of_accounts/verified/de_kontenplan_SKR04_with_account_number.json @@ -659,6 +659,7 @@ }, "Abziehbare Vorsteuer (Gruppe)": { "is_group": 1, + "account_type": "Tax", "Abziehbare Vorsteuer": { "account_number": "1400" }, diff --git a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.json b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.json index cf55d554fb3..5858f10bb0b 100644 --- a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.json +++ b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.json @@ -30,6 +30,7 @@ "fieldtype": "Link", "label": "Reference Document Type", "options": "DocType", + "read_only_depends_on": "eval:!doc.__islocal", "reqd": 1 }, { @@ -48,7 +49,7 @@ } ], "links": [], - "modified": "2020-03-22 20:34:39.805728", + "modified": "2021-02-08 16:37:53.936656", "modified_by": "Administrator", "module": "Accounts", "name": "Accounting Dimension", diff --git a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py index 239588f8979..0ebf0eb541d 100644 --- a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py +++ b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py @@ -29,6 +29,16 @@ class AccountingDimension(Document): if exists and self.is_new(): frappe.throw("Document Type already used as a dimension") + if not self.is_new(): + self.validate_document_type_change() + + def validate_document_type_change(self): + doctype_before_save = frappe.db.get_value("Accounting Dimension", self.name, "document_type") + if doctype_before_save != self.document_type: + message = _("Cannot change Reference Document Type.") + message += _("Please create a new Accounting Dimension if required.") + frappe.throw(message) + def after_insert(self): if frappe.flags.in_test: make_dimension_in_accounting_doctypes(doc=self) @@ -51,8 +61,10 @@ class AccountingDimension(Document): def on_update(self): frappe.flags.accounting_dimensions = None -def make_dimension_in_accounting_doctypes(doc): - doclist = get_doctypes_with_dimensions() +def make_dimension_in_accounting_doctypes(doc, doclist=None): + if not doclist: + doclist = get_doctypes_with_dimensions() + doc_count = len(get_accounting_dimensions()) count = 0 @@ -72,13 +84,13 @@ def make_dimension_in_accounting_doctypes(doc): "owner": "Administrator" } - if doctype == "Budget": - add_dimension_to_budget_doctype(df, doc) - else: - meta = frappe.get_meta(doctype, cached=False) - fieldnames = [d.fieldname for d in meta.get("fields")] + meta = frappe.get_meta(doctype, cached=False) + fieldnames = [d.fieldname for d in meta.get("fields")] - if df['fieldname'] not in fieldnames: + if df['fieldname'] not in fieldnames: + if doctype == "Budget": + add_dimension_to_budget_doctype(df.copy(), doc) + else: create_custom_field(doctype, df) count += 1 @@ -168,15 +180,7 @@ def toggle_disabling(doc): frappe.clear_cache(doctype=doctype) def get_doctypes_with_dimensions(): - doclist = ["GL Entry", "Sales Invoice", "POS Invoice", "Purchase Invoice", "Payment Entry", "Asset", - "Expense Claim", "Expense Claim Detail", "Expense Taxes and Charges", "Stock Entry", "Budget", "Payroll Entry", "Delivery Note", - "Sales Invoice Item", "POS Invoice Item", "Purchase Invoice Item", "Purchase Order Item", "Journal Entry Account", "Material Request Item", "Delivery Note Item", - "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"] - - return doclist + return frappe.get_hooks("accounting_dimension_doctypes") def get_accounting_dimensions(as_list=True): if frappe.flags.accounting_dimensions is None: diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.js b/erpnext/accounts/doctype/accounts_settings/accounts_settings.js index 0627675de79..541901c9abf 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.js +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.js @@ -6,3 +6,46 @@ frappe.ui.form.on('Accounts Settings', { } }); + +frappe.tour['Accounts Settings'] = [ + { + fieldname: "acc_frozen_upto", + title: "Accounts Frozen Upto", + description: __("Freeze accounting transactions up to specified date, nobody can make/modify entry except the specified Role."), + }, + { + fieldname: "frozen_accounts_modifier", + title: "Role Allowed to Set Frozen Accounts & Edit Frozen Entries", + description: __("Users with this Role are allowed to set frozen accounts and create/modify accounting entries against frozen accounts.") + }, + { + fieldname: "determine_address_tax_category_from", + title: "Determine Address Tax Category From", + description: __("Tax category can be set on Addresses. An address can be Shipping or Billing address. Set which addres to select when applying Tax Category.") + }, + { + fieldname: "over_billing_allowance", + title: "Over Billing Allowance Percentage", + description: __("The percentage by which you can overbill transactions. For example, if the order value is $100 for an Item and percentage here is set as 10% then you are allowed to bill for $110.") + }, + { + fieldname: "credit_controller", + title: "Credit Controller", + description: __("Select the role that is allowed to submit transactions that exceed credit limits set. The credit limit can be set in the Customer form.") + }, + { + fieldname: "make_payment_via_journal_entry", + title: "Make Payment via Journal Entry", + description: __("When checked, if user proceeds to make payment from an invoice, the system will open a Journal Entry instead of a Payment Entry.") + }, + { + fieldname: "unlink_payment_on_cancellation_of_invoice", + title: "Unlink Payment on Cancellation of Invoice", + description: __("If checked, system will unlink the payment against the respective invoice.") + }, + { + fieldname: "unlink_advance_payment_on_cancelation_of_order", + title: "Unlink Advance Payment on Cancellation of Order", + description: __("Similar to the previous option, this unlinks any advance payments made against Purchase/Sales Orders.") + } +]; \ No newline at end of file diff --git a/erpnext/accounts/doctype/bank_account/bank_account.json b/erpnext/accounts/doctype/bank_account/bank_account.json index b42f1f9d583..de67ab1ce5d 100644 --- a/erpnext/accounts/doctype/bank_account/bank_account.json +++ b/erpnext/accounts/doctype/bank_account/bank_account.json @@ -86,6 +86,7 @@ }, { "default": "0", + "description": "Setting the account as a Company Account is necessary for Bank Reconciliation", "fieldname": "is_company_account", "fieldtype": "Check", "label": "Is Company Account" @@ -207,7 +208,7 @@ } ], "links": [], - "modified": "2020-07-17 13:59:50.795412", + "modified": "2020-10-23 16:48:06.303658", "modified_by": "Administrator", "module": "Accounts", "name": "Bank Account", diff --git a/erpnext/accounts/doctype/bank_statement_settings/__init__.py b/erpnext/accounts/doctype/bank_reconciliation_tool/__init__.py similarity index 100% rename from erpnext/accounts/doctype/bank_statement_settings/__init__.py rename to erpnext/accounts/doctype/bank_reconciliation_tool/__init__.py diff --git a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.js b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.js new file mode 100644 index 00000000000..297dd4333ff --- /dev/null +++ b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.js @@ -0,0 +1,162 @@ +// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt +frappe.provide("erpnext.accounts.bank_reconciliation"); + +frappe.ui.form.on("Bank Reconciliation Tool", { + setup: function (frm) { + frm.set_query("bank_account", function () { + return { + filters: { + company: ["in", frm.doc.company], + }, + }; + }); + }, + + refresh: function (frm) { + frappe.require("assets/js/bank-reconciliation-tool.min.js", () => + frm.trigger("make_reconciliation_tool") + ); + frm.upload_statement_button = frm.page.set_secondary_action( + __("Upload Bank Statement"), + () => + frappe.call({ + method: + "erpnext.accounts.doctype.bank_statement_import.bank_statement_import.upload_bank_statement", + args: { + dt: frm.doc.doctype, + dn: frm.doc.name, + company: frm.doc.company, + bank_account: frm.doc.bank_account, + }, + callback: function (r) { + if (!r.exc) { + var doc = frappe.model.sync(r.message); + frappe.set_route( + "Form", + doc[0].doctype, + doc[0].name + ); + } + }, + }) + ); + }, + + after_save: function (frm) { + frm.trigger("make_reconciliation_tool"); + }, + + bank_account: function (frm) { + frappe.db.get_value( + "Bank Account", + frm.bank_account, + "account", + (r) => { + frappe.db.get_value( + "Account", + r.account, + "account_currency", + (r) => { + frm.currency = r.account_currency; + } + ); + } + ); + frm.trigger("get_account_opening_balance"); + }, + + bank_statement_from_date: function (frm) { + frm.trigger("get_account_opening_balance"); + }, + + make_reconciliation_tool(frm) { + frm.get_field("reconciliation_tool_cards").$wrapper.empty(); + if (frm.doc.bank_account && frm.doc.bank_statement_to_date) { + frm.trigger("get_cleared_balance").then(() => { + if ( + frm.doc.bank_account && + frm.doc.bank_statement_from_date && + frm.doc.bank_statement_to_date && + frm.doc.bank_statement_closing_balance + ) { + frm.trigger("render_chart"); + frm.trigger("render"); + frappe.utils.scroll_to( + frm.get_field("reconciliation_tool_cards").$wrapper, + true, + 30 + ); + } + }); + } + }, + + get_account_opening_balance(frm) { + if (frm.doc.bank_account && frm.doc.bank_statement_from_date) { + frappe.call({ + method: + "erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.get_account_balance", + args: { + bank_account: frm.doc.bank_account, + till_date: frm.doc.bank_statement_from_date, + }, + callback: (response) => { + frm.set_value("account_opening_balance", response.message); + }, + }); + } + }, + + get_cleared_balance(frm) { + if (frm.doc.bank_account && frm.doc.bank_statement_to_date) { + return frappe.call({ + method: + "erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.get_account_balance", + args: { + bank_account: frm.doc.bank_account, + till_date: frm.doc.bank_statement_to_date, + }, + callback: (response) => { + frm.cleared_balance = response.message; + }, + }); + } + }, + + render_chart(frm) { + frm.cards_manager = new erpnext.accounts.bank_reconciliation.NumberCardManager( + { + $reconciliation_tool_cards: frm.get_field( + "reconciliation_tool_cards" + ).$wrapper, + bank_statement_closing_balance: + frm.doc.bank_statement_closing_balance, + cleared_balance: frm.cleared_balance, + currency: frm.currency, + } + ); + }, + + render(frm) { + if (frm.doc.bank_account) { + frm.bank_reconciliation_data_table_manager = new erpnext.accounts.bank_reconciliation.DataTableManager( + { + company: frm.doc.company, + bank_account: frm.doc.bank_account, + $reconciliation_tool_dt: frm.get_field( + "reconciliation_tool_dt" + ).$wrapper, + $no_bank_transactions: frm.get_field( + "no_bank_transactions" + ).$wrapper, + bank_statement_from_date: frm.doc.bank_statement_from_date, + bank_statement_to_date: frm.doc.bank_statement_to_date, + bank_statement_closing_balance: + frm.doc.bank_statement_closing_balance, + cards_manager: frm.cards_manager, + } + ); + } + }, +}); diff --git a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.json b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.json new file mode 100644 index 00000000000..4837db3b867 --- /dev/null +++ b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.json @@ -0,0 +1,113 @@ +{ + "actions": [], + "creation": "2020-12-02 10:13:02.148040", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "company", + "bank_account", + "column_break_1", + "bank_statement_from_date", + "bank_statement_to_date", + "column_break_2", + "account_opening_balance", + "bank_statement_closing_balance", + "section_break_1", + "reconciliation_tool_cards", + "reconciliation_tool_dt", + "no_bank_transactions" + ], + "fields": [ + { + "fieldname": "company", + "fieldtype": "Link", + "label": "Company", + "options": "Company" + }, + { + "fieldname": "bank_account", + "fieldtype": "Link", + "label": "Bank Account", + "options": "Bank Account" + }, + { + "fieldname": "column_break_1", + "fieldtype": "Column Break" + }, + { + "depends_on": "eval: doc.bank_account", + "fieldname": "bank_statement_from_date", + "fieldtype": "Date", + "label": "Bank Statement From Date" + }, + { + "depends_on": "eval: doc.bank_statement_from_date", + "fieldname": "bank_statement_to_date", + "fieldtype": "Date", + "label": "Bank Statement To Date" + }, + { + "fieldname": "column_break_2", + "fieldtype": "Column Break" + }, + { + "depends_on": "eval: doc.bank_statement_from_date", + "fieldname": "account_opening_balance", + "fieldtype": "Currency", + "label": "Account Opening Balance", + "options": "Currency", + "read_only": 1 + }, + { + "depends_on": "eval: doc.bank_statement_to_date", + "fieldname": "bank_statement_closing_balance", + "fieldtype": "Currency", + "label": "Bank Statement Closing Balance", + "options": "Currency" + }, + { + "depends_on": "eval: doc.bank_statement_closing_balance", + "fieldname": "section_break_1", + "fieldtype": "Section Break", + "label": "Reconcile" + }, + { + "fieldname": "reconciliation_tool_cards", + "fieldtype": "HTML" + }, + { + "fieldname": "reconciliation_tool_dt", + "fieldtype": "HTML" + }, + { + "fieldname": "no_bank_transactions", + "fieldtype": "HTML", + "options": "
No Matching Bank Transactions Found
" + } + ], + "hide_toolbar": 1, + "index_web_pages_for_search": 1, + "issingle": 1, + "links": [], + "modified": "2021-02-02 01:35:53.043578", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Bank Reconciliation Tool", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC" +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py new file mode 100644 index 00000000000..8a17233cf74 --- /dev/null +++ b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py @@ -0,0 +1,452 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import json + +import frappe +from frappe.model.document import Document +from frappe import _ +from frappe.utils import flt + +from erpnext import get_company_currency +from erpnext.accounts.utils import get_balance_on +from erpnext.accounts.report.bank_reconciliation_statement.bank_reconciliation_statement import get_entries, get_amounts_not_reflected_in_system +from erpnext.accounts.doctype.bank_transaction.bank_transaction import get_paid_amount + + +class BankReconciliationTool(Document): + pass + +@frappe.whitelist() +def get_bank_transactions(bank_account, from_date = None, to_date = None): + # returns bank transactions for a bank account + filters = [] + filters.append(['bank_account', '=', bank_account]) + filters.append(['docstatus', '=', 1]) + filters.append(['unallocated_amount', '>', 0]) + if to_date: + filters.append(['date', '<=', to_date]) + if from_date: + filters.append(['date', '>=', from_date]) + transactions = frappe.get_all( + 'Bank Transaction', + fields = ['date', 'deposit', 'withdrawal', 'currency', + 'description', 'name', 'bank_account', 'company', + 'unallocated_amount', 'reference_number', 'party_type', 'party'], + filters = filters + ) + return transactions + +@frappe.whitelist() +def get_account_balance(bank_account, till_date): + # returns account balance till the specified date + account = frappe.db.get_value('Bank Account', bank_account, 'account') + filters = frappe._dict({ + "account": account, + "report_date": till_date, + "include_pos_transactions": 1 + }) + data = get_entries(filters) + + balance_as_per_system = get_balance_on(filters["account"], filters["report_date"]) + + total_debit, total_credit = 0,0 + for d in data: + total_debit += flt(d.debit) + total_credit += flt(d.credit) + + amounts_not_reflected_in_system = get_amounts_not_reflected_in_system(filters) + + bank_bal = flt(balance_as_per_system) - flt(total_debit) + flt(total_credit) \ + + amounts_not_reflected_in_system + + return bank_bal + + +@frappe.whitelist() +def update_bank_transaction(bank_transaction_name, reference_number, party_type=None, party=None): + # updates bank transaction based on the new parameters provided by the user from Vouchers + bank_transaction = frappe.get_doc("Bank Transaction", bank_transaction_name) + bank_transaction.reference_number = reference_number + bank_transaction.party_type = party_type + bank_transaction.party = party + bank_transaction.save() + return frappe.db.get_all('Bank Transaction', + filters={ + 'name': bank_transaction_name + }, + fields=['date', 'deposit', 'withdrawal', 'currency', + 'description', 'name', 'bank_account', 'company', + 'unallocated_amount', 'reference_number', + 'party_type', 'party'], + )[0] + + +@frappe.whitelist() +def create_journal_entry_bts( bank_transaction_name, reference_number=None, reference_date=None, posting_date=None, entry_type=None, + second_account=None, mode_of_payment=None, party_type=None, party=None, allow_edit=None): + # Create a new journal entry based on the bank transaction + bank_transaction = frappe.db.get_values( + "Bank Transaction", bank_transaction_name, + fieldname=["name", "deposit", "withdrawal", "bank_account"] , + as_dict=True + )[0] + company_account = frappe.get_value("Bank Account", bank_transaction.bank_account, "account") + account_type = frappe.db.get_value("Account", second_account, "account_type") + if account_type in ["Receivable", "Payable"]: + if not (party_type and party): + frappe.throw(_("Party Type and Party is required for Receivable / Payable account {0}").format( second_account)) + accounts = [] + # Multi Currency? + accounts.append({ + "account": second_account, + "credit_in_account_currency": bank_transaction.deposit + if bank_transaction.deposit > 0 + else 0, + "debit_in_account_currency":bank_transaction.withdrawal + if bank_transaction.withdrawal > 0 + else 0, + "party_type":party_type, + "party":party, + }) + + accounts.append({ + "account": company_account, + "bank_account": bank_transaction.bank_account, + "credit_in_account_currency": bank_transaction.withdrawal + if bank_transaction.withdrawal > 0 + else 0, + "debit_in_account_currency":bank_transaction.deposit + if bank_transaction.deposit > 0 + else 0, + }) + + company = frappe.get_value("Account", company_account, "company") + + journal_entry_dict = { + "voucher_type" : entry_type, + "company" : company, + "posting_date" : posting_date, + "cheque_date" : reference_date, + "cheque_no" : reference_number, + "mode_of_payment" : mode_of_payment + } + journal_entry = frappe.new_doc('Journal Entry') + journal_entry.update(journal_entry_dict) + journal_entry.set("accounts", accounts) + + + if allow_edit: + return journal_entry + + journal_entry.insert() + journal_entry.submit() + + if bank_transaction.deposit > 0: + paid_amount = bank_transaction.deposit + else: + paid_amount = bank_transaction.withdrawal + + vouchers = json.dumps([{ + "payment_doctype":"Journal Entry", + "payment_name":journal_entry.name, + "amount":paid_amount}]) + + return reconcile_vouchers(bank_transaction.name, vouchers) + +@frappe.whitelist() +def create_payment_entry_bts( bank_transaction_name, reference_number=None, reference_date=None, party_type=None, party=None, posting_date=None, + mode_of_payment=None, project=None, cost_center=None, allow_edit=None): + # Create a new payment entry based on the bank transaction + bank_transaction = frappe.db.get_values( + "Bank Transaction", bank_transaction_name, + fieldname=["name", "unallocated_amount", "deposit", "bank_account"] , + as_dict=True + )[0] + paid_amount = bank_transaction.unallocated_amount + payment_type = "Receive" if bank_transaction.deposit > 0 else "Pay" + + company_account = frappe.get_value("Bank Account", bank_transaction.bank_account, "account") + company = frappe.get_value("Account", company_account, "company") + payment_entry_dict = { + "company" : company, + "payment_type" : payment_type, + "reference_no" : reference_number, + "reference_date" : reference_date, + "party_type" : party_type, + "party" : party, + "posting_date" : posting_date, + "paid_amount": paid_amount, + "received_amount": paid_amount + } + payment_entry = frappe.new_doc("Payment Entry") + + + payment_entry.update(payment_entry_dict) + + if mode_of_payment: + payment_entry.mode_of_payment = mode_of_payment + if project: + payment_entry.project = project + if cost_center: + payment_entry.cost_center = cost_center + if payment_type == "Receive": + payment_entry.paid_to = company_account + else: + payment_entry.paid_from = company_account + + payment_entry.validate() + + if allow_edit: + return payment_entry + + payment_entry.insert() + + payment_entry.submit() + vouchers = json.dumps([{ + "payment_doctype":"Payment Entry", + "payment_name":payment_entry.name, + "amount":paid_amount}]) + return reconcile_vouchers(bank_transaction.name, vouchers) + +@frappe.whitelist() +def reconcile_vouchers(bank_transaction_name, vouchers): + # updated clear date of all the vouchers based on the bank transaction + vouchers = json.loads(vouchers) + transaction = frappe.get_doc("Bank Transaction", bank_transaction_name) + if transaction.unallocated_amount == 0: + frappe.throw(_("This bank transaction is already fully reconciled")) + total_amount = 0 + for voucher in vouchers: + voucher['payment_entry'] = frappe.get_doc(voucher['payment_doctype'], voucher['payment_name']) + total_amount += get_paid_amount(frappe._dict({ + 'payment_document': voucher['payment_doctype'], + 'payment_entry': voucher['payment_name'], + }), transaction.currency) + + if total_amount > transaction.unallocated_amount: + frappe.throw(_("The Sum Total of Amounts of All Selected Vouchers Should be Less than the Unallocated Amount of the Bank Transaction")) + account = frappe.db.get_value("Bank Account", transaction.bank_account, "account") + + for voucher in vouchers: + gl_entry = frappe.db.get_value("GL Entry", dict(account=account, voucher_type=voucher['payment_doctype'], voucher_no=voucher['payment_name']), ['credit', 'debit'], as_dict=1) + gl_amount, transaction_amount = (gl_entry.credit, transaction.deposit) if gl_entry.credit > 0 else (gl_entry.debit, transaction.withdrawal) + allocated_amount = gl_amount if gl_amount >= transaction_amount else transaction_amount + + transaction.append("payment_entries", { + "payment_document": voucher['payment_entry'].doctype, + "payment_entry": voucher['payment_entry'].name, + "allocated_amount": allocated_amount + }) + + transaction.save() + transaction.update_allocations() + return frappe.get_doc("Bank Transaction", bank_transaction_name) + +@frappe.whitelist() +def get_linked_payments(bank_transaction_name, document_types = None): + # get all matching payments for a bank transaction + transaction = frappe.get_doc("Bank Transaction", bank_transaction_name) + bank_account = frappe.db.get_values( + "Bank Account", + transaction.bank_account, + ["account", "company"], + as_dict=True)[0] + (account, company) = (bank_account.account, bank_account.company) + matching = check_matching(account, company, transaction, document_types) + return matching + +def check_matching(bank_account, company, transaction, document_types): + # combine all types of vocuhers + subquery = get_queries(bank_account, company, transaction, document_types) + filters = { + "amount": transaction.unallocated_amount, + "payment_type" : "Receive" if transaction.deposit > 0 else "Pay", + "reference_no": transaction.reference_number, + "party_type": transaction.party_type, + "party": transaction.party, + "bank_account": bank_account + } + + matching_vouchers = [] + for query in subquery: + matching_vouchers.extend( + frappe.db.sql(query, filters,) + ) + + return sorted(matching_vouchers, key = lambda x: x[0], reverse=True) if matching_vouchers else [] + +def get_queries(bank_account, company, transaction, document_types): + # get queries to get matching vouchers + amount_condition = "=" if "exact_match" in document_types else "<=" + account_from_to = "paid_to" if transaction.deposit > 0 else "paid_from" + queries = [] + + if "payment_entry" in document_types: + pe_amount_matching = get_pe_matching_query(amount_condition, account_from_to, transaction) + queries.extend([pe_amount_matching]) + + if "journal_entry" in document_types: + je_amount_matching = get_je_matching_query(amount_condition, transaction) + queries.extend([je_amount_matching]) + + if transaction.deposit > 0 and "sales_invoice" in document_types: + si_amount_matching = get_si_matching_query(amount_condition) + queries.extend([si_amount_matching]) + + if transaction.withdrawal > 0: + if "purchase_invoice" in document_types: + pi_amount_matching = get_pi_matching_query(amount_condition) + queries.extend([pi_amount_matching]) + + if "expense_claim" in document_types: + ec_amount_matching = get_ec_matching_query(bank_account, company, amount_condition) + queries.extend([ec_amount_matching]) + + return queries + +def get_pe_matching_query(amount_condition, account_from_to, transaction): + # get matching payment entries query + if transaction.deposit > 0: + currency_field = "paid_to_account_currency as currency" + else: + currency_field = "paid_from_account_currency as currency" + return f""" + SELECT + (CASE WHEN reference_no=%(reference_no)s THEN 1 ELSE 0 END + + CASE WHEN (party_type = %(party_type)s AND party = %(party)s ) THEN 1 ELSE 0 END + + 1 ) AS rank, + 'Payment Entry' as doctype, + name, + paid_amount, + reference_no, + reference_date, + party, + party_type, + posting_date, + {currency_field} + FROM + `tabPayment Entry` + WHERE + paid_amount {amount_condition} %(amount)s + AND docstatus = 1 + AND payment_type IN (%(payment_type)s, 'Internal Transfer') + AND ifnull(clearance_date, '') = "" + AND {account_from_to} = %(bank_account)s + """ + + +def get_je_matching_query(amount_condition, transaction): + # get matching journal entry query + cr_or_dr = "credit" if transaction.withdrawal > 0 else "debit" + return f""" + + SELECT + (CASE WHEN je.cheque_no=%(reference_no)s THEN 1 ELSE 0 END + + 1) AS rank , + 'Journal Entry' as doctype, + je.name, + jea.{cr_or_dr}_in_account_currency as paid_amount, + je.cheque_no as reference_no, + je.cheque_date as reference_date, + je.pay_to_recd_from as party, + jea.party_type, + je.posting_date, + jea.account_currency as currency + FROM + `tabJournal Entry Account` as jea + JOIN + `tabJournal Entry` as je + ON + jea.parent = je.name + WHERE + (je.clearance_date is null or je.clearance_date='0000-00-00') + AND jea.account = %(bank_account)s + AND jea.{cr_or_dr}_in_account_currency {amount_condition} %(amount)s + AND je.docstatus = 1 + """ + + +def get_si_matching_query(amount_condition): + # get matchin sales invoice query + return f""" + SELECT + ( CASE WHEN si.customer = %(party)s THEN 1 ELSE 0 END + + 1 ) AS rank, + 'Sales Invoice' as doctype, + si.name, + sip.amount as paid_amount, + '' as reference_no, + '' as reference_date, + si.customer as party, + 'Customer' as party_type, + si.posting_date, + si.currency + + FROM + `tabSales Invoice Payment` as sip + JOIN + `tabSales Invoice` as si + ON + sip.parent = si.name + WHERE (sip.clearance_date is null or sip.clearance_date='0000-00-00') + AND sip.account = %(bank_account)s + AND sip.amount {amount_condition} %(amount)s + AND si.docstatus = 1 + """ + +def get_pi_matching_query(amount_condition): + # get matching purchase invoice query + return f""" + SELECT + ( CASE WHEN supplier = %(party)s THEN 1 ELSE 0 END + + 1 ) AS rank, + 'Purchase Invoice' as doctype, + name, + paid_amount, + '' as reference_no, + '' as reference_date, + supplier as party, + 'Supplier' as party_type, + posting_date, + currency + FROM + `tabPurchase Invoice` + WHERE + paid_amount {amount_condition} %(amount)s + AND docstatus = 1 + AND is_paid = 1 + AND ifnull(clearance_date, '') = "" + AND cash_bank_account = %(bank_account)s + """ + +def get_ec_matching_query(bank_account, company, amount_condition): + # get matching Expense Claim query + mode_of_payments = [x["parent"] for x in frappe.db.get_list("Mode of Payment Account", + filters={"default_account": bank_account}, fields=["parent"])] + mode_of_payments = '(\'' + '\', \''.join(mode_of_payments) + '\' )' + company_currency = get_company_currency(company) + return f""" + SELECT + ( CASE WHEN employee = %(party)s THEN 1 ELSE 0 END + + 1 ) AS rank, + 'Expense Claim' as doctype, + name, + total_sanctioned_amount as paid_amount, + '' as reference_no, + '' as reference_date, + employee as party, + 'Employee' as party_type, + posting_date, + '{company_currency}' as currency + FROM + `tabExpense Claim` + WHERE + total_sanctioned_amount {amount_condition} %(amount)s + AND docstatus = 1 + AND is_paid = 1 + AND ifnull(clearance_date, '') = "" + AND mode_of_payment in {mode_of_payments} + """ diff --git a/erpnext/accounts/doctype/bank_reconciliation_tool/test_bank_reconciliation_tool.py b/erpnext/accounts/doctype/bank_reconciliation_tool/test_bank_reconciliation_tool.py new file mode 100644 index 00000000000..d96950abbce --- /dev/null +++ b/erpnext/accounts/doctype/bank_reconciliation_tool/test_bank_reconciliation_tool.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestBankReconciliationTool(unittest.TestCase): + pass diff --git a/erpnext/accounts/doctype/bank_statement_settings_item/__init__.py b/erpnext/accounts/doctype/bank_statement_import/__init__.py similarity index 100% rename from erpnext/accounts/doctype/bank_statement_settings_item/__init__.py rename to erpnext/accounts/doctype/bank_statement_import/__init__.py diff --git a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.css b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.css new file mode 100644 index 00000000000..5206540a33c --- /dev/null +++ b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.css @@ -0,0 +1,3 @@ +.warnings .warning { + margin-bottom: 40px; +} diff --git a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.js b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.js new file mode 100644 index 00000000000..ad4ff9ee60a --- /dev/null +++ b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.js @@ -0,0 +1,574 @@ +// Copyright (c) 2019, Frappe Technologies and contributors +// For license information, please see license.txt + +frappe.ui.form.on("Bank Statement Import", { + setup(frm) { + frappe.realtime.on("data_import_refresh", ({ data_import }) => { + frm.import_in_progress = false; + if (data_import !== frm.doc.name) return; + frappe.model.clear_doc("Bank Statement Import", frm.doc.name); + frappe.model + .with_doc("Bank Statement Import", frm.doc.name) + .then(() => { + frm.refresh(); + }); + }); + frappe.realtime.on("data_import_progress", (data) => { + frm.import_in_progress = true; + if (data.data_import !== frm.doc.name) { + return; + } + let percent = Math.floor((data.current * 100) / data.total); + let seconds = Math.floor(data.eta); + let minutes = Math.floor(data.eta / 60); + let eta_message = + // prettier-ignore + seconds < 60 + ? __('About {0} seconds remaining', [seconds]) + : minutes === 1 + ? __('About {0} minute remaining', [minutes]) + : __('About {0} minutes remaining', [minutes]); + + let message; + if (data.success) { + let message_args = [data.current, data.total, eta_message]; + message = + frm.doc.import_type === "Insert New Records" + ? __("Importing {0} of {1}, {2}", message_args) + : __("Updating {0} of {1}, {2}", message_args); + } + if (data.skipping) { + message = __( + "Skipping {0} of {1}, {2}", + [ + data.current, + data.total, + eta_message, + ] + ); + } + frm.dashboard.show_progress( + __("Import Progress"), + percent, + message + ); + frm.page.set_indicator(__("In Progress"), "orange"); + + // hide progress when complete + if (data.current === data.total) { + setTimeout(() => { + frm.dashboard.hide(); + frm.refresh(); + }, 2000); + } + }); + + frm.set_query("reference_doctype", () => { + return { + filters: { + name: ["in", frappe.boot.user.can_import], + }, + }; + }); + + frm.get_field("import_file").df.options = { + restrictions: { + allowed_file_types: [".csv", ".xls", ".xlsx"], + }, + }; + + frm.has_import_file = () => { + return frm.doc.import_file || frm.doc.google_sheets_url; + }; + }, + + refresh(frm) { + frm.page.hide_icon_group(); + frm.trigger("update_indicators"); + frm.trigger("import_file"); + frm.trigger("show_import_log"); + frm.trigger("show_import_warnings"); + frm.trigger("toggle_submit_after_import"); + frm.trigger("show_import_status"); + frm.trigger("show_report_error_button"); + + if (frm.doc.status === "Partial Success") { + frm.add_custom_button(__("Export Errored Rows"), () => + frm.trigger("export_errored_rows") + ); + } + + if (frm.doc.status.includes("Success")) { + frm.add_custom_button( + __("Go to {0} List", [frm.doc.reference_doctype]), + () => frappe.set_route("List", frm.doc.reference_doctype) + ); + } + }, + + onload_post_render(frm) { + frm.trigger("update_primary_action"); + }, + + update_primary_action(frm) { + if (frm.is_dirty()) { + frm.enable_save(); + return; + } + frm.disable_save(); + if (frm.doc.status !== "Success") { + if (!frm.is_new() && frm.has_import_file()) { + let label = + frm.doc.status === "Pending" + ? __("Start Import") + : __("Retry"); + frm.page.set_primary_action(label, () => + frm.events.start_import(frm) + ); + } else { + frm.page.set_primary_action(__("Save"), () => frm.save()); + } + } + }, + + update_indicators(frm) { + const indicator = frappe.get_indicator(frm.doc); + if (indicator) { + frm.page.set_indicator(indicator[0], indicator[1]); + } else { + frm.page.clear_indicator(); + } + }, + + show_import_status(frm) { + let import_log = JSON.parse(frm.doc.import_log || "[]"); + let successful_records = import_log.filter((log) => log.success); + let failed_records = import_log.filter((log) => !log.success); + if (successful_records.length === 0) return; + + let message; + if (failed_records.length === 0) { + let message_args = [successful_records.length]; + if (frm.doc.import_type === "Insert New Records") { + message = + successful_records.length > 1 + ? __("Successfully imported {0} records.", message_args) + : __("Successfully imported {0} record.", message_args); + } else { + message = + successful_records.length > 1 + ? __("Successfully updated {0} records.", message_args) + : __("Successfully updated {0} record.", message_args); + } + } else { + let message_args = [successful_records.length, import_log.length]; + if (frm.doc.import_type === "Insert New Records") { + message = + successful_records.length > 1 + ? __( + "Successfully imported {0} records out of {1}. Click on Export Errored Rows, fix the errors and import again.", + message_args + ) + : __( + "Successfully imported {0} record out of {1}. Click on Export Errored Rows, fix the errors and import again.", + message_args + ); + } else { + message = + successful_records.length > 1 + ? __( + "Successfully updated {0} records out of {1}. Click on Export Errored Rows, fix the errors and import again.", + message_args + ) + : __( + "Successfully updated {0} record out of {1}. Click on Export Errored Rows, fix the errors and import again.", + message_args + ); + } + } + frm.dashboard.set_headline(message); + }, + + show_report_error_button(frm) { + if (frm.doc.status === "Error") { + frappe.db + .get_list("Error Log", { + filters: { method: frm.doc.name }, + fields: ["method", "error"], + order_by: "creation desc", + limit: 1, + }) + .then((result) => { + if (result.length > 0) { + frm.add_custom_button("Report Error", () => { + let fake_xhr = { + responseText: JSON.stringify({ + exc: result[0].error, + }), + }; + frappe.request.report_error(fake_xhr, {}); + }); + } + }); + } + }, + + start_import(frm) { + frm.call({ + method: "form_start_import", + args: { data_import: frm.doc.name }, + btn: frm.page.btn_primary, + }).then((r) => { + if (r.message === true) { + frm.disable_save(); + } + }); + }, + + download_template() { + let method = + "/api/method/frappe.core.doctype.data_import.data_import.download_template"; + + open_url_post(method, { + doctype: "Bank Transaction", + export_records: "5_records", + export_fields: { + "Bank Transaction": [ + "date", + "deposit", + "withdrawal", + "description", + "reference_number", + ], + }, + }); + }, + + reference_doctype(frm) { + frm.trigger("toggle_submit_after_import"); + }, + + toggle_submit_after_import(frm) { + frm.toggle_display("submit_after_import", false); + let doctype = frm.doc.reference_doctype; + if (doctype) { + frappe.model.with_doctype(doctype, () => { + let meta = frappe.get_meta(doctype); + frm.toggle_display("submit_after_import", meta.is_submittable); + }); + } + }, + + google_sheets_url(frm) { + if (!frm.is_dirty()) { + frm.trigger("import_file"); + } else { + frm.trigger("update_primary_action"); + } + }, + + refresh_google_sheet(frm) { + frm.trigger("import_file"); + }, + + import_file(frm) { + frm.toggle_display("section_import_preview", frm.has_import_file()); + if (!frm.has_import_file()) { + frm.get_field("import_preview").$wrapper.empty(); + return; + } else { + frm.trigger("update_primary_action"); + } + + // load import preview + frm.get_field("import_preview").$wrapper.empty(); + $('') + .html(__("Loading import file...")) + .appendTo(frm.get_field("import_preview").$wrapper); + + frm.call({ + method: "get_preview_from_template", + args: { + data_import: frm.doc.name, + import_file: frm.doc.import_file, + google_sheets_url: frm.doc.google_sheets_url, + }, + error_handlers: { + TimestampMismatchError() { + // ignore this error + }, + }, + }).then((r) => { + let preview_data = r.message; + frm.events.show_import_preview(frm, preview_data); + frm.events.show_import_warnings(frm, preview_data); + }); + }, + // method: 'frappe.core.doctype.data_import.data_import.get_preview_from_template', + + show_import_preview(frm, preview_data) { + let import_log = JSON.parse(frm.doc.import_log || "[]"); + + if ( + frm.import_preview && + frm.import_preview.doctype === frm.doc.reference_doctype + ) { + frm.import_preview.preview_data = preview_data; + frm.import_preview.import_log = import_log; + frm.import_preview.refresh(); + return; + } + + frappe.require("/assets/js/data_import_tools.min.js", () => { + frm.import_preview = new frappe.data_import.ImportPreview({ + wrapper: frm.get_field("import_preview").$wrapper, + doctype: frm.doc.reference_doctype, + preview_data, + import_log, + frm, + events: { + remap_column(changed_map) { + let template_options = JSON.parse( + frm.doc.template_options || "{}" + ); + template_options.column_to_field_map = + template_options.column_to_field_map || {}; + Object.assign( + template_options.column_to_field_map, + changed_map + ); + frm.set_value( + "template_options", + JSON.stringify(template_options) + ); + frm.save().then(() => frm.trigger("import_file")); + }, + }, + }); + }); + }, + + export_errored_rows(frm) { + open_url_post( + "/api/method/frappe.core.doctype.data_import.data_import.download_errored_template", + { + data_import_name: frm.doc.name, + } + ); + }, + + show_import_warnings(frm, preview_data) { + let columns = preview_data.columns; + let warnings = JSON.parse(frm.doc.template_warnings || "[]"); + warnings = warnings.concat(preview_data.warnings || []); + + frm.toggle_display("import_warnings_section", warnings.length > 0); + if (warnings.length === 0) { + frm.get_field("import_warnings").$wrapper.html(""); + return; + } + + // group warnings by row + let warnings_by_row = {}; + let other_warnings = []; + for (let warning of warnings) { + if (warning.row) { + warnings_by_row[warning.row] = + warnings_by_row[warning.row] || []; + warnings_by_row[warning.row].push(warning); + } else { + other_warnings.push(warning); + } + } + + let html = ""; + html += Object.keys(warnings_by_row) + .map((row_number) => { + let message = warnings_by_row[row_number] + .map((w) => { + if (w.field) { + let label = + w.field.label + + (w.field.parent !== frm.doc.reference_doctype + ? ` (${w.field.parent})` + : ""); + return `
  • ${label}: ${w.message}
  • `; + } + return `
  • ${w.message}
  • `; + }) + .join(""); + return ` +
    +
    ${__("Row {0}", [row_number])}
    +
      ${message}
    +
    + `; + }) + .join(""); + + html += other_warnings + .map((warning) => { + let header = ""; + if (warning.col) { + let column_number = `${__( + "Column {0}", + [warning.col] + )}`; + let column_header = columns[warning.col].header_title; + header = `${column_number} (${column_header})`; + } + return ` +
    +
    ${header}
    +
    ${warning.message}
    +
    + `; + }) + .join(""); + frm.get_field("import_warnings").$wrapper.html(` +
    +
    ${html}
    +
    + `); + }, + + show_failed_logs(frm) { + frm.trigger("show_import_log"); + }, + + show_import_log(frm) { + let import_log = JSON.parse(frm.doc.import_log || "[]"); + let logs = import_log; + frm.toggle_display("import_log", false); + frm.toggle_display("import_log_section", logs.length > 0); + + if (logs.length === 0) { + frm.get_field("import_log_preview").$wrapper.empty(); + return; + } + + let rows = logs + .map((log) => { + let html = ""; + if (log.success) { + if (frm.doc.import_type === "Insert New Records") { + html = __( + "Successfully imported {0}", [ + `${frappe.utils.get_form_link( + frm.doc.reference_doctype, + log.docname, + true + )}`, + ] + ); + } else { + html = __( + "Successfully updated {0}", [ + `${frappe.utils.get_form_link( + frm.doc.reference_doctype, + log.docname, + true + )}`, + ] + ); + } + } else { + let messages = log.messages + .map(JSON.parse) + .map((m) => { + let title = m.title + ? `${m.title}` + : ""; + let message = m.message + ? `
    ${m.message}
    ` + : ""; + return title + message; + }) + .join(""); + let id = frappe.dom.get_unique_id(); + html = `${messages} + +
    +
    +
    ${log.exception}
    +
    +
    `; + } + let indicator_color = log.success ? "green" : "red"; + let title = log.success ? __("Success") : __("Failure"); + + if (frm.doc.show_failed_logs && log.success) { + return ""; + } + + return ` + ${log.row_indexes.join(", ")} + +
    ${title}
    + + + ${html} + + `; + }) + .join(""); + + if (!rows && frm.doc.show_failed_logs) { + rows = ` + ${__("No failed logs")} + `; + } + + frm.get_field("import_log_preview").$wrapper.html(` + + + + + + + ${rows} +
    ${__("Row Number")}${__("Status")}${__("Message")}
    + `); + }, + + show_missing_link_values(frm, missing_link_values) { + let can_be_created_automatically = missing_link_values.every( + (d) => d.has_one_mandatory_field + ); + + let html = missing_link_values + .map((d) => { + let doctype = d.doctype; + let values = d.missing_values; + return ` +
    ${doctype}
    +
      ${values.map((v) => `
    • ${v}
    • `).join("")}
    + `; + }) + .join(""); + + if (can_be_created_automatically) { + // prettier-ignore + let message = __('There are some linked records which needs to be created before we can import your file. Do you want to create the following missing records automatically?'); + frappe.confirm(message + html, () => { + frm.call("create_missing_link_values", { + missing_link_values, + }).then((r) => { + let records = r.message; + frappe.msgprint(__( + "Created {0} records successfully.", [ + records.length, + ] + )); + }); + }); + } else { + frappe.msgprint( + // prettier-ignore + __('The following records needs to be created before we can import your file.') + html + ); + } + }, +}); diff --git a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.json b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.json new file mode 100644 index 00000000000..5e913cc2aac --- /dev/null +++ b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.json @@ -0,0 +1,227 @@ +{ + "actions": [], + "autoname": "format:Bank Statement Import on {creation}", + "beta": 1, + "creation": "2019-08-04 14:16:08.318714", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "company", + "bank_account", + "bank", + "column_break_4", + "google_sheets_url", + "refresh_google_sheet", + "html_5", + "import_file", + "download_template", + "status", + "template_options", + "import_warnings_section", + "template_warnings", + "import_warnings", + "section_import_preview", + "import_preview", + "import_log_section", + "import_log", + "show_failed_logs", + "import_log_preview", + "reference_doctype", + "import_type", + "submit_after_import", + "mute_emails" + ], + "fields": [ + { + "fieldname": "company", + "fieldtype": "Link", + "label": "Company", + "options": "Company", + "reqd": 1, + "set_only_once": 1 + }, + { + "fieldname": "bank_account", + "fieldtype": "Link", + "label": "Bank Account", + "options": "Bank Account", + "reqd": 1, + "set_only_once": 1 + }, + { + "depends_on": "eval:doc.bank_account", + "fetch_from": "bank_account.bank", + "fieldname": "bank", + "fieldtype": "Link", + "label": "Bank", + "options": "Bank", + "read_only": 1, + "set_only_once": 1 + }, + { + "depends_on": "eval:!doc.__islocal", + "fieldname": "download_template", + "fieldtype": "Button", + "label": "Download Template" + }, + { + "depends_on": "eval:!doc.__islocal", + "fieldname": "import_file", + "fieldtype": "Attach", + "in_list_view": 1, + "label": "Import File" + }, + { + "fieldname": "import_preview", + "fieldtype": "HTML", + "label": "Import Preview" + }, + { + "fieldname": "section_import_preview", + "fieldtype": "Section Break", + "label": "Preview" + }, + { + "fieldname": "template_options", + "fieldtype": "Code", + "hidden": 1, + "label": "Template Options", + "options": "JSON", + "read_only": 1 + }, + { + "fieldname": "import_log", + "fieldtype": "Code", + "label": "Import Log", + "options": "JSON" + }, + { + "fieldname": "import_log_section", + "fieldtype": "Section Break", + "label": "Import Log" + }, + { + "fieldname": "import_log_preview", + "fieldtype": "HTML", + "label": "Import Log Preview" + }, + { + "default": "Pending", + "fieldname": "status", + "fieldtype": "Select", + "hidden": 1, + "label": "Status", + "options": "Pending\nSuccess\nPartial Success\nError", + "read_only": 1 + }, + { + "fieldname": "template_warnings", + "fieldtype": "Code", + "hidden": 1, + "label": "Template Warnings", + "options": "JSON" + }, + { + "fieldname": "import_warnings_section", + "fieldtype": "Section Break", + "label": "Import File Errors and Warnings" + }, + { + "fieldname": "import_warnings", + "fieldtype": "HTML", + "label": "Import Warnings" + }, + { + "default": "0", + "fieldname": "show_failed_logs", + "fieldtype": "Check", + "label": "Show Failed Logs" + }, + { + "depends_on": "eval:!doc.__islocal && !doc.import_file", + "fieldname": "html_5", + "fieldtype": "HTML", + "options": "
    Or
    " + }, + { + "depends_on": "eval:!doc.__islocal && !doc.import_file\n", + "description": "Must be a publicly accessible Google Sheets URL", + "fieldname": "google_sheets_url", + "fieldtype": "Data", + "label": "Import from Google Sheets" + }, + { + "depends_on": "eval:doc.google_sheets_url && !doc.__unsaved", + "fieldname": "refresh_google_sheet", + "fieldtype": "Button", + "label": "Refresh Google Sheet" + }, + { + "default": "Bank Transaction", + "fieldname": "reference_doctype", + "fieldtype": "Link", + "hidden": 1, + "in_list_view": 1, + "label": "Document Type", + "options": "DocType", + "reqd": 1, + "set_only_once": 1 + }, + { + "default": "Insert New Records", + "fieldname": "import_type", + "fieldtype": "Select", + "hidden": 1, + "in_list_view": 1, + "label": "Import Type", + "options": "\nInsert New Records\nUpdate Existing Records", + "reqd": 1, + "set_only_once": 1 + }, + { + "default": "1", + "fieldname": "submit_after_import", + "fieldtype": "Check", + "hidden": 1, + "label": "Submit After Import", + "set_only_once": 1 + }, + { + "default": "1", + "fieldname": "mute_emails", + "fieldtype": "Check", + "hidden": 1, + "label": "Don't Send Emails", + "set_only_once": 1 + }, + { + "fieldname": "column_break_4", + "fieldtype": "Column Break" + } + ], + "hide_toolbar": 1, + "links": [], + "modified": "2021-02-10 19:29:59.027325", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Bank Statement Import", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py new file mode 100644 index 00000000000..9f41b13f4b6 --- /dev/null +++ b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py @@ -0,0 +1,205 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2019, Frappe Technologies and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import csv +import json +import re + +import openpyxl +from openpyxl.styles import Font +from openpyxl.utils import get_column_letter +from six import string_types + +import frappe +from frappe.core.doctype.data_import.importer import Importer, ImportFile +from frappe.utils.background_jobs import enqueue +from frappe.utils.xlsxutils import handle_html, ILLEGAL_CHARACTERS_RE +from frappe import _ + +from frappe.core.doctype.data_import.data_import import DataImport + +class BankStatementImport(DataImport): + def __init__(self, *args, **kwargs): + super(BankStatementImport, self).__init__(*args, **kwargs) + + def validate(self): + doc_before_save = self.get_doc_before_save() + if ( + not (self.import_file or self.google_sheets_url) + or (doc_before_save and doc_before_save.import_file != self.import_file) + or (doc_before_save and doc_before_save.google_sheets_url != self.google_sheets_url) + ): + + template_options_dict = {} + column_to_field_map = {} + bank = frappe.get_doc("Bank", self.bank) + for i in bank.bank_transaction_mapping: + column_to_field_map[i.file_field] = i.bank_transaction_field + template_options_dict["column_to_field_map"] = column_to_field_map + self.template_options = json.dumps(template_options_dict) + + self.template_warnings = "" + + self.validate_import_file() + self.validate_google_sheets_url() + + def start_import(self): + + from frappe.core.page.background_jobs.background_jobs import get_info + from frappe.utils.scheduler import is_scheduler_inactive + + if is_scheduler_inactive() and not frappe.flags.in_test: + frappe.throw( + _("Scheduler is inactive. Cannot import data."), title=_("Scheduler Inactive") + ) + + enqueued_jobs = [d.get("job_name") for d in get_info()] + + if self.name not in enqueued_jobs: + enqueue( + start_import, + queue="default", + timeout=6000, + event="data_import", + job_name=self.name, + data_import=self.name, + bank_account=self.bank_account, + import_file_path=self.import_file, + bank=self.bank, + template_options=self.template_options, + now=frappe.conf.developer_mode or frappe.flags.in_test, + ) + return True + + return False + +@frappe.whitelist() +def get_preview_from_template(data_import, import_file=None, google_sheets_url=None): + return frappe.get_doc("Bank Statement Import", data_import).get_preview_from_template( + import_file, google_sheets_url + ) + +@frappe.whitelist() +def form_start_import(data_import): + return frappe.get_doc("Bank Statement Import", data_import).start_import() + +@frappe.whitelist() +def download_errored_template(data_import_name): + data_import = frappe.get_doc("Bank Statement Import", data_import_name) + data_import.export_errored_rows() + +def start_import(data_import, bank_account, import_file_path, bank, template_options): + """This method runs in background job""" + + update_mapping_db(bank, template_options) + + data_import = frappe.get_doc("Bank Statement Import", data_import) + + import_file = ImportFile("Bank Transaction", file = import_file_path, import_type="Insert New Records") + data = import_file.raw_data + + add_bank_account(data, bank_account) + write_files(import_file, data) + + try: + i = Importer(data_import.reference_doctype, data_import=data_import) + i.import_data() + except Exception: + frappe.db.rollback() + data_import.db_set("status", "Error") + frappe.log_error(title=data_import.name) + finally: + frappe.flags.in_import = False + + frappe.publish_realtime("data_import_refresh", {"data_import": data_import.name}) + +def update_mapping_db(bank, template_options): + bank = frappe.get_doc("Bank", bank) + for d in bank.bank_transaction_mapping: + d.delete() + + for d in json.loads(template_options)["column_to_field_map"].items(): + bank.append("bank_transaction_mapping", {"bank_transaction_field": d[1] ,"file_field": d[0]} ) + + bank.save() + +def add_bank_account(data, bank_account): + bank_account_loc = None + if "Bank Account" not in data[0]: + data[0].append("Bank Account") + else: + for loc, header in enumerate(data[0]): + if header == "Bank Account": + bank_account_loc = loc + + for row in data[1:]: + if bank_account_loc: + row[bank_account_loc] = bank_account + else: + row.append(bank_account) + +def write_files(import_file, data): + full_file_path = import_file.file_doc.get_full_path() + parts = import_file.file_doc.get_extension() + extension = parts[1] + extension = extension.lstrip(".") + + if extension == "csv": + with open(full_file_path, 'w', newline='') as file: + writer = csv.writer(file) + writer.writerows(data) + elif extension == "xlsx" or "xls": + write_xlsx(data, "trans", file_path = full_file_path) + +def write_xlsx(data, sheet_name, wb=None, column_widths=None, file_path=None): + # from xlsx utils with changes + column_widths = column_widths or [] + if wb is None: + wb = openpyxl.Workbook(write_only=True) + + ws = wb.create_sheet(sheet_name, 0) + + for i, column_width in enumerate(column_widths): + if column_width: + ws.column_dimensions[get_column_letter(i + 1)].width = column_width + + row1 = ws.row_dimensions[1] + row1.font = Font(name='Calibri', bold=True) + + for row in data: + clean_row = [] + for item in row: + if isinstance(item, string_types) and (sheet_name not in ['Data Import Template', 'Data Export']): + value = handle_html(item) + else: + value = item + + if isinstance(item, string_types) and next(ILLEGAL_CHARACTERS_RE.finditer(value), None): + # Remove illegal characters from the string + value = re.sub(ILLEGAL_CHARACTERS_RE, '', value) + + clean_row.append(value) + + ws.append(clean_row) + + wb.save(file_path) + return True + +@frappe.whitelist() +def upload_bank_statement(**args): + args = frappe._dict(args) + bsi = frappe.new_doc("Bank Statement Import") + + if args.company: + bsi.update({ + "company": args.company, + }) + + if args.bank_account: + bsi.update({ + "bank_account": args.bank_account + }) + + return bsi diff --git a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import_list.js b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import_list.js new file mode 100644 index 00000000000..6c754022e68 --- /dev/null +++ b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import_list.js @@ -0,0 +1,36 @@ +let imports_in_progress = []; + +frappe.listview_settings['Bank Statement Import'] = { + onload(listview) { + frappe.realtime.on('data_import_progress', data => { + if (!imports_in_progress.includes(data.data_import)) { + imports_in_progress.push(data.data_import); + } + }); + frappe.realtime.on('data_import_refresh', data => { + imports_in_progress = imports_in_progress.filter( + d => d !== data.data_import + ); + listview.refresh(); + }); + }, + get_indicator: function(doc) { + var colors = { + 'Pending': 'orange', + 'Not Started': 'orange', + 'Partial Success': 'orange', + 'Success': 'green', + 'In Progress': 'orange', + 'Error': 'red' + }; + let status = doc.status; + if (imports_in_progress.includes(doc.name)) { + status = 'In Progress'; + } + if (status == 'Pending') { + status = 'Not Started'; + } + return [__(status), colors[status], 'status,=,' + doc.status]; + }, + hide_name_column: true +}; diff --git a/erpnext/accounts/doctype/bank_statement_import/test_bank_statement_import.py b/erpnext/accounts/doctype/bank_statement_import/test_bank_statement_import.py new file mode 100644 index 00000000000..cd5831412d9 --- /dev/null +++ b/erpnext/accounts/doctype/bank_statement_import/test_bank_statement_import.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestBankStatementImport(unittest.TestCase): + pass diff --git a/erpnext/accounts/doctype/bank_statement_settings/bank_statement_settings.js b/erpnext/accounts/doctype/bank_statement_settings/bank_statement_settings.js deleted file mode 100644 index 46aa4f20311..00000000000 --- a/erpnext/accounts/doctype/bank_statement_settings/bank_statement_settings.js +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) 2017, sathishpy@gmail.com and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Bank Statement Settings', { - refresh: function(frm) { - - } -}); diff --git a/erpnext/accounts/doctype/bank_statement_settings/bank_statement_settings.json b/erpnext/accounts/doctype/bank_statement_settings/bank_statement_settings.json deleted file mode 100644 index 53fbf7d446c..00000000000 --- a/erpnext/accounts/doctype/bank_statement_settings/bank_statement_settings.json +++ /dev/null @@ -1,272 +0,0 @@ -{ - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 1, - "beta": 0, - "creation": "2017-11-13 13:38:10.863592", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", - "fields": [ - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "bank", - "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": "Bank Account", - "length": 0, - "no_copy": 0, - "options": "Bank", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "'%d/%m/%Y'", - "fieldname": "date_format", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Date Format", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "statement_header_mapping", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Statement Header Mapping", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "header_items", - "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Statement Headers", - "length": 0, - "no_copy": 0, - "options": "Bank Statement Settings Item", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "transaction_data_mapping", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Transaction Data Mapping", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "mapped_items", - "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Mapped Items", - "length": 0, - "no_copy": 0, - "options": "Bank Statement Transaction Settings Item", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2018-04-07 18:57:04.048423", - "modified_by": "Administrator", - "module": "Accounts", - "name": "Bank Statement Settings", - "name_case": "", - "owner": "Administrator", - "permissions": [ - { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - }, - { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Accounts Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - } - ], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0 -} \ No newline at end of file diff --git a/erpnext/accounts/doctype/bank_statement_settings/bank_statement_settings.py b/erpnext/accounts/doctype/bank_statement_settings/bank_statement_settings.py deleted file mode 100644 index 6c4dd1b85b3..00000000000 --- a/erpnext/accounts/doctype/bank_statement_settings/bank_statement_settings.py +++ /dev/null @@ -1,11 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2017, sathishpy@gmail.com and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals -import frappe -from frappe.model.document import Document - -class BankStatementSettings(Document): - def autoname(self): - self.name = self.bank + "-Statement-Settings" diff --git a/erpnext/accounts/doctype/bank_statement_settings/test_bank_statement_settings.js b/erpnext/accounts/doctype/bank_statement_settings/test_bank_statement_settings.js deleted file mode 100644 index f2381c042ee..00000000000 --- a/erpnext/accounts/doctype/bank_statement_settings/test_bank_statement_settings.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Bank Statement Settings", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Bank Statement Settings - () => frappe.tests.make('Bank Statement Settings', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/accounts/doctype/bank_statement_settings/test_bank_statement_settings.py b/erpnext/accounts/doctype/bank_statement_settings/test_bank_statement_settings.py deleted file mode 100644 index aa7fe833285..00000000000 --- a/erpnext/accounts/doctype/bank_statement_settings/test_bank_statement_settings.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2017, sathishpy@gmail.com and Contributors -# See license.txt -from __future__ import unicode_literals - -import frappe -import unittest - -class TestBankStatementSettings(unittest.TestCase): - pass diff --git a/erpnext/accounts/doctype/bank_statement_settings_item/bank_statement_settings_item.json b/erpnext/accounts/doctype/bank_statement_settings_item/bank_statement_settings_item.json deleted file mode 100644 index 7c93f268f53..00000000000 --- a/erpnext/accounts/doctype/bank_statement_settings_item/bank_statement_settings_item.json +++ /dev/null @@ -1,101 +0,0 @@ -{ - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2018-01-08 00:16:42.762980", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", - "fields": [ - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "mapped_header", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Mapped Header", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "stmt_header", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Bank Header", - "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, - "unique": 0 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2018-01-08 00:19:14.841134", - "modified_by": "Administrator", - "module": "Accounts", - "name": "Bank Statement Settings Item", - "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 -} \ No newline at end of file diff --git a/erpnext/accounts/doctype/bank_statement_transaction_entry/bank_statement_transaction_entry.js b/erpnext/accounts/doctype/bank_statement_transaction_entry/bank_statement_transaction_entry.js deleted file mode 100644 index 736ed35ae13..00000000000 --- a/erpnext/accounts/doctype/bank_statement_transaction_entry/bank_statement_transaction_entry.js +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright (c) 2017, sathishpy@gmail.com and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Bank Statement Transaction Entry', { - setup: function(frm) { - frm.events.account_filters(frm) - frm.events.invoice_filter(frm) - }, - refresh: function(frm) { - frm.set_df_property("bank_account", "read_only", frm.doc.__islocal ? 0 : 1); - frm.set_df_property("from_date", "read_only", frm.doc.__islocal ? 0 : 1); - frm.set_df_property("to_date", "read_only", frm.doc.__islocal ? 0 : 1); - }, - invoke_doc_function(frm, method) { - frappe.call({ - doc: frm.doc, - method: method, - callback: function(r) { - if(!r.exe) { - frm.refresh_fields(); - } - } - }); - }, - account_filters: function(frm) { - frm.fields_dict['bank_account'].get_query = function(doc, dt, dn) { - return { - filters:[ - ["Account", "account_type", "in", ["Bank"]] - ] - } - }; - frm.fields_dict['receivable_account'].get_query = function(doc, dt, dn) { - return { - filters: {"account_type": "Receivable"} - } - }; - frm.fields_dict['payable_account'].get_query = function(doc, dt, dn) { - return { - filters: {"account_type": "Payable"} - } - }; - }, - - invoice_filter: function(frm) { - frm.set_query("invoice", "payment_invoice_items", function(doc, cdt, cdn) { - let row = locals[cdt][cdn] - if (row.party_type == "Customer") { - return { - filters:[[row.invoice_type, "customer", "in", [row.party]], - [row.invoice_type, "status", "!=", "Cancelled" ], - [row.invoice_type, "posting_date", "<", row.transaction_date ], - [row.invoice_type, "outstanding_amount", ">", 0 ]] - } - } else if (row.party_type == "Supplier") { - return { - filters:[[row.invoice_type, "supplier", "in", [row.party]], - [row.invoice_type, "status", "!=", "Cancelled" ], - [row.invoice_type, "posting_date", "<", row.transaction_date ], - [row.invoice_type, "outstanding_amount", ">", 0 ]] - } - } - }); - }, - - match_invoices: function(frm) { - frm.events.invoke_doc_function(frm, "populate_matching_invoices"); - }, - create_payments: function(frm) { - frm.events.invoke_doc_function(frm, "create_payment_entries"); - }, - submit_payments: function(frm) { - frm.events.invoke_doc_function(frm, "submit_payment_entries"); - }, -}); - - -frappe.ui.form.on('Bank Statement Transaction Invoice Item', { - party_type: function(frm, cdt, cdn) { - let row = locals[cdt][cdn]; - if (row.party_type == "Customer") { - row.invoice_type = "Sales Invoice"; - } else if (row.party_type == "Supplier") { - row.invoice_type = "Purchase Invoice"; - } else if (row.party_type == "Account") { - row.invoice_type = "Journal Entry"; - } - refresh_field("invoice_type", row.name, "payment_invoice_items"); - - }, - invoice_type: function(frm, cdt, cdn) { - let row = locals[cdt][cdn]; - if (row.invoice_type == "Purchase Invoice") { - row.party_type = "Supplier"; - } else if (row.invoice_type == "Sales Invoice") { - row.party_type = "Customer"; - } - refresh_field("party_type", row.name, "payment_invoice_items"); - } -}); \ No newline at end of file diff --git a/erpnext/accounts/doctype/bank_statement_transaction_entry/bank_statement_transaction_entry.json b/erpnext/accounts/doctype/bank_statement_transaction_entry/bank_statement_transaction_entry.json deleted file mode 100644 index fb80169c378..00000000000 --- a/erpnext/accounts/doctype/bank_statement_transaction_entry/bank_statement_transaction_entry.json +++ /dev/null @@ -1,792 +0,0 @@ -{ - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 1, - "beta": 0, - "creation": "2017-11-07 13:48:13.123185", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", - "fields": [ - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "bank_account", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Bank Account", - "length": 0, - "no_copy": 0, - "options": "Account", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "from_date", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "From Date", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "to_date", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "To Date", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "bank_settings", - "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": "Bank Statement Settings", - "length": 0, - "no_copy": 0, - "options": "Bank Statement Settings", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "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 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "bank", - "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": "Bank", - "length": 0, - "no_copy": 0, - "options": "Bank", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "receivable_account", - "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": "Receivable Account", - "length": 0, - "no_copy": 0, - "options": "Account", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "payable_account", - "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": "Payable Account", - "length": 0, - "no_copy": 0, - "options": "Account", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "bank_statement", - "fieldtype": "Attach", - "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": "Bank Statement", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "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, - "label": "Bank Transaction Entries", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "new_transaction_items", - "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "New Transactions", - "length": 0, - "no_copy": 0, - "options": "Bank Statement Transaction Payment Item", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.new_transaction_items && doc.new_transaction_items.length", - "fieldname": "section_break_9", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "fieldname": "match_invoices", - "fieldtype": "Button", - "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": "Match Transaction to Invoices", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_14", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "create_payments", - "fieldtype": "Button", - "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": "Create New Payment/Journal Entry", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_16", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "submit_payments", - "fieldtype": "Button", - "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": "Submit/Reconcile Payments", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.new_transaction_items && doc.new_transaction_items.length", - "fieldname": "section_break_18", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Matching Invoices", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "payment_invoice_items", - "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Payment Invoice Items", - "length": 0, - "no_copy": 0, - "options": "Bank Statement Transaction Invoice Item", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "reconciled_transactions", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Reconciled Transactions", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "reconciled_transaction_items", - "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Reconciled Transactions", - "length": 0, - "no_copy": 0, - "options": "Bank Statement Transaction Payment Item", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "amended_from", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Amended From", - "length": 0, - "no_copy": 1, - "options": "Bank Statement Transaction Entry", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 1, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2018-09-14 18:04:44.170455", - "modified_by": "Administrator", - "module": "Accounts", - "name": "Bank Statement Transaction Entry", - "name_case": "", - "owner": "Administrator", - "permissions": [ - { - "amend": 1, - "cancel": 1, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 1, - "write": 1 - }, - { - "amend": 1, - "cancel": 1, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Accounts Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 1, - "write": 1 - } - ], - "quick_entry": 0, - "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 -} \ No newline at end of file diff --git a/erpnext/accounts/doctype/bank_statement_transaction_entry/bank_statement_transaction_entry.py b/erpnext/accounts/doctype/bank_statement_transaction_entry/bank_statement_transaction_entry.py deleted file mode 100644 index 27dd8e463f6..00000000000 --- a/erpnext/accounts/doctype/bank_statement_transaction_entry/bank_statement_transaction_entry.py +++ /dev/null @@ -1,443 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2017, sathishpy@gmail.com 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 -from erpnext.accounts.utils import get_outstanding_invoices -from frappe.utils import nowdate -from datetime import datetime -import csv, os, re, io -import difflib -import copy - -class BankStatementTransactionEntry(Document): - def autoname(self): - self.name = self.bank_account + "-" + self.from_date + "-" + self.to_date - if self.bank: - mapper_name = self.bank + "-Statement-Settings" - if not frappe.db.exists("Bank Statement Settings", mapper_name): - self.create_settings(self.bank) - self.bank_settings = mapper_name - - def create_settings(self, bank): - mapper = frappe.new_doc("Bank Statement Settings") - mapper.bank = bank - mapper.date_format = "%Y-%m-%d" - mapper.bank_account = self.bank_account - for header in ["Date", "Particulars", "Withdrawals", "Deposits", "Balance"]: - header_item = mapper.append("header_items", {}) - header_item.mapped_header = header_item.stmt_header = header - mapper.save() - - def on_update(self): - if (not self.bank_statement): - self.reconciled_transaction_items = self.new_transaction_items = [] - return - - if len(self.new_transaction_items + self.reconciled_transaction_items) == 0: - self.populate_payment_entries() - else: - self.match_invoice_to_payment() - - def validate(self): - if not self.new_transaction_items: - self.populate_payment_entries() - - def get_statement_headers(self): - if not self.bank_settings: - frappe.throw(_("Bank Data mapper doesn't exist")) - mapper_doc = frappe.get_doc("Bank Statement Settings", self.bank_settings) - headers = {entry.mapped_header:entry.stmt_header for entry in mapper_doc.header_items} - return headers - - def populate_payment_entries(self): - if self.bank_statement is None: return - file_url = self.bank_statement - if (len(self.new_transaction_items + self.reconciled_transaction_items) > 0): - frappe.throw(_("Transactions already retreived from the statement")) - - date_format = frappe.get_value("Bank Statement Settings", self.bank_settings, "date_format") - if (date_format is None): - date_format = '%Y-%m-%d' - if self.bank_settings: - mapped_items = frappe.get_doc("Bank Statement Settings", self.bank_settings).mapped_items - statement_headers = self.get_statement_headers() - transactions = get_transaction_entries(file_url, statement_headers) - for entry in transactions: - date = entry[statement_headers["Date"]].strip() - #print("Processing entry DESC:{0}-W:{1}-D:{2}-DT:{3}".format(entry["Particulars"], entry["Withdrawals"], entry["Deposits"], entry["Date"])) - if (not date): continue - transaction_date = datetime.strptime(date, date_format).date() - if (self.from_date and transaction_date < datetime.strptime(self.from_date, '%Y-%m-%d').date()): continue - if (self.to_date and transaction_date > datetime.strptime(self.to_date, '%Y-%m-%d').date()): continue - bank_entry = self.append('new_transaction_items', {}) - bank_entry.transaction_date = transaction_date - bank_entry.description = entry[statement_headers["Particulars"]] - - mapped_item = next((entry for entry in mapped_items if entry.mapping_type == "Transaction" and frappe.safe_decode(entry.bank_data.lower()) in frappe.safe_decode(bank_entry.description.lower())), None) - if (mapped_item is not None): - bank_entry.party_type = mapped_item.mapped_data_type - bank_entry.party = mapped_item.mapped_data - else: - bank_entry.party_type = "Supplier" if not entry[statement_headers["Deposits"]].strip() else "Customer" - party_list = frappe.get_all(bank_entry.party_type, fields=["name"]) - parties = [party.name for party in party_list] - matches = difflib.get_close_matches(frappe.safe_decode(bank_entry.description.lower()), parties, 1, 0.4) - if len(matches) > 0: bank_entry.party = matches[0] - bank_entry.amount = -float(entry[statement_headers["Withdrawals"]]) if not entry[statement_headers["Deposits"]].strip() else float(entry[statement_headers["Deposits"]]) - self.map_unknown_transactions() - self.map_transactions_on_journal_entry() - - def map_transactions_on_journal_entry(self): - for entry in self.new_transaction_items: - vouchers = frappe.db.sql("""select name, posting_date from `tabJournal Entry` - where posting_date='{0}' and total_credit={1} and cheque_no='{2}' and docstatus != 2 - """.format(entry.transaction_date, abs(entry.amount), frappe.safe_decode(entry.description)), as_dict=True) - if (len(vouchers) == 1): - entry.reference_name = vouchers[0].name - - def populate_matching_invoices(self): - self.payment_invoice_items = [] - self.map_unknown_transactions() - added_invoices = [] - for entry in self.new_transaction_items: - if (not entry.party or entry.party_type == "Account"): continue - account = self.receivable_account if entry.party_type == "Customer" else self.payable_account - invoices = get_outstanding_invoices(entry.party_type, entry.party, account) - transaction_date = datetime.strptime(entry.transaction_date, "%Y-%m-%d").date() - outstanding_invoices = [invoice for invoice in invoices if invoice.posting_date <= transaction_date] - amount = abs(entry.amount) - matching_invoices = [invoice for invoice in outstanding_invoices if invoice.outstanding_amount == amount] - sorted(outstanding_invoices, key=lambda k: k['posting_date']) - for e in (matching_invoices + outstanding_invoices): - added = next((inv for inv in added_invoices if inv == e.get('voucher_no')), None) - if (added is not None): continue - ent = self.append('payment_invoice_items', {}) - ent.transaction_date = entry.transaction_date - ent.payment_description = frappe.safe_decode(entry.description) - ent.party_type = entry.party_type - ent.party = entry.party - ent.invoice = e.get('voucher_no') - added_invoices += [ent.invoice] - ent.invoice_type = "Sales Invoice" if entry.party_type == "Customer" else "Purchase Invoice" - ent.invoice_date = e.get('posting_date') - ent.outstanding_amount = e.get('outstanding_amount') - ent.allocated_amount = min(float(e.get('outstanding_amount')), amount) - amount -= float(e.get('outstanding_amount')) - if (amount <= 5): break - self.match_invoice_to_payment() - self.populate_matching_vouchers() - self.map_transactions_on_journal_entry() - - def match_invoice_to_payment(self): - added_payments = [] - for entry in self.new_transaction_items: - if (not entry.party or entry.party_type == "Account"): continue - entry.account = self.receivable_account if entry.party_type == "Customer" else self.payable_account - amount = abs(entry.amount) - payment, matching_invoices = None, [] - for inv_entry in self.payment_invoice_items: - if (inv_entry.payment_description != frappe.safe_decode(entry.description) or inv_entry.transaction_date != entry.transaction_date): continue - if (inv_entry.party != entry.party): continue - matching_invoices += [inv_entry.invoice_type + "|" + inv_entry.invoice] - payment = get_payments_matching_invoice(inv_entry.invoice, entry.amount, entry.transaction_date) - doc = frappe.get_doc(inv_entry.invoice_type, inv_entry.invoice) - inv_entry.invoice_date = doc.posting_date - inv_entry.outstanding_amount = doc.outstanding_amount - inv_entry.allocated_amount = min(float(doc.outstanding_amount), amount) - amount -= inv_entry.allocated_amount - if (amount < 0): break - - amount = abs(entry.amount) - if (payment is None): - order_doctype = "Sales Order" if entry.party_type=="Customer" else "Purchase Order" - from erpnext.controllers.accounts_controller import get_advance_payment_entries - payment_entries = get_advance_payment_entries(entry.party_type, entry.party, entry.account, order_doctype, against_all_orders=True) - payment_entries += self.get_matching_payments(entry.party, amount, entry.transaction_date) - payment = next((payment for payment in payment_entries if payment.amount == amount and payment not in added_payments), None) - if (payment is None): - print("Failed to find payments for {0}:{1}".format(entry.party, amount)) - continue - added_payments += [payment] - entry.reference_type = payment.reference_type - entry.reference_name = payment.reference_name - entry.mode_of_payment = "Wire Transfer" - entry.outstanding_amount = min(amount, 0) - if (entry.payment_reference is None): - entry.payment_reference = frappe.safe_decode(entry.description) - entry.invoices = ",".join(matching_invoices) - #print("Matching payment is {0}:{1}".format(entry.reference_type, entry.reference_name)) - - def get_matching_payments(self, party, amount, pay_date): - query = """select 'Payment Entry' as reference_type, name as reference_name, paid_amount as amount - from `tabPayment Entry` where party='{0}' and paid_amount={1} and posting_date='{2}' and docstatus != 2 - """.format(party, amount, pay_date) - matching_payments = frappe.db.sql(query, as_dict=True) - return matching_payments - - def map_unknown_transactions(self): - for entry in self.new_transaction_items: - if (entry.party): continue - inv_type = "Sales Invoice" if (entry.amount > 0) else "Purchase Invoice" - party_type = "customer" if (entry.amount > 0) else "supplier" - - query = """select posting_date, name, {0}, outstanding_amount - from `tab{1}` where ROUND(outstanding_amount)={2} and posting_date < '{3}' - """.format(party_type, inv_type, round(abs(entry.amount)), entry.transaction_date) - invoices = frappe.db.sql(query, as_dict = True) - if(len(invoices) > 0): - entry.party = invoices[0].get(party_type) - - def populate_matching_vouchers(self): - for entry in self.new_transaction_items: - if (not entry.party or entry.reference_name): continue - print("Finding matching voucher for {0}".format(frappe.safe_decode(entry.description))) - amount = abs(entry.amount) - invoices = [] - vouchers = get_matching_journal_entries(self.from_date, self.to_date, entry.party, self.bank_account, amount) - if len(vouchers) == 0: continue - for voucher in vouchers: - added = next((entry.invoice for entry in self.payment_invoice_items if entry.invoice == voucher.voucher_no), None) - if (added): - print("Found voucher {0}".format(added)) - continue - print("Adding voucher {0} {1} {2}".format(voucher.voucher_no, voucher.posting_date, voucher.debit)) - ent = self.append('payment_invoice_items', {}) - ent.invoice_date = voucher.posting_date - ent.invoice_type = "Journal Entry" - ent.invoice = voucher.voucher_no - ent.payment_description = frappe.safe_decode(entry.description) - ent.allocated_amount = max(voucher.debit, voucher.credit) - - invoices += [ent.invoice_type + "|" + ent.invoice] - entry.reference_type = "Journal Entry" - entry.mode_of_payment = "Wire Transfer" - entry.reference_name = ent.invoice - #entry.account = entry.party - entry.invoices = ",".join(invoices) - break - - - def create_payment_entries(self): - for payment_entry in self.new_transaction_items: - if (not payment_entry.party): continue - if (payment_entry.reference_name): continue - print("Creating payment entry for {0}".format(frappe.safe_decode(payment_entry.description))) - if (payment_entry.party_type == "Account"): - payment = self.create_journal_entry(payment_entry) - invoices = [payment.doctype + "|" + payment.name] - payment_entry.invoices = ",".join(invoices) - else: - payment = self.create_payment_entry(payment_entry) - invoices = [entry.reference_doctype + "|" + entry.reference_name for entry in payment.references if entry is not None] - payment_entry.invoices = ",".join(invoices) - payment_entry.mode_of_payment = payment.mode_of_payment - payment_entry.account = self.receivable_account if payment_entry.party_type == "Customer" else self.payable_account - payment_entry.reference_name = payment.name - payment_entry.reference_type = payment.doctype - frappe.msgprint(_("Successfully created payment entries")) - - def create_payment_entry(self, pe): - payment = frappe.new_doc("Payment Entry") - payment.posting_date = pe.transaction_date - payment.payment_type = "Receive" if pe.party_type == "Customer" else "Pay" - payment.mode_of_payment = "Wire Transfer" - payment.party_type = pe.party_type - payment.party = pe.party - payment.paid_to = self.bank_account if pe.party_type == "Customer" else self.payable_account - payment.paid_from = self.receivable_account if pe.party_type == "Customer" else self.bank_account - payment.paid_amount = payment.received_amount = abs(pe.amount) - payment.reference_no = pe.description - payment.reference_date = pe.transaction_date - payment.save() - for inv_entry in self.payment_invoice_items: - if (pe.description != inv_entry.payment_description or pe.transaction_date != inv_entry.transaction_date): continue - if (pe.party != inv_entry.party): continue - reference = payment.append("references", {}) - reference.reference_doctype = inv_entry.invoice_type - reference.reference_name = inv_entry.invoice - reference.allocated_amount = inv_entry.allocated_amount - print ("Adding invoice {0} {1}".format(reference.reference_name, reference.allocated_amount)) - payment.setup_party_account_field() - payment.set_missing_values() - #payment.set_exchange_rate() - #payment.set_amounts() - #print("Created payment entry {0}".format(payment.as_dict())) - payment.save() - return payment - - def create_journal_entry(self, pe): - je = frappe.new_doc("Journal Entry") - je.is_opening = "No" - je.voucher_type = "Bank Entry" - je.cheque_no = pe.description - je.cheque_date = pe.transaction_date - je.remark = pe.description - je.posting_date = pe.transaction_date - if (pe.amount < 0): - je.append("accounts", {"account": pe.party, "debit_in_account_currency": abs(pe.amount)}) - je.append("accounts", {"account": self.bank_account, "credit_in_account_currency": abs(pe.amount)}) - else: - je.append("accounts", {"account": pe.party, "credit_in_account_currency": pe.amount}) - je.append("accounts", {"account": self.bank_account, "debit_in_account_currency": pe.amount}) - je.save() - return je - - def update_payment_entry(self, payment): - lst = [] - invoices = payment.invoices.strip().split(',') - if (len(invoices) == 0): return - amount = float(abs(payment.amount)) - for invoice_entry in invoices: - if (not invoice_entry.strip()): continue - invs = invoice_entry.split('|') - invoice_type, invoice = invs[0], invs[1] - outstanding_amount = frappe.get_value(invoice_type, invoice, 'outstanding_amount') - - lst.append(frappe._dict({ - 'voucher_type': payment.reference_type, - 'voucher_no' : payment.reference_name, - 'against_voucher_type' : invoice_type, - 'against_voucher' : invoice, - 'account' : payment.account, - 'party_type': payment.party_type, - 'party': frappe.get_value("Payment Entry", payment.reference_name, "party"), - 'unadjusted_amount' : float(amount), - 'allocated_amount' : min(outstanding_amount, amount) - })) - amount -= outstanding_amount - if lst: - from erpnext.accounts.utils import reconcile_against_document - try: - reconcile_against_document(lst) - except: - frappe.throw(_("Exception occurred while reconciling {0}").format(payment.reference_name)) - - def submit_payment_entries(self): - for payment in self.new_transaction_items: - if payment.reference_name is None: continue - doc = frappe.get_doc(payment.reference_type, payment.reference_name) - if doc.docstatus == 1: - if (payment.reference_type == "Journal Entry"): continue - if doc.unallocated_amount == 0: continue - print("Reconciling payment {0}".format(payment.reference_name)) - self.update_payment_entry(payment) - else: - print("Submitting payment {0}".format(payment.reference_name)) - if (payment.reference_type == "Payment Entry"): - if (payment.payment_reference): - doc.reference_no = payment.payment_reference - doc.mode_of_payment = payment.mode_of_payment - doc.save() - doc.submit() - self.move_reconciled_entries() - self.populate_matching_invoices() - - def move_reconciled_entries(self): - idx = 0 - while idx < len(self.new_transaction_items): - entry = self.new_transaction_items[idx] - try: - print("Checking transaction {0}: {2} in {1} entries".format(idx, len(self.new_transaction_items), frappe.safe_decode(entry.description))) - except UnicodeEncodeError: - pass - idx += 1 - if entry.reference_name is None: continue - doc = frappe.get_doc(entry.reference_type, entry.reference_name) - if doc.docstatus == 1 and (entry.reference_type == "Journal Entry" or doc.unallocated_amount == 0): - self.remove(entry) - rc_entry = self.append('reconciled_transaction_items', {}) - dentry = entry.as_dict() - dentry.pop('idx', None) - rc_entry.update(dentry) - idx -= 1 - - -def get_matching_journal_entries(from_date, to_date, account, against, amount): - query = """select voucher_no, posting_date, account, against, debit_in_account_currency as debit, credit_in_account_currency as credit - from `tabGL Entry` - where posting_date between '{0}' and '{1}' and account = '{2}' and against = '{3}' and debit = '{4}' - """.format(from_date, to_date, account, against, amount) - jv_entries = frappe.db.sql(query, as_dict=True) - #print("voucher query:{0}\n Returned {1} entries".format(query, len(jv_entries))) - return jv_entries - -def get_payments_matching_invoice(invoice, amount, pay_date): - query = """select pe.name as reference_name, per.reference_doctype as reference_type, per.outstanding_amount, per.allocated_amount - from `tabPayment Entry Reference` as per JOIN `tabPayment Entry` as pe on pe.name = per.parent - where per.reference_name='{0}' and (posting_date='{1}' or reference_date='{1}') and pe.docstatus != 2 - """.format(invoice, pay_date) - payments = frappe.db.sql(query, as_dict=True) - if (len(payments) == 0): return - payment = next((payment for payment in payments if payment.allocated_amount == amount), payments[0]) - #Hack: Update the reference type which is set to invoice type - payment.reference_type = "Payment Entry" - return payment - -def is_headers_present(headers, row): - for header in headers: - if header not in row: - return False - return True - -def get_header_index(headers, row): - header_index = {} - for header in headers: - if header in row: - header_index[header] = row.index(header) - return header_index - -def get_transaction_info(headers, header_index, row): - transaction = {} - for header in headers: - transaction[header] = row[header_index[header]] - if (transaction[header] == None): - transaction[header] = "" - return transaction - -def get_transaction_entries(file_url, headers): - header_index = {} - rows, transactions = [], [] - - if (file_url.lower().endswith("xlsx")): - from frappe.utils.xlsxutils import read_xlsx_file_from_attached_file - rows = read_xlsx_file_from_attached_file(file_url=file_url) - elif (file_url.lower().endswith("csv")): - from frappe.utils.csvutils import read_csv_content - _file = frappe.get_doc("File", {"file_url": file_url}) - filepath = _file.get_full_path() - with open(filepath,'rb') as csvfile: - rows = read_csv_content(csvfile.read()) - elif (file_url.lower().endswith("xls")): - filename = file_url.split("/")[-1] - rows = get_rows_from_xls_file(filename) - else: - frappe.throw(_("Only .csv and .xlsx files are supported currently")) - - stmt_headers = headers.values() - for row in rows: - if len(row) == 0 or row[0] == None or not row[0]: continue - #print("Processing row {0}".format(row)) - if header_index: - transaction = get_transaction_info(stmt_headers, header_index, row) - transactions.append(transaction) - elif is_headers_present(stmt_headers, row): - header_index = get_header_index(stmt_headers, row) - return transactions - -def get_rows_from_xls_file(filename): - _file = frappe.get_doc("File", {"file_name": filename}) - filepath = _file.get_full_path() - import xlrd - book = xlrd.open_workbook(filepath) - sheets = book.sheets() - rows = [] - for row in range(1, sheets[0].nrows): - row_values = [] - for col in range(1, sheets[0].ncols): - row_values.append(sheets[0].cell_value(row, col)) - rows.append(row_values) - return rows diff --git a/erpnext/accounts/doctype/bank_statement_transaction_entry/test_bank_statement_transaction_entry.js b/erpnext/accounts/doctype/bank_statement_transaction_entry/test_bank_statement_transaction_entry.js deleted file mode 100644 index 46d570f515a..00000000000 --- a/erpnext/accounts/doctype/bank_statement_transaction_entry/test_bank_statement_transaction_entry.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Bank Statement Transaction Entry", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Bank Statement Transaction Entry - () => frappe.tests.make('Bank Statement Transaction Entry', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/accounts/doctype/bank_statement_transaction_entry/test_bank_statement_transaction_entry.py b/erpnext/accounts/doctype/bank_statement_transaction_entry/test_bank_statement_transaction_entry.py deleted file mode 100644 index 458948372fb..00000000000 --- a/erpnext/accounts/doctype/bank_statement_transaction_entry/test_bank_statement_transaction_entry.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2017, sathishpy@gmail.com and Contributors -# See license.txt -from __future__ import unicode_literals - -import frappe -import unittest - -class TestBankStatementTransactionEntry(unittest.TestCase): - pass diff --git a/erpnext/accounts/doctype/bank_statement_transaction_invoice_item/bank_statement_transaction_invoice_item.json b/erpnext/accounts/doctype/bank_statement_transaction_invoice_item/bank_statement_transaction_invoice_item.json deleted file mode 100644 index d96c94d8cac..00000000000 --- a/erpnext/accounts/doctype/bank_statement_transaction_invoice_item/bank_statement_transaction_invoice_item.json +++ /dev/null @@ -1,365 +0,0 @@ -{ - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2017-11-07 13:58:53.827058", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", - "fields": [ - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "transaction_date", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Transaction Date", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 4, - "fieldname": "payment_description", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Payment Description", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "party_type", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Party Type", - "length": 0, - "no_copy": 0, - "options": "Customer\nSupplier\nAccount", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "party", - "fieldtype": "Dynamic 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": "Party", - "length": 0, - "no_copy": 0, - "options": "party_type", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_4", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 2, - "fieldname": "invoice_date", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Invoice Date", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "invoice_type", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Invoice Type", - "length": 0, - "no_copy": 0, - "options": "Sales Invoice\nPurchase Invoice\nJournal Entry", - "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 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 2, - "fieldname": "invoice", - "fieldtype": "Dynamic 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": "invoice", - "length": 0, - "no_copy": 0, - "options": "invoice_type", - "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 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 1, - "fieldname": "outstanding_amount", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Outstanding Amount", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 1, - "fieldname": "allocated_amount", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Allocated Amount", - "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 - } - ], - "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-09-14 19:03:30.949831", - "modified_by": "Administrator", - "module": "Accounts", - "name": "Bank Statement Transaction Invoice Item", - "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 -} \ No newline at end of file diff --git a/erpnext/accounts/doctype/bank_statement_transaction_payment_item/bank_statement_transaction_payment_item.json b/erpnext/accounts/doctype/bank_statement_transaction_payment_item/bank_statement_transaction_payment_item.json deleted file mode 100644 index 177dccd82cd..00000000000 --- a/erpnext/accounts/doctype/bank_statement_transaction_payment_item/bank_statement_transaction_payment_item.json +++ /dev/null @@ -1,494 +0,0 @@ -{ - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2017-11-07 14:03:05.651413", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", - "fields": [ - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 1, - "fieldname": "transaction_date", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Transaction Date", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 4, - "fieldname": "description", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Description", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 1, - "fieldname": "amount", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Amount", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 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, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 1, - "fieldname": "party_type", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Party Type", - "length": 0, - "no_copy": 0, - "options": "Customer\nSupplier\nAccount", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 2, - "fieldname": "party", - "fieldtype": "Dynamic 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": "Party", - "length": 0, - "no_copy": 0, - "options": "party_type", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 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, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "reference_type", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Reference Type", - "length": 0, - "no_copy": 0, - "options": "Payment Entry\nJournal Entry", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "account", - "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": "Account", - "length": 0, - "no_copy": 0, - "options": "Account", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "mode_of_payment", - "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": "Mode of Payment", - "length": 0, - "no_copy": 0, - "options": "Mode of Payment", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "outstanding_amount", - "fieldtype": "Currency", - "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": "outstanding_amount", - "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, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_10", - "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, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 2, - "fieldname": "reference_name", - "fieldtype": "Dynamic 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": "Reference Name", - "length": 0, - "no_copy": 0, - "options": "reference_type", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "payment_reference", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Payment Reference", - "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, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "invoices", - "fieldtype": "Text", - "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": "Invoices", - "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, - "unique": 0 - } - ], - "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": "2017-11-15 19:18:52.876221", - "modified_by": "Administrator", - "module": "Accounts", - "name": "Bank Statement Transaction Payment Item", - "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 -} diff --git a/erpnext/accounts/doctype/bank_statement_transaction_settings/bank_statement_transaction_settings.js b/erpnext/accounts/doctype/bank_statement_transaction_settings/bank_statement_transaction_settings.js deleted file mode 100644 index 46aa4f20311..00000000000 --- a/erpnext/accounts/doctype/bank_statement_transaction_settings/bank_statement_transaction_settings.js +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) 2017, sathishpy@gmail.com and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Bank Statement Settings', { - refresh: function(frm) { - - } -}); diff --git a/erpnext/accounts/doctype/bank_statement_transaction_settings/bank_statement_transaction_settings.json b/erpnext/accounts/doctype/bank_statement_transaction_settings/bank_statement_transaction_settings.json deleted file mode 100644 index 474bb90db75..00000000000 --- a/erpnext/accounts/doctype/bank_statement_transaction_settings/bank_statement_transaction_settings.json +++ /dev/null @@ -1,266 +0,0 @@ -{ - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 1, - "beta": 0, - "creation": "2017-11-13 13:38:10.863592", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", - "fields": [ - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "bank_account", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Bank Account", - "length": 0, - "no_copy": 0, - "options": "Account", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "'%d/%m/%Y'", - "fieldname": "date_format", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Date Format", - "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, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "statement_header_mapping", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Statement Header Mapping", - "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, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "header_items", - "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Statement Headers", - "length": 0, - "no_copy": 0, - "options": "Bank Statement Settings Item", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "transaction_data_mapping", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Transaction Data Mapping", - "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, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "mapped_items", - "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Mapped Items", - "length": 0, - "no_copy": 0, - "options": "Bank Statement Transaction Settings Item", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2018-01-12 10:34:32.840487", - "modified_by": "Administrator", - "module": "Accounts", - "name": "Bank Statement Settings", - "name_case": "", - "owner": "Administrator", - "permissions": [ - { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - }, - { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Accounts Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - } - ], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0 -} \ No newline at end of file diff --git a/erpnext/accounts/doctype/bank_statement_transaction_settings/bank_statement_transaction_settings.py b/erpnext/accounts/doctype/bank_statement_transaction_settings/bank_statement_transaction_settings.py deleted file mode 100644 index de9a85fe5c6..00000000000 --- a/erpnext/accounts/doctype/bank_statement_transaction_settings/bank_statement_transaction_settings.py +++ /dev/null @@ -1,11 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2017, sathishpy@gmail.com and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals -import frappe -from frappe.model.document import Document - -class BankStatementSettings(Document): - def autoname(self): - self.name = self.bank_account + "-Mappings" diff --git a/erpnext/accounts/doctype/bank_statement_transaction_settings/test_bank_statement_transaction_settings.js b/erpnext/accounts/doctype/bank_statement_transaction_settings/test_bank_statement_transaction_settings.js deleted file mode 100644 index f2381c042ee..00000000000 --- a/erpnext/accounts/doctype/bank_statement_transaction_settings/test_bank_statement_transaction_settings.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Bank Statement Settings", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Bank Statement Settings - () => frappe.tests.make('Bank Statement Settings', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/accounts/doctype/bank_statement_transaction_settings/test_bank_statement_transaction_settings.py b/erpnext/accounts/doctype/bank_statement_transaction_settings/test_bank_statement_transaction_settings.py deleted file mode 100644 index aa7fe833285..00000000000 --- a/erpnext/accounts/doctype/bank_statement_transaction_settings/test_bank_statement_transaction_settings.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2017, sathishpy@gmail.com and Contributors -# See license.txt -from __future__ import unicode_literals - -import frappe -import unittest - -class TestBankStatementSettings(unittest.TestCase): - pass diff --git a/erpnext/accounts/doctype/bank_statement_transaction_settings_item/bank_statement_transaction_settings_item.json b/erpnext/accounts/doctype/bank_statement_transaction_settings_item/bank_statement_transaction_settings_item.json deleted file mode 100644 index 47c32097a9e..00000000000 --- a/erpnext/accounts/doctype/bank_statement_transaction_settings_item/bank_statement_transaction_settings_item.json +++ /dev/null @@ -1,166 +0,0 @@ -{ - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2017-11-13 13:42:00.335432", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", - "fields": [ - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Transaction", - "fieldname": "mapping_type", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Mapping Type", - "length": 0, - "no_copy": 0, - "options": "Transaction", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "bank_data", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Bank Data", - "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, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Account", - "fieldname": "mapped_data_type", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Mapped Data Type", - "length": 0, - "no_copy": 0, - "options": "Account\nCustomer\nSupplier\nAccount", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "mapped_data", - "fieldtype": "Dynamic 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": "Mapped Data", - "length": 0, - "no_copy": 0, - "options": "mapped_data_type", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2018-01-08 00:13:49.973501", - "modified_by": "Administrator", - "module": "Accounts", - "name": "Bank Statement Transaction Settings Item", - "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 -} \ No newline at end of file diff --git a/erpnext/accounts/doctype/bank_statement_transaction_settings_item/bank_statement_transaction_settings_item.py b/erpnext/accounts/doctype/bank_statement_transaction_settings_item/bank_statement_transaction_settings_item.py deleted file mode 100644 index bf0a590d484..00000000000 --- a/erpnext/accounts/doctype/bank_statement_transaction_settings_item/bank_statement_transaction_settings_item.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2017, sathishpy@gmail.com and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals -import frappe -from frappe.model.document import Document - -class BankStatementTransactionSettingsItem(Document): - pass diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction.js b/erpnext/accounts/doctype/bank_transaction/bank_transaction.js index 8b1bab16189..3758b524da5 100644 --- a/erpnext/accounts/doctype/bank_transaction/bank_transaction.js +++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction.js @@ -1,32 +1,70 @@ // Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -frappe.ui.form.on('Bank Transaction', { +frappe.ui.form.on("Bank Transaction", { onload(frm) { - frm.set_query('payment_document', 'payment_entries', function() { + frm.set_query("payment_document", "payment_entries", function () { return { - "filters": { - "name": ["in", ["Payment Entry", "Journal Entry", "Sales Invoice", "Purchase Invoice", "Expense Claim"]] - } + filters: { + name: [ + "in", + [ + "Payment Entry", + "Journal Entry", + "Sales Invoice", + "Purchase Invoice", + "Expense Claim", + ], + ], + }, }; }); - } + }, + bank_account: function (frm) { + set_bank_statement_filter(frm); + }, + + setup: function (frm) { + frm.set_query("party_type", function () { + return { + filters: { + name: ["in", Object.keys(frappe.boot.party_account_types)], + }, + }; + }); + }, }); -frappe.ui.form.on('Bank Transaction Payments', { - payment_entries_remove: function(frm, cdt, cdn) { +frappe.ui.form.on("Bank Transaction Payments", { + payment_entries_remove: function (frm, cdt, cdn) { update_clearance_date(frm, cdt, cdn); - } + }, }); const update_clearance_date = (frm, cdt, cdn) => { if (frm.doc.docstatus === 1) { - frappe.xcall('erpnext.accounts.doctype.bank_transaction.bank_transaction.unclear_reference_payment', - {doctype: cdt, docname: cdn}) - .then(e => { + frappe + .xcall( + "erpnext.accounts.doctype.bank_transaction.bank_transaction.unclear_reference_payment", + { doctype: cdt, docname: cdn } + ) + .then((e) => { if (e == "success") { - frappe.show_alert({message:__("Document {0} successfully uncleared", [e]), indicator:'green'}); + frappe.show_alert({ + message: __("Document {0} successfully uncleared", [e]), + indicator: "green", + }); } }); } -}; \ No newline at end of file +}; + +function set_bank_statement_filter(frm) { + frm.set_query("bank_statement", function () { + return { + filters: { + bank_account: frm.doc.bank_account, + }, + }; + }); +} diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction.json b/erpnext/accounts/doctype/bank_transaction/bank_transaction.json index 39937bb3645..69ee4971cd5 100644 --- a/erpnext/accounts/doctype/bank_transaction/bank_transaction.json +++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction.json @@ -1,833 +1,245 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, + "actions": [], "allow_import": 1, - "allow_rename": 0, "autoname": "naming_series:", - "beta": 0, "creation": "2018-10-22 18:19:02.784533", - "custom": 0, - "docstatus": 0, "doctype": "DocType", - "document_type": "", "editable_grid": 1, "engine": "InnoDB", + "field_order": [ + "naming_series", + "date", + "column_break_2", + "status", + "bank_account", + "company", + "section_break_4", + "deposit", + "withdrawal", + "column_break_7", + "currency", + "section_break_10", + "description", + "section_break_14", + "reference_number", + "transaction_id", + "payment_entries", + "section_break_18", + "allocated_amount", + "amended_from", + "column_break_17", + "unallocated_amount", + "party_section", + "party_type", + "party" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "default": "ACC-BTN-.YYYY.-", - "fetch_if_empty": 0, "fieldname": "naming_series", "fieldtype": "Select", "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": "Series", - "length": 0, "no_copy": 1, "options": "ACC-BTN-.YYYY.-", - "permlevel": 0, - "precision": "", "print_hide": 1, - "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": 1, - "translatable": 0, - "unique": 0 + "set_only_once": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "date", "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Date", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Date" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "column_break_2", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "default": "Pending", - "fetch_if_empty": 0, "fieldname": "status", "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, "in_standard_filter": 1, "label": "Status", - "length": 0, - "no_copy": 0, - "options": "\nPending\nSettled\nUnreconciled\nReconciled", - "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 + "options": "\nPending\nSettled\nUnreconciled\nReconciled" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "bank_account", "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": 1, "label": "Bank Account", - "length": 0, - "no_copy": 0, - "options": "Bank Account", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "Bank Account" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "", "fetch_from": "bank_account.company", - "fetch_if_empty": 0, "fieldname": "company", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, "in_standard_filter": 1, "label": "Company", - "length": 0, - "no_copy": 0, "options": "Company", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "section_break_4", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldtype": "Section Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "debit", - "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": "Debit", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "credit", - "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": "Credit", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "column_break_7", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "currency", "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": "Currency", - "length": 0, - "no_copy": 0, - "options": "Currency", - "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 + "options": "Currency" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "section_break_10", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldtype": "Section Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "description", "fieldtype": "Small Text", - "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": "Description", - "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 + "label": "Description" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "section_break_14", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldtype": "Section Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, + "allow_on_submit": 1, "fieldname": "reference_number", "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Reference Number", - "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 + "label": "Reference Number" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "transaction_id", "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Transaction ID", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, "unique": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "payment_entries", "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Payment Entries", - "length": 0, - "no_copy": 0, - "options": "Bank Transaction Payments", - "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 + "options": "Bank Transaction Payments" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "section_break_18", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldtype": "Section Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "allocated_amount", "fieldtype": "Currency", - "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": "Allocated Amount", - "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 + "label": "Allocated Amount" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "amended_from", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Amended From", - "length": 0, "no_copy": 1, "options": "Bank Transaction", - "permlevel": 0, "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "column_break_17", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "fetch_if_empty": 0, "fieldname": "unallocated_amount", "fieldtype": "Currency", - "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": "Unallocated Amount", - "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 + "label": "Unallocated Amount" + }, + { + "fieldname": "party_section", + "fieldtype": "Section Break", + "label": "Payment From / To" + }, + { + "allow_on_submit": 1, + "fieldname": "party_type", + "fieldtype": "Link", + "label": "Party Type", + "options": "DocType" + }, + { + "allow_on_submit": 1, + "fieldname": "party", + "fieldtype": "Dynamic Link", + "label": "Party", + "options": "party_type" + }, + { + "fieldname": "deposit", + "oldfieldname": "debit", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Deposit" + }, + { + "fieldname": "withdrawal", + "oldfieldname": "credit", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Withdrawal" } ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, "is_submittable": 1, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2019-05-11 05:27:55.244721", + "links": [], + "modified": "2020-12-30 19:40:54.221070", "modified_by": "Administrator", "module": "Accounts", "name": "Bank Transaction", - "name_case": "", "owner": "Administrator", "permissions": [ { - "amend": 0, "cancel": 1, "create": 1, "delete": 1, "email": 1, "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, "print": 1, "read": 1, "report": 1, "role": "System Manager", - "set_user_permissions": 0, "share": 1, "submit": 1, "write": 1 }, { - "amend": 0, "cancel": 1, "create": 1, "delete": 1, "email": 1, "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, "print": 1, "read": 1, "report": 1, "role": "Accounts Manager", - "set_user_permissions": 0, "share": 1, "submit": 1, "write": 1 }, { - "amend": 0, - "cancel": 0, "create": 1, "delete": 1, "email": 1, "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, "print": 1, "read": 1, "report": 1, "role": "Accounts User", - "set_user_permissions": 0, "share": 1, "submit": 1, "write": 1 } ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, "sort_field": "date", "sort_order": "DESC", "title_field": "bank_account", - "track_changes": 0, - "track_seen": 0, - "track_views": 0 + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py index 0e45db3dbc0..5246baa02b3 100644 --- a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py +++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py @@ -11,7 +11,7 @@ from frappe import _ class BankTransaction(StatusUpdater): def after_insert(self): - self.unallocated_amount = abs(flt(self.credit) - flt(self.debit)) + self.unallocated_amount = abs(flt(self.withdrawal) - flt(self.deposit)) def on_submit(self): self.clear_linked_payment_entries() @@ -30,13 +30,13 @@ class BankTransaction(StatusUpdater): if allocated_amount: frappe.db.set_value(self.doctype, self.name, "allocated_amount", flt(allocated_amount)) - frappe.db.set_value(self.doctype, self.name, "unallocated_amount", abs(flt(self.credit) - flt(self.debit)) - flt(allocated_amount)) + frappe.db.set_value(self.doctype, self.name, "unallocated_amount", abs(flt(self.withdrawal) - flt(self.deposit)) - flt(allocated_amount)) else: frappe.db.set_value(self.doctype, self.name, "allocated_amount", 0) - frappe.db.set_value(self.doctype, self.name, "unallocated_amount", abs(flt(self.credit) - flt(self.debit))) + frappe.db.set_value(self.doctype, self.name, "unallocated_amount", abs(flt(self.withdrawal) - flt(self.deposit))) - amount = self.debit or self.credit + amount = self.deposit or self.withdrawal if amount == self.allocated_amount: frappe.db.set_value(self.doctype, self.name, "status", "Reconciled") @@ -44,18 +44,11 @@ class BankTransaction(StatusUpdater): def clear_linked_payment_entries(self): for payment_entry in self.payment_entries: - allocated_amount = get_total_allocated_amount(payment_entry) - paid_amount = get_paid_amount(payment_entry, self.currency) + if payment_entry.payment_document in ["Payment Entry", "Journal Entry", "Purchase Invoice", "Expense Claim"]: + self.clear_simple_entry(payment_entry) - if paid_amount and allocated_amount: - if flt(allocated_amount[0]["allocated_amount"]) > flt(paid_amount): - frappe.throw(_("The total allocated amount ({0}) is greated than the paid amount ({1}).").format(flt(allocated_amount[0]["allocated_amount"]), flt(paid_amount))) - else: - if payment_entry.payment_document in ["Payment Entry", "Journal Entry", "Purchase Invoice", "Expense Claim"]: - self.clear_simple_entry(payment_entry) - - elif payment_entry.payment_document == "Sales Invoice": - self.clear_sales_invoice(payment_entry) + elif payment_entry.payment_document == "Sales Invoice": + self.clear_sales_invoice(payment_entry) def clear_simple_entry(self, payment_entry): frappe.db.set_value(payment_entry.payment_document, payment_entry.payment_entry, "clearance_date", self.date) @@ -112,3 +105,4 @@ def unclear_reference_payment(doctype, docname): frappe.db.set_value(doc.payment_document, doc.payment_entry, "clearance_date", None) return doc.payment_entry + diff --git a/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py b/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py index e9fc5f0a1d6..3b14e4efa02 100644 --- a/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py +++ b/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py @@ -5,10 +5,11 @@ from __future__ import unicode_literals import frappe import unittest +import json from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry -from erpnext.accounts.page.bank_reconciliation.bank_reconciliation import reconcile, get_linked_payments +from erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool import reconcile_vouchers, get_linked_payments from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile test_dependencies = ["Item", "Cost Center"] @@ -17,7 +18,7 @@ class TestBankTransaction(unittest.TestCase): def setUp(self): make_pos_profile() add_transactions() - add_payments() + add_vouchers() def tearDown(self): for bt in frappe.get_all("Bank Transaction"): @@ -38,14 +39,18 @@ class TestBankTransaction(unittest.TestCase): # This test checks if ERPNext is able to provide a linked payment for a bank transaction based on the amount of the bank transaction. def test_linked_payments(self): bank_transaction = frappe.get_doc("Bank Transaction", dict(description="Re 95282925234 FE/000002917 AT171513000281183046 Conrad Electronic")) - linked_payments = get_linked_payments(bank_transaction.name) - self.assertTrue(linked_payments[0].party == "Conrad Electronic") + linked_payments = get_linked_payments(bank_transaction.name, ['payment_entry', 'exact_match']) + self.assertTrue(linked_payments[0][6] == "Conrad Electronic") # This test validates a simple reconciliation leading to the clearance of the bank transaction and the payment def test_reconcile(self): bank_transaction = frappe.get_doc("Bank Transaction", dict(description="1512567 BG/000002918 OPSKATTUZWXXX AT776000000098709837 Herr G")) payment = frappe.get_doc("Payment Entry", dict(party="Mr G", paid_amount=1200)) - reconcile(bank_transaction.name, "Payment Entry", payment.name) + vouchers = json.dumps([{ + "payment_doctype":"Payment Entry", + "payment_name":payment.name, + "amount":bank_transaction.unallocated_amount}]) + reconcile_vouchers(bank_transaction.name, vouchers) unallocated_amount = frappe.db.get_value("Bank Transaction", bank_transaction.name, "unallocated_amount") self.assertTrue(unallocated_amount == 0) @@ -53,45 +58,40 @@ class TestBankTransaction(unittest.TestCase): clearance_date = frappe.db.get_value("Payment Entry", payment.name, "clearance_date") self.assertTrue(clearance_date is not None) - # Check if ERPNext can correctly fetch a linked payment based on the party - def test_linked_payments_based_on_party(self): - bank_transaction = frappe.get_doc("Bank Transaction", dict(description="1512567 BG/000003025 OPSKATTUZWXXX AT776000000098709849 Herr G")) - linked_payments = get_linked_payments(bank_transaction.name) - self.assertTrue(len(linked_payments)==1) - # Check if ERPNext can correctly filter a linked payments based on the debit/credit amount def test_debit_credit_output(self): bank_transaction = frappe.get_doc("Bank Transaction", dict(description="Auszahlung Karte MC/000002916 AUTOMAT 698769 K002 27.10. 14:07")) - linked_payments = get_linked_payments(bank_transaction.name) - self.assertTrue(linked_payments[0].payment_type == "Pay") + linked_payments = get_linked_payments(bank_transaction.name, ['payment_entry', 'exact_match']) + print(linked_payments) + self.assertTrue(linked_payments[0][3]) # Check error if already reconciled def test_already_reconciled(self): bank_transaction = frappe.get_doc("Bank Transaction", dict(description="1512567 BG/000002918 OPSKATTUZWXXX AT776000000098709837 Herr G")) payment = frappe.get_doc("Payment Entry", dict(party="Mr G", paid_amount=1200)) - reconcile(bank_transaction.name, "Payment Entry", payment.name) + vouchers = json.dumps([{ + "payment_doctype":"Payment Entry", + "payment_name":payment.name, + "amount":bank_transaction.unallocated_amount}]) + reconcile_vouchers(bank_transaction.name, vouchers) bank_transaction = frappe.get_doc("Bank Transaction", dict(description="1512567 BG/000002918 OPSKATTUZWXXX AT776000000098709837 Herr G")) payment = frappe.get_doc("Payment Entry", dict(party="Mr G", paid_amount=1200)) - self.assertRaises(frappe.ValidationError, reconcile, bank_transaction=bank_transaction.name, payment_doctype="Payment Entry", payment_name=payment.name) - - # Raise an error if creditor transaction vs creditor payment - def test_invalid_creditor_reconcilation(self): - bank_transaction = frappe.get_doc("Bank Transaction", dict(description="I2015000011 VD/000002514 ATWWXXX AT4701345000003510057 Bio")) - payment = frappe.get_doc("Payment Entry", dict(party="Conrad Electronic", paid_amount=690)) - self.assertRaises(frappe.ValidationError, reconcile, bank_transaction=bank_transaction.name, payment_doctype="Payment Entry", payment_name=payment.name) - - # Raise an error if debitor transaction vs debitor payment - def test_invalid_debitor_reconcilation(self): - bank_transaction = frappe.get_doc("Bank Transaction", dict(description="Auszahlung Karte MC/000002916 AUTOMAT 698769 K002 27.10. 14:07")) - payment = frappe.get_doc("Payment Entry", dict(party="Fayva", paid_amount=109080)) - self.assertRaises(frappe.ValidationError, reconcile, bank_transaction=bank_transaction.name, payment_doctype="Payment Entry", payment_name=payment.name) + vouchers = json.dumps([{ + "payment_doctype":"Payment Entry", + "payment_name":payment.name, + "amount":bank_transaction.unallocated_amount}]) + self.assertRaises(frappe.ValidationError, reconcile_vouchers, bank_transaction_name=bank_transaction.name, vouchers=vouchers) # Raise an error if debitor transaction vs debitor payment def test_clear_sales_invoice(self): bank_transaction = frappe.get_doc("Bank Transaction", dict(description="I2015000011 VD/000002514 ATWWXXX AT4701345000003510057 Bio")) payment = frappe.get_doc("Sales Invoice", dict(customer="Fayva", status=["=", "Paid"])) - reconcile(bank_transaction.name, "Sales Invoice", payment.name) + vouchers = json.dumps([{ + "payment_doctype":"Sales Invoice", + "payment_name":payment.name, + "amount":bank_transaction.unallocated_amount}]) + reconcile_vouchers(bank_transaction.name, vouchers=vouchers) self.assertEqual(frappe.db.get_value("Bank Transaction", bank_transaction.name, "unallocated_amount"), 0) self.assertTrue(frappe.db.get_value("Sales Invoice Payment", dict(parent=payment.name), "clearance_date") is not None) @@ -126,7 +126,7 @@ def add_transactions(): "doctype": "Bank Transaction", "description":"1512567 BG/000002918 OPSKATTUZWXXX AT776000000098709837 Herr G", "date": "2018-10-23", - "debit": 1200, + "deposit": 1200, "currency": "INR", "bank_account": "Checking Account - Citi Bank" }).insert() @@ -136,7 +136,7 @@ def add_transactions(): "doctype": "Bank Transaction", "description":"1512567 BG/000003025 OPSKATTUZWXXX AT776000000098709849 Herr G", "date": "2018-10-23", - "debit": 1700, + "deposit": 1700, "currency": "INR", "bank_account": "Checking Account - Citi Bank" }).insert() @@ -146,7 +146,7 @@ def add_transactions(): "doctype": "Bank Transaction", "description":"Re 95282925234 FE/000002917 AT171513000281183046 Conrad Electronic", "date": "2018-10-26", - "debit": 690, + "withdrawal": 690, "currency": "INR", "bank_account": "Checking Account - Citi Bank" }).insert() @@ -156,7 +156,7 @@ def add_transactions(): "doctype": "Bank Transaction", "description":"Auszahlung Karte MC/000002916 AUTOMAT 698769 K002 27.10. 14:07", "date": "2018-10-27", - "debit": 3900, + "deposit": 3900, "currency": "INR", "bank_account": "Checking Account - Citi Bank" }).insert() @@ -166,7 +166,7 @@ def add_transactions(): "doctype": "Bank Transaction", "description":"I2015000011 VD/000002514 ATWWXXX AT4701345000003510057 Bio", "date": "2018-10-27", - "credit": 109080, + "withdrawal": 109080, "currency": "INR", "bank_account": "Checking Account - Citi Bank" }).insert() @@ -174,7 +174,7 @@ def add_transactions(): frappe.flags.test_bank_transactions_created = True -def add_payments(): +def add_vouchers(): if frappe.flags.test_payments_created: return @@ -192,6 +192,7 @@ def add_payments(): pass pi = make_purchase_invoice(supplier="Conrad Electronic", qty=1, rate=690) + pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC") pe.reference_no = "Conrad Oct 18" pe.reference_date = "2018-10-24" @@ -242,10 +243,15 @@ def add_payments(): except frappe.DuplicateEntryError: pass - pi = make_purchase_invoice(supplier="Poore Simon's", qty=1, rate=3900) + pi = make_purchase_invoice(supplier="Poore Simon's", qty=1, rate=3900, is_paid=1, do_not_save =1) + pi.cash_bank_account = "_Test Bank - _TC" + pi.insert() + pi.submit() pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC") pe.reference_no = "Poore Simon's Oct 18" pe.reference_date = "2018-10-28" + pe.paid_amount = 690 + pe.received_amount = 690 pe.insert() pe.submit() @@ -295,4 +301,4 @@ def add_payments(): si.save() si.submit() - frappe.flags.test_payments_created = True \ No newline at end of file + frappe.flags.test_payments_created = True 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 342f21b93a3..03c3eb0ac0b 100644 --- a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py +++ b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py @@ -22,9 +22,10 @@ def validate_company(company): 'allow_account_creation_against_child_company']) if parent_company and (not allow_account_creation_against_child_company): - frappe.throw(_("""{0} is a child company. Please import accounts against parent company - or enable {1} in company master""").format(frappe.bold(company), - frappe.bold('Allow Account Creation Against Child Company')), title='Wrong Company') + msg = _("{} is a child company. ").format(frappe.bold(company)) + msg += _("Please import accounts against parent company or enable {} in company master.").format( + frappe.bold('Allow Account Creation Against Child Company')) + frappe.throw(msg, title=_('Wrong Company')) if frappe.db.get_all('GL Entry', {"company": company}, "name", limit=1): return False @@ -74,7 +75,9 @@ def generate_data_from_csv(file_doc, as_dict=False): if as_dict: data.append({frappe.scrub(header): row[index] for index, header in enumerate(headers)}) else: - if not row[1]: row[1] = row[0] + if not row[1]: + row[1] = row[0] + row[3] = row[2] data.append(row) # convert csv data @@ -96,7 +99,9 @@ def generate_data_from_excel(file_doc, extension, as_dict=False): if as_dict: data.append({frappe.scrub(header): row[index] for index, header in enumerate(headers)}) else: - if not row[1]: row[1] = row[0] + if not row[1]: + row[1] = row[0] + row[3] = row[2] data.append(row) return data @@ -147,7 +152,13 @@ def build_forest(data): from frappe import _ for row in data: - account_name, parent_account = row[0:2] + account_name, parent_account, account_number, parent_account_number = row[0:4] + if account_number: + account_name = "{} - {}".format(account_number, account_name) + if parent_account_number: + parent_account_number = cstr(parent_account_number).strip() + parent_account = "{} - {}".format(parent_account_number, parent_account) + if parent_account == account_name == child: return [parent_account] elif account_name == child: @@ -159,20 +170,23 @@ def build_forest(data): charts_map, paths = {}, [] - line_no = 3 + line_no = 2 error_messages = [] for i in data: - account_name, dummy, account_number, is_group, account_type, root_type = i + account_name, parent_account, account_number, parent_account_number, is_group, account_type, root_type = i if not account_name: error_messages.append("Row {0}: Please enter Account Name".format(line_no)) + if account_number: + account_number = cstr(account_number).strip() + account_name = "{} - {}".format(account_number, account_name) + charts_map[account_name] = {} if cint(is_group) == 1: charts_map[account_name]["is_group"] = is_group if account_type: charts_map[account_name]["account_type"] = account_type if root_type: charts_map[account_name]["root_type"] = root_type - if account_number: charts_map[account_name]["account_number"] = account_number path = return_parent(data, account_name)[::-1] paths.append(path) # List of path is created line_no += 1 @@ -221,7 +235,7 @@ def download_template(file_type, template_type): def get_template(template_type): - fields = ["Account Name", "Parent Account", "Account Number", "Is Group", "Account Type", "Root Type"] + fields = ["Account Name", "Parent Account", "Account Number", "Parent Account Number", "Is Group", "Account Type", "Root Type"] writer = UnicodeWriter() writer.writerow(fields) @@ -241,23 +255,23 @@ def get_template(template_type): def get_sample_template(writer): template = [ - ["Application Of Funds(Assets)", "", "", 1, "", "Asset"], - ["Sources Of Funds(Liabilities)", "", "", 1, "", "Liability"], - ["Equity", "", "", 1, "", "Equity"], - ["Expenses", "", "", 1, "", "Expense"], - ["Income", "", "", 1, "", "Income"], - ["Bank Accounts", "Application Of Funds(Assets)", "", 1, "Bank", "Asset"], - ["Cash In Hand", "Application Of Funds(Assets)", "", 1, "Cash", "Asset"], - ["Stock Assets", "Application Of Funds(Assets)", "", 1, "Stock", "Asset"], - ["Cost Of Goods Sold", "Expenses", "", 0, "Cost of Goods Sold", "Expense"], - ["Asset Depreciation", "Expenses", "", 0, "Depreciation", "Expense"], - ["Fixed Assets", "Application Of Funds(Assets)", "", 0, "Fixed Asset", "Asset"], - ["Accounts Payable", "Sources Of Funds(Liabilities)", "", 0, "Payable", "Liability"], - ["Accounts Receivable", "Application Of Funds(Assets)", "", 1, "Receivable", "Asset"], - ["Stock Expenses", "Expenses", "", 0, "Stock Adjustment", "Expense"], - ["Sample Bank", "Bank Accounts", "", 0, "Bank", "Asset"], - ["Cash", "Cash In Hand", "", 0, "Cash", "Asset"], - ["Stores", "Stock Assets", "", 0, "Stock", "Asset"], + ["Application Of Funds(Assets)", "", "", "", 1, "", "Asset"], + ["Sources Of Funds(Liabilities)", "", "", "", 1, "", "Liability"], + ["Equity", "", "", "", 1, "", "Equity"], + ["Expenses", "", "", "", 1, "", "Expense"], + ["Income", "", "", "", 1, "", "Income"], + ["Bank Accounts", "Application Of Funds(Assets)", "", "", 1, "Bank", "Asset"], + ["Cash In Hand", "Application Of Funds(Assets)", "", "", 1, "Cash", "Asset"], + ["Stock Assets", "Application Of Funds(Assets)", "", "", 1, "Stock", "Asset"], + ["Cost Of Goods Sold", "Expenses", "", "", 0, "Cost of Goods Sold", "Expense"], + ["Asset Depreciation", "Expenses", "", "", 0, "Depreciation", "Expense"], + ["Fixed Assets", "Application Of Funds(Assets)", "", "", 0, "Fixed Asset", "Asset"], + ["Accounts Payable", "Sources Of Funds(Liabilities)", "", "", 0, "Payable", "Liability"], + ["Accounts Receivable", "Application Of Funds(Assets)", "", "", 1, "Receivable", "Asset"], + ["Stock Expenses", "Expenses", "", "", 0, "Stock Adjustment", "Expense"], + ["Sample Bank", "Bank Accounts", "", "", 0, "Bank", "Asset"], + ["Cash", "Cash In Hand", "", "", 0, "Cash", "Asset"], + ["Stores", "Stock Assets", "", "", 0, "Stock", "Asset"], ] for row in template: diff --git a/erpnext/accounts/doctype/item_tax_template/item_tax_template.json b/erpnext/accounts/doctype/item_tax_template/item_tax_template.json index 8915f79b926..77c9e95b759 100644 --- a/erpnext/accounts/doctype/item_tax_template/item_tax_template.json +++ b/erpnext/accounts/doctype/item_tax_template/item_tax_template.json @@ -1,7 +1,7 @@ { + "actions": [], "allow_import": 1, "allow_rename": 1, - "autoname": "field:title", "creation": "2018-11-22 22:45:00.370913", "doctype": "DocType", "document_type": "Setup", @@ -20,8 +20,7 @@ "in_list_view": 1, "label": "Title", "no_copy": 1, - "reqd": 1, - "unique": 1 + "reqd": 1 }, { "fieldname": "taxes", @@ -33,12 +32,14 @@ { "fieldname": "company", "fieldtype": "Link", + "in_list_view": 1, "label": "Company", "options": "Company", "reqd": 1 } ], - "modified": "2020-09-18 17:26:09.703215", + "links": [], + "modified": "2021-03-08 19:50:21.416513", "modified_by": "Administrator", "module": "Accounts", "name": "Item Tax Template", @@ -81,5 +82,6 @@ "show_name_in_global_search": 1, "sort_field": "modified", "sort_order": "DESC", + "title_field": "title", "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/accounts/doctype/item_tax_template/item_tax_template.py b/erpnext/accounts/doctype/item_tax_template/item_tax_template.py index e77481d44f5..d9155cbab4a 100644 --- a/erpnext/accounts/doctype/item_tax_template/item_tax_template.py +++ b/erpnext/accounts/doctype/item_tax_template/item_tax_template.py @@ -11,6 +11,11 @@ class ItemTaxTemplate(Document): def validate(self): self.validate_tax_accounts() + def autoname(self): + if self.company and self.title: + abbr = frappe.get_cached_value('Company', self.company, 'abbr') + self.name = '{0} - {1}'.format(self.title, abbr) + def validate_tax_accounts(self): """Check whether Tax Rate is not entered twice for same Tax Type""" check_list = [] diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index cb90f8036e2..3419bb6c3e9 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -102,7 +102,7 @@ class JournalEntry(AccountsController): if account_currency == previous_account_currency: if self.total_credit != doc.total_debit or self.total_debit != doc.total_credit: frappe.throw(_("Total Credit/ Debit Amount should be same as linked Journal Entry")) - + def validate_stock_accounts(self): stock_accounts = get_stock_accounts(self.company, self.doctype, self.name) for account in stock_accounts: @@ -229,11 +229,11 @@ class JournalEntry(AccountsController): if d.reference_type=="Journal Entry": account_root_type = frappe.db.get_value("Account", d.account, "root_type") if account_root_type == "Asset" and flt(d.debit) > 0: - frappe.throw(_("For {0}, only credit accounts can be linked against another debit entry") - .format(d.account)) + frappe.throw(_("Row #{0}: For {1}, you can select reference document only if account gets credited") + .format(d.idx, d.account)) elif account_root_type == "Liability" and flt(d.credit) > 0: - frappe.throw(_("For {0}, only debit accounts can be linked against another credit entry") - .format(d.account)) + frappe.throw(_("Row #{0}: For {1}, you can select reference document only if account gets debited") + .format(d.idx, d.account)) if d.reference_name == self.name: frappe.throw(_("You can not enter current voucher in 'Against Journal Entry' column")) @@ -1077,4 +1077,4 @@ def make_reverse_journal_entry(source_name, target_doc=None): }, }, target_doc) - return doclist \ No newline at end of file + return doclist diff --git a/erpnext/accounts/doctype/mode_of_payment/mode_of_payment.py b/erpnext/accounts/doctype/mode_of_payment/mode_of_payment.py index d54a47e3c96..32473694c80 100644 --- a/erpnext/accounts/doctype/mode_of_payment/mode_of_payment.py +++ b/erpnext/accounts/doctype/mode_of_payment/mode_of_payment.py @@ -12,7 +12,7 @@ class ModeofPayment(Document): self.validate_accounts() self.validate_repeating_companies() self.validate_pos_mode_of_payment() - + def validate_repeating_companies(self): """Error when Same Company is entered multiple times in accounts""" accounts_list = [] @@ -31,10 +31,10 @@ class ModeofPayment(Document): def validate_pos_mode_of_payment(self): if not self.enabled: - pos_profiles = frappe.db.sql("""SELECT sip.parent FROM `tabSales Invoice Payment` sip + pos_profiles = frappe.db.sql("""SELECT sip.parent FROM `tabSales Invoice Payment` sip WHERE sip.parenttype = 'POS Profile' and sip.mode_of_payment = %s""", (self.name)) pos_profiles = list(map(lambda x: x[0], pos_profiles)) - + if pos_profiles: message = "POS Profile " + frappe.bold(", ".join(pos_profiles)) + " contains \ Mode of Payment " + frappe.bold(str(self.name)) + ". Please remove them to disable this mode." diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.js b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.js index c087980798c..b2e86267c8f 100644 --- a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.js +++ b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.js @@ -121,7 +121,8 @@ frappe.ui.form.on('Opening Invoice Creation Tool', { frappe.render_template('opening_invoice_creation_tool_dashboard', { data: opening_invoices_summary, max_count: max_count - }) + }), + __("Opening Invoices Summary") ); section.on('click', '.invoice-link', function() { diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py index ee2092adcc8..e6449b78316 100644 --- a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py +++ b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py @@ -64,11 +64,11 @@ class OpeningInvoiceCreationTool(Document): prepare_invoice_summary(doctype, invoices) return invoices_summary, max_count - + def validate_company(self): if not self.company: frappe.throw(_("Please select the Company")) - + def set_missing_values(self, row): row.qty = row.qty or 1.0 row.temporary_opening_account = row.temporary_opening_account or get_temporary_opening_account(self.company) @@ -198,6 +198,7 @@ def start_import(invoices): try: publish(idx, len(invoices), d.doctype) doc = frappe.get_doc(d) + doc.flags.ignore_mandatory = True doc.insert() doc.submit() frappe.db.commit() @@ -210,7 +211,7 @@ def start_import(invoices): frappe.db.commit() if errors: frappe.msgprint(_("You had {} errors while creating opening invoices. Check {} for more details") - .format(errors, "Error Log"), indicator="red", title=_("Error Occured")) + .format(errors, "Error Log"), indicator="red", title=_("Error Occured")) return names def publish(index, total, doctype): diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool_dashboard.html b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool_dashboard.html index 5b136d4f666..afbcfa5602a 100644 --- a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool_dashboard.html +++ b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool_dashboard.html @@ -1,4 +1,3 @@ -
    {{ __("Opening Invoices Summary") }}
    {% $.each(data, (company, summary) => { %}
    {{ company }}
    diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.json b/erpnext/accounts/doctype/payment_entry/payment_entry.json index 2e1f201e253..328584a61a0 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.json +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.json @@ -536,7 +536,8 @@ "fieldtype": "Data", "hidden": 1, "label": "Title", - "print_hide": 1 + "print_hide": 1, + "read_only": 1 }, { "depends_on": "party", @@ -588,7 +589,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2020-10-30 13:56:20.007336", + "modified": "2021-03-08 13:05:16.958866", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Entry", @@ -632,4 +633,4 @@ "sort_order": "DESC", "title_field": "title", "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 203d06a41f5..8acd92cb6b5 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -244,7 +244,7 @@ class PaymentEntry(AccountsController): elif self.party_type == "Supplier": valid_reference_doctypes = ("Purchase Order", "Purchase Invoice", "Journal Entry") elif self.party_type == "Employee": - valid_reference_doctypes = ("Expense Claim", "Journal Entry", "Employee Advance") + valid_reference_doctypes = ("Expense Claim", "Journal Entry", "Employee Advance", "Gratuity") elif self.party_type == "Shareholder": valid_reference_doctypes = ("Journal Entry") elif self.party_type == "Donor": @@ -459,6 +459,10 @@ class PaymentEntry(AccountsController): .format(total_negative_outstanding), InvalidPaymentEntry) def set_title(self): + if frappe.flags.in_import and self.title: + # do not set title dynamically if title exists during data import. + return + if self.payment_type in ("Receive", "Pay"): self.title = self.party else: @@ -608,7 +612,7 @@ class PaymentEntry(AccountsController): if self.payment_type in ("Receive", "Pay") and self.party: for d in self.get("references"): if d.allocated_amount \ - and d.reference_doctype in ("Sales Order", "Purchase Order", "Employee Advance"): + and d.reference_doctype in ("Sales Order", "Purchase Order", "Employee Advance", "Gratuity"): frappe.get_doc(d.reference_doctype, d.reference_name).set_total_advance_paid() def update_expense_claim(self): @@ -946,6 +950,8 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre exchange_rate = ref_doc.get("exchange_rate") if party_account_currency != ref_doc.currency: total_amount = flt(total_amount) * flt(exchange_rate) + elif ref_doc.doctype == "Gratuity": + total_amount = ref_doc.amount if not total_amount: if party_account_currency == company_currency: total_amount = ref_doc.base_grand_total @@ -969,6 +975,8 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre outstanding_amount = flt(outstanding_amount) * flt(exchange_rate) if party_account_currency == company_currency: exchange_rate = 1 + elif reference_doctype == "Gratuity": + outstanding_amount = ref_doc.amount - flt(ref_doc.paid_amount) else: outstanding_amount = flt(total_amount) - flt(ref_doc.advance_paid) else: @@ -1010,7 +1018,7 @@ def get_amounts_based_on_ref_doc(reference_doctype, ref_doc, party_account_curre total_amount = flt(ref_doc.total_sanctioned_amount) + flt(ref_doc.total_taxes_and_charges) elif ref_doc.doctype == "Employee Advance": total_amount, exchange_rate = get_total_amount_exchange_rate_for_employee_advance(party_account_currency, ref_doc) - + if not total_amount: total_amount, exchange_rate = get_total_amount_exchange_rate_base_on_currency( party_account_currency, company_currency, ref_doc) @@ -1174,7 +1182,7 @@ def set_party_type(dt): party_type = "Customer" elif dt in ("Purchase Invoice", "Purchase Order"): party_type = "Supplier" - elif dt in ("Expense Claim", "Employee Advance"): + elif dt in ("Expense Claim", "Employee Advance", "Gratuity"): party_type = "Employee" elif dt == "Fees": party_type = "Student" @@ -1193,6 +1201,8 @@ def set_party_account(dt, dn, doc, party_type): party_account = doc.advance_account elif dt == "Expense Claim": party_account = doc.payable_account + elif dt == "Gratuity": + party_account = doc.payable_account else: party_account = get_party_account(party_type, doc.get(party_type.lower()), doc.company) return party_account @@ -1241,6 +1251,9 @@ def set_grand_total_and_outstanding_amount(party_amount, dt, party_account_curre elif dt == "Donation": grand_total = doc.amount outstanding_amount = doc.amount + elif dt == "Gratuity": + grand_total = doc.amount + outstanding_amount = flt(doc.amount) - flt(doc.paid_amount) else: if party_account_currency == doc.company_currency: grand_total = flt(doc.get("base_rounded_total") or doc.base_grand_total) @@ -1345,4 +1358,4 @@ def make_payment_order(source_name, target_doc=None): }, target_doc, set_missing_values) - return doclist \ No newline at end of file + return doclist diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index 791b03a0d78..f7a15c04faa 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -88,19 +88,19 @@ class PaymentReconciliation(Document): voucher_type = ('Sales Invoice' if self.party_type == 'Customer' else "Purchase Invoice") - return frappe.db.sql(""" SELECT `tab{doc}`.name as reference_name, %(voucher_type)s as reference_type, - (sum(`tabGL Entry`.{dr_or_cr}) - sum(`tabGL Entry`.{reconciled_dr_or_cr})) as amount, + return frappe.db.sql(""" SELECT doc.name as reference_name, %(voucher_type)s as reference_type, + (sum(gl.{dr_or_cr}) - sum(gl.{reconciled_dr_or_cr})) as amount, account_currency as currency - FROM `tab{doc}`, `tabGL Entry` + FROM `tab{doc}` doc, `tabGL Entry` gl WHERE - (`tab{doc}`.name = `tabGL Entry`.against_voucher or `tab{doc}`.name = `tabGL Entry`.voucher_no) - and `tab{doc}`.{party_type_field} = %(party)s - and `tab{doc}`.is_return = 1 and `tab{doc}`.return_against IS NULL - and `tabGL Entry`.against_voucher_type = %(voucher_type)s - and `tab{doc}`.docstatus = 1 and `tabGL Entry`.party = %(party)s - and `tabGL Entry`.party_type = %(party_type)s and `tabGL Entry`.account = %(account)s - and `tabGL Entry`.is_cancelled = 0 - GROUP BY `tab{doc}`.name + (doc.name = gl.against_voucher or doc.name = gl.voucher_no) + and doc.{party_type_field} = %(party)s + and doc.is_return = 1 and ifnull(doc.return_against, "") = "" + and gl.against_voucher_type = %(voucher_type)s + and doc.docstatus = 1 and gl.party = %(party)s + and gl.party_type = %(party_type)s and gl.account = %(account)s + and gl.is_cancelled = 0 + GROUP BY doc.name Having amount > 0 """.format( @@ -113,7 +113,7 @@ class PaymentReconciliation(Document): 'party_type': self.party_type, 'voucher_type': voucher_type, 'account': self.receivable_payable_account - }, as_dict=1) + }, as_dict=1, debug=1) def add_payment_entries(self, entries): self.set('payments', []) diff --git a/erpnext/accounts/doctype/payment_request/payment_request_list.js b/erpnext/accounts/doctype/payment_request/payment_request_list.js index 72833d235f8..85d729cd61c 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request_list.js +++ b/erpnext/accounts/doctype/payment_request/payment_request_list.js @@ -2,7 +2,7 @@ frappe.listview_settings['Payment Request'] = { add_fields: ["status"], get_indicator: function(doc) { if(doc.status == "Draft") { - return [__("Draft"), "darkgrey", "status,=,Draft"]; + return [__("Draft"), "gray", "status,=,Draft"]; } if(doc.status == "Requested") { return [__("Requested"), "green", "status,=,Requested"]; @@ -19,5 +19,5 @@ frappe.listview_settings['Payment Request'] = { else if(doc.status == "Cancelled") { return [__("Cancelled"), "red", "status,=,Cancelled"]; } - } + } } diff --git a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.json b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.json index 18d430f59f8..a9b91e02a9d 100644 --- a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.json +++ b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.json @@ -6,6 +6,7 @@ "editable_grid": 1, "engine": "InnoDB", "field_order": [ + "period_details_section", "period_start_date", "period_end_date", "column_break_3", @@ -65,7 +66,8 @@ }, { "fieldname": "section_break_5", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "label": "User Details" }, { "fieldname": "company", @@ -121,7 +123,7 @@ "collapsible_depends_on": "eval:doc.docstatus==0", "fieldname": "section_break_13", "fieldtype": "Section Break", - "label": "Details" + "label": "Totals" }, { "default": "0", @@ -196,6 +198,11 @@ "options": "Draft\nSubmitted\nQueued\nCancelled", "print_hide": 1, "read_only": 1 + }, + { + "fieldname": "period_details_section", + "fieldtype": "Section Break", + "label": "Period Details" } ], "is_submittable": 1, @@ -205,7 +212,7 @@ "link_fieldname": "pos_closing_entry" } ], - "modified": "2021-01-12 12:21:05.388650", + "modified": "2021-02-01 13:47:20.722104", "modified_by": "Administrator", "module": "Accounts", "name": "POS Closing Entry", diff --git a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py index edf3d5a5748..f5224a269e1 100644 --- a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py +++ b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py @@ -20,11 +20,16 @@ class POSClosingEntry(StatusUpdater): self.validate_pos_invoices() def validate_pos_closing(self): - user = frappe.get_all("POS Closing Entry", - filters = { "user": self.user, "docstatus": 1, "pos_profile": self.pos_profile }, - or_filters = { - "period_start_date": ("between", [self.period_start_date, self.period_end_date]), - "period_end_date": ("between", [self.period_start_date, self.period_end_date]) + user = frappe.db.sql(""" + SELECT name FROM `tabPOS Closing Entry` + WHERE + user = %(user)s AND docstatus = 1 AND pos_profile = %(profile)s AND + (period_start_date between %(start)s and %(end)s OR period_end_date between %(start)s and %(end)s) + """, { + 'user': self.user, + 'profile': self.pos_profile, + 'start': self.period_start_date, + 'end': self.period_end_date }) if user: diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.json b/erpnext/accounts/doctype/pos_invoice/pos_invoice.json index 5bc57b4a84f..7459c11d4d9 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.json +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.json @@ -13,11 +13,11 @@ "customer", "customer_name", "tax_id", - "is_pos", "pos_profile", - "offline_pos_name", - "is_return", "consolidated_invoice", + "is_pos", + "is_return", + "update_billed_amount_in_sales_order", "column_break1", "company", "posting_date", @@ -25,10 +25,7 @@ "set_posting_time", "due_date", "amended_from", - "returns", "return_against", - "column_break_21", - "update_billed_amount_in_sales_order", "accounting_dimensions_section", "project", "dimension_col_break", @@ -183,8 +180,7 @@ "column_break_140", "auto_repeat", "update_auto_repeat_reference", - "against_income_account", - "pos_total_qty" + "against_income_account" ], "fields": [ { @@ -265,14 +261,6 @@ "options": "POS Profile", "print_hide": 1 }, - { - "fieldname": "offline_pos_name", - "fieldtype": "Data", - "hidden": 1, - "label": "Offline POS Name", - "print_hide": 1, - "read_only": 1 - }, { "allow_on_submit": 1, "default": "0", @@ -348,26 +336,16 @@ "print_hide": 1, "read_only": 1 }, - { - "depends_on": "return_against", - "fieldname": "returns", - "fieldtype": "Section Break", - "label": "Returns" - }, { "depends_on": "return_against", "fieldname": "return_against", "fieldtype": "Link", - "label": "Return Against POS Invoice", + "label": "Return Against", "no_copy": 1, "options": "POS Invoice", "print_hide": 1, "read_only": 1 }, - { - "fieldname": "column_break_21", - "fieldtype": "Column Break" - }, { "default": "0", "depends_on": "eval: doc.is_return && doc.return_against", @@ -587,19 +565,21 @@ }, { "fieldname": "sec_warehouse", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "label": "Warehouse" }, { "depends_on": "update_stock", "fieldname": "set_warehouse", "fieldtype": "Link", - "label": "Set Source Warehouse", + "label": "Source Warehouse", "options": "Warehouse", "print_hide": 1 }, { "fieldname": "items_section", "fieldtype": "Section Break", + "label": "Items", "oldfieldtype": "Section Break", "options": "fa fa-shopping-cart" }, @@ -1501,7 +1481,7 @@ "allow_on_submit": 1, "fieldname": "sales_team", "fieldtype": "Table", - "label": "Sales Team1", + "label": "Sales Team", "oldfieldname": "sales_team", "oldfieldtype": "Table", "options": "Sales Team", @@ -1560,15 +1540,6 @@ "print_hide": 1, "report_hide": 1 }, - { - "fieldname": "pos_total_qty", - "fieldtype": "Float", - "hidden": 1, - "label": "Total Qty", - "print_hide": 1, - "print_hide_if_no_value": 1, - "read_only": 1 - }, { "allow_on_submit": 1, "fieldname": "consolidated_invoice", @@ -1581,7 +1552,7 @@ "icon": "fa fa-file-text", "is_submittable": 1, "links": [], - "modified": "2020-10-30 13:56:51.056083", + "modified": "2021-02-01 15:03:33.800707", "modified_by": "Administrator", "module": "Accounts", "name": "POS Invoice", @@ -1626,7 +1597,6 @@ "role": "All" } ], - "quick_entry": 1, "search_fields": "posting_date, due_date, customer, base_grand_total, outstanding_amount", "show_name_in_global_search": 1, "sort_field": "modified", diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py index 0c1406c1ce8..3fa9846c727 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py @@ -178,10 +178,18 @@ class POSInvoice(SalesInvoice): if d.get("serial_no"): serial_nos = get_serial_nos(d.serial_no) for sr in serial_nos: - serial_no_exists = frappe.db.exists("POS Invoice Item", { - "parent": self.return_against, - "serial_no": ["like", d.get("serial_no")] - }) + serial_no_exists = frappe.db.sql(""" + SELECT name + FROM `tabPOS Invoice Item` + WHERE + parent = %s + and (serial_no = %s + or serial_no like %s + or serial_no like %s + or serial_no like %s + ) + """, (self.return_against, sr, sr+'\n%', '%\n'+sr, '%\n'+sr+'\n%')) + if not serial_no_exists: bold_return_against = frappe.bold(self.return_against) bold_serial_no = frappe.bold(sr) @@ -189,7 +197,7 @@ class POSInvoice(SalesInvoice): _("Row #{}: Serial No {} cannot be returned since it was not transacted in original invoice {}") .format(d.idx, bold_serial_no, bold_return_against) ) - + def validate_non_stock_items(self): for d in self.get("items"): is_stock_item = frappe.get_cached_value("Item", d.get("item_code"), "is_stock_item") @@ -291,7 +299,7 @@ class POSInvoice(SalesInvoice): if not self.get('payments') and not for_validate: update_multi_mode_option(self, profile) - + if self.is_return and not for_validate: add_return_modes(self, profile) @@ -428,21 +436,6 @@ class POSInvoice(SalesInvoice): if pr: return frappe.get_doc('Payment Request', pr[0][0]) -def add_return_modes(doc, pos_profile): - def append_payment(payment_mode): - payment = doc.append('payments', {}) - payment.default = payment_mode.default - payment.mode_of_payment = payment_mode.parent - payment.account = payment_mode.default_account - payment.type = payment_mode.type - - for pos_payment_method in pos_profile.get('payments'): - pos_payment_method = pos_payment_method.as_dict() - mode_of_payment = pos_payment_method.mode_of_payment - if pos_payment_method.allow_in_returns and not [d for d in doc.get('payments') if d.mode_of_payment == mode_of_payment]: - payment_mode = get_mode_of_payment_info(mode_of_payment, doc.company) - append_payment(payment_mode[0]) - @frappe.whitelist() def get_stock_availability(item_code, warehouse): latest_sle = frappe.db.sql("""select qty_after_transaction diff --git a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py index 57a23af8af0..eb52fd62759 100644 --- a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py @@ -99,10 +99,10 @@ class TestPOSInvoice(unittest.TestCase): item_row = inv.get("items")[0] add_items = [ - (54, '_Test Account Excise Duty @ 12'), - (288, '_Test Account Excise Duty @ 15'), - (144, '_Test Account Excise Duty @ 20'), - (430, '_Test Item Tax Template 1') + (54, '_Test Account Excise Duty @ 12 - _TC'), + (288, '_Test Account Excise Duty @ 15 - _TC'), + (144, '_Test Account Excise Duty @ 20 - _TC'), + (430, '_Test Item Tax Template 1 - _TC') ] for qty, item_tax_template in add_items: item_row_copy = copy.deepcopy(item_row) @@ -198,6 +198,65 @@ class TestPOSInvoice(unittest.TestCase): self.assertEqual(pos_return.get('payments')[0].amount, -500) self.assertEqual(pos_return.get('payments')[1].amount, -500) + def test_pos_return_for_serialized_item(self): + from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item + from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos + + se = make_serialized_item(company='_Test Company', + target_warehouse="Stores - _TC", cost_center='Main - _TC', expense_account='Cost of Goods Sold - _TC') + + serial_nos = get_serial_nos(se.get("items")[0].serial_no) + + pos = create_pos_invoice(company='_Test Company', debit_to='Debtors - _TC', + account_for_change_amount='Cash - _TC', warehouse='Stores - _TC', income_account='Sales - _TC', + expense_account='Cost of Goods Sold - _TC', cost_center='Main - _TC', + item=se.get("items")[0].item_code, rate=1000, do_not_save=1) + + pos.get("items")[0].serial_no = serial_nos[0] + pos.append("payments", {'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 1000, 'default': 1}) + + pos.insert() + pos.submit() + + pos_return = make_sales_return(pos.name) + + pos_return.insert() + pos_return.submit() + self.assertEqual(pos_return.get('items')[0].serial_no, serial_nos[0]) + + def test_partial_pos_returns(self): + from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item + from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos + + se = make_serialized_item(company='_Test Company', + target_warehouse="Stores - _TC", cost_center='Main - _TC', expense_account='Cost of Goods Sold - _TC') + + serial_nos = get_serial_nos(se.get("items")[0].serial_no) + + pos = create_pos_invoice(company='_Test Company', debit_to='Debtors - _TC', + account_for_change_amount='Cash - _TC', warehouse='Stores - _TC', income_account='Sales - _TC', + expense_account='Cost of Goods Sold - _TC', cost_center='Main - _TC', + item=se.get("items")[0].item_code, qty=2, rate=1000, do_not_save=1) + + pos.get("items")[0].serial_no = serial_nos[0] + "\n" + serial_nos[1] + pos.append("payments", {'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 1000, 'default': 1}) + + pos.insert() + pos.submit() + + pos_return1 = make_sales_return(pos.name) + + # partial return 1 + pos_return1.get('items')[0].qty = -1 + pos_return1.get('items')[0].serial_no = serial_nos[0] + pos_return1.insert() + pos_return1.submit() + + # partial return 2 + pos_return2 = make_sales_return(pos.name) + self.assertEqual(pos_return2.get('items')[0].qty, -1) + self.assertEqual(pos_return2.get('items')[0].serial_no, serial_nos[1]) + def test_pos_change_amount(self): pos = create_pos_invoice(company= "_Test Company", debit_to="Debtors - _TC", income_account = "Sales - _TC", expense_account = "Cost of Goods Sold - _TC", rate=105, diff --git a/erpnext/accounts/doctype/pos_invoice_item/pos_invoice_item.json b/erpnext/accounts/doctype/pos_invoice_item/pos_invoice_item.json index 2b6e7de118a..8b71eb02fd7 100644 --- a/erpnext/accounts/doctype/pos_invoice_item/pos_invoice_item.json +++ b/erpnext/accounts/doctype/pos_invoice_item/pos_invoice_item.json @@ -87,6 +87,7 @@ "edit_references", "sales_order", "so_detail", + "pos_invoice_item", "column_break_74", "delivery_note", "dn_detail", @@ -790,11 +791,20 @@ "fieldtype": "Link", "label": "Project", "options": "Project" + }, + { + "fieldname": "pos_invoice_item", + "fieldtype": "Data", + "ignore_user_permissions": 1, + "label": "POS Invoice Item", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 } ], "istable": 1, "links": [], - "modified": "2020-07-22 13:40:34.418346", + "modified": "2021-01-04 17:34:49.924531", "modified_by": "Administrator", "module": "Accounts", "name": "POS Invoice Item", 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 58409cd3c6d..40f77b4088d 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 @@ -29,7 +29,7 @@ class POSInvoiceMergeLog(Document): for d in self.pos_invoices: status, docstatus, is_return, return_against = frappe.db.get_value( 'POS Invoice', d.pos_invoice, ['status', 'docstatus', 'is_return', 'return_against']) - + bold_pos_invoice = frappe.bold(d.pos_invoice) bold_status = frappe.bold(status) if docstatus != 1: @@ -58,7 +58,7 @@ class POSInvoiceMergeLog(Document): sales_invoice, credit_note = "", "" if sales: sales_invoice = self.process_merging_into_sales_invoice(sales) - + if returns: credit_note = self.process_merging_into_credit_note(returns) @@ -74,7 +74,7 @@ class POSInvoiceMergeLog(Document): def process_merging_into_sales_invoice(self, data): sales_invoice = self.get_new_sales_invoice() - + sales_invoice = self.merge_pos_invoice_into(sales_invoice, data) sales_invoice.is_consolidated = 1 @@ -98,19 +98,19 @@ class POSInvoiceMergeLog(Document): self.consolidated_credit_note = credit_note.name return credit_note.name - + def merge_pos_invoice_into(self, invoice, data): items, payments, taxes = [], [], [] loyalty_amount_sum, loyalty_points_sum = 0, 0 for doc in data: map_doc(doc, invoice, table_map={ "doctype": invoice.doctype }) - + if doc.redeem_loyalty_points: invoice.loyalty_redemption_account = doc.loyalty_redemption_account invoice.loyalty_redemption_cost_center = doc.loyalty_redemption_cost_center loyalty_points_sum += doc.loyalty_points loyalty_amount_sum += doc.loyalty_amount - + for item in doc.get('items'): found = False for i in items: @@ -118,12 +118,13 @@ class POSInvoiceMergeLog(Document): i.uom == item.uom and i.net_rate == item.net_rate): found = True i.qty = i.qty + item.qty + if not found: item.rate = item.net_rate item.price_list_rate = 0 si_item = map_child_doc(item, invoice, {"doctype": "Sales Invoice Item"}) items.append(si_item) - + for tax in doc.get('taxes'): found = False for t in taxes: @@ -162,7 +163,7 @@ class POSInvoiceMergeLog(Document): invoice.ignore_pricing_rule = 1 return invoice - + def get_new_sales_invoice(self): sales_invoice = frappe.new_doc('Sales Invoice') sales_invoice.customer = self.customer @@ -194,7 +195,7 @@ def get_all_unconsolidated_invoices(): } pos_invoices = frappe.db.get_all('POS Invoice', filters=filters, fields=["name as pos_invoice", 'posting_date', 'grand_total', 'customer']) - + return pos_invoices def get_invoice_customer_map(pos_invoices): @@ -204,7 +205,7 @@ def get_invoice_customer_map(pos_invoices): customer = invoice.get('customer') pos_invoice_customer_map.setdefault(customer, []) pos_invoice_customer_map[customer].append(invoice) - + return pos_invoice_customer_map def consolidate_pos_invoices(pos_invoices=[], closing_entry={}): @@ -212,8 +213,8 @@ def consolidate_pos_invoices(pos_invoices=[], closing_entry={}): invoice_by_customer = get_invoice_customer_map(invoices) if len(invoices) >= 5 and closing_entry: - enqueue_job(create_merge_logs, invoice_by_customer, closing_entry) closing_entry.set_status(update=True, status='Queued') + enqueue_job(create_merge_logs, invoice_by_customer, closing_entry) else: create_merge_logs(invoice_by_customer, closing_entry) @@ -225,8 +226,8 @@ def unconsolidate_pos_invoices(closing_entry): ) if len(merge_logs) >= 5: - enqueue_job(cancel_merge_logs, merge_logs, closing_entry) closing_entry.set_status(update=True, status='Queued') + enqueue_job(cancel_merge_logs, merge_logs, closing_entry) else: cancel_merge_logs(merge_logs, closing_entry) diff --git a/erpnext/accounts/doctype/pos_profile/pos_profile.json b/erpnext/accounts/doctype/pos_profile/pos_profile.json index 4b69f6e2efe..8afa0abd36c 100644 --- a/erpnext/accounts/doctype/pos_profile/pos_profile.json +++ b/erpnext/accounts/doctype/pos_profile/pos_profile.json @@ -6,11 +6,10 @@ "doctype": "DocType", "engine": "InnoDB", "field_order": [ - "disabled", - "section_break_2", - "customer", "company", + "customer", "country", + "disabled", "column_break_9", "warehouse", "campaign", @@ -61,10 +60,6 @@ "fieldtype": "Check", "label": "Disabled" }, - { - "fieldname": "section_break_2", - "fieldtype": "Section Break" - }, { "fieldname": "customer", "fieldtype": "Link", @@ -348,9 +343,9 @@ "label": "Allow User to Edit Discount" }, { - "collapsible": 1, "fieldname": "section_break_23", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "label": "Filters" }, { "fieldname": "column_break_25", @@ -360,8 +355,29 @@ "icon": "icon-cog", "idx": 1, "index_web_pages_for_search": 1, - "links": [], - "modified": "2021-01-06 14:42:41.713864", + "links": [ + { + "group": "Invoices", + "link_doctype": "Sales Invoice", + "link_fieldname": "pos_profile" + }, + { + "group": "Invoices", + "link_doctype": "POS Invoice", + "link_fieldname": "pos_profile" + }, + { + "group": "Opening & Closing", + "link_doctype": "POS Opening Entry", + "link_fieldname": "pos_profile" + }, + { + "group": "Opening & Closing", + "link_doctype": "POS Closing Entry", + "link_fieldname": "pos_profile" + } + ], + "modified": "2021-02-01 13:52:51.081311", "modified_by": "Administrator", "module": "Accounts", "name": "POS Profile", diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py index 05652642eb0..f0b4e2976d0 100644 --- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py @@ -136,7 +136,7 @@ class PricingRule(Document): for d in self.items: max_discount = frappe.get_cached_value("Item", d.item_code, "max_discount") if max_discount and flt(self.discount_percentage) > flt(max_discount): - throw(_("Max discount allowed for item: {0} is {1}%").format(self.item_code, max_discount)) + throw(_("Max discount allowed for item: {0} is {1}%").format(d.item_code, max_discount)) def validate_price_list_with_currency(self): if self.currency and self.for_price_list: diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index 3ff0efe29e7..ff35d677c4a 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -1378,7 +1378,7 @@ "idx": 204, "is_submittable": 1, "links": [], - "modified": "2021-03-09 21:56:28.748582", + "modified": "2021-03-09 21:15:30.422084", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice", diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index dacd50a3e24..5c4e32e493e 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -968,7 +968,7 @@ class PurchaseInvoice(BuyingController): # base_rounding_adjustment may become zero due to small precision # eg: rounding_adjustment = 0.01 and exchange rate = 0.05 and precision of base_rounding_adjustment is 2 # then base_rounding_adjustment becomes zero and error is thrown in GL Entry - if self.rounding_adjustment and self.base_rounding_adjustment: + if not self.is_internal_transfer() and self.rounding_adjustment and self.base_rounding_adjustment: round_off_account, round_off_cost_center = \ get_round_off_account_and_cost_center(self.company) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice_list.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice_list.js index 8da7d6fe13d..914a2457d45 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice_list.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice_list.js @@ -19,7 +19,7 @@ frappe.listview_settings['Purchase Invoice'] = { return [__("Unpaid"), "orange", "outstanding_amount,>,0|due_date,>=,Today"]; } } else if (cint(doc.is_return)) { - return [__("Return"), "darkgrey", "is_return,=,Yes"]; + 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) { diff --git a/erpnext/accounts/doctype/purchase_invoice/test_records.json b/erpnext/accounts/doctype/purchase_invoice/test_records.json index 7030faf2b73..e7166c5a12d 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_records.json +++ b/erpnext/accounts/doctype/purchase_invoice/test_records.json @@ -18,7 +18,7 @@ "expense_account": "_Test Account Cost for Goods Sold - _TC", "item_code": "_Test Item Home Desktop 100", "item_name": "_Test Item Home Desktop 100", - "item_tax_template": "_Test Account Excise Duty @ 10", + "item_tax_template": "_Test Account Excise Duty @ 10 - _TC", "parentfield": "items", "qty": 10, "rate": 50, diff --git a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.py b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.py index 56576df0791..50ec7d8b4d8 100644 --- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.py +++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.py @@ -6,8 +6,5 @@ import frappe from frappe.model.document import Document -from erpnext.controllers.print_settings import print_settings_for_item_table - class PurchaseInvoiceItem(Document): - def __setup__(self): - print_settings_for_item_table(self) + pass diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index 5bef9e242d7..b361c0c3457 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -669,12 +669,12 @@ frappe.ui.form.on('Sales Invoice', { }; }, // When multiple companies are set up. in case company name is changed set default company address - company:function(frm){ - if (frm.doc.company) - { + company: function(frm){ + if (frm.doc.company) { frappe.call({ - method:"erpnext.setup.doctype.company.company.get_default_company_address", - args:{name:frm.doc.company, existing_address: frm.doc.company_address}, + method: "erpnext.setup.doctype.company.company.get_default_company_address", + args: {name:frm.doc.company, existing_address: frm.doc.company_address || ""}, + debounce: 2000, callback: function(r){ if (r.message){ frm.set_value("company_address",r.message) @@ -695,6 +695,7 @@ frappe.ui.form.on('Sales Invoice', { refresh_field(['timesheets']) } }) + frm.refresh(); }, onload: function(frm) { @@ -810,6 +811,65 @@ frappe.ui.form.on('Sales Invoice', { }, refresh: function(frm) { + if (frm.doc.project) { + frm.add_custom_button(__('Fetch Timesheet'), function() { + let d = new frappe.ui.Dialog({ + title: __('Fetch Timesheet'), + fields: [ + { + "label" : "From", + "fieldname": "from_time", + "fieldtype": "Date", + "reqd": 1, + }, + { + fieldtype: 'Column Break', + fieldname: 'col_break_1', + }, + { + "label" : "To", + "fieldname": "to_time", + "fieldtype": "Date", + "reqd": 1, + } + ], + primary_action: function() { + let data = d.get_values(); + frappe.call({ + method: "erpnext.projects.doctype.timesheet.timesheet.get_projectwise_timesheet_data", + args: { + from_time: data.from_time, + to_time: data.to_time, + project: frm.doc.project + }, + callback: function(r) { + if(!r.exc) { + if(r.message.length > 0) { + frm.clear_table('timesheets') + r.message.forEach((d) => { + frm.add_child('timesheets',{ + 'time_sheet': d.parent, + 'billing_hours': d.billing_hours, + 'billing_amount': d.billing_amt, + 'timesheet_detail': d.name + }); + }); + frm.refresh_field('timesheets') + } + else { + frappe.msgprint(__('No Timesheet Found.')) + } + d.hide(); + } + } + }); + }, + primary_action_label: __('Get Timesheets') + }); + d.show(); + }) + } + if (frappe.boot.active_domains.includes("Healthcare")) { frm.set_df_property("patient", "hidden", 0); frm.set_df_property("patient_name", "hidden", 0); diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index 018bc7e6419..720a9175e66 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -12,11 +12,11 @@ "customer", "customer_name", "tax_id", + "pos_profile", "is_pos", "is_consolidated", - "pos_profile", - "offline_pos_name", "is_return", + "update_billed_amount_in_sales_order", "column_break1", "company", "company_tax_id", @@ -24,11 +24,8 @@ "posting_time", "set_posting_time", "due_date", - "amended_from", - "returns", "return_against", - "column_break_21", - "update_billed_amount_in_sales_order", + "amended_from", "accounting_dimensions_section", "project", "dimension_col_break", @@ -188,8 +185,7 @@ "column_break_140", "auto_repeat", "update_auto_repeat_reference", - "against_income_account", - "pos_total_qty" + "against_income_account" ], "fields": [ { @@ -296,16 +292,6 @@ "options": "POS Profile", "print_hide": 1 }, - { - "fieldname": "offline_pos_name", - "fieldtype": "Data", - "hidden": 1, - "hide_days": 1, - "hide_seconds": 1, - "label": "Offline POS Name", - "print_hide": 1, - "read_only": 1 - }, { "default": "0", "fieldname": "is_return", @@ -404,33 +390,19 @@ "print_hide": 1, "read_only": 1 }, - { - "depends_on": "return_against", - "fieldname": "returns", - "fieldtype": "Section Break", - "hide_days": 1, - "hide_seconds": 1, - "label": "Returns" - }, { "depends_on": "return_against", "fieldname": "return_against", "fieldtype": "Link", "hide_days": 1, "hide_seconds": 1, - "label": "Return Against Sales Invoice", + "label": "Return Against", "no_copy": 1, "options": "Sales Invoice", "print_hide": 1, "read_only": 1, "search_index": 1 }, - { - "fieldname": "column_break_21", - "fieldtype": "Column Break", - "hide_days": 1, - "hide_seconds": 1 - }, { "default": "0", "depends_on": "eval: doc.is_return && doc.return_against", @@ -678,7 +650,8 @@ "fieldname": "sec_warehouse", "fieldtype": "Section Break", "hide_days": 1, - "hide_seconds": 1 + "hide_seconds": 1, + "label": "Warehouse" }, { "depends_on": "update_stock", @@ -686,7 +659,7 @@ "fieldtype": "Link", "hide_days": 1, "hide_seconds": 1, - "label": "Set Source Warehouse", + "label": "Source Warehouse", "options": "Warehouse", "print_hide": 1 }, @@ -695,6 +668,7 @@ "fieldtype": "Section Break", "hide_days": 1, "hide_seconds": 1, + "label": "Items", "oldfieldtype": "Section Break", "options": "fa fa-shopping-cart" }, @@ -1904,17 +1878,6 @@ "print_hide": 1, "report_hide": 1 }, - { - "fieldname": "pos_total_qty", - "fieldtype": "Float", - "hidden": 1, - "hide_days": 1, - "hide_seconds": 1, - "label": "Total Qty", - "print_hide": 1, - "print_hide_if_no_value": 1, - "read_only": 1 - }, { "collapsible": 1, "fieldname": "accounting_dimensions_section", @@ -1995,7 +1958,7 @@ "link_fieldname": "consolidated_invoice" } ], - "modified": "2021-01-12 12:16:15.192520", + "modified": "2021-02-01 15:42:26.261540", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 903a2ef5f70..3bdb111b62c 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -21,6 +21,7 @@ from erpnext.accounts.general_ledger import get_round_off_account_and_cost_cente from erpnext.accounts.doctype.loyalty_program.loyalty_program import \ get_loyalty_program_details_with_points, get_loyalty_details, validate_loyalty_points from erpnext.accounts.deferred_revenue import validate_service_stop_date +from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import get_party_tax_withholding_details from frappe.model.utils import get_fetch_values from frappe.contacts.doctype.address.address import get_address_display @@ -55,7 +56,7 @@ class SalesInvoice(SellingController): """Set indicator for portal""" if self.outstanding_amount < 0: self.indicator_title = _("Credit Note Issued") - self.indicator_color = "darkgrey" + self.indicator_color = "gray" elif self.outstanding_amount > 0 and getdate(self.due_date) >= getdate(nowdate()): self.indicator_color = "orange" self.indicator_title = _("Unpaid") @@ -64,7 +65,7 @@ class SalesInvoice(SellingController): self.indicator_title = _("Overdue") elif cint(self.is_return) == 1: self.indicator_title = _("Return") - self.indicator_color = "darkgrey" + self.indicator_color = "gray" else: self.indicator_color = "green" self.indicator_title = _("Paid") @@ -75,6 +76,8 @@ class SalesInvoice(SellingController): if not self.is_pos: self.so_dn_required() + + self.set_tax_withholding() self.validate_proj_cust() self.validate_pos_return() @@ -153,6 +156,32 @@ class SalesInvoice(SellingController): if cost_center_company != self.company: frappe.throw(_("Row #{0}: Cost Center {1} does not belong to company {2}").format(frappe.bold(item.idx), frappe.bold(item.cost_center), frappe.bold(self.company))) + def set_tax_withholding(self): + tax_withholding_details = get_party_tax_withholding_details(self) + + if not tax_withholding_details: + return + + accounts = [] + tax_withholding_account = tax_withholding_details.get("account_head") + + for d in self.taxes: + if d.account_head == tax_withholding_account: + d.update(tax_withholding_details) + accounts.append(d.account_head) + + if not accounts or tax_withholding_account not in accounts: + self.append("taxes", tax_withholding_details) + + to_remove = [d for d in self.taxes + if not d.tax_amount and d.charge_type == "Actual" and d.account_head == tax_withholding_account] + + for d in to_remove: + self.remove(d) + + # calculate totals again after applying TDS + self.calculate_taxes_and_totals() + def before_save(self): set_account_for_mode_of_payment(self) @@ -1033,7 +1062,8 @@ class SalesInvoice(SellingController): ) def make_gle_for_rounding_adjustment(self, gl_entries): - if flt(self.rounding_adjustment, self.precision("rounding_adjustment")) and self.base_rounding_adjustment: + if flt(self.rounding_adjustment, self.precision("rounding_adjustment")) and self.base_rounding_adjustment \ + and not self.is_internal_transfer(): round_off_account, round_off_cost_center = \ get_round_off_account_and_cost_center(self.company) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice_list.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice_list.js index 41140d19388..1a01cb58f2a 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice_list.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice_list.js @@ -10,8 +10,8 @@ frappe.listview_settings['Sales Invoice'] = { "Draft": "grey", "Unpaid": "orange", "Paid": "green", - "Return": "darkgrey", - "Credit Note Issued": "darkgrey", + "Return": "gray", + "Credit Note Issued": "gray", "Unpaid and Discounted": "orange", "Overdue and Discounted": "red", "Overdue": "red", diff --git a/erpnext/accounts/doctype/sales_invoice/test_records.json b/erpnext/accounts/doctype/sales_invoice/test_records.json index ee6419db20a..e00a58f8641 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_records.json +++ b/erpnext/accounts/doctype/sales_invoice/test_records.json @@ -148,7 +148,7 @@ "expense_account": "_Test Account Cost for Goods Sold - _TC", "item_code": "_Test Item Home Desktop 100", "item_name": "_Test Item Home Desktop 100", - "item_tax_template": "_Test Account Excise Duty @ 10", + "item_tax_template": "_Test Account Excise Duty @ 10 - _TC", "parentfield": "items", "price_list_rate": 50, "qty": 10, @@ -276,7 +276,7 @@ "expense_account": "_Test Account Cost for Goods Sold - _TC", "item_code": "_Test Item Home Desktop 100", "item_name": "_Test Item Home Desktop 100", - "item_tax_template": "_Test Account Excise Duty @ 10", + "item_tax_template": "_Test Account Excise Duty @ 10 - _TC", "parentfield": "items", "price_list_rate": 62.5, "qty": 10, diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 7cd1828343b..1b9557839fe 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -405,10 +405,10 @@ class TestSalesInvoice(unittest.TestCase): item_row = si.get("items")[0] add_items = [ - (54, '_Test Account Excise Duty @ 12'), - (288, '_Test Account Excise Duty @ 15'), - (144, '_Test Account Excise Duty @ 20'), - (430, '_Test Item Tax Template 1') + (54, '_Test Account Excise Duty @ 12 - _TC'), + (288, '_Test Account Excise Duty @ 15 - _TC'), + (144, '_Test Account Excise Duty @ 20 - _TC'), + (430, '_Test Item Tax Template 1 - _TC') ] for qty, item_tax_template in add_items: item_row_copy = copy.deepcopy(item_row) @@ -2077,14 +2077,14 @@ def check_gl_entries(doc, voucher_no, expected_gle, posting_date): item.save() item.append("taxes", { - "item_tax_template": "_Test Item Tax Template 1", + "item_tax_template": "_Test Item Tax Template 1 - _TC", "valid_from": add_days(nowdate(), 1) }) item.save() sales_invoice = create_sales_invoice(item = "_Test Item 2", do_not_save=1) - sales_invoice.items[0].item_tax_template = "_Test Item Tax Template 1" + sales_invoice.items[0].item_tax_template = "_Test Item Tax Template 1 - _TC" self.assertRaises(frappe.ValidationError, sales_invoice.save) item.taxes = [] diff --git a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.py b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.py index 7a62f8e2815..a73b03acc84 100644 --- a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.py +++ b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.py @@ -5,8 +5,6 @@ from __future__ import unicode_literals import frappe from frappe.model.document import Document -from erpnext.controllers.print_settings import print_settings_for_item_table class SalesInvoiceItem(Document): - def __setup__(self): - print_settings_for_item_table(self) + pass diff --git a/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.js b/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.js index 97a6fdd3366..0e011883b15 100644 --- a/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.js +++ b/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.js @@ -5,3 +5,25 @@ cur_frm.cscript.tax_table = "Sales Taxes and Charges"; {% include "erpnext/public/js/controllers/accounts.js" %} +frappe.tour['Sales Taxes and Charges Template'] = [ + { + fieldname: "title", + title: __("Title"), + description: __("A name by which you will identify this template. You can change this later."), + }, + { + fieldname: "company", + title: __("Company"), + description: __("Company for which this tax template will be applicable"), + }, + { + fieldname: "is_default", + title: __("Is this Default?"), + description: __("Set this template as the default for all sales transactions"), + }, + { + fieldname: "taxes", + title: __("Taxes Table"), + description: __("You can add a row for a tax rule here. These rules can be applied on the net total, or can be a flat amount."), + } +]; diff --git a/erpnext/accounts/doctype/subscription/subscription.js b/erpnext/accounts/doctype/subscription/subscription.js index ba98eb9b2a2..1a9066470a5 100644 --- a/erpnext/accounts/doctype/subscription/subscription.js +++ b/erpnext/accounts/doctype/subscription/subscription.js @@ -10,6 +10,14 @@ frappe.ui.form.on('Subscription', { } } }); + + frm.set_query('cost_center', function() { + return { + filters: { + company: frm.doc.company + } + }; + }); }, refresh: function(frm) { diff --git a/erpnext/accounts/doctype/subscription/subscription.json b/erpnext/accounts/doctype/subscription/subscription.json index afb94fe9c95..e80df2ab886 100644 --- a/erpnext/accounts/doctype/subscription/subscription.json +++ b/erpnext/accounts/doctype/subscription/subscription.json @@ -7,9 +7,10 @@ "engine": "InnoDB", "field_order": [ "party_type", - "status", - "cb_1", "party", + "cb_1", + "company", + "status", "subscription_period", "start_date", "end_date", @@ -44,80 +45,107 @@ { "allow_on_submit": 1, "fieldname": "cb_1", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "status", "fieldtype": "Select", "label": "Status", + "no_copy": 1, "options": "\nTrialling\nActive\nPast Due Date\nCancelled\nUnpaid\nCompleted", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "subscription_period", "fieldtype": "Section Break", - "label": "Subscription Period" + "label": "Subscription Period", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "cancelation_date", "fieldtype": "Date", "label": "Cancelation Date", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "allow_on_submit": 1, "fieldname": "trial_period_start", "fieldtype": "Date", "label": "Trial Period Start Date", - "set_only_once": 1 + "set_only_once": 1, + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:doc.trial_period_start", "fieldname": "trial_period_end", "fieldtype": "Date", "label": "Trial Period End Date", - "set_only_once": 1 + "set_only_once": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_11", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "current_invoice_start", "fieldtype": "Date", "label": "Current Invoice Start Date", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "current_invoice_end", "fieldtype": "Date", "label": "Current Invoice End Date", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "default": "0", "description": "Number of days that the subscriber has to pay invoices generated by this subscription", "fieldname": "days_until_due", "fieldtype": "Int", - "label": "Days Until Due" + "label": "Days Until Due", + "show_days": 1, + "show_seconds": 1 }, { "default": "0", "fieldname": "cancel_at_period_end", "fieldtype": "Check", - "label": "Cancel At End Of Period" + "label": "Cancel At End Of Period", + "show_days": 1, + "show_seconds": 1 }, { "default": "0", "fieldname": "generate_invoice_at_period_start", "fieldtype": "Check", - "label": "Generate Invoice At Beginning Of Period" + "label": "Generate Invoice At Beginning Of Period", + "show_days": 1, + "show_seconds": 1 }, { "allow_on_submit": 1, "fieldname": "sb_4", "fieldtype": "Section Break", - "label": "Plans" + "label": "Plans", + "show_days": 1, + "show_seconds": 1 }, { "allow_on_submit": 1, @@ -125,62 +153,84 @@ "fieldtype": "Table", "label": "Plans", "options": "Subscription Plan Detail", - "reqd": 1 + "reqd": 1, + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:['Customer', 'Supplier'].includes(doc.party_type)", "fieldname": "sb_1", "fieldtype": "Section Break", - "label": "Taxes" + "label": "Taxes", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "sb_2", "fieldtype": "Section Break", - "label": "Discounts" + "label": "Discounts", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "apply_additional_discount", "fieldtype": "Select", "label": "Apply Additional Discount On", - "options": "\nGrand Total\nNet Total" + "options": "\nGrand Total\nNet Total", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "cb_2", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "additional_discount_percentage", "fieldtype": "Percent", - "label": "Additional DIscount Percentage" + "label": "Additional DIscount Percentage", + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, "fieldname": "additional_discount_amount", "fieldtype": "Currency", - "label": "Additional DIscount Amount" + "label": "Additional DIscount Amount", + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:doc.invoices", "fieldname": "sb_3", "fieldtype": "Section Break", - "label": "Invoices" + "label": "Invoices", + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, "fieldname": "invoices", "fieldtype": "Table", "label": "Invoices", - "options": "Subscription Invoice" + "options": "Subscription Invoice", + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, "fieldname": "accounting_dimensions_section", "fieldtype": "Section Break", - "label": "Accounting Dimensions" + "label": "Accounting Dimensions", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "dimension_col_break", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "party_type", @@ -188,7 +238,9 @@ "label": "Party Type", "options": "DocType", "reqd": 1, - "set_only_once": 1 + "set_only_once": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "party", @@ -197,21 +249,27 @@ "label": "Party", "options": "party_type", "reqd": 1, - "set_only_once": 1 + "set_only_once": 1, + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:doc.party_type === 'Customer'", "fieldname": "sales_tax_template", "fieldtype": "Link", "label": "Sales Taxes and Charges Template", - "options": "Sales Taxes and Charges Template" + "options": "Sales Taxes and Charges Template", + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:doc.party_type === 'Supplier'", "fieldname": "purchase_tax_template", "fieldtype": "Link", "label": "Purchase Taxes and Charges Template", - "options": "Purchase Taxes and Charges Template" + "options": "Purchase Taxes and Charges Template", + "show_days": 1, + "show_seconds": 1 }, { "default": "0", @@ -219,36 +277,55 @@ "fieldname": "follow_calendar_months", "fieldtype": "Check", "label": "Follow Calendar Months", - "set_only_once": 1 + "set_only_once": 1, + "show_days": 1, + "show_seconds": 1 }, { "default": "0", "description": "New invoices will be generated as per schedule even if current invoices are unpaid or past due date", "fieldname": "generate_new_invoices_past_due_date", "fieldtype": "Check", - "label": "Generate New Invoices Past Due Date" + "label": "Generate New Invoices Past Due Date", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "end_date", "fieldtype": "Date", "label": "Subscription End Date", - "set_only_once": 1 + "set_only_once": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "start_date", "fieldtype": "Date", "label": "Subscription Start Date", - "set_only_once": 1 + "set_only_once": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "cost_center", "fieldtype": "Link", "label": "Cost Center", - "options": "Cost Center" + "options": "Cost Center", + "show_days": 1, + "show_seconds": 1 + }, + { + "fieldname": "company", + "fieldtype": "Link", + "label": "Company", + "options": "Company", + "show_days": 1, + "show_seconds": 1 } ], + "index_web_pages_for_search": 1, "links": [], - "modified": "2020-06-25 10:52:52.265105", + "modified": "2021-02-09 15:44:20.024789", "modified_by": "Administrator", "module": "Accounts", "name": "Subscription", diff --git a/erpnext/accounts/doctype/subscription/subscription.py b/erpnext/accounts/doctype/subscription/subscription.py index e023b47caca..826044a4075 100644 --- a/erpnext/accounts/doctype/subscription/subscription.py +++ b/erpnext/accounts/doctype/subscription/subscription.py @@ -1,3 +1,4 @@ + # -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt @@ -5,12 +6,13 @@ from __future__ import unicode_literals import frappe +import erpnext from frappe import _ from frappe.model.document import Document from frappe.utils.data import nowdate, getdate, cstr, cint, add_days, date_diff, get_last_day, add_to_date, flt from erpnext.accounts.doctype.subscription_plan.subscription_plan import get_plan_rate from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions - +from erpnext import get_default_company class Subscription(Document): def before_insert(self): @@ -243,6 +245,7 @@ class Subscription(Document): self.validate_plans_billing_cycle(self.get_billing_cycle_and_interval()) self.validate_end_date() self.validate_to_follow_calendar_months() + self.cost_center = erpnext.get_default_cost_center(self.get('company')) def validate_trial_period(self): """ @@ -304,6 +307,14 @@ class Subscription(Document): doctype = 'Sales Invoice' if self.party_type == 'Customer' else 'Purchase Invoice' invoice = frappe.new_doc(doctype) + + # For backward compatibility + # Earlier subscription didn't had any company field + company = self.get('company') or get_default_company() + if not company: + frappe.throw(_("Company is mandatory was generating invoice. Please set default company in Global Defaults")) + + invoice.company = company invoice.set_posting_time = 1 invoice.posting_date = self.current_invoice_start if self.generate_invoice_at_period_start \ else self.current_invoice_end @@ -330,6 +341,7 @@ class Subscription(Document): # for that reason items_list = self.get_items_from_plans(self.plans, prorate) for item in items_list: + item['cost_center'] = self.cost_center invoice.append('items', item) # Taxes @@ -380,7 +392,8 @@ class Subscription(Document): Returns the `Item`s linked to `Subscription Plan` """ if prorate: - prorate_factor = get_prorata_factor(self.current_invoice_end, self.current_invoice_start) + prorate_factor = get_prorata_factor(self.current_invoice_end, self.current_invoice_start, + self.generate_invoice_at_period_start) items = [] party = self.party @@ -583,10 +596,13 @@ def get_calendar_months(billing_interval): return calendar_months -def get_prorata_factor(period_end, period_start): - diff = flt(date_diff(nowdate(), period_start) + 1) - plan_days = flt(date_diff(period_end, period_start) + 1) - prorate_factor = diff / plan_days +def get_prorata_factor(period_end, period_start, is_prepaid): + if is_prepaid: + prorate_factor = 1 + else: + diff = flt(date_diff(nowdate(), period_start) + 1) + plan_days = flt(date_diff(period_end, period_start) + 1) + prorate_factor = diff / plan_days return prorate_factor diff --git a/erpnext/accounts/doctype/subscription/subscription_list.js b/erpnext/accounts/doctype/subscription/subscription_list.js index a4edb77dc9e..c7325fb9f74 100644 --- a/erpnext/accounts/doctype/subscription/subscription_list.js +++ b/erpnext/accounts/doctype/subscription/subscription_list.js @@ -11,7 +11,7 @@ frappe.listview_settings['Subscription'] = { } else if(doc.status === 'Unpaid') { return [__("Unpaid"), "red"]; } else if(doc.status === 'Cancelled') { - return [__("Cancelled"), "darkgrey"]; + return [__("Cancelled"), "gray"]; } } }; \ No newline at end of file diff --git a/erpnext/accounts/doctype/subscription/test_subscription.py b/erpnext/accounts/doctype/subscription/test_subscription.py index c17fccdce04..7c58e9865fd 100644 --- a/erpnext/accounts/doctype/subscription/test_subscription.py +++ b/erpnext/accounts/doctype/subscription/test_subscription.py @@ -321,7 +321,8 @@ class TestSubscription(unittest.TestCase): self.assertEqual( flt( - get_prorata_factor(subscription.current_invoice_end, subscription.current_invoice_start), + get_prorata_factor(subscription.current_invoice_end, subscription.current_invoice_start, + subscription.generate_invoice_at_period_start), 2), flt(prorate_factor, 2) ) @@ -561,9 +562,7 @@ class TestSubscription(unittest.TestCase): current_inv = subscription.get_current_invoice() self.assertEqual(current_inv.status, "Unpaid") - diff = flt(date_diff(nowdate(), subscription.current_invoice_start) + 1) - plan_days = flt(date_diff(subscription.current_invoice_end, subscription.current_invoice_start) + 1) - prorate_factor = flt(diff / plan_days) + prorate_factor = 1 self.assertEqual(flt(current_inv.grand_total, 2), flt(prorate_factor * 900, 2)) diff --git a/erpnext/accounts/doctype/subscription_invoice/subscription_invoice.json b/erpnext/accounts/doctype/subscription_invoice/subscription_invoice.json index f54e887f263..8a0d1de94c1 100644 --- a/erpnext/accounts/doctype/subscription_invoice/subscription_invoice.json +++ b/erpnext/accounts/doctype/subscription_invoice/subscription_invoice.json @@ -13,21 +13,28 @@ "fieldname": "document_type", "fieldtype": "Link", "label": "Document Type ", + "no_copy": 1, "options": "DocType", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "invoice", "fieldtype": "Dynamic Link", "in_list_view": 1, "label": "Invoice", + "no_copy": 1, "options": "document_type", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 } ], + "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2020-06-01 22:23:54.462718", + "modified": "2021-02-09 15:43:32.026233", "modified_by": "Administrator", "module": "Accounts", "name": "Subscription Invoice", diff --git a/erpnext/accounts/doctype/tax_category/tax_category.json b/erpnext/accounts/doctype/tax_category/tax_category.json index 6f682a0466d..f7145af44c3 100644 --- a/erpnext/accounts/doctype/tax_category/tax_category.json +++ b/erpnext/accounts/doctype/tax_category/tax_category.json @@ -11,15 +11,18 @@ ], "fields": [ { + "allow_in_quick_entry": 1, "fieldname": "title", "fieldtype": "Data", + "in_list_view": 1, "label": "Title", + "reqd": 1, "unique": 1 } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2020-08-30 19:41:25.783852", + "modified": "2021-03-03 11:50:38.748872", "modified_by": "Administrator", "module": "Accounts", "name": "Tax Category", 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 32ad4cb03ab..961bdb147f2 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -12,37 +12,62 @@ from erpnext.accounts.utils import get_fiscal_year class TaxWithholdingCategory(Document): pass -def get_party_tax_withholding_details(ref_doc, tax_withholding_category=None): +def get_party_details(inv): + party_type, party = '', '' + if inv.doctype == 'Sales Invoice': + party_type = 'Customer' + party = inv.customer + else: + party_type = 'Supplier' + party = inv.supplier + + return party_type, party + +def get_party_tax_withholding_details(inv, tax_withholding_category=None): pan_no = '' - suppliers = [] + parties = [] + party_type, party = get_party_details(inv) if not tax_withholding_category: - tax_withholding_category, pan_no = frappe.db.get_value('Supplier', ref_doc.supplier, ['tax_withholding_category', 'pan']) + tax_withholding_category, pan_no = frappe.db.get_value(party_type, party, ['tax_withholding_category', 'pan']) if not tax_withholding_category: return + # if tax_withholding_category passed as an argument but not pan_no if not pan_no: - pan_no = frappe.db.get_value('Supplier', ref_doc.supplier, 'pan') + pan_no = frappe.db.get_value(party_type, party, 'pan') # Get others suppliers with the same PAN No if pan_no: - suppliers = [d.name for d in frappe.get_all('Supplier', fields=['name'], filters={'pan': pan_no})] + parties = frappe.get_all(party_type, filters={ 'pan': pan_no }, pluck='name') - if not suppliers: - suppliers.append(ref_doc.supplier) + if not parties: + parties.append(party) + + fiscal_year = get_fiscal_year(inv.posting_date, company=inv.company) + tax_details = get_tax_withholding_details(tax_withholding_category, fiscal_year[0], inv.company) - fy = get_fiscal_year(ref_doc.posting_date, company=ref_doc.company) - tax_details = get_tax_withholding_details(tax_withholding_category, fy[0], ref_doc.company) if not tax_details: frappe.throw(_('Please set associated account in Tax Withholding Category {0} against Company {1}') - .format(tax_withholding_category, ref_doc.company)) + .format(tax_withholding_category, inv.company)) - tds_amount = get_tds_amount(suppliers, ref_doc.net_total, ref_doc.company, - tax_details, fy, ref_doc.posting_date, pan_no) + if party_type == 'Customer' and not tax_details.cumulative_threshold: + # TCS is only chargeable on sum of invoiced value + frappe.throw(_('Tax Withholding Category {} against Company {} for Customer {} should have Cumulative Threshold value.') + .format(tax_withholding_category, inv.company, party)) - tax_row = get_tax_row(tax_details, tds_amount) + tax_amount, tax_deducted = get_tax_amount( + party_type, parties, + inv, tax_details, + fiscal_year, pan_no + ) + + if party_type == 'Supplier': + tax_row = get_tax_row_for_tds(tax_details, tax_amount) + else: + tax_row = get_tax_row_for_tcs(inv, tax_details, tax_amount, tax_deducted) return tax_row @@ -69,147 +94,254 @@ def get_tax_withholding_rates(tax_withholding, fiscal_year): frappe.throw(_("No Tax Withholding data found for the current Fiscal Year.")) -def get_tax_row(tax_details, tds_amount): - - return { +def get_tax_row_for_tcs(inv, tax_details, tax_amount, tax_deducted): + row = { "category": "Total", - "add_deduct_tax": "Deduct", "charge_type": "Actual", - "account_head": tax_details.account_head, + "tax_amount": tax_amount, "description": tax_details.description, - "tax_amount": tds_amount + "account_head": tax_details.account_head } -def get_tds_amount(suppliers, net_total, company, tax_details, fiscal_year_details, posting_date, pan_no=None): - fiscal_year, year_start_date, year_end_date = fiscal_year_details - tds_amount = 0 - tds_deducted = 0 + if tax_deducted: + # TCS already deducted on previous invoices + # So, TCS will be calculated by 'Previous Row Total' - def _get_tds(amount, rate): - if amount <= 0: - return 0 - - return amount * rate / 100 - - ldc_name = frappe.db.get_value('Lower Deduction Certificate', - { - 'pan_no': pan_no, - 'fiscal_year': fiscal_year - }, 'name') - ldc = '' - - if ldc_name: - ldc = frappe.get_doc('Lower Deduction Certificate', ldc_name) - - entries = frappe.db.sql(""" - select voucher_no, credit - from `tabGL Entry` - where company = %s and - party in %s and fiscal_year=%s and credit > 0 - and is_opening = 'No' - """, (company, tuple(suppliers), fiscal_year), as_dict=1) - - vouchers = [d.voucher_no for d in entries] - advance_vouchers = get_advance_vouchers(suppliers, fiscal_year=fiscal_year, company=company) - - tds_vouchers = vouchers + advance_vouchers - - if tds_vouchers: - tds_deducted = frappe.db.sql(""" - SELECT sum(credit) FROM `tabGL Entry` - WHERE - account=%s and fiscal_year=%s and credit > 0 - and voucher_no in ({0})""". format(','.join(['%s'] * len(tds_vouchers))), - ((tax_details.account_head, fiscal_year) + tuple(tds_vouchers))) - - tds_deducted = tds_deducted[0][0] if tds_deducted and tds_deducted[0][0] else 0 - - if tds_deducted: - if ldc: - limit_consumed = frappe.db.get_value('Purchase Invoice', - { - 'supplier': ('in', suppliers), - 'apply_tds': 1, - 'docstatus': 1 - }, 'sum(net_total)') - - if ldc and is_valid_certificate(ldc.valid_from, ldc.valid_upto, posting_date, limit_consumed, net_total, - ldc.certificate_limit): - - tds_amount = get_ltds_amount(net_total, limit_consumed, ldc.certificate_limit, ldc.rate, tax_details) + taxes_excluding_tcs = [d for d in inv.taxes if d.account_head != tax_details.account_head] + if taxes_excluding_tcs: + # chargeable amount is the total amount after other charges are applied + row.update({ + "charge_type": "On Previous Row Total", + "row_id": len(taxes_excluding_tcs), + "rate": tax_details.rate + }) else: - tds_amount = _get_tds(net_total, tax_details.rate) - else: - supplier_credit_amount = frappe.get_all('Purchase Invoice', - fields = ['sum(net_total)'], - filters = {'name': ('in', vouchers), 'docstatus': 1, "apply_tds": 1}, as_list=1) + # if only TCS is to be charged, then net total is chargeable amount + row.update({ + "charge_type": "On Net Total", + "rate": tax_details.rate + }) - supplier_credit_amount = (supplier_credit_amount[0][0] - if supplier_credit_amount and supplier_credit_amount[0][0] else 0) + return row - jv_supplier_credit_amt = frappe.get_all('Journal Entry Account', - fields = ['sum(credit_in_account_currency)'], - filters = { - 'parent': ('in', vouchers), 'docstatus': 1, - 'party': ('in', suppliers), - 'reference_type': ('not in', ['Purchase Invoice']) - }, as_list=1) +def get_tax_row_for_tds(tax_details, tax_amount): + return { + "category": "Total", + "charge_type": "Actual", + "tax_amount": tax_amount, + "add_deduct_tax": "Deduct", + "description": tax_details.description, + "account_head": tax_details.account_head + } - supplier_credit_amount += (jv_supplier_credit_amt[0][0] - if jv_supplier_credit_amt and jv_supplier_credit_amt[0][0] else 0) +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') + if ldc_name: + return frappe.get_doc('Lower Deduction Certificate', ldc_name) - supplier_credit_amount += net_total +def get_tax_amount(party_type, parties, inv, tax_details, fiscal_year_details, pan_no=None): + fiscal_year = fiscal_year_details[0] - debit_note_amount = get_debit_note_amount(suppliers, year_start_date, year_end_date) - supplier_credit_amount -= debit_note_amount + 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) + taxable_vouchers = vouchers + advance_vouchers - if ((tax_details.get('threshold', 0) and supplier_credit_amount >= tax_details.threshold) - or (tax_details.get('cumulative_threshold', 0) and supplier_credit_amount >= tax_details.cumulative_threshold)): + tax_deducted = 0 + if taxable_vouchers: + tax_deducted = get_deducted_tax(taxable_vouchers, fiscal_year, tax_details) - if ldc and is_valid_certificate(ldc.valid_from, ldc.valid_upto, posting_date, tds_deducted, net_total, - ldc.certificate_limit): - tds_amount = get_ltds_amount(supplier_credit_amount, 0, ldc.certificate_limit, ldc.rate, - tax_details) + tax_amount = 0 + posting_date = inv.posting_date + if party_type == 'Supplier': + ldc = get_lower_deduction_certificate(fiscal_year, 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) else: - tds_amount = _get_tds(supplier_credit_amount, tax_details.rate) + 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 + ) + + elif party_type == 'Customer': + if tax_deducted: + # if already TCS is charged, then amount will be calculated based on 'Previous Row Total' + tax_amount = 0 + 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 + ) + + return tax_amount, tax_deducted + +def get_invoice_vouchers(parties, fiscal_year, company, party_type='Supplier'): + dr_or_cr = 'credit' if party_type == 'Supplier' else 'debit' + + filters = { + dr_or_cr: ['>', 0], + 'company': company, + 'party_type': party_type, + 'party': ['in', parties], + 'fiscal_year': fiscal_year, + 'is_opening': 'No', + 'is_cancelled': 0 + } + + return frappe.get_all('GL Entry', filters=filters, distinct=1, pluck="voucher_no") or [""] + +def get_advance_vouchers(parties, fiscal_year=None, company=None, from_date=None, to_date=None, party_type='Supplier'): + # for advance vouchers, debit and credit is reversed + dr_or_cr = 'debit' if party_type == 'Supplier' else 'credit' + + filters = { + dr_or_cr: ['>', 0], + 'is_opening': 'No', + 'is_cancelled': 0, + 'party_type': party_type, + 'party': ['in', parties], + 'against_voucher': ['is', 'not set'] + } + + if fiscal_year: + filters['fiscal_year'] = fiscal_year + if company: + filters['company'] = company + if from_date and to_date: + filters['posting_date'] = ['between', (from_date, to_date)] + + return frappe.get_all('GL Entry', filters=filters, distinct=1, pluck='voucher_no') or [""] + +def get_deducted_tax(taxable_vouchers, fiscal_year, tax_details): + # check if TDS / TCS account is already charged on taxable vouchers + filters = { + 'is_cancelled': 0, + 'credit': ['>', 0], + 'fiscal_year': fiscal_year, + 'account': tax_details.account_head, + 'voucher_no': ['in', taxable_vouchers], + } + field = "sum(credit)" + + return frappe.db.get_value('GL Entry', filters, field) or 0.0 + +def get_tds_amount(ldc, parties, inv, tax_details, fiscal_year_details, tax_deducted, vouchers): + tds_amount = 0 + + supp_credit_amt = frappe.db.get_value('Purchase Invoice', { + 'name': ('in', vouchers), 'docstatus': 1, 'apply_tds': 1 + }, 'sum(net_total)') or 0.0 + + supp_jv_credit_amt = frappe.db.get_value('Journal Entry Account', { + 'parent': ('in', vouchers), 'docstatus': 1, + 'party': ('in', parties), 'reference_type': ('!=', 'Purchase Invoice') + }, 'sum(credit_in_account_currency)') or 0.0 + + 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) + supp_credit_amt -= debit_note_amount + + threshold = tax_details.get('threshold', 0) + cumulative_threshold = tax_details.get('cumulative_threshold', 0) + + if ((threshold and supp_credit_amt >= threshold) or (cumulative_threshold and supp_credit_amt >= cumulative_threshold)): + if ldc and is_valid_certificate( + ldc.valid_from, ldc.valid_upto, + inv.posting_date, tax_deducted, + inv.net_total, ldc.certificate_limit + ): + tds_amount = get_ltds_amount(supp_credit_amt, 0, ldc.certificate_limit, ldc.rate, tax_details) + else: + tds_amount = supp_credit_amt * tax_details.rate / 100 if supp_credit_amt > 0 else 0 return tds_amount -def get_advance_vouchers(suppliers, fiscal_year=None, company=None, from_date=None, to_date=None): - condition = "fiscal_year=%s" % fiscal_year +def get_tcs_amount(parties, inv, tax_details, fiscal_year_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', { + 'is_cancelled': 0, + 'party': ['in', parties], + 'company': inv.company, + 'voucher_no': ['in', vouchers], + }, 'sum(debit)') or 0.0 + + # sum of credit entries made from PE / JV with unset 'against voucher' + advance_amt = frappe.db.get_value('GL Entry', { + 'is_cancelled': 0, + 'party': ['in', parties], + 'company': inv.company, + 'voucher_no': ['in', adv_vouchers], + }, 'sum(credit)') or 0.0 + + # sum of credit entries made from sales invoice + credit_note_amt = frappe.db.get_value('GL Entry', { + 'is_cancelled': 0, + 'credit': ['>', 0], + 'party': ['in', parties], + 'fiscal_year': fiscal_year, + 'company': inv.company, + 'voucher_type': 'Sales Invoice', + }, 'sum(credit)') or 0.0 + + cumulative_threshold = tax_details.get('cumulative_threshold', 0) + + current_invoice_total = get_invoice_total_without_tcs(inv, tax_details) + total_invoiced_amt = current_invoice_total + invoiced_amt + advance_amt - credit_note_amt + + if ((cumulative_threshold and total_invoiced_amt >= cumulative_threshold)): + chargeable_amt = total_invoiced_amt - cumulative_threshold + tcs_amount = chargeable_amt * tax_details.rate / 100 if chargeable_amt > 0 else 0 + + return tcs_amount + +def get_invoice_total_without_tcs(inv, tax_details): + tcs_tax_row = [d for d in inv.taxes if d.account_head == tax_details.account_head] + tcs_tax_row_amount = tcs_tax_row[0].base_tax_amount if tcs_tax_row else 0 + + 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): + tds_amount = 0 + limit_consumed = frappe.db.get_value('Purchase Invoice', { + 'supplier': ('in', parties), + 'apply_tds': 1, + 'docstatus': 1 + }, 'sum(net_total)') + + if is_valid_certificate( + ldc.valid_from, ldc.valid_upto, + posting_date, limit_consumed, + net_total, ldc.certificate_limit + ): + tds_amount = get_ltds_amount(net_total, limit_consumed, ldc.certificate_limit, ldc.rate, tax_details) + + return tds_amount + +def get_debit_note_amount(suppliers, fiscal_year_details, company=None): + _, year_start_date, year_end_date = fiscal_year_details + + filters = { + 'supplier': ['in', suppliers], + 'is_return': 1, + 'docstatus': 1, + 'posting_date': ['between', (year_start_date, year_end_date)] + } + fields = ['abs(sum(net_total)) as net_total'] if company: - condition += "and company =%s" % (company) - if from_date and to_date: - condition += "and posting_date between %s and %s" % (from_date, to_date) + filters['company'] = company - ## Appending the same supplier again if length of suppliers list is 1 - ## since tuple of single element list contains None, For example ('Test Supplier 1', ) - ## and the below query fails - if len(suppliers) == 1: - suppliers.append(suppliers[0]) - - return frappe.db.sql_list(""" - select distinct voucher_no - from `tabGL Entry` - where party in %s and %s and debit > 0 - and is_opening = 'No' - """, (tuple(suppliers), condition)) or [] - -def get_debit_note_amount(suppliers, year_start_date, year_end_date, company=None): - condition = "and 1=1" - if company: - condition = " and company=%s " % company - - if len(suppliers) == 1: - suppliers.append(suppliers[0]) - - return flt(frappe.db.sql(""" - select abs(sum(net_total)) - from `tabPurchase Invoice` - where supplier in %s and is_return=1 and docstatus=1 - and posting_date between %s and %s %s - """, (tuple(suppliers), year_start_date, year_end_date, condition))) + return frappe.get_all('Purchase Invoice', filters, fields)[0].get('net_total') or 0.0 def get_ltds_amount(current_amount, deducted_amount, certificate_limit, rate, tax_details): if current_amount < (certificate_limit - deducted_amount): @@ -227,4 +359,4 @@ def is_valid_certificate(valid_from, valid_upto, posting_date, deducted_amount, certificate_limit > deducted_amount): valid = True - return valid \ No newline at end of file + return valid 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 ef77674372b..9ce8e3fe83c 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 @@ -9,7 +9,7 @@ from frappe.utils import today from erpnext.accounts.utils import get_fiscal_year from erpnext.buying.doctype.supplier.test_supplier import create_supplier -test_dependencies = ["Supplier Group"] +test_dependencies = ["Supplier Group", "Customer Group"] class TestTaxWithholdingCategory(unittest.TestCase): @classmethod @@ -18,6 +18,9 @@ class TestTaxWithholdingCategory(unittest.TestCase): create_records() create_tax_with_holding_category() + def tearDown(self): + cancel_invoices() + def test_cumulative_threshold_tds(self): frappe.db.set_value("Supplier", "Test TDS Supplier", "tax_withholding_category", "Cumulative Threshold TDS") invoices = [] @@ -128,9 +131,59 @@ class TestTaxWithholdingCategory(unittest.TestCase): for d in invoices: d.cancel() + def test_cumulative_threshold_tcs(self): + frappe.db.set_value("Customer", "Test TCS Customer", "tax_withholding_category", "Cumulative Threshold TCS") + invoices = [] + + # create invoices for lower than single threshold tax rate + for _ in range(2): + si = create_sales_invoice(customer = "Test TCS Customer") + si.submit() + invoices.append(si) + + # create another invoice whose total when added to previously created invoice, + # surpasses cumulative threshhold + si = create_sales_invoice(customer = "Test TCS Customer", rate=12000) + si.submit() + + # assert tax collection on total invoice amount created until now + tcs_charged = sum([d.base_tax_amount for d in si.taxes if d.account_head == 'TCS - _TC']) + self.assertEqual(tcs_charged, 200) + self.assertEqual(si.grand_total, 12200) + invoices.append(si) + + # TCS is already collected once, so going forward system will collect TCS on every invoice + si = create_sales_invoice(customer = "Test TCS Customer", rate=5000) + si.submit() + + tcs_charged = sum([d.base_tax_amount for d in si.taxes if d.account_head == 'TCS - _TC']) + self.assertEqual(tcs_charged, 500) + invoices.append(si) + + #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']], + 'docstatus': 1 + }, pluck="name") + + sales_invoices = frappe.get_all("Sales Invoice", { + 'customer': 'Test TCS Customer', + 'docstatus': 1 + }, pluck="name") + + for d in purchase_invoices: + frappe.get_doc('Purchase Invoice', d).cancel() + + for d in sales_invoices: + frappe.get_doc('Sales Invoice', d).cancel() + def create_purchase_invoice(**args): # return sales invoice doc object - item = frappe.get_doc('Item', {'item_name': 'TDS Item'}) + item = frappe.db.get_value('Item', {'item_name': 'TDS Item'}, "name") args = frappe._dict(args) pi = frappe.get_doc({ @@ -145,7 +198,7 @@ def create_purchase_invoice(**args): "taxes": [], "items": [{ 'doctype': 'Purchase Invoice Item', - 'item_code': item.name, + 'item_code': item, 'qty': args.qty or 1, 'rate': args.rate or 10000, 'cost_center': 'Main - _TC', @@ -156,6 +209,33 @@ def create_purchase_invoice(**args): pi.save() return pi +def create_sales_invoice(**args): + # return sales invoice doc object + item = frappe.db.get_value('Item', {'item_name': 'TCS Item'}, "name") + + args = frappe._dict(args) + si = frappe.get_doc({ + "doctype": "Sales Invoice", + "posting_date": today(), + "customer": args.customer, + "company": '_Test Company', + "taxes_and_charges": "", + "currency": "INR", + "debit_to": "Debtors - _TC", + "taxes": [], + "items": [{ + 'doctype': 'Sales Invoice Item', + 'item_code': item, + 'qty': args.qty or 1, + 'rate': args.rate or 10000, + 'cost_center': 'Main - _TC', + 'expense_account': 'Cost of Goods Sold - _TC' + }] + }) + + si.save() + return si + def create_records(): # create a new suppliers for name in ['Test TDS Supplier', 'Test TDS Supplier1', 'Test TDS Supplier2']: @@ -168,7 +248,17 @@ def create_records(): "doctype": "Supplier", }).insert() - # create an item + for name in ['Test TCS Customer']: + if frappe.db.exists('Customer', name): + continue + + frappe.get_doc({ + "customer_group": "_Test Customer Group", + "customer_name": name, + "doctype": "Customer" + }).insert() + + # create item if not frappe.db.exists('Item', "TDS Item"): frappe.get_doc({ "doctype": "Item", @@ -178,7 +268,16 @@ def create_records(): "is_stock_item": 0, }).insert() - # create an account + if not frappe.db.exists('Item', "TCS Item"): + frappe.get_doc({ + "doctype": "Item", + "item_code": "TCS Item", + "item_name": "TCS Item", + "item_group": "All Item Groups", + "is_stock_item": 1 + }).insert() + + # create tds account if not frappe.db.exists("Account", "TDS - _TC"): frappe.get_doc({ 'doctype': 'Account', @@ -189,6 +288,17 @@ def create_records(): 'root_type': 'Asset' }).insert() + # create tcs account + if not frappe.db.exists("Account", "TCS - _TC"): + frappe.get_doc({ + 'doctype': 'Account', + 'company': '_Test Company', + 'account_name': 'TCS', + 'parent_account': 'Duties and Taxes - _TC', + 'report_type': 'Balance Sheet', + 'root_type': 'Liability' + }).insert() + def create_tax_with_holding_category(): fiscal_year = get_fiscal_year(today(), company="_Test Company")[0] @@ -210,6 +320,23 @@ def create_tax_with_holding_category(): }] }).insert() + if not frappe.db.exists("Tax Withholding Category", "Cumulative Threshold TCS"): + frappe.get_doc({ + "doctype": "Tax Withholding Category", + "name": "Cumulative Threshold TCS", + "category_name": "10% TCS", + "rates": [{ + 'fiscal_year': fiscal_year, + 'tax_withholding_rate': 10, + 'single_threshold': 0, + 'cumulative_threshold': 30000.00 + }], + "accounts": [{ + 'company': '_Test Company', + 'account': 'TCS - _TC' + }] + }).insert() + # Single thresold if not frappe.db.exists("Tax Withholding Category", "Single Threshold TDS"): frappe.get_doc({ diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index b42c0c61d9e..dac0c216c8a 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -196,7 +196,7 @@ def make_round_off_gle(gl_map, debit_credit_diff, precision): if not round_off_gle: for k in ["voucher_type", "voucher_no", "company", - "posting_date", "remarks", "is_opening"]: + "posting_date", "remarks"]: round_off_gle[k] = gl_map[0][k] round_off_gle.update({ @@ -208,6 +208,7 @@ def make_round_off_gle(gl_map, debit_credit_diff, precision): "cost_center": round_off_cost_center, "party_type": None, "party": None, + "is_opening": "No", "against_voucher_type": None, "against_voucher": None }) diff --git a/erpnext/accounts/module_onboarding/accounts/accounts.json b/erpnext/accounts/module_onboarding/accounts/accounts.json index 570d2bd98e2..6b5c5a1db88 100644 --- a/erpnext/accounts/module_onboarding/accounts/accounts.json +++ b/erpnext/accounts/module_onboarding/accounts/accounts.json @@ -13,7 +13,7 @@ "documentation_url": "https://docs.erpnext.com/docs/user/manual/en/accounts", "idx": 0, "is_complete": 0, - "modified": "2020-07-08 14:06:09.033880", + "modified": "2020-10-30 15:41:15.547225", "modified_by": "Administrator", "module": "Accounts", "name": "Accounts", diff --git a/erpnext/accounts/onboarding_step/chart_of_accounts/chart_of_accounts.json b/erpnext/accounts/onboarding_step/chart_of_accounts/chart_of_accounts.json index 48637bf550b..fc49bd652b3 100644 --- a/erpnext/accounts/onboarding_step/chart_of_accounts/chart_of_accounts.json +++ b/erpnext/accounts/onboarding_step/chart_of_accounts/chart_of_accounts.json @@ -1,19 +1,24 @@ { "action": "Go to Page", + "action_label": "View Chart of Accounts", + "callback_message": "You can continue with the onboarding after exploring this page", + "callback_title": "Awesome Work", "creation": "2020-05-13 19:58:20.928127", + "description": "# Chart Of Accounts\n\nThe Chart of Accounts is the blueprint of the accounts in your organization.\nIt is a tree view of the names of the Accounts (Ledgers and Groups) that a Company requires to manage its books of accounts. ERPNext sets up a simple chart of accounts for each Company you create, but you can modify it according to your needs and legal requirements.\n\nFor each company, Chart of Accounts signifies the way to classify the accounting entries, mostly\nbased on statutory (tax, compliance to government regulations) requirements.\n\nThere's a brief video tutorial about chart of accounts in the next step.", "docstatus": 0, "doctype": "Onboarding Step", "idx": 0, + "intro_video_url": "https://www.youtube.com/embed/AcfMCT7wLLo", "is_complete": 0, - "is_mandatory": 0, "is_single": 0, "is_skipped": 0, - "modified": "2020-05-14 17:40:28.410447", + "modified": "2020-10-30 14:35:59.474920", "modified_by": "Administrator", "name": "Chart of Accounts", "owner": "Administrator", "path": "Tree/Account", "reference_document": "Account", + "show_form_tour": 0, "show_full_form": 0, "title": "Review Chart of Accounts", "validate_action": 0 diff --git a/erpnext/accounts/onboarding_step/configure_account_settings/configure_account_settings.json b/erpnext/accounts/onboarding_step/configure_account_settings/configure_account_settings.json index c8be357de0a..c84430a0c64 100644 --- a/erpnext/accounts/onboarding_step/configure_account_settings/configure_account_settings.json +++ b/erpnext/accounts/onboarding_step/configure_account_settings/configure_account_settings.json @@ -1,18 +1,19 @@ { - "action": "Create Entry", + "action": "Show Form Tour", "creation": "2020-05-14 17:53:00.876946", + "description": "# Account Settings\n\nThis is a crucial piece of configuration. There are various account settings in ERPNext to restrict and configure actions in the Accounting module.\n\nThe following settings are avaialble for you to configure\n\n1. Account Freezing \n2. Credit and Overbilling\n3. Invoicing and Tax Automations\n4. Balance Sheet configurations\n\nThere's much more, you can check it all out in this step", "docstatus": 0, "doctype": "Onboarding Step", "idx": 0, "is_complete": 0, - "is_mandatory": 0, "is_single": 1, "is_skipped": 0, - "modified": "2020-05-14 18:06:25.212923", + "modified": "2020-10-19 14:40:55.584484", "modified_by": "Administrator", "name": "Configure Account Settings", "owner": "Administrator", "reference_document": "Accounts Settings", + "show_form_tour": 0, "show_full_form": 1, "title": "Configure Account Settings", "validate_action": 1 diff --git a/erpnext/accounts/onboarding_step/create_a_customer/create_a_customer.json b/erpnext/accounts/onboarding_step/create_a_customer/create_a_customer.json index 5a403b06cf0..0b6750c5f8e 100644 --- a/erpnext/accounts/onboarding_step/create_a_customer/create_a_customer.json +++ b/erpnext/accounts/onboarding_step/create_a_customer/create_a_customer.json @@ -1,18 +1,19 @@ { "action": "Create Entry", "creation": "2020-05-14 17:46:41.831517", + "description": "## Who is a Customer?\n\nA customer, who is sometimes known as a client, buyer, or purchaser is the one who receives goods, services, products, or ideas, from a seller for a monetary consideration.\n\nEvery customer needs to be assigned a unique id. Customer name itself can be the id or you can set a naming series for ids to be generated in Selling Settings.\n\nJust like the supplier, let's quickly create a customer.", "docstatus": 0, "doctype": "Onboarding Step", "idx": 0, "is_complete": 0, - "is_mandatory": 0, "is_single": 0, "is_skipped": 0, - "modified": "2020-06-01 13:16:19.731719", + "modified": "2020-10-30 15:28:46.659660", "modified_by": "Administrator", "name": "Create a Customer", "owner": "Administrator", "reference_document": "Customer", + "show_form_tour": 0, "show_full_form": 0, "title": "Create a Customer", "validate_action": 1 diff --git a/erpnext/accounts/onboarding_step/create_a_product/create_a_product.json b/erpnext/accounts/onboarding_step/create_a_product/create_a_product.json index d2068e167b7..d76f645d96f 100644 --- a/erpnext/accounts/onboarding_step/create_a_product/create_a_product.json +++ b/erpnext/accounts/onboarding_step/create_a_product/create_a_product.json @@ -1,19 +1,21 @@ { "action": "Create Entry", "creation": "2020-05-12 18:16:06.624554", + "description": "## Products and Services\n\nDepending on the nature of your business, you might be selling products or services to your clients or even both. \nERPNext is optimized for itemized management of your sales and purchase.\n\nThe **Item Master** is where you can add all your sales items. If you are in services, you can create an Item for each service that you offer. If you run a manufacturing business, the same master is used for keeping a record of raw materials, sub-assemblies etc.\n\nCompleting the Item Master is very essential for the successful implementation of ERPNext. We have a brief video introducing the item master for you, you can watch it in the next step.", "docstatus": 0, "doctype": "Onboarding Step", "idx": 0, + "intro_video_url": "https://www.youtube.com/watch?v=Sl5UFA5H5EQ", "is_complete": 0, - "is_mandatory": 0, "is_single": 0, "is_skipped": 0, - "modified": "2020-05-12 18:30:02.489949", + "modified": "2020-10-30 15:20:30.133495", "modified_by": "Administrator", "name": "Create a Product", "owner": "Administrator", "reference_document": "Item", + "show_form_tour": 0, "show_full_form": 0, - "title": "Create a Product", + "title": "Create a Sales Item", "validate_action": 1 } \ No newline at end of file diff --git a/erpnext/accounts/onboarding_step/create_a_supplier/create_a_supplier.json b/erpnext/accounts/onboarding_step/create_a_supplier/create_a_supplier.json index 7a64224bd43..64bc7bbfef9 100644 --- a/erpnext/accounts/onboarding_step/create_a_supplier/create_a_supplier.json +++ b/erpnext/accounts/onboarding_step/create_a_supplier/create_a_supplier.json @@ -1,18 +1,19 @@ { "action": "Create Entry", "creation": "2020-05-14 22:09:10.043554", + "description": "## Who is a Supplier?\n\nSuppliers are companies or individuals who provide you with products or services. ERPNext has comprehensive features for purchase cycles. \n\nLet's quickly create a supplier with the minimal details required. You need the name of the supplier, assign the supplier to a group, and select the type of the supplier, viz. Company or Individual.", "docstatus": 0, "doctype": "Onboarding Step", "idx": 0, "is_complete": 0, - "is_mandatory": 0, "is_single": 0, "is_skipped": 0, - "modified": "2020-05-14 22:09:10.043554", + "modified": "2020-10-30 15:26:48.315772", "modified_by": "Administrator", "name": "Create a Supplier", "owner": "Administrator", "reference_document": "Supplier", + "show_form_tour": 0, "show_full_form": 0, "title": "Create a Supplier", "validate_action": 1 diff --git a/erpnext/accounts/onboarding_step/create_your_first_purchase_invoice/create_your_first_purchase_invoice.json b/erpnext/accounts/onboarding_step/create_your_first_purchase_invoice/create_your_first_purchase_invoice.json index 3a2b8d39253..ddbc89ec0a7 100644 --- a/erpnext/accounts/onboarding_step/create_your_first_purchase_invoice/create_your_first_purchase_invoice.json +++ b/erpnext/accounts/onboarding_step/create_your_first_purchase_invoice/create_your_first_purchase_invoice.json @@ -1,18 +1,19 @@ { "action": "Create Entry", "creation": "2020-05-14 22:10:07.049704", + "description": "# What's a Purchase Invoice?\n\nA Purchase Invoice is a bill you receive from your Suppliers against which you need to make the payment.\nPurchase Invoice is the exact opposite of your Sales Invoice. Here you accrue expenses to your Supplier. \n\nThe following is what a typical purchase cycle looks like, however you can create a purchase invoice directly as well.\n\n![Purchase Flow](https://docs.erpnext.com/docs/assets/img/accounts/pi-flow.png)\n\n", "docstatus": 0, "doctype": "Onboarding Step", "idx": 0, "is_complete": 0, - "is_mandatory": 0, "is_single": 0, "is_skipped": 0, - "modified": "2020-05-14 22:10:07.049704", + "modified": "2020-10-30 15:30:26.337773", "modified_by": "Administrator", "name": "Create Your First Purchase Invoice", "owner": "Administrator", "reference_document": "Purchase Invoice", + "show_form_tour": 0, "show_full_form": 1, "title": "Create Your First Purchase Invoice ", "validate_action": 1 diff --git a/erpnext/accounts/onboarding_step/create_your_first_sales_invoice/create_your_first_sales_invoice.json b/erpnext/accounts/onboarding_step/create_your_first_sales_invoice/create_your_first_sales_invoice.json index 473de5079f5..9e7dd679001 100644 --- a/erpnext/accounts/onboarding_step/create_your_first_sales_invoice/create_your_first_sales_invoice.json +++ b/erpnext/accounts/onboarding_step/create_your_first_sales_invoice/create_your_first_sales_invoice.json @@ -1,18 +1,19 @@ { "action": "Create Entry", "creation": "2020-05-14 17:48:21.019019", + "description": "# All about sales invoice\n\nA Sales Invoice is a bill that you send to your Customers against which the Customer makes the payment. Sales Invoice is an accounting transaction. On submission of Sales Invoice, the system updates the receivable and books income against a Customer Account.\n\nHere's the flow of how a sales invoice is generally created\n\n\n![Sales Flow](https://docs.erpnext.com/docs/assets/img/accounts/so-flow.png)", "docstatus": 0, "doctype": "Onboarding Step", "idx": 0, "is_complete": 0, - "is_mandatory": 0, "is_single": 0, "is_skipped": 0, - "modified": "2020-05-14 17:48:21.019019", + "modified": "2020-10-16 12:59:16.987507", "modified_by": "Administrator", "name": "Create Your First Sales Invoice", "owner": "Administrator", "reference_document": "Sales Invoice", + "show_form_tour": 0, "show_full_form": 1, "title": "Create Your First Sales Invoice ", "validate_action": 1 diff --git a/erpnext/accounts/onboarding_step/setup_taxes/setup_taxes.json b/erpnext/accounts/onboarding_step/setup_taxes/setup_taxes.json index 8e0006762d1..a4922013dab 100644 --- a/erpnext/accounts/onboarding_step/setup_taxes/setup_taxes.json +++ b/erpnext/accounts/onboarding_step/setup_taxes/setup_taxes.json @@ -1,18 +1,20 @@ { "action": "Create Entry", + "action_label": "Make a Sales Tax Template", "creation": "2020-05-13 19:29:43.844463", + "description": "# Setting up Taxes\n\nAny sophisticated accounting system, including ERPNext will have automatic tax calculations for your transactions. These calculations are based on user defined rules in compliance to local rules and regulations.\n\nERPNext allows this via *Tax Templates*. These templates can be used in Sales Orders and Sales Invoices. Other types of charges that may apply to your invoices (like shipping, insurance etc.) can also be configured as taxes.\n\nFor Tax Accounts that you want to use in the tax templates, go to:\n\n`> Accounting > Taxes > Sales Taxes and Charges Template`\n\nYou can read more about these templates in our documentation [here](https://docs.erpnext.com/docs/user/manual/en/selling/sales-taxes-and-charges-template)\n", "docstatus": 0, "doctype": "Onboarding Step", "idx": 0, "is_complete": 0, - "is_mandatory": 0, "is_single": 0, "is_skipped": 0, - "modified": "2020-05-14 17:40:16.014413", + "modified": "2020-10-30 14:54:18.087383", "modified_by": "Administrator", "name": "Setup Taxes", "owner": "Administrator", "reference_document": "Sales Taxes and Charges Template", + "show_form_tour": 1, "show_full_form": 1, "title": "Lets create a Tax Template for Sales ", "validate_action": 0 diff --git a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.js b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.js deleted file mode 100644 index 6ae81d74021..00000000000 --- a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.js +++ /dev/null @@ -1,583 +0,0 @@ -frappe.provide("erpnext.accounts"); - -frappe.pages['bank-reconciliation'].on_page_load = function(wrapper) { - new erpnext.accounts.bankReconciliation(wrapper); -} - -erpnext.accounts.bankReconciliation = class BankReconciliation { - constructor(wrapper) { - this.page = frappe.ui.make_app_page({ - parent: wrapper, - title: __("Bank Reconciliation"), - single_column: true - }); - this.parent = wrapper; - this.page = this.parent.page; - - this.check_plaid_status(); - this.make(); - } - - make() { - const me = this; - - me.$main_section = $(`
    `).appendTo(me.page.main); - const empty_state = __("Upload a bank statement, link or reconcile a bank account") - me.$main_section.append(`
    ${empty_state}
    `) - - me.page.add_field({ - fieldtype: 'Link', - label: __('Company'), - fieldname: 'company', - options: "Company", - onchange: function() { - if (this.value) { - me.company = this.value; - } else { - me.company = null; - me.bank_account = null; - } - } - }) - me.page.add_field({ - fieldtype: 'Link', - label: __('Bank Account'), - fieldname: 'bank_account', - options: "Bank Account", - get_query: function() { - if(!me.company) { - frappe.throw(__("Please select company first")); - return - } - - return { - filters: { - "company": me.company - } - } - }, - onchange: function() { - if (this.value) { - me.bank_account = this.value; - me.add_actions(); - } else { - me.bank_account = null; - me.page.hide_actions_menu(); - } - } - }) - } - - check_plaid_status() { - const me = this; - frappe.db.get_value("Plaid Settings", "Plaid Settings", "enabled", (r) => { - if (r && r.enabled === "1") { - me.plaid_status = "active" - } else { - me.plaid_status = "inactive" - } - }) - } - - add_actions() { - const me = this; - - me.page.show_menu() - - me.page.add_menu_item(__("Upload a statement"), function() { - me.clear_page_content(); - new erpnext.accounts.bankTransactionUpload(me); - }, true) - - if (me.plaid_status==="active") { - me.page.add_menu_item(__("Synchronize this account"), function() { - me.clear_page_content(); - new erpnext.accounts.bankTransactionSync(me); - }, true) - } - - me.page.add_menu_item(__("Reconcile this account"), function() { - me.clear_page_content(); - me.make_reconciliation_tool(); - }, true) - } - - clear_page_content() { - const me = this; - $(me.page.body).find('.frappe-list').remove(); - me.$main_section.empty(); - } - - make_reconciliation_tool() { - const me = this; - frappe.model.with_doctype("Bank Transaction", () => { - erpnext.accounts.ReconciliationList = new erpnext.accounts.ReconciliationTool({ - parent: me.parent, - doctype: "Bank Transaction" - }); - }) - } -} - - -erpnext.accounts.bankTransactionUpload = class bankTransactionUpload { - constructor(parent) { - this.parent = parent; - this.data = []; - - const assets = [ - "/assets/frappe/css/frappe-datatable.css", - "/assets/frappe/js/lib/clusterize.min.js", - "/assets/frappe/js/lib/Sortable.min.js", - "/assets/frappe/js/lib/frappe-datatable.js" - ]; - - frappe.require(assets, () => { - this.make(); - }); - } - - make() { - const me = this; - new frappe.ui.FileUploader({ - method: 'erpnext.accounts.doctype.bank_transaction.bank_transaction_upload.upload_bank_statement', - allow_multiple: 0, - on_success: function(attachment, r) { - if (!r.exc && r.message) { - me.data = r.message; - me.setup_transactions_dom(); - me.create_datatable(); - me.add_primary_action(); - } - } - }) - } - - setup_transactions_dom() { - const me = this; - me.parent.$main_section.append('
    '); - } - - create_datatable() { - try { - this.datatable = new DataTable('.transactions-table', { - columns: this.data.columns, - data: this.data.data - }) - } - catch(err) { - let msg = __("Your file could not be processed. It should be a standard CSV or XLSX file with headers in the first row."); - frappe.throw(msg) - } - - } - - add_primary_action() { - const me = this; - me.parent.page.set_primary_action(__("Submit"), function() { - me.add_bank_entries() - }, null, __("Creating bank entries...")) - } - - add_bank_entries() { - const me = this; - frappe.xcall('erpnext.accounts.doctype.bank_transaction.bank_transaction_upload.create_bank_entries', - {columns: this.datatable.datamanager.columns, data: this.datatable.datamanager.data, bank_account: me.parent.bank_account} - ).then((result) => { - let result_title = result.errors == 0 ? __("{0} bank transaction(s) created", [result.success]) : __("{0} bank transaction(s) created and {1} errors", [result.success, result.errors]) - let result_msg = ` -
    -
    ${result_title}
    -
    ` - me.parent.page.clear_primary_action(); - me.parent.$main_section.empty(); - me.parent.$main_section.append(result_msg); - if (result.errors == 0) { - frappe.show_alert({message:__("All bank transactions have been created"), indicator:'green'}); - } else { - frappe.show_alert({message:__("Please check the error log for details about the import errors"), indicator:'red'}); - } - }) - } -} - -erpnext.accounts.bankTransactionSync = class bankTransactionSync { - constructor(parent) { - this.parent = parent; - this.data = []; - - this.init_config() - } - - init_config() { - const me = this; - frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.get_plaid_configuration') - .then(result => { - me.plaid_env = result.plaid_env; - me.client_name = result.client_name; - me.link_token = result.link_token; - me.sync_transactions(); - }) - } - - sync_transactions() { - const me = this; - frappe.db.get_value("Bank Account", me.parent.bank_account, "bank", (r) => { - frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.sync_transactions', { - bank: r.bank, - bank_account: me.parent.bank_account, - freeze: true - }) - .then((result) => { - let result_title = (result && result.length > 0) - ? __("{0} bank transaction(s) created", [result.length]) - : __("This bank account is already synchronized"); - - let result_msg = ` -
    -
    ${result_title}
    -
    ` - - this.parent.$main_section.append(result_msg) - frappe.show_alert({ message: __("Bank account '{0}' has been synchronized", [me.parent.bank_account]), indicator: 'green' }); - }) - }) - } -} - - -erpnext.accounts.ReconciliationTool = class ReconciliationTool extends frappe.views.BaseList { - constructor(opts) { - super(opts); - this.show(); - } - - setup_defaults() { - super.setup_defaults(); - - this.page_title = __("Bank Reconciliation"); - this.doctype = 'Bank Transaction'; - this.fields = ['date', 'description', 'debit', 'credit', 'currency'] - - } - - setup_view() { - this.render_header(); - } - - setup_side_bar() { - // - } - - make_standard_filters() { - // - } - - freeze() { - this.$result.find('.list-count').html(`${__('Refreshing')}...`); - } - - get_args() { - const args = super.get_args(); - - return Object.assign({}, args, { - ...args.filters.push(["Bank Transaction", "docstatus", "=", 1], - ["Bank Transaction", "unallocated_amount", ">", 0]) - }); - - } - - update_data(r) { - let data = r.message || []; - - if (this.start === 0) { - this.data = data; - } else { - this.data = this.data.concat(data); - } - } - - render() { - const me = this; - this.$result.find('.list-row-container').remove(); - $('[data-fieldname="name"]').remove(); - me.data.map((value) => { - const row = $('
    ').data("data", value).appendTo(me.$result).get(0); - new erpnext.accounts.ReconciliationRow(row, value); - }) - } - - render_header() { - const me = this; - if ($(this.wrapper).find('.transaction-header').length === 0) { - me.$result.append(frappe.render_template("bank_transaction_header")); - } - } -} - -erpnext.accounts.ReconciliationRow = class ReconciliationRow { - constructor(row, data) { - this.data = data; - this.row = row; - this.make(); - this.bind_events(); - } - - make() { - $(this.row).append(frappe.render_template("bank_transaction_row", this.data)) - } - - bind_events() { - const me = this; - $(me.row).on('click', '.clickable-section', function() { - me.bank_entry = $(this).attr("data-name"); - me.show_dialog($(this).attr("data-name")); - }) - - $(me.row).on('click', '.new-reconciliation', function() { - me.bank_entry = $(this).attr("data-name"); - me.show_dialog($(this).attr("data-name")); - }) - - $(me.row).on('click', '.new-payment', function() { - me.bank_entry = $(this).attr("data-name"); - me.new_payment(); - }) - - $(me.row).on('click', '.new-invoice', function() { - me.bank_entry = $(this).attr("data-name"); - me.new_invoice(); - }) - - $(me.row).on('click', '.new-expense', function() { - me.bank_entry = $(this).attr("data-name"); - me.new_expense(); - }) - } - - new_payment() { - const me = this; - const paid_amount = me.data.credit > 0 ? me.data.credit : me.data.debit; - const payment_type = me.data.credit > 0 ? "Receive": "Pay"; - const party_type = me.data.credit > 0 ? "Customer": "Supplier"; - - frappe.new_doc("Payment Entry", {"payment_type": payment_type, "paid_amount": paid_amount, - "party_type": party_type, "paid_from": me.data.bank_account}) - } - - new_invoice() { - const me = this; - const invoice_type = me.data.credit > 0 ? "Sales Invoice" : "Purchase Invoice"; - - frappe.new_doc(invoice_type) - } - - new_expense() { - frappe.new_doc("Expense Claim") - } - - - show_dialog(data) { - const me = this; - - frappe.db.get_value("Bank Account", me.data.bank_account, "account", (r) => { - me.gl_account = r.account; - }) - - frappe.xcall('erpnext.accounts.page.bank_reconciliation.bank_reconciliation.get_linked_payments', - { bank_transaction: data, freeze: true, freeze_message: __("Finding linked payments") } - ).then((result) => { - me.make_dialog(result) - }) - } - - make_dialog(data) { - const me = this; - me.selected_payment = null; - - const fields = [ - { - fieldtype: 'Section Break', - fieldname: 'section_break_1', - label: __('Automatic Reconciliation') - }, - { - fieldtype: 'HTML', - fieldname: 'payment_proposals' - }, - { - fieldtype: 'Section Break', - fieldname: 'section_break_2', - label: __('Search for a payment') - }, - { - fieldtype: 'Link', - fieldname: 'payment_doctype', - options: 'DocType', - label: 'Payment DocType', - get_query: () => { - return { - filters : { - "name": ["in", ["Payment Entry", "Journal Entry", "Sales Invoice", "Purchase Invoice", "Expense Claim"]] - } - } - }, - }, - { - fieldtype: 'Column Break', - fieldname: 'column_break_1', - }, - { - fieldtype: 'Dynamic Link', - fieldname: 'payment_entry', - options: 'payment_doctype', - label: 'Payment Document', - get_query: () => { - let dt = this.dialog.fields_dict.payment_doctype.value; - if (dt === "Payment Entry") { - return { - query: "erpnext.accounts.page.bank_reconciliation.bank_reconciliation.payment_entry_query", - filters : { - "bank_account": this.data.bank_account, - "company": this.data.company - } - } - } else if (dt === "Journal Entry") { - return { - query: "erpnext.accounts.page.bank_reconciliation.bank_reconciliation.journal_entry_query", - filters : { - "bank_account": this.data.bank_account, - "company": this.data.company - } - } - } else if (dt === "Sales Invoice") { - return { - query: "erpnext.accounts.page.bank_reconciliation.bank_reconciliation.sales_invoices_query" - } - } else if (dt === "Purchase Invoice") { - return { - filters : [ - ["Purchase Invoice", "ifnull(clearance_date, '')", "=", ""], - ["Purchase Invoice", "docstatus", "=", 1], - ["Purchase Invoice", "company", "=", this.data.company] - ] - } - } else if (dt === "Expense Claim") { - return { - filters : [ - ["Expense Claim", "ifnull(clearance_date, '')", "=", ""], - ["Expense Claim", "docstatus", "=", 1], - ["Expense Claim", "company", "=", this.data.company] - ] - } - } - }, - onchange: function() { - if (me.selected_payment !== this.value) { - me.selected_payment = this.value; - me.display_payment_details(this); - } - } - }, - { - fieldtype: 'Section Break', - fieldname: 'section_break_3' - }, - { - fieldtype: 'HTML', - fieldname: 'payment_details' - }, - ]; - - me.dialog = new frappe.ui.Dialog({ - title: __("Choose a corresponding payment"), - fields: fields, - size: "large" - }); - - const proposals_wrapper = me.dialog.fields_dict.payment_proposals.$wrapper; - if (data && data.length > 0) { - proposals_wrapper.append(frappe.render_template("linked_payment_header")); - data.map(value => { - proposals_wrapper.append(frappe.render_template("linked_payment_row", value)) - }) - } else { - const empty_data_msg = __("ERPNext could not find any matching payment entry") - proposals_wrapper.append(`
    ${empty_data_msg}
    `) - } - - $(me.dialog.body).on('click', '.reconciliation-btn', (e) => { - const payment_entry = $(e.target).attr('data-name'); - const payment_doctype = $(e.target).attr('data-doctype'); - frappe.xcall('erpnext.accounts.page.bank_reconciliation.bank_reconciliation.reconcile', - {bank_transaction: me.bank_entry, payment_doctype: payment_doctype, payment_name: payment_entry}) - .then((result) => { - setTimeout(function(){ - erpnext.accounts.ReconciliationList.refresh(); - }, 2000); - me.dialog.hide(); - }) - }) - - me.dialog.show(); - } - - display_payment_details(event) { - const me = this; - if (event.value) { - let dt = me.dialog.fields_dict.payment_doctype.value; - me.dialog.fields_dict['payment_details'].$wrapper.empty(); - frappe.db.get_doc(dt, event.value) - .then(doc => { - let displayed_docs = [] - let payment = [] - if (dt === "Payment Entry") { - payment.currency = doc.payment_type == "Receive" ? doc.paid_to_account_currency : doc.paid_from_account_currency; - payment.doctype = dt - payment.posting_date = doc.posting_date; - payment.party = doc.party; - payment.reference_no = doc.reference_no; - payment.reference_date = doc.reference_date; - payment.paid_amount = doc.paid_amount; - payment.name = doc.name; - displayed_docs.push(payment); - } else if (dt === "Journal Entry") { - doc.accounts.forEach(payment => { - if (payment.account === me.gl_account) { - payment.doctype = dt; - payment.posting_date = doc.posting_date; - payment.party = doc.pay_to_recd_from; - payment.reference_no = doc.cheque_no; - payment.reference_date = doc.cheque_date; - payment.currency = payment.account_currency; - payment.paid_amount = payment.credit > 0 ? payment.credit : payment.debit; - payment.name = doc.name; - displayed_docs.push(payment); - } - }) - } else if (dt === "Sales Invoice") { - doc.payments.forEach(payment => { - if (payment.clearance_date === null || payment.clearance_date === "") { - payment.doctype = dt; - payment.posting_date = doc.posting_date; - payment.party = doc.customer; - payment.reference_no = doc.remarks; - payment.currency = doc.currency; - payment.paid_amount = payment.amount; - payment.name = doc.name; - displayed_docs.push(payment); - } - }) - } - - const details_wrapper = me.dialog.fields_dict.payment_details.$wrapper; - details_wrapper.append(frappe.render_template("linked_payment_header")); - displayed_docs.forEach(payment => { - details_wrapper.append(frappe.render_template("linked_payment_row", payment)); - }) - }) - } - - } -} diff --git a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.json b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.json deleted file mode 100644 index feea36860b1..00000000000 --- a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "content": null, - "creation": "2018-11-24 12:03:14.646669", - "docstatus": 0, - "doctype": "Page", - "idx": 0, - "modified": "2018-11-24 12:03:14.646669", - "modified_by": "Administrator", - "module": "Accounts", - "name": "bank-reconciliation", - "owner": "Administrator", - "page_name": "bank-reconciliation", - "roles": [ - { - "role": "System Manager" - }, - { - "role": "Accounts Manager" - }, - { - "role": "Accounts User" - } - ], - "script": null, - "standard": "Yes", - "style": null, - "system_page": 0, - "title": "Bank Reconciliation" -} \ No newline at end of file diff --git a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.py b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.py deleted file mode 100644 index 8abe20c00a4..00000000000 --- a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.py +++ /dev/null @@ -1,369 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals -import frappe -from frappe import _ -import difflib -from frappe.utils import flt -from six import iteritems -from erpnext import get_company_currency - -@frappe.whitelist() -def reconcile(bank_transaction, payment_doctype, payment_name): - transaction = frappe.get_doc("Bank Transaction", bank_transaction) - payment_entry = frappe.get_doc(payment_doctype, payment_name) - - account = frappe.db.get_value("Bank Account", transaction.bank_account, "account") - gl_entry = frappe.get_doc("GL Entry", dict(account=account, voucher_type=payment_doctype, voucher_no=payment_name)) - - if payment_doctype == "Payment Entry" and payment_entry.unallocated_amount > transaction.unallocated_amount: - frappe.throw(_("The unallocated amount of Payment Entry {0} is greater than the Bank Transaction's unallocated amount").format(payment_name)) - - if transaction.unallocated_amount == 0: - frappe.throw(_("This bank transaction is already fully reconciled")) - - if transaction.credit > 0 and gl_entry.credit > 0: - frappe.throw(_("The selected payment entry should be linked with a debtor bank transaction")) - - if transaction.debit > 0 and gl_entry.debit > 0: - frappe.throw(_("The selected payment entry should be linked with a creditor bank transaction")) - - add_payment_to_transaction(transaction, payment_entry, gl_entry) - - return 'reconciled' - -def add_payment_to_transaction(transaction, payment_entry, gl_entry): - gl_amount, transaction_amount = (gl_entry.credit, transaction.debit) if gl_entry.credit > 0 else (gl_entry.debit, transaction.credit) - allocated_amount = gl_amount if gl_amount <= transaction_amount else transaction_amount - transaction.append("payment_entries", { - "payment_document": payment_entry.doctype, - "payment_entry": payment_entry.name, - "allocated_amount": allocated_amount - }) - - transaction.save() - transaction.update_allocations() - -@frappe.whitelist() -def get_linked_payments(bank_transaction): - transaction = frappe.get_doc("Bank Transaction", bank_transaction) - bank_account = frappe.db.get_values("Bank Account", transaction.bank_account, ["account", "company"], as_dict=True) - - # Get all payment entries with a matching amount - amount_matching = check_matching_amount(bank_account[0].account, bank_account[0].company, transaction) - - # Get some data from payment entries linked to a corresponding bank transaction - description_matching = get_matching_descriptions_data(bank_account[0].company, transaction) - - if amount_matching: - return check_amount_vs_description(amount_matching, description_matching) - - elif description_matching: - description_matching = filter(lambda x: not x.get('clearance_date'), description_matching) - if not description_matching: - return [] - - return sorted(list(description_matching), key = lambda x: x["posting_date"], reverse=True) - - else: - return [] - -def check_matching_amount(bank_account, company, transaction): - payments = [] - amount = transaction.credit if transaction.credit > 0 else transaction.debit - - payment_type = "Receive" if transaction.credit > 0 else "Pay" - account_from_to = "paid_to" if transaction.credit > 0 else "paid_from" - currency_field = "paid_to_account_currency as currency" if transaction.credit > 0 else "paid_from_account_currency as currency" - - payment_entries = frappe.get_all("Payment Entry", fields=["'Payment Entry' as doctype", "name", "paid_amount", "payment_type", "reference_no", "reference_date", - "party", "party_type", "posting_date", "{0}".format(currency_field)], filters=[["paid_amount", "like", "{0}%".format(amount)], - ["docstatus", "=", "1"], ["payment_type", "=", [payment_type, "Internal Transfer"]], ["ifnull(clearance_date, '')", "=", ""], ["{0}".format(account_from_to), "=", "{0}".format(bank_account)]]) - - jea_side = "debit" if transaction.credit > 0 else "credit" - journal_entries = frappe.db.sql(f""" - SELECT - 'Journal Entry' as doctype, je.name, je.posting_date, je.cheque_no as reference_no, - jea.account_currency as currency, je.pay_to_recd_from as party, je.cheque_date as reference_date, - jea.{jea_side}_in_account_currency as paid_amount - FROM - `tabJournal Entry Account` as jea - JOIN - `tabJournal Entry` as je - ON - jea.parent = je.name - WHERE - (je.clearance_date is null or je.clearance_date='0000-00-00') - AND - jea.account = %(bank_account)s - AND - jea.{jea_side}_in_account_currency like %(txt)s - AND - je.docstatus = 1 - """, { - 'bank_account': bank_account, - 'txt': '%%%s%%' % amount - }, as_dict=True) - - if transaction.credit > 0: - sales_invoices = frappe.db.sql(""" - SELECT - 'Sales Invoice' as doctype, si.name, si.customer as party, - si.posting_date, sip.amount as paid_amount - FROM - `tabSales Invoice Payment` as sip - JOIN - `tabSales Invoice` as si - ON - sip.parent = si.name - WHERE - (sip.clearance_date is null or sip.clearance_date='0000-00-00') - AND - sip.account = %s - AND - sip.amount like %s - AND - si.docstatus = 1 - """, (bank_account, amount), as_dict=True) - else: - sales_invoices = [] - - if transaction.debit > 0: - purchase_invoices = frappe.get_all("Purchase Invoice", - fields = ["'Purchase Invoice' as doctype", "name", "paid_amount", "supplier as party", "posting_date", "currency"], - filters=[ - ["paid_amount", "like", "{0}%".format(amount)], - ["docstatus", "=", "1"], - ["is_paid", "=", "1"], - ["ifnull(clearance_date, '')", "=", ""], - ["cash_bank_account", "=", "{0}".format(bank_account)] - ] - ) - - mode_of_payments = [x["parent"] for x in frappe.db.get_list("Mode of Payment Account", - filters={"default_account": bank_account}, fields=["parent"])] - - company_currency = get_company_currency(company) - - expense_claims = frappe.get_all("Expense Claim", - fields=["'Expense Claim' as doctype", "name", "total_sanctioned_amount as paid_amount", - "employee as party", "posting_date", "'{0}' as currency".format(company_currency)], - filters=[ - ["total_sanctioned_amount", "like", "{0}%".format(amount)], - ["docstatus", "=", "1"], - ["is_paid", "=", "1"], - ["ifnull(clearance_date, '')", "=", ""], - ["mode_of_payment", "in", "{0}".format(tuple(mode_of_payments))] - ] - ) - else: - purchase_invoices = expense_claims = [] - - for data in [payment_entries, journal_entries, sales_invoices, purchase_invoices, expense_claims]: - if data: - payments.extend(data) - - return payments - -def get_matching_descriptions_data(company, transaction): - if not transaction.description : - return [] - - bank_transactions = frappe.db.sql(""" - SELECT - bt.name, bt.description, bt.date, btp.payment_document, btp.payment_entry - FROM - `tabBank Transaction` as bt - LEFT JOIN - `tabBank Transaction Payments` as btp - ON - bt.name = btp.parent - WHERE - bt.allocated_amount > 0 - AND - bt.docstatus = 1 - """, as_dict=True) - - selection = [] - for bank_transaction in bank_transactions: - if bank_transaction.description: - seq=difflib.SequenceMatcher(lambda x: x == " ", transaction.description, bank_transaction.description) - - if seq.ratio() > 0.6: - bank_transaction["ratio"] = seq.ratio() - selection.append(bank_transaction) - - document_types = set([x["payment_document"] for x in selection]) - - links = {} - for document_type in document_types: - links[document_type] = [x["payment_entry"] for x in selection if x["payment_document"]==document_type] - - - data = [] - company_currency = get_company_currency(company) - for key, value in iteritems(links): - if key == "Payment Entry": - data.extend(frappe.get_all("Payment Entry", filters=[["name", "in", value]], - fields=["'Payment Entry' as doctype", "posting_date", "party", "reference_no", - "reference_date", "paid_amount", "paid_to_account_currency as currency", "clearance_date"])) - if key == "Journal Entry": - journal_entries = frappe.get_all("Journal Entry", filters=[["name", "in", value]], - fields=["name", "'Journal Entry' as doctype", "posting_date", - "pay_to_recd_from as party", "cheque_no as reference_no", "cheque_date as reference_date", - "total_credit as paid_amount", "clearance_date"]) - for journal_entry in journal_entries: - journal_entry_accounts = frappe.get_all("Journal Entry Account", filters={"parenttype": journal_entry["doctype"], "parent": journal_entry["name"]}, fields=["account_currency"]) - journal_entry["currency"] = journal_entry_accounts[0]["account_currency"] if journal_entry_accounts else company_currency - data.extend(journal_entries) - if key == "Sales Invoice": - data.extend(frappe.get_all("Sales Invoice", filters=[["name", "in", value]], fields=["'Sales Invoice' as doctype", "posting_date", "customer_name as party", "paid_amount", "currency"])) - if key == "Purchase Invoice": - data.extend(frappe.get_all("Purchase Invoice", filters=[["name", "in", value]], fields=["'Purchase Invoice' as doctype", "posting_date", "supplier_name as party", "paid_amount", "currency"])) - if key == "Expense Claim": - expense_claims = frappe.get_all("Expense Claim", filters=[["name", "in", value]], fields=["'Expense Claim' as doctype", "posting_date", "employee_name as party", "total_amount_reimbursed as paid_amount"]) - data.extend([dict(x,**{"currency": company_currency}) for x in expense_claims]) - - return data - -def check_amount_vs_description(amount_matching, description_matching): - result = [] - - if description_matching: - for am_match in amount_matching: - for des_match in description_matching: - if des_match.get("clearance_date"): - continue - - if am_match["party"] == des_match["party"]: - if am_match not in result: - result.append(am_match) - continue - - if "reference_no" in am_match and "reference_no" in des_match: - # Sequence Matcher does not handle None as input - am_reference = am_match["reference_no"] or "" - des_reference = des_match["reference_no"] or "" - - if difflib.SequenceMatcher(lambda x: x == " ", am_reference, des_reference).ratio() > 70: - if am_match not in result: - result.append(am_match) - if result: - return sorted(result, key = lambda x: x["posting_date"], reverse=True) - else: - return sorted(amount_matching, key = lambda x: x["posting_date"], reverse=True) - - else: - return sorted(amount_matching, key = lambda x: x["posting_date"], reverse=True) - -def get_matching_transactions_payments(description_matching): - payments = [x["payment_entry"] for x in description_matching] - - payment_by_ratio = {x["payment_entry"]: x["ratio"] for x in description_matching} - - if payments: - reference_payment_list = frappe.get_all("Payment Entry", fields=["name", "paid_amount", "payment_type", "reference_no", "reference_date", - "party", "party_type", "posting_date", "paid_to_account_currency"], filters=[["name", "in", payments]]) - - return sorted(reference_payment_list, key=lambda x: payment_by_ratio[x["name"]]) - - else: - return [] - -@frappe.whitelist() -@frappe.validate_and_sanitize_search_inputs -def payment_entry_query(doctype, txt, searchfield, start, page_len, filters): - account = frappe.db.get_value("Bank Account", filters.get("bank_account"), "account") - if not account: - return - - return frappe.db.sql(""" - SELECT - name, party, paid_amount, received_amount, reference_no - FROM - `tabPayment Entry` - WHERE - (clearance_date is null or clearance_date='0000-00-00') - AND (paid_from = %(account)s or paid_to = %(account)s) - AND (name like %(txt)s or party like %(txt)s) - AND docstatus = 1 - ORDER BY - if(locate(%(_txt)s, name), locate(%(_txt)s, name), 99999), name - LIMIT - %(start)s, %(page_len)s""", - { - 'txt': "%%%s%%" % txt, - '_txt': txt.replace("%", ""), - 'start': start, - 'page_len': page_len, - 'account': account - } - ) - -@frappe.whitelist() -@frappe.validate_and_sanitize_search_inputs -def journal_entry_query(doctype, txt, searchfield, start, page_len, filters): - account = frappe.db.get_value("Bank Account", filters.get("bank_account"), "account") - - return frappe.db.sql(""" - SELECT - jea.parent, je.pay_to_recd_from, - if(jea.debit_in_account_currency > 0, jea.debit_in_account_currency, jea.credit_in_account_currency) - FROM - `tabJournal Entry Account` as jea - LEFT JOIN - `tabJournal Entry` as je - ON - jea.parent = je.name - WHERE - (je.clearance_date is null or je.clearance_date='0000-00-00') - AND - jea.account = %(account)s - AND - (jea.parent like %(txt)s or je.pay_to_recd_from like %(txt)s) - AND - je.docstatus = 1 - ORDER BY - if(locate(%(_txt)s, jea.parent), locate(%(_txt)s, jea.parent), 99999), - jea.parent - LIMIT - %(start)s, %(page_len)s""", - { - 'txt': "%%%s%%" % txt, - '_txt': txt.replace("%", ""), - 'start': start, - 'page_len': page_len, - 'account': account - } - ) - -@frappe.whitelist() -@frappe.validate_and_sanitize_search_inputs -def sales_invoices_query(doctype, txt, searchfield, start, page_len, filters): - return frappe.db.sql(""" - SELECT - sip.parent, si.customer, sip.amount, sip.mode_of_payment - FROM - `tabSales Invoice Payment` as sip - LEFT JOIN - `tabSales Invoice` as si - ON - sip.parent = si.name - WHERE - (sip.clearance_date is null or sip.clearance_date='0000-00-00') - AND - (sip.parent like %(txt)s or si.customer like %(txt)s) - ORDER BY - if(locate(%(_txt)s, sip.parent), locate(%(_txt)s, sip.parent), 99999), - sip.parent - LIMIT - %(start)s, %(page_len)s""", - { - 'txt': "%%%s%%" % txt, - '_txt': txt.replace("%", ""), - 'start': start, - 'page_len': page_len - } - ) diff --git a/erpnext/accounts/page/bank_reconciliation/bank_transaction_header.html b/erpnext/accounts/page/bank_reconciliation/bank_transaction_header.html deleted file mode 100644 index 94f183b793b..00000000000 --- a/erpnext/accounts/page/bank_reconciliation/bank_transaction_header.html +++ /dev/null @@ -1,21 +0,0 @@ -
    -
    - -
    - {{ __("Description") }} -
    - - - -
    -
    -
    -
    diff --git a/erpnext/accounts/page/bank_reconciliation/bank_transaction_row.html b/erpnext/accounts/page/bank_reconciliation/bank_transaction_row.html deleted file mode 100644 index 742b84c63f5..00000000000 --- a/erpnext/accounts/page/bank_reconciliation/bank_transaction_row.html +++ /dev/null @@ -1,36 +0,0 @@ -
    -
    -
    - -
    - {{ description }} -
    - - - -
    - -
    -
    diff --git a/erpnext/accounts/page/bank_reconciliation/linked_payment_header.html b/erpnext/accounts/page/bank_reconciliation/linked_payment_header.html deleted file mode 100644 index 4542c36e0dc..00000000000 --- a/erpnext/accounts/page/bank_reconciliation/linked_payment_header.html +++ /dev/null @@ -1,21 +0,0 @@ -
    -
    -
    - {{ __("Payment Name") }} -
    -
    - {{ __("Reference Date") }} -
    - - -
    - {{ __("Reference Number") }} -
    -
    -
    -
    -
    diff --git a/erpnext/accounts/page/bank_reconciliation/linked_payment_row.html b/erpnext/accounts/page/bank_reconciliation/linked_payment_row.html deleted file mode 100644 index bdbc9fce033..00000000000 --- a/erpnext/accounts/page/bank_reconciliation/linked_payment_row.html +++ /dev/null @@ -1,36 +0,0 @@ -
    -
    -
    - {{ name }} -
    -
    - {% if (typeof reference_date !== "undefined") %} - {%= frappe.datetime.str_to_user(reference_date) %} - {% else %} - {% if (typeof posting_date !== "undefined") %} - {%= frappe.datetime.str_to_user(posting_date) %} - {% endif %} - {% endif %} -
    - - -
    - {% if (typeof reference_no !== "undefined") %} - {{ reference_no }} - {% else %} - {{ "" }} - {% endif %} -
    -
    -
    - -
    -
    -
    -
    \ No newline at end of file diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index 38b228477f7..e01cb6e151e 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -617,6 +617,7 @@ def get_partywise_advanced_payment_amount(party_type, posting_date = None, futur FROM `tabGL Entry` WHERE party_type = %s and against_voucher is null + and is_cancelled = 0 and {1} GROUP BY party""" .format(("credit") if party_type == "Customer" else "debit", cond) , party_type) diff --git a/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html b/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html index 8eef2adce3e..71c26e8c55a 100644 --- a/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html +++ b/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html @@ -22,8 +22,8 @@

    {% endif %} +
    1. Transaction Details
    -
    1. Transaction Details
    @@ -54,8 +54,8 @@
    +
    2. Party Details
    -
    2. Party Details
    {%- set seller = einvoice.SellerDtls -%}
    Seller
    @@ -89,7 +89,7 @@
    -
    3. Item Details
    +
    3. Item Details
    diff --git a/erpnext/accounts/print_format/sales_invoice_return/sales_invoice_return.html b/erpnext/accounts/print_format/sales_invoice_return/sales_invoice_return.html index 1d758e89355..3d5a9b1da94 100644 --- a/erpnext/accounts/print_format/sales_invoice_return/sales_invoice_return.html +++ b/erpnext/accounts/print_format/sales_invoice_return/sales_invoice_return.html @@ -1,5 +1,5 @@ {%- from "templates/print_formats/standard_macros.html" import add_header, render_field, print_value, fieldmeta, - get_width, get_align_class -%} + get_width, get_align_class with context -%} {%- macro render_currency(df, doc) -%}
    @@ -63,14 +63,19 @@
    {% for tdf in visible_columns %} - {% if not d.flags.compact_item_print or tdf.fieldname in doc.get(df.fieldname)[0].flags.compact_item_fields %} + {% if not print_settings.compact_item_print or tdf.fieldname in doc.flags.compact_item_fields %} {% elif tdf.fieldtype == 'Currency' %}
    {{ frappe.utils.fmt_money((d[tdf.fieldname])|abs, currency=doc.currency) }}
    {% else %} -
    {{ print_value(tdf, d, doc, visible_columns) }}
    + {% if doc.child_print_templates %} + {%- set child_templates = doc.child_print_templates.get(df.fieldname) -%} +
    {{ print_value(tdf, d, doc, visible_columns, child_templates) }}
    + {% else %} +
    {{ print_value(tdf, d, doc, visible_columns) }}
    + {% endif %} {% endif %} {% endif %} {% endfor %} diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.html b/erpnext/accounts/report/accounts_receivable/accounts_receivable.html index 79a6aabd987..f4fd06ba037 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.html +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.html @@ -258,7 +258,7 @@ {% } %} {% } else { %} {% if(data[i]["party"]|| " ") { %} - {% if((data[i]["party"]) != __("'Total'")) { %} + {% if(!data[i]["is_total_row"]) { %} + + + + `).join(''); + const html = ` -
    {{ d.idx }} {% if tdf.fieldname == 'qty' %}
    {{ (d[tdf.fieldname])|abs }}
    {% if(!(filters.customer || filters.supplier)) { %} {%= data[i]["party"] %} diff --git a/erpnext/accounts/report/balance_sheet/balance_sheet.py b/erpnext/accounts/report/balance_sheet/balance_sheet.py index a858c1998f5..1729abce9ef 100644 --- a/erpnext/accounts/report/balance_sheet/balance_sheet.py +++ b/erpnext/accounts/report/balance_sheet/balance_sheet.py @@ -147,7 +147,6 @@ def get_report_summary(period_list, asset, liability, equity, provisional_profit { "value": net_asset, "label": "Total Asset", - "indicator": "Green", "datatype": "Currency", "currency": currency }, @@ -155,14 +154,12 @@ def get_report_summary(period_list, asset, liability, equity, provisional_profit "value": net_liability, "label": "Total Liability", "datatype": "Currency", - "indicator": "Red", "currency": currency }, { "value": net_equity, "label": "Total Equity", "datatype": "Currency", - "indicator": "Blue", "currency": currency }, { diff --git a/erpnext/accounts/report/bank_clearance_summary/bank_clearance_summary.py b/erpnext/accounts/report/bank_clearance_summary/bank_clearance_summary.py index 0861b20f14a..79b0a6f30ec 100644 --- a/erpnext/accounts/report/bank_clearance_summary/bank_clearance_summary.py +++ b/erpnext/accounts/report/bank_clearance_summary/bank_clearance_summary.py @@ -15,15 +15,51 @@ def execute(filters=None): return columns, data def get_columns(): - return [ - _("Payment Document") + "::130", - _("Payment Entry") + ":Dynamic Link/"+_("Payment Document")+":110", - _("Posting Date") + ":Date:100", - _("Cheque/Reference No") + "::120", - _("Clearance Date") + ":Date:100", - _("Against Account") + ":Link/Account:170", - _("Amount") + ":Currency:120" - ] + columns = [{ + "label": _("Payment Document Type"), + "fieldname": "payment_document_type", + "fieldtype": "Link", + "options": "Doctype", + "width": 130 + }, + { + "label": _("Payment Entry"), + "fieldname": "payment_entry", + "fieldtype": "Dynamic Link", + "options": "payment_document_type", + "width": 140 + }, + { + "label": _("Posting Date"), + "fieldname": "posting_date", + "fieldtype": "Date", + "width": 100 + }, + { + "label": _("Cheque/Reference No"), + "fieldname": "cheque_no", + "width": 120 + }, + { + "label": _("Clearance Date"), + "fieldname": "clearance_date", + "fieldtype": "Date", + "width": 100 + }, + { + "label": _("Against Account"), + "fieldname": "against", + "fieldtype": "Link", + "options": "Account", + "width": 170 + }, + { + "label": _("Amount"), + "fieldname": "amount", + "width": 120 + }] + + return columns def get_conditions(filters): conditions = "" diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index f735d87a764..b5d7992604f 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -129,6 +129,9 @@ def get_gl_entries(filters, accounting_dimensions): order_by_statement = "order by posting_date, account, creation" + if filters.get("include_dimensions"): + order_by_statement = "order by posting_date, creation" + if filters.get("group_by") == _("Group by Voucher"): order_by_statement = "order by posting_date, voucher_type, voucher_no" @@ -142,7 +145,9 @@ def get_gl_entries(filters, accounting_dimensions): distributed_cost_center_query = "" if filters and filters.get('cost_center'): - select_fields_with_percentage = """, debit*(DCC_allocation.percentage_allocation/100) as debit, credit*(DCC_allocation.percentage_allocation/100) as credit, debit_in_account_currency*(DCC_allocation.percentage_allocation/100) as debit_in_account_currency, + select_fields_with_percentage = """, debit*(DCC_allocation.percentage_allocation/100) as debit, + credit*(DCC_allocation.percentage_allocation/100) as credit, + debit_in_account_currency*(DCC_allocation.percentage_allocation/100) as debit_in_account_currency, credit_in_account_currency*(DCC_allocation.percentage_allocation/100) as credit_in_account_currency """ distributed_cost_center_query = """ @@ -200,7 +205,7 @@ def get_gl_entries(filters, accounting_dimensions): def get_conditions(filters): conditions = [] - if filters.get("account"): + if filters.get("account") and not filters.get("include_dimensions"): lft, rgt = frappe.db.get_value("Account", filters["account"], ["lft", "rgt"]) conditions.append("""account in (select name from tabAccount where lft>=%s and rgt<=%s and docstatus<2)""" % (lft, rgt)) @@ -245,17 +250,19 @@ def get_conditions(filters): if match_conditions: conditions.append(match_conditions) - accounting_dimensions = get_accounting_dimensions(as_list=False) + if filters.get("include_dimensions"): + accounting_dimensions = get_accounting_dimensions(as_list=False) - if accounting_dimensions: - for dimension in accounting_dimensions: - if filters.get(dimension.fieldname): - if frappe.get_cached_value('DocType', dimension.document_type, 'is_tree'): - filters[dimension.fieldname] = get_dimension_with_children(dimension.document_type, - filters.get(dimension.fieldname)) - conditions.append("{0} in %({0})s".format(dimension.fieldname)) - else: - conditions.append("{0} in (%({0})s)".format(dimension.fieldname)) + if accounting_dimensions: + for dimension in accounting_dimensions: + if not dimension.disabled: + if filters.get(dimension.fieldname): + if frappe.get_cached_value('DocType', dimension.document_type, 'is_tree'): + filters[dimension.fieldname] = get_dimension_with_children(dimension.document_type, + filters.get(dimension.fieldname)) + conditions.append("{0} in %({0})s".format(dimension.fieldname)) + else: + conditions.append("{0} in (%({0})s)".format(dimension.fieldname)) return "and {}".format(" and ".join(conditions)) if conditions else "" diff --git a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py index 998003ac698..928b373effe 100644 --- a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py +++ b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py @@ -52,8 +52,8 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum row = { 'item_code': d.item_code, - 'item_name': item_record.item_name, - 'item_group': item_record.item_group, + 'item_name': item_record.item_name if item_record else d.item_name, + 'item_group': item_record.item_group if item_record else d.item_group, 'description': d.description, 'invoice': d.parent, 'posting_date': d.posting_date, @@ -383,6 +383,7 @@ def get_items(filters, additional_query_columns): `tabSales Invoice`.project, `tabSales Invoice`.customer, `tabSales Invoice`.remarks, `tabSales Invoice`.territory, `tabSales Invoice`.company, `tabSales Invoice`.base_net_total, `tabSales Invoice Item`.item_code, `tabSales Invoice Item`.description, + `tabSales Invoice Item`.`item_name`, `tabSales Invoice Item`.`item_group`, `tabSales Invoice Item`.sales_order, `tabSales Invoice Item`.delivery_note, `tabSales Invoice Item`.income_account, `tabSales Invoice Item`.cost_center, `tabSales Invoice Item`.stock_qty, `tabSales Invoice Item`.stock_uom, diff --git a/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.py b/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.py index b34d037f04d..fe261b30b45 100644 --- a/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.py +++ b/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.py @@ -59,24 +59,26 @@ def get_report_summary(period_list, periodicity, income, expense, net_profit_los expense_label = _("Total Expense") return [ - { - "value": net_profit, - "indicator": "Green" if net_profit > 0 else "Red", - "label": profit_label, - "datatype": "Currency", - "currency": currency - }, { "value": net_income, "label": income_label, "datatype": "Currency", "currency": currency }, + { "type": "separator", "value": "-"}, { "value": net_expense, "label": expense_label, "datatype": "Currency", "currency": currency + }, + { "type": "separator", "value": "=", "color": "blue"}, + { + "value": net_profit, + "indicator": "Green" if net_profit > 0 else "Red", + "label": profit_label, + "datatype": "Currency", + "currency": currency } ] 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 c7cfee74cb0..a8280c1b18e 100644 --- a/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py +++ b/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py @@ -55,7 +55,7 @@ def get_result(filters): 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.company, filters.from_date, filters.to_date, filters.fiscal_year) if total_invoiced_amount or tds_deducted: row = [supplier.pan, supplier.name] @@ -68,7 +68,7 @@ def get_result(filters): return out -def get_invoice_and_tds_amount(supplier, account, company, from_date, to_date): +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(""" @@ -94,7 +94,9 @@ def get_invoice_and_tds_amount(supplier, account, company, from_date, to_date): """.format(', '.join(["'%s'" % d for d in vouchers])), (account, from_date, to_date, company))[0][0]) - debit_note_amount = get_debit_note_amount([supplier], from_date, to_date, company=company) + 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 diff --git a/erpnext/accounts/workspace/accounting/accounting.json b/erpnext/accounts/workspace/accounting/accounting.json new file mode 100644 index 00000000000..fadb66535f5 --- /dev/null +++ b/erpnext/accounts/workspace/accounting/accounting.json @@ -0,0 +1,1119 @@ +{ + "category": "Modules", + "charts": [ + { + "chart_name": "Profit and Loss", + "label": "Profit and Loss" + } + ], + "creation": "2020-03-02 15:41:59.515192", + "developer_mode_only": 0, + "disable_user_customization": 0, + "docstatus": 0, + "doctype": "Workspace", + "extends_another_page": 0, + "hide_custom": 0, + "icon": "accounting", + "idx": 0, + "is_standard": 1, + "label": "Accounting", + "links": [ + { + "hidden": 0, + "is_query_report": 0, + "label": "Accounting Masters", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Company", + "link_to": "Company", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Chart of Accounts", + "link_to": "Account", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Accounts Settings", + "link_to": "Accounts Settings", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Fiscal Year", + "link_to": "Fiscal Year", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Accounting Dimension", + "link_to": "Accounting Dimension", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Finance Book", + "link_to": "Finance Book", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Accounting Period", + "link_to": "Accounting Period", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Payment Term", + "link_to": "Payment Term", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "General Ledger", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Journal Entry", + "link_to": "Journal Entry", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Journal Entry Template", + "link_to": "Journal Entry Template", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "GL Entry", + "hidden": 0, + "is_query_report": 1, + "label": "General Ledger", + "link_to": "General Ledger", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Sales Invoice", + "hidden": 0, + "is_query_report": 1, + "label": "Customer Ledger Summary", + "link_to": "Customer Ledger Summary", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Sales Invoice", + "hidden": 0, + "is_query_report": 1, + "label": "Supplier Ledger Summary", + "link_to": "Supplier Ledger Summary", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Accounts Receivable", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Sales Invoice", + "link_to": "Sales Invoice", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Customer", + "link_to": "Customer", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Payment Entry", + "link_to": "Payment Entry", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Payment Request", + "link_to": "Payment Request", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Sales Invoice", + "hidden": 0, + "is_query_report": 1, + "label": "Accounts Receivable", + "link_to": "Accounts Receivable", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Sales Invoice", + "hidden": 0, + "is_query_report": 1, + "label": "Accounts Receivable Summary", + "link_to": "Accounts Receivable Summary", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Sales Invoice", + "hidden": 0, + "is_query_report": 1, + "label": "Sales Register", + "link_to": "Sales Register", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Sales Invoice", + "hidden": 0, + "is_query_report": 1, + "label": "Item-wise Sales Register", + "link_to": "Item-wise Sales Register", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Sales Invoice", + "hidden": 0, + "is_query_report": 1, + "label": "Sales Order Analysis", + "link_to": "Sales Order Analysis", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Sales Invoice", + "hidden": 0, + "is_query_report": 1, + "label": "Delivered Items To Be Billed", + "link_to": "Delivered Items To Be Billed", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Accounts Payable", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Purchase Invoice", + "link_to": "Purchase Invoice", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Supplier", + "link_to": "Supplier", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Payment Entry", + "link_to": "Payment Entry", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Purchase Invoice", + "hidden": 0, + "is_query_report": 1, + "label": "Accounts Payable", + "link_to": "Accounts Payable", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Purchase Invoice", + "hidden": 0, + "is_query_report": 1, + "label": "Accounts Payable Summary", + "link_to": "Accounts Payable Summary", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Purchase Invoice", + "hidden": 0, + "is_query_report": 1, + "label": "Purchase Register", + "link_to": "Purchase Register", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Purchase Invoice", + "hidden": 0, + "is_query_report": 1, + "label": "Item-wise Purchase Register", + "link_to": "Item-wise Purchase Register", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Purchase Order", + "hidden": 0, + "is_query_report": 1, + "label": "Purchase Order Analysis", + "link_to": "Purchase Order Analysis", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Purchase Invoice", + "hidden": 0, + "is_query_report": 1, + "label": "Received Items To Be Billed", + "link_to": "Received Items To Be Billed", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Reports", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "GL Entry", + "hidden": 0, + "is_query_report": 1, + "label": "Trial Balance for Party", + "link_to": "Trial Balance for Party", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Journal Entry", + "hidden": 0, + "is_query_report": 1, + "label": "Payment Period Based On Invoice Date", + "link_to": "Payment Period Based On Invoice Date", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Sales Invoice", + "hidden": 0, + "is_query_report": 1, + "label": "Sales Partners Commission", + "link_to": "Sales Partners Commission", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Customer", + "hidden": 0, + "is_query_report": 1, + "label": "Customer Credit Balance", + "link_to": "Customer Credit Balance", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Sales Invoice", + "hidden": 0, + "is_query_report": 1, + "label": "Sales Payment Summary", + "link_to": "Sales Payment Summary", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Address", + "hidden": 0, + "is_query_report": 1, + "label": "Address And Contacts", + "link_to": "Address And Contacts", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "GL Entry", + "hidden": 0, + "is_query_report": 1, + "label": "DATEV Export", + "link_to": "DATEV", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Financial Statements", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "GL Entry", + "hidden": 0, + "is_query_report": 1, + "label": "Trial Balance", + "link_to": "Trial Balance", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "GL Entry", + "hidden": 0, + "is_query_report": 1, + "label": "Profit and Loss Statement", + "link_to": "Profit and Loss Statement", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "GL Entry", + "hidden": 0, + "is_query_report": 1, + "label": "Balance Sheet", + "link_to": "Balance Sheet", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "GL Entry", + "hidden": 0, + "is_query_report": 1, + "label": "Cash Flow", + "link_to": "Cash Flow", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "GL Entry", + "hidden": 0, + "is_query_report": 1, + "label": "Consolidated Financial Statement", + "link_to": "Consolidated Financial Statement", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Multi Currency", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Currency", + "link_to": "Currency", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Currency Exchange", + "link_to": "Currency Exchange", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Exchange Rate Revaluation", + "link_to": "Exchange Rate Revaluation", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Settings", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Payment Gateway Account", + "link_to": "Payment Gateway Account", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Terms and Conditions Template", + "link_to": "Terms and Conditions", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Mode of Payment", + "link_to": "Mode of Payment", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Bank Statement", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Bank", + "link_to": "Bank", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Bank Account", + "link_to": "Bank Account", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Bank Clearance", + "link_to": "Bank Clearance", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Bank Reconciliation", + "link_to": "bank-reconciliation", + "link_type": "Page", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "GL Entry", + "hidden": 0, + "is_query_report": 1, + "label": "Bank Reconciliation Statement", + "link_to": "Bank Reconciliation Statement", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Bank Statement Transaction Entry", + "link_to": "Bank Statement Transaction Entry", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Bank Statement Settings", + "link_to": "Bank Statement Settings", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Subscription Management", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Subscription Plan", + "link_to": "Subscription Plan", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Subscription", + "link_to": "Subscription", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Subscription Settings", + "link_to": "Subscription Settings", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Goods and Services Tax (GST India)", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "GST Settings", + "link_to": "GST Settings", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "GST HSN Code", + "link_to": "GST HSN Code", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 1, + "label": "GSTR-1", + "link_to": "GSTR-1", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 1, + "label": "GSTR-2", + "link_to": "GSTR-2", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "GSTR 3B Report", + "link_to": "GSTR 3B Report", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 1, + "label": "GST Sales Register", + "link_to": "GST Sales Register", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 1, + "label": "GST Purchase Register", + "link_to": "GST Purchase Register", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 1, + "label": "GST Itemised Sales Register", + "link_to": "GST Itemised Sales Register", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 1, + "label": "GST Itemised Purchase Register", + "link_to": "GST Itemised Purchase Register", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "C-Form", + "link_to": "C-Form", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Lower Deduction Certificate", + "link_to": "Lower Deduction Certificate", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Share Management", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Shareholder", + "link_to": "Shareholder", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Share Transfer", + "link_to": "Share Transfer", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Share Transfer", + "hidden": 0, + "is_query_report": 1, + "label": "Share Ledger", + "link_to": "Share Ledger", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Share Transfer", + "hidden": 0, + "is_query_report": 1, + "label": "Share Balance", + "link_to": "Share Balance", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Cost Center and Budgeting", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Chart of Cost Centers", + "link_to": "Cost Center", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Budget", + "link_to": "Budget", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Accounting Dimension", + "link_to": "Accounting Dimension", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Cost Center", + "hidden": 0, + "is_query_report": 1, + "label": "Budget Variance Report", + "link_to": "Budget Variance Report", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Monthly Distribution", + "link_to": "Monthly Distribution", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Opening and Closing", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Opening Invoice Creation Tool", + "link_to": "Opening Invoice Creation Tool", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Chart of Accounts Importer", + "link_to": "Chart of Accounts Importer", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Period Closing Voucher", + "link_to": "Period Closing Voucher", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Taxes", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Sales Taxes and Charges Template", + "link_to": "Sales Taxes and Charges Template", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Purchase Taxes and Charges Template", + "link_to": "Purchase Taxes and Charges Template", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Item Tax Template", + "link_to": "Item Tax Template", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Tax Category", + "link_to": "Tax Category", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Tax Rule", + "link_to": "Tax Rule", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Tax Withholding Category", + "link_to": "Tax Withholding Category", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Profitability", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "Sales Invoice", + "hidden": 0, + "is_query_report": 1, + "label": "Gross Profit", + "link_to": "Gross Profit", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "GL Entry", + "hidden": 0, + "is_query_report": 1, + "label": "Profitability Analysis", + "link_to": "Profitability Analysis", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Sales Invoice", + "hidden": 0, + "is_query_report": 1, + "label": "Sales Invoice Trends", + "link_to": "Sales Invoice Trends", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Purchase Invoice", + "hidden": 0, + "is_query_report": 1, + "label": "Purchase Invoice Trends", + "link_to": "Purchase Invoice Trends", + "link_type": "Report", + "onboard": 0, + "type": "Link" + } + ], + "modified": "2021-03-04 00:38:35.349024", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Accounting", + "onboarding": "Accounts", + "owner": "Administrator", + "pin_to_bottom": 0, + "pin_to_top": 0, + "shortcuts": [ + { + "label": "Chart of Accounts", + "link_to": "Account", + "type": "DocType" + }, + { + "label": "Sales Invoice", + "link_to": "Sales Invoice", + "type": "DocType" + }, + { + "label": "Purchase Invoice", + "link_to": "Purchase Invoice", + "type": "DocType" + }, + { + "label": "Journal Entry", + "link_to": "Journal Entry", + "type": "DocType" + }, + { + "label": "Payment Entry", + "link_to": "Payment Entry", + "type": "DocType" + }, + { + "label": "Accounts Receivable", + "link_to": "Accounts Receivable", + "type": "Report" + }, + { + "label": "General Ledger", + "link_to": "General Ledger", + "type": "Report" + }, + { + "label": "Trial Balance", + "link_to": "Trial Balance", + "type": "Report" + }, + { + "label": "Dashboard", + "link_to": "Accounts", + "type": "Dashboard" + } + ] +} diff --git a/erpnext/agriculture/desk_page/agriculture/agriculture.json b/erpnext/agriculture/desk_page/agriculture/agriculture.json deleted file mode 100644 index e0d2c9ca25a..00000000000 --- a/erpnext/agriculture/desk_page/agriculture/agriculture.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "cards": [ - { - "hidden": 0, - "label": "Crops & Lands", - "links": "[\n {\n \"label\": \"Crop\",\n \"name\": \"Crop\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Crop Cycle\",\n \"name\": \"Crop Cycle\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Location\",\n \"name\": \"Location\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n }\n]" - }, - { - "hidden": 0, - "label": "Analytics", - "links": "[\n {\n \"label\": \"Plant Analysis\",\n \"name\": \"Plant Analysis\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Soil Analysis\",\n \"name\": \"Soil Analysis\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Water Analysis\",\n \"name\": \"Water Analysis\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Soil Texture\",\n \"name\": \"Soil Texture\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Weather\",\n \"name\": \"Weather\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Agriculture Analysis Criteria\",\n \"name\": \"Agriculture Analysis Criteria\",\n \"type\": \"doctype\"\n }\n]" - }, - { - "hidden": 0, - "label": "Diseases & Fertilizers", - "links": "[\n {\n \"label\": \"Disease\",\n \"name\": \"Disease\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Fertilizer\",\n \"name\": \"Fertilizer\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n }\n]" - } - ], - "category": "Domains", - "charts": [], - "creation": "2020-03-02 17:23:34.339274", - "developer_mode_only": 0, - "disable_user_customization": 0, - "docstatus": 0, - "doctype": "Desk Page", - "extends_another_page": 0, - "idx": 0, - "is_standard": 1, - "label": "Agriculture", - "modified": "2020-04-01 11:28:51.032822", - "modified_by": "Administrator", - "module": "Agriculture", - "name": "Agriculture", - "owner": "Administrator", - "pin_to_bottom": 0, - "pin_to_top": 0, - "restrict_to_domain": "Agriculture", - "shortcuts": [] -} \ No newline at end of file diff --git a/erpnext/agriculture/workspace/agriculture/agriculture.json b/erpnext/agriculture/workspace/agriculture/agriculture.json new file mode 100644 index 00000000000..2cc252491d3 --- /dev/null +++ b/erpnext/agriculture/workspace/agriculture/agriculture.json @@ -0,0 +1,157 @@ +{ + "category": "Domains", + "charts": [], + "creation": "2020-03-02 17:23:34.339274", + "developer_mode_only": 0, + "disable_user_customization": 0, + "docstatus": 0, + "doctype": "Workspace", + "extends_another_page": 0, + "hide_custom": 0, + "icon": "agriculture", + "idx": 0, + "is_standard": 1, + "label": "Agriculture", + "links": [ + { + "hidden": 0, + "is_query_report": 0, + "label": "Crops & Lands", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Crop", + "link_to": "Crop", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Crop Cycle", + "link_to": "Crop Cycle", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Location", + "link_to": "Location", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Analytics", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Plant Analysis", + "link_to": "Plant Analysis", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Soil Analysis", + "link_to": "Soil Analysis", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Water Analysis", + "link_to": "Water Analysis", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Soil Texture", + "link_to": "Soil Texture", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Weather", + "link_to": "Weather", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Agriculture Analysis Criteria", + "link_to": "Agriculture Analysis Criteria", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Diseases & Fertilizers", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Disease", + "link_to": "Disease", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Fertilizer", + "link_to": "Fertilizer", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + } + ], + "modified": "2020-12-01 13:38:38.477493", + "modified_by": "Administrator", + "module": "Agriculture", + "name": "Agriculture", + "owner": "Administrator", + "pin_to_bottom": 0, + "pin_to_top": 0, + "restrict_to_domain": "Agriculture", + "shortcuts": [] +} \ No newline at end of file diff --git a/erpnext/assets/desk_page/assets/assets.json b/erpnext/assets/desk_page/assets/assets.json deleted file mode 100644 index 449a5facb08..00000000000 --- a/erpnext/assets/desk_page/assets/assets.json +++ /dev/null @@ -1,66 +0,0 @@ -{ - "cards": [ - { - "hidden": 0, - "label": "Assets", - "links": "[\n {\n \"label\": \"Asset\",\n \"name\": \"Asset\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Location\",\n \"name\": \"Location\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Asset Category\",\n \"name\": \"Asset Category\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Transfer an asset from one warehouse to another\",\n \"label\": \"Asset Movement\",\n \"name\": \"Asset Movement\",\n \"type\": \"doctype\"\n }\n]" - }, - { - "hidden": 0, - "label": "Maintenance", - "links": "[\n {\n \"label\": \"Asset Maintenance Team\",\n \"name\": \"Asset Maintenance Team\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Asset Maintenance Team\"\n ],\n \"label\": \"Asset Maintenance\",\n \"name\": \"Asset Maintenance\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Asset Maintenance\"\n ],\n \"label\": \"Asset Maintenance Log\",\n \"name\": \"Asset Maintenance Log\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Asset\"\n ],\n \"label\": \"Asset Value Adjustment\",\n \"name\": \"Asset Value Adjustment\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Asset\"\n ],\n \"label\": \"Asset Repair\",\n \"name\": \"Asset Repair\",\n \"type\": \"doctype\"\n }\n]" - }, - { - "hidden": 0, - "label": "Reports", - "links": "[\n {\n \"dependencies\": [\n \"Asset\"\n ],\n \"doctype\": \"Asset\",\n \"is_query_report\": true,\n \"label\": \"Asset Depreciation Ledger\",\n \"name\": \"Asset Depreciation Ledger\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Asset\"\n ],\n \"doctype\": \"Asset\",\n \"is_query_report\": true,\n \"label\": \"Asset Depreciations and Balances\",\n \"name\": \"Asset Depreciations and Balances\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Asset Maintenance\"\n ],\n \"doctype\": \"Asset Maintenance\",\n \"label\": \"Asset Maintenance\",\n \"name\": \"Asset Maintenance\",\n \"type\": \"report\"\n }\n]" - } - ], - "category": "Modules", - "charts": [ - { - "chart_name": "Asset Value Analytics", - "label": "Asset Value Analytics" - } - ], - "creation": "2020-03-02 15:43:27.634865", - "developer_mode_only": 0, - "disable_user_customization": 0, - "docstatus": 0, - "doctype": "Desk Page", - "extends_another_page": 0, - "hide_custom": 0, - "idx": 0, - "is_standard": 1, - "label": "Assets", - "modified": "2020-05-20 18:05:23.994795", - "modified_by": "Administrator", - "module": "Assets", - "name": "Assets", - "onboarding": "Assets", - "owner": "Administrator", - "pin_to_bottom": 0, - "pin_to_top": 0, - "shortcuts": [ - { - "label": "Asset", - "link_to": "Asset", - "type": "DocType" - }, - { - "label": "Asset Category", - "link_to": "Asset Category", - "type": "DocType" - }, - { - "label": "Fixed Asset Register", - "link_to": "Fixed Asset Register", - "type": "Report" - }, - { - "label": "Dashboard", - "link_to": "Asset", - "type": "Dashboard" - } - ] -} \ No newline at end of file diff --git a/erpnext/assets/doctype/asset/asset.json b/erpnext/assets/doctype/asset/asset.json index a3152abf205..421b9a6c378 100644 --- a/erpnext/assets/doctype/asset/asset.json +++ b/erpnext/assets/doctype/asset/asset.json @@ -8,21 +8,20 @@ "document_type": "Document", "engine": "InnoDB", "field_order": [ - "is_existing_asset", - "section_break_2", - "naming_series", + "company", "item_code", "item_name", - "asset_category", "asset_owner", "asset_owner_company", + "is_existing_asset", "supplier", "customer", "image", "journal_entry_for_scrap", "column_break_3", - "company", + "naming_series", "asset_name", + "asset_category", "location", "custodian", "department", @@ -95,12 +94,14 @@ "reqd": 1 }, { + "depends_on": "item_code", "fetch_from": "item_code.item_name", "fieldname": "item_name", "fieldtype": "Read Only", "label": "Item Name" }, { + "depends_on": "item_code", "fetch_from": "item_code.asset_category", "fieldname": "asset_category", "fieldtype": "Link", @@ -307,12 +308,13 @@ { "depends_on": "calculate_depreciation", "fieldname": "section_break_14", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "label": "Depreciation Schedule" }, { "fieldname": "schedules", "fieldtype": "Table", - "label": "Depreciation Schedules", + "label": "Depreciation Schedule", "no_copy": 1, "options": "Depreciation Schedule" }, @@ -458,10 +460,6 @@ "fieldtype": "Check", "label": "Allow Monthly Depreciation" }, - { - "fieldname": "section_break_2", - "fieldtype": "Section Break" - }, { "collapsible": 1, "collapsible_depends_on": "is_existing_asset", @@ -480,14 +478,31 @@ { "depends_on": "calculate_depreciation", "fieldname": "section_break_36", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "label": "Finance Books" } ], "idx": 72, "image_field": "image", "is_submittable": 1, - "links": [], - "modified": "2020-07-28 15:04:44.452224", + "links": [ + { + "group": "Maintenance", + "link_doctype": "Asset Maintenance", + "link_fieldname": "asset_name" + }, + { + "group": "Repair", + "link_doctype": "Asset Repair", + "link_fieldname": "asset_name" + }, + { + "group": "Value", + "link_doctype": "Asset Value Adjustment", + "link_fieldname": "asset" + } + ], + "modified": "2021-01-22 12:38:59.091510", "modified_by": "Administrator", "module": "Assets", "name": "Asset", @@ -527,5 +542,6 @@ "show_name_in_global_search": 1, "sort_field": "modified", "sort_order": "DESC", - "title_field": "asset_name" + "title_field": "asset_name", + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 30abc66a024..e8e8ec6cc0b 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -133,9 +133,10 @@ class Asset(AccountsController): if self.is_existing_asset: return if self.gross_purchase_amount and self.gross_purchase_amount != self.purchase_receipt_amount: - frappe.throw(_("Gross Purchase Amount should be {} to purchase amount of one single Asset. {}\ - Please do not book expense of multiple assets against one single Asset.") - .format(frappe.bold("equal"), "
    "), title=_("Invalid Gross Purchase Amount")) + error_message = _("Gross Purchase Amount should be equal to purchase amount of one single Asset.") + error_message += "
    " + error_message += _("Please do not book expense of multiple assets against one single Asset.") + frappe.throw(error_message, title=_("Invalid Gross Purchase Amount")) def make_asset_movement(self): reference_doctype = 'Purchase Receipt' if self.purchase_receipt else 'Purchase Invoice' @@ -471,7 +472,7 @@ class Asset(AccountsController): asset_bought_with_invoice = (purchase_document == self.purchase_invoice) fixed_asset_account = self.get_fixed_asset_account() - + cwip_enabled = is_cwip_accounting_enabled(self.asset_category) cwip_account = self.get_cwip_account(cwip_enabled=cwip_enabled) @@ -503,10 +504,10 @@ class Asset(AccountsController): purchase_document = self.purchase_invoice if asset_bought_with_invoice else self.purchase_receipt return purchase_document - + def get_fixed_asset_account(self): return get_asset_category_account('fixed_asset_account', None, self.name, None, self.asset_category, self.company) - + def get_cwip_account(self, cwip_enabled=False): cwip_account = None try: @@ -659,7 +660,7 @@ def transfer_asset(args): frappe.db.commit() - frappe.msgprint(_("Asset Movement record {0} created").format("{0}").format(movement_entry.name)) + frappe.msgprint(_("Asset Movement record {0} created").format("{0}").format(movement_entry.name)) @frappe.whitelist() def get_item_details(item_code, asset_category): diff --git a/erpnext/assets/doctype/asset/asset_dashboard.py b/erpnext/assets/doctype/asset/asset_dashboard.py index b48989923e2..a5cf23803d2 100644 --- a/erpnext/assets/doctype/asset/asset_dashboard.py +++ b/erpnext/assets/doctype/asset/asset_dashboard.py @@ -2,19 +2,10 @@ from __future__ import unicode_literals def get_data(): return { - 'fieldname': 'asset_name', 'non_standard_fieldnames': { 'Asset Movement': 'asset' }, 'transactions': [ - { - 'label': ['Maintenance'], - 'items': ['Asset Maintenance', 'Asset Maintenance Log'] - }, - { - 'label': ['Repair'], - 'items': ['Asset Repair'] - }, { 'label': ['Movement'], 'items': ['Asset Movement'] diff --git a/erpnext/assets/doctype/asset_category/asset_category.json b/erpnext/assets/doctype/asset_category/asset_category.json index 7483b41d4de..a25f5469039 100644 --- a/erpnext/assets/doctype/asset_category/asset_category.json +++ b/erpnext/assets/doctype/asset_category/asset_category.json @@ -1,4 +1,5 @@ { + "actions": [], "allow_import": 1, "allow_rename": 1, "autoname": "field:asset_category_name", @@ -64,7 +65,8 @@ "label": "Enable Capital Work in Progress Accounting" } ], - "modified": "2019-10-11 12:19:59.759136", + "links": [], + "modified": "2021-02-24 15:05:38.621803", "modified_by": "Administrator", "module": "Assets", "name": "Asset Category", @@ -111,5 +113,6 @@ ], "show_name_in_global_search": 1, "sort_field": "modified", - "sort_order": "DESC" + "sort_order": "DESC", + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/assets/doctype/asset_maintenance/asset_maintenance.js b/erpnext/assets/doctype/asset_maintenance/asset_maintenance.js index 001fc26ffe7..70b8654509f 100644 --- a/erpnext/assets/doctype/asset_maintenance/asset_maintenance.js +++ b/erpnext/assets/doctype/asset_maintenance/asset_maintenance.js @@ -40,14 +40,13 @@ frappe.ui.form.on('Asset Maintenance', { if(!r.message) { return; } - var section = frm.dashboard.add_section(`
    - ${ __("Maintenance Log") }
    `); + const section = frm.dashboard.add_section('', __("Maintenance Log")); var rows = $('
    ').appendTo(section); // show (r.message || []).forEach(function(d) { $(`
    - ${d.maintenance_status} ${d.count} diff --git a/erpnext/assets/doctype/asset_maintenance_log/asset_maintenance_log.json b/erpnext/assets/doctype/asset_maintenance_log/asset_maintenance_log.json index 7395bec1e62..7d33176e2f3 100644 --- a/erpnext/assets/doctype/asset_maintenance_log/asset_maintenance_log.json +++ b/erpnext/assets/doctype/asset_maintenance_log/asset_maintenance_log.json @@ -18,15 +18,13 @@ "task_name", "maintenance_type", "periodicity", - "assign_to_name", - "column_break_6", - "due_date", - "completion_date", - "maintenance_status", - "section_break_12", "has_certificate", "certificate_attachement", - "section_break_6", + "column_break_6", + "maintenance_status", + "assign_to_name", + "due_date", + "completion_date", "description", "column_break_9", "actions_performed", @@ -70,7 +68,8 @@ }, { "fieldname": "section_break_5", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "label": "Maintenance Details" }, { "fieldname": "task", @@ -123,10 +122,6 @@ "options": "Planned\nCompleted\nCancelled\nOverdue", "reqd": 1 }, - { - "fieldname": "section_break_12", - "fieldtype": "Section Break" - }, { "default": "0", "fetch_from": "task.certificate_required", @@ -140,10 +135,6 @@ "fieldtype": "Attach", "label": "Certificate" }, - { - "fieldname": "section_break_6", - "fieldtype": "Column Break" - }, { "fetch_from": "task.description", "fieldname": "description", @@ -179,9 +170,10 @@ "read_only": 1 } ], + "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2020-05-28 20:51:48.238397", + "modified": "2021-01-22 12:33:45.888124", "modified_by": "Administrator", "module": "Assets", "name": "Asset Maintenance Log", diff --git a/erpnext/assets/doctype/asset_maintenance_team/asset_maintenance_team.json b/erpnext/assets/doctype/asset_maintenance_team/asset_maintenance_team.json index e2aa548e269..ffa04e58f02 100644 --- a/erpnext/assets/doctype/asset_maintenance_team/asset_maintenance_team.json +++ b/erpnext/assets/doctype/asset_maintenance_team/asset_maintenance_team.json @@ -1,282 +1,87 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "field:maintenance_team_name", - "beta": 0, - "creation": "2017-10-20 11:43:47.712616", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", + "actions": [], + "autoname": "field:maintenance_team_name", + "creation": "2017-10-20 11:43:47.712616", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "maintenance_team_name", + "maintenance_manager", + "maintenance_manager_name", + "column_break_2", + "company", + "section_break_2", + "maintenance_team_members" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "maintenance_team_name", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Maintenance Team Name", - "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": "maintenance_team_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Maintenance Team Name", + "reqd": 1, + "unique": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "maintenance_manager", - "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": "Maintenance Manager", - "length": 0, - "no_copy": 0, - "options": "User", - "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": "maintenance_manager", + "fieldtype": "Link", + "label": "Maintenance Manager", + "options": "User" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fetch_from": "maintenance_manager.full_name", - "fieldname": "maintenance_manager_name", - "fieldtype": "Read Only", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Maintenance Manager Name", - "length": 0, - "no_copy": 0, - "options": "", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "maintenance_manager_name", + "fieldtype": "Read Only", + "label": "Maintenance Manager Name" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_2", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break_2", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "company", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Company", - "length": 0, - "no_copy": 0, - "options": "Company", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "company", + "fieldtype": "Link", + "label": "Company", + "options": "Company", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_2", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "section_break_2", + "fieldtype": "Section Break", + "label": "Team" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "maintenance_team_members", - "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Maintenance Team Members", - "length": 0, - "no_copy": 0, - "options": "Maintenance Team Member", - "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": "maintenance_team_members", + "fieldtype": "Table", + "label": "Maintenance Team Members", + "options": "Maintenance Team Member", + "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": 0, - "max_attachments": 0, - "modified": "2018-05-16 22:43:24.195349", - "modified_by": "Administrator", - "module": "Assets", - "name": "Asset Maintenance Team", - "name_case": "", - "owner": "Administrator", + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2021-01-22 15:09:03.347345", + "modified_by": "Administrator", + "module": "Assets", + "name": "Asset Maintenance Team", + "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Manufacturing User", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Manufacturing User", + "share": 1, "write": 1 } - ], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0 + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/assets/doctype/asset_movement/asset_movement.json b/erpnext/assets/doctype/asset_movement/asset_movement.json index 3472ab5d7da..bdce639b039 100644 --- a/erpnext/assets/doctype/asset_movement/asset_movement.json +++ b/erpnext/assets/doctype/asset_movement/asset_movement.json @@ -1,4 +1,5 @@ { + "actions": [], "allow_import": 1, "autoname": "format:ACC-ASM-{YYYY}-{#####}", "creation": "2016-04-25 18:00:23.559973", @@ -91,8 +92,10 @@ "fieldtype": "Column Break" } ], + "index_web_pages_for_search": 1, "is_submittable": 1, - "modified": "2019-11-23 13:28:47.256935", + "links": [], + "modified": "2021-01-22 12:30:55.295670", "modified_by": "Administrator", "module": "Assets", "name": "Asset Movement", diff --git a/erpnext/assets/doctype/asset_repair/asset_repair.json b/erpnext/assets/doctype/asset_repair/asset_repair.json index 6df6e27bd0c..d338fc0fb79 100644 --- a/erpnext/assets/doctype/asset_repair/asset_repair.json +++ b/erpnext/assets/doctype/asset_repair/asset_repair.json @@ -1,763 +1,208 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "naming_series:", - "beta": 0, - "creation": "2017-10-23 11:38:54.004355", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Document", - "editable_grid": 1, - "engine": "InnoDB", + "actions": [], + "autoname": "naming_series:", + "creation": "2017-10-23 11:38:54.004355", + "doctype": "DocType", + "document_type": "Document", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "naming_series", + "asset_name", + "column_break_2", + "item_code", + "item_name", + "section_break_5", + "failure_date", + "assign_to", + "assign_to_name", + "column_break_6", + "completion_date", + "repair_status", + "repair_cost", + "section_break_9", + "description", + "column_break_9", + "actions_performed", + "section_break_17", + "downtime", + "column_break_19", + "amended_from" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 1, - "fieldname": "asset_name", - "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": "Asset Name", - "length": 0, - "no_copy": 0, - "options": "Asset", - "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": "asset_name", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Asset", + "options": "Asset", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "", - "fieldname": "naming_series", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Series", - "length": 0, - "no_copy": 0, - "options": "ACC-ASR-.YYYY.-", - "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": "naming_series", + "fieldtype": "Select", + "label": "Series", + "options": "ACC-ASR-.YYYY.-", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_2", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break_2", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "asset_name.item_code", - "fieldname": "item_code", - "fieldtype": "Read Only", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Item Code", - "length": 0, - "no_copy": 0, - "options": "", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fetch_from": "asset_name.item_code", + "fieldname": "item_code", + "fieldtype": "Read Only", + "label": "Item Code" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "asset_name.item_name", - "fieldname": "item_name", - "fieldtype": "Read Only", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Item Name", - "length": 0, - "no_copy": 0, - "options": "", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fetch_from": "asset_name.item_name", + "fieldname": "item_name", + "fieldtype": "Read Only", + "label": "Item Name" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_5", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "section_break_5", + "fieldtype": "Section Break", + "label": "Repair Details" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 1, - "fieldname": "failure_date", - "fieldtype": "Datetime", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Failure Date", - "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": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "columns": 1, + "fieldname": "failure_date", + "fieldtype": "Datetime", + "label": "Failure Date", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "assign_to", - "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": "Assign To", - "length": 0, - "no_copy": 0, - "options": "User", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "allow_on_submit": 1, + "fieldname": "assign_to", + "fieldtype": "Link", + "label": "Assign To", + "options": "User" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "assign_to.full_name", - "fieldname": "assign_to_name", - "fieldtype": "Read Only", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Assign To Name", - "length": 0, - "no_copy": 0, - "options": "", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "allow_on_submit": 1, + "fetch_from": "assign_to.full_name", + "fieldname": "assign_to_name", + "fieldtype": "Read Only", + "label": "Assign To Name" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_6", - "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_6", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "completion_date", - "fieldtype": "Datetime", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Completion Date", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "allow_on_submit": 1, + "fieldname": "completion_date", + "fieldtype": "Datetime", + "label": "Completion Date" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Pending", - "fieldname": "repair_status", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Repair Status", - "length": 0, - "no_copy": 1, - "options": "Pending\nCompleted\nCancelled", - "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, - "width": "" - }, + "allow_on_submit": 1, + "default": "Pending", + "fieldname": "repair_status", + "fieldtype": "Select", + "label": "Repair Status", + "no_copy": 1, + "options": "Pending\nCompleted\nCancelled", + "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_9", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "section_break_9", + "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": "description", - "fieldtype": "Long Text", - "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": "Error Description", - "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": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "description", + "fieldtype": "Long Text", + "label": "Error Description", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_9", - "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_9", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "actions_performed", - "fieldtype": "Long Text", - "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": "Actions performed", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "allow_on_submit": 1, + "fieldname": "actions_performed", + "fieldtype": "Long Text", + "label": "Actions performed" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_17", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "section_break_17", + "fieldtype": "Section Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "downtime", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Downtime", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "allow_on_submit": 1, + "fieldname": "downtime", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Downtime", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_19", - "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_19", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "repair_cost", - "fieldtype": "Currency", - "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": "Repair Cost", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "allow_on_submit": 1, + "fieldname": "repair_cost", + "fieldtype": "Currency", + "label": "Repair Cost" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "amended_from", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Amended From", - "length": 0, - "no_copy": 1, - "options": "Asset Repair", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Asset Repair", + "print_hide": 1, + "read_only": 1 } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 1, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2018-08-21 14:44:27.181876", - "modified_by": "Administrator", - "module": "Assets", - "name": "Asset Repair", - "name_case": "", - "owner": "Administrator", + ], + "index_web_pages_for_search": 1, + "is_submittable": 1, + "links": [], + "modified": "2021-01-22 15:08:12.495850", + "modified_by": "Administrator", + "module": "Assets", + "name": "Asset Repair", + "owner": "Administrator", "permissions": [ { - "amend": 1, - "cancel": 1, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Manufacturing Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 1, + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Manufacturing Manager", + "share": 1, + "submit": 1, "write": 1 - }, + }, { - "amend": 1, - "cancel": 1, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Quality Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 1, + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Quality Manager", + "share": 1, + "submit": 1, "write": 1 } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "title_field": "", - "track_changes": 1, - "track_seen": 1, - "track_views": 0 + ], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1, + "track_seen": 1 } \ No newline at end of file diff --git a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.json b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.json index 3236e726ded..57e04e2567f 100644 --- a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.json +++ b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.json @@ -1,4 +1,5 @@ { + "actions": [], "creation": "2018-05-11 00:22:43.695151", "doctype": "DocType", "editable_grid": 1, @@ -7,14 +8,16 @@ "company", "asset", "asset_category", - "finance_book", - "journal_entry", "column_break_4", "date", + "finance_book", + "amended_from", + "value_details_section", "current_asset_value", "new_asset_value", + "column_break_11", "difference_amount", - "amended_from", + "journal_entry", "accounting_dimensions_section", "cost_center", "dimension_col_break" @@ -108,10 +111,21 @@ { "fieldname": "dimension_col_break", "fieldtype": "Column Break" + }, + { + "fieldname": "value_details_section", + "fieldtype": "Section Break", + "label": "Value Details" + }, + { + "fieldname": "column_break_11", + "fieldtype": "Column Break" } ], + "index_web_pages_for_search": 1, "is_submittable": 1, - "modified": "2019-11-22 14:09:25.800375", + "links": [], + "modified": "2021-01-22 14:10:23.085181", "modified_by": "Administrator", "module": "Assets", "name": "Asset Value Adjustment", diff --git a/erpnext/assets/workspace/assets/assets.json b/erpnext/assets/workspace/assets/assets.json new file mode 100644 index 00000000000..c4015817583 --- /dev/null +++ b/erpnext/assets/workspace/assets/assets.json @@ -0,0 +1,193 @@ +{ + "category": "Modules", + "charts": [ + { + "chart_name": "Asset Value Analytics", + "label": "Asset Value Analytics" + } + ], + "creation": "2020-03-02 15:43:27.634865", + "developer_mode_only": 0, + "disable_user_customization": 0, + "docstatus": 0, + "doctype": "Workspace", + "extends_another_page": 0, + "hide_custom": 0, + "icon": "assets", + "idx": 0, + "is_standard": 1, + "label": "Assets", + "links": [ + { + "hidden": 0, + "is_query_report": 0, + "label": "Assets", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Asset", + "link_to": "Asset", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Location", + "link_to": "Location", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Asset Category", + "link_to": "Asset Category", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Asset Movement", + "link_to": "Asset Movement", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Maintenance", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Asset Maintenance Team", + "link_to": "Asset Maintenance Team", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "Asset Maintenance Team", + "hidden": 0, + "is_query_report": 0, + "label": "Asset Maintenance", + "link_to": "Asset Maintenance", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "Asset Maintenance", + "hidden": 0, + "is_query_report": 0, + "label": "Asset Maintenance Log", + "link_to": "Asset Maintenance Log", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Asset", + "hidden": 0, + "is_query_report": 0, + "label": "Asset Value Adjustment", + "link_to": "Asset Value Adjustment", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Asset", + "hidden": 0, + "is_query_report": 0, + "label": "Asset Repair", + "link_to": "Asset Repair", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Reports", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "Asset", + "hidden": 0, + "is_query_report": 1, + "label": "Asset Depreciation Ledger", + "link_to": "Asset Depreciation Ledger", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Asset", + "hidden": 0, + "is_query_report": 1, + "label": "Asset Depreciations and Balances", + "link_to": "Asset Depreciations and Balances", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Asset Maintenance", + "hidden": 0, + "is_query_report": 0, + "label": "Asset Maintenance", + "link_to": "Asset Maintenance", + "link_type": "Report", + "onboard": 0, + "type": "Link" + } + ], + "modified": "2020-12-01 13:38:37.977119", + "modified_by": "Administrator", + "module": "Assets", + "name": "Assets", + "onboarding": "Assets", + "owner": "Administrator", + "pin_to_bottom": 0, + "pin_to_top": 0, + "shortcuts": [ + { + "label": "Asset", + "link_to": "Asset", + "type": "DocType" + }, + { + "label": "Asset Category", + "link_to": "Asset Category", + "type": "DocType" + }, + { + "label": "Fixed Asset Register", + "link_to": "Fixed Asset Register", + "type": "Report" + }, + { + "label": "Dashboard", + "link_to": "Asset", + "type": "Dashboard" + } + ] +} \ No newline at end of file diff --git a/erpnext/buying/desk_page/buying/buying.json b/erpnext/buying/desk_page/buying/buying.json deleted file mode 100644 index 2e870fea82e..00000000000 --- a/erpnext/buying/desk_page/buying/buying.json +++ /dev/null @@ -1,113 +0,0 @@ -{ - "cards": [ - { - "hidden": 0, - "label": "Buying", - "links": "[ \n {\n \"dependencies\": [\n \"Item\"\n ],\n \"description\": \"Request for purchase.\",\n \"label\": \"Material Request\",\n \"name\": \"Material Request\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\",\n \"Supplier\"\n ],\n \"description\": \"Purchase Orders given to Suppliers.\",\n \"label\": \"Purchase Order\",\n \"name\": \"Purchase Order\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\",\n \"Supplier\"\n ],\n \"label\": \"Purchase Invoice\",\n \"name\": \"Purchase Invoice\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\",\n \"Supplier\"\n ],\n \"description\": \"Request for quotation.\",\n \"label\": \"Request for Quotation\",\n \"name\": \"Request for Quotation\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\",\n \"Supplier\"\n ],\n \"description\": \"Quotations received from Suppliers.\",\n \"label\": \"Supplier Quotation\",\n \"name\": \"Supplier Quotation\",\n \"type\": \"doctype\"\n }\n]" - }, - { - "hidden": 0, - "label": "Items & Pricing", - "links": "[\n {\n \"description\": \"All Products or Services.\",\n \"label\": \"Item\",\n \"name\": \"Item\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Multiple Item prices.\",\n \"label\": \"Item Price\",\n \"name\": \"Item Price\",\n \"onboard\": 1,\n \"route\": \"#Report/Item Price\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Price List master.\",\n \"label\": \"Price List\",\n \"name\": \"Price List\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Bundle items at time of sale.\",\n \"label\": \"Product Bundle\",\n \"name\": \"Product Bundle\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Tree of Item Groups.\",\n \"icon\": \"fa fa-sitemap\",\n \"label\": \"Item Group\",\n \"link\": \"Tree/Item Group\",\n \"name\": \"Item Group\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Rules for applying different promotional schemes.\",\n \"label\": \"Promotional Scheme\",\n \"name\": \"Promotional Scheme\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Rules for applying pricing and discount.\",\n \"label\": \"Pricing Rule\",\n \"name\": \"Pricing Rule\",\n \"type\": \"doctype\"\n }\n]" - }, - { - "hidden": 0, - "label": "Settings", - "links": "[\n {\n \"description\": \"Default settings for buying transactions.\",\n \"label\": \"Buying Settings\",\n \"name\": \"Buying Settings\",\n \"settings\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Tax template for buying transactions.\",\n \"label\": \"Purchase Taxes and Charges Template\",\n \"name\": \"Purchase Taxes and Charges Template\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Template of terms or contract.\",\n \"label\": \"Terms and Conditions Template\",\n \"name\": \"Terms and Conditions\",\n \"type\": \"doctype\"\n }\n]" - }, - { - "hidden": 0, - "label": "Supplier", - "links": "[\n {\n \"description\": \"Supplier database.\",\n \"label\": \"Supplier\",\n \"name\": \"Supplier\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Supplier Group master.\",\n \"label\": \"Supplier Group\",\n \"name\": \"Supplier Group\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"All Contacts.\",\n \"label\": \"Contact\",\n \"name\": \"Contact\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"All Addresses.\",\n \"label\": \"Address\",\n \"name\": \"Address\",\n \"type\": \"doctype\"\n }\n]" - }, - { - "hidden": 0, - "label": "Supplier Scorecard", - "links": "[\n {\n \"description\": \"All Supplier scorecards.\",\n \"label\": \"Supplier Scorecard\",\n \"name\": \"Supplier Scorecard\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Templates of supplier scorecard variables.\",\n \"label\": \"Supplier Scorecard Variable\",\n \"name\": \"Supplier Scorecard Variable\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Templates of supplier scorecard criteria.\",\n \"label\": \"Supplier Scorecard Criteria\",\n \"name\": \"Supplier Scorecard Criteria\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Templates of supplier standings.\",\n \"label\": \"Supplier Scorecard Standing\",\n \"name\": \"Supplier Scorecard Standing\",\n \"type\": \"doctype\"\n }\n]" - }, - { - "hidden": 0, - "label": "Key Reports", - "links": "[\n {\n \"is_query_report\": true,\n \"label\": \"Purchase Analytics\",\n \"name\": \"Purchase Analytics\",\n \"onboard\": 1,\n \"reference_doctype\": \"Purchase Order\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Purchase Order Analysis\",\n \"name\": \"Purchase Order Analysis\",\n \"onboard\": 1,\n \"reference_doctype\": \"Purchase Order\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Supplier-Wise Sales Analytics\",\n \"name\": \"Supplier-Wise Sales Analytics\",\n \"onboard\": 1,\n \"reference_doctype\": \"Stock Ledger Entry\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Requested Items to Order\",\n \"name\": \"Requested Items to Order\",\n \"onboard\": 1,\n \"reference_doctype\": \"Material Request\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Purchase Order Trends\",\n \"name\": \"Purchase Order Trends\",\n \"onboard\": 1,\n \"reference_doctype\": \"Purchase Order\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Procurement Tracker\",\n \"name\": \"Procurement Tracker\",\n \"onboard\": 1,\n \"reference_doctype\": \"Purchase Order\",\n \"type\": \"report\"\n }\n]" - }, - { - "hidden": 0, - "label": "Other Reports", - "links": "[\n {\n \"is_query_report\": true,\n \"label\": \"Items To Be Requested\",\n \"name\": \"Items To Be Requested\",\n \"onboard\": 1,\n \"reference_doctype\": \"Item\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Item-wise Purchase History\",\n \"name\": \"Item-wise Purchase History\",\n \"onboard\": 1,\n \"reference_doctype\": \"Item\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Purchase Receipt Trends\",\n \"name\": \"Purchase Receipt Trends\",\n \"reference_doctype\": \"Purchase Receipt\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Purchase Invoice Trends\",\n \"name\": \"Purchase Invoice Trends\",\n \"reference_doctype\": \"Purchase Invoice\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Subcontracted Raw Materials To Be Transferred\",\n \"name\": \"Subcontracted Raw Materials To Be Transferred\",\n \"reference_doctype\": \"Purchase Order\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Subcontracted Item To Be Received\",\n \"name\": \"Subcontracted Item To Be Received\",\n \"reference_doctype\": \"Purchase Order\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Supplier Quotation Comparison\",\n \"name\": \"Supplier Quotation Comparison\",\n \"onboard\": 1,\n \"reference_doctype\": \"Supplier Quotation\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Material Requests for which Supplier Quotations are not created\",\n \"name\": \"Material Requests for which Supplier Quotations are not created\",\n \"reference_doctype\": \"Material Request\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Supplier Addresses And Contacts\",\n \"name\": \"Address And Contacts\",\n \"reference_doctype\": \"Address\",\n \"route_options\": {\n \"party_type\": \"Supplier\"\n },\n \"type\": \"report\"\n }\n]" - }, - { - "hidden": 0, - "label": "Regional", - "links": "[\n {\n \"description\": \"Import Italian Purchase Invoices\",\n \"label\": \"Import Supplier Invoice\",\n \"name\": \"Import Supplier Invoice\",\n \"type\": \"doctype\"\n } \n]" - } - ], - "cards_label": "", - "category": "Modules", - "charts": [ - { - "chart_name": "Purchase Order Trends", - "label": "Purchase Order Trends" - } - ], - "charts_label": "", - "creation": "2020-01-28 11:50:26.195467", - "developer_mode_only": 0, - "disable_user_customization": 0, - "docstatus": 0, - "doctype": "Desk Page", - "extends_another_page": 0, - "hide_custom": 0, - "idx": 0, - "is_standard": 1, - "label": "Buying", - "modified": "2020-09-30 14:40:55.638458", - "modified_by": "Administrator", - "module": "Buying", - "name": "Buying", - "onboarding": "Buying", - "owner": "Administrator", - "pin_to_bottom": 0, - "pin_to_top": 0, - "shortcuts": [ - { - "color": "#cef6d1", - "format": "{} Available", - "label": "Item", - "link_to": "Item", - "stats_filter": "{\n \"disabled\": 0\n}", - "type": "DocType" - }, - { - "color": "#ffe8cd", - "format": "{} Pending", - "label": "Material Request", - "link_to": "Material Request", - "stats_filter": "{\n \"company\": [\"like\", '%' + frappe.defaults.get_global_default(\"company\") + '%'],\n \"status\": \"Pending\"\n}", - "type": "DocType" - }, - { - "color": "#ffe8cd", - "format": "{} To Receive", - "label": "Purchase Order", - "link_to": "Purchase Order", - "stats_filter": "{\n \"company\": [\"like\", '%' + frappe.defaults.get_global_default(\"company\") + '%'],\n \"status\":[\"in\", [\"To Receive\", \"To Receive and Bill\"]]\n}", - "type": "DocType" - }, - { - "label": "Purchase Analytics", - "link_to": "Purchase Analytics", - "type": "Report" - }, - { - "label": "Purchase Order Analysis", - "link_to": "Purchase Order Analysis", - "type": "Report" - }, - { - "label": "Dashboard", - "link_to": "Buying", - "type": "Dashboard" - } - ], - "shortcuts_label": "" -} \ No newline at end of file diff --git a/erpnext/buying/doctype/buying_settings/buying_settings.json b/erpnext/buying/doctype/buying_settings/buying_settings.json index 618212da804..248cb9a8a0e 100644 --- a/erpnext/buying/doctype/buying_settings/buying_settings.json +++ b/erpnext/buying/doctype/buying_settings/buying_settings.json @@ -96,7 +96,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2020-10-13 12:00:23.276329", + "modified": "2021-03-02 17:34:04.190677", "modified_by": "Administrator", "module": "Buying", "name": "Buying Settings", @@ -113,5 +113,6 @@ } ], "sort_field": "modified", - "sort_order": "DESC" -} + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py index d568ef1ceda..02d48653203 100644 --- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py @@ -231,12 +231,12 @@ class TestPurchaseOrder(unittest.TestCase): new_item_with_tax = frappe.get_doc("Item", "Test Item with Tax") new_item_with_tax.append("taxes", { - "item_tax_template": "Test Update Items Template", + "item_tax_template": "Test Update Items Template - _TC", "valid_from": nowdate() }) new_item_with_tax.save() - tax_template = "_Test Account Excise Duty @ 10" + tax_template = "_Test Account Excise Duty @ 10 - _TC" item = "_Test Item Home Desktop 100" if not frappe.db.exists("Item Tax", {"parent":item, "item_tax_template":tax_template}): item_doc = frappe.get_doc("Item", item) @@ -287,7 +287,7 @@ class TestPurchaseOrder(unittest.TestCase): po.cancel() po.delete() new_item_with_tax.delete() - frappe.get_doc("Item Tax Template", "Test Update Items Template").delete() + frappe.get_doc("Item Tax Template", "Test Update Items Template - _TC").delete() def test_update_child_uom_conv_factor_change(self): po = create_purchase_order(item_code="_Test FG Item", is_subcontracted="Yes") diff --git a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.py b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.py index b711e36bf99..8bdcd47e028 100644 --- a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.py +++ b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.py @@ -6,11 +6,8 @@ import frappe from frappe.model.document import Document -from erpnext.controllers.print_settings import print_settings_for_item_table - class PurchaseOrderItem(Document): - def __setup__(self): - print_settings_for_item_table(self) + pass def on_doctype_update(): frappe.db.add_index("Purchase Order Item", ["item_code", "warehouse"]) \ No newline at end of file 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 a51498e9354..7cf22f87e4f 100644 --- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py +++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py @@ -127,6 +127,10 @@ class RequestforQuotation(BuyingController): 'link_doctype': 'Supplier', 'link_name': rfq_supplier.supplier }) + contact.append('email_ids', { + 'email_id': user.name, + 'is_primary': 1 + }) if not contact.email_id and not contact.user: contact.email_id = user.name diff --git a/erpnext/buying/doctype/supplier/supplier.json b/erpnext/buying/doctype/supplier/supplier.json index 40362b1d404..4cc5753cbd0 100644 --- a/erpnext/buying/doctype/supplier/supplier.json +++ b/erpnext/buying/doctype/supplier/supplier.json @@ -26,7 +26,6 @@ "supplier_group", "supplier_type", "pan", - "language", "allow_purchase_invoice_creation_without_purchase_order", "allow_purchase_invoice_creation_without_purchase_receipt", "disabled", @@ -57,6 +56,7 @@ "website", "supplier_details", "column_break_30", + "language", "is_frozen" ], "fields": [ @@ -384,7 +384,7 @@ "idx": 370, "image_field": "image", "links": [], - "modified": "2020-06-17 23:18:20", + "modified": "2021-01-06 19:51:40.939087", "modified_by": "Administrator", "module": "Buying", "name": "Supplier", diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py index ae5611f3c41..6a4c02c075c 100644 --- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py +++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py @@ -71,7 +71,7 @@ class SupplierQuotation(BuyingController): doc_sup = doc_sup[0] if doc_sup else None if not doc_sup: frappe.throw(_("Supplier {0} not found in {1}").format(self.supplier, - " Request for Quotation {0} ".format(doc.name))) + " Request for Quotation {0} ".format(doc.name))) quote_status = _('Received') for item in doc.items: diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation_list.js b/erpnext/buying/doctype/supplier_quotation/supplier_quotation_list.js index 9f4fecea86b..5ab6c980d00 100644 --- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation_list.js +++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation_list.js @@ -4,9 +4,9 @@ frappe.listview_settings['Supplier Quotation'] = { if(doc.status==="Ordered") { return [__("Ordered"), "green", "status,=,Ordered"]; } else if(doc.status==="Rejected") { - return [__("Lost"), "darkgrey", "status,=,Lost"]; + return [__("Lost"), "gray", "status,=,Lost"]; } else if(doc.status==="Expired") { - return [__("Expired"), "darkgrey", "status,=,Expired"]; + return [__("Expired"), "gray", "status,=,Expired"]; } } }; diff --git a/erpnext/buying/doctype/supplier_quotation_item/supplier_quotation_item.py b/erpnext/buying/doctype/supplier_quotation_item/supplier_quotation_item.py index f24e5be0768..64dda879450 100644 --- a/erpnext/buying/doctype/supplier_quotation_item/supplier_quotation_item.py +++ b/erpnext/buying/doctype/supplier_quotation_item/supplier_quotation_item.py @@ -6,8 +6,5 @@ import frappe from frappe.model.document import Document -from erpnext.controllers.print_settings import print_settings_for_item_table - class SupplierQuotationItem(Document): - def __setup__(self): - print_settings_for_item_table(self) + pass diff --git a/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard_list.js b/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard_list.js index c50916e4fae..dc5474e3b43 100644 --- a/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard_list.js +++ b/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard_list.js @@ -10,7 +10,7 @@ frappe.listview_settings["Supplier Scorecard"] = { if (doc.indicator_color) { return [__(doc.status), doc.indicator_color.toLowerCase(), "status,=," + doc.status]; } else { - return [__("Unknown"), "darkgrey", "status,=,''"]; + return [__("Unknown"), "gray", "status,=,''"]; } }, diff --git a/erpnext/buying/workspace/buying/buying.json b/erpnext/buying/workspace/buying/buying.json new file mode 100644 index 00000000000..6c9c0f3011b --- /dev/null +++ b/erpnext/buying/workspace/buying/buying.json @@ -0,0 +1,520 @@ +{ + "cards_label": "", + "category": "Modules", + "charts": [ + { + "chart_name": "Purchase Order Trends", + "label": "Purchase Order Trends" + } + ], + "charts_label": "", + "creation": "2020-01-28 11:50:26.195467", + "developer_mode_only": 0, + "disable_user_customization": 0, + "docstatus": 0, + "doctype": "Workspace", + "extends_another_page": 0, + "hide_custom": 0, + "icon": "buying", + "idx": 0, + "is_standard": 1, + "label": "Buying", + "links": [ + { + "hidden": 0, + "is_query_report": 0, + "label": "Buying", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "Item", + "hidden": 0, + "is_query_report": 0, + "label": "Material Request", + "link_to": "Material Request", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "Item, Supplier", + "hidden": 0, + "is_query_report": 0, + "label": "Purchase Order", + "link_to": "Purchase Order", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "Item, Supplier", + "hidden": 0, + "is_query_report": 0, + "label": "Purchase Invoice", + "link_to": "Purchase Invoice", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "Item, Supplier", + "hidden": 0, + "is_query_report": 0, + "label": "Request for Quotation", + "link_to": "Request for Quotation", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "Item, Supplier", + "hidden": 0, + "is_query_report": 0, + "label": "Supplier Quotation", + "link_to": "Supplier Quotation", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Items & Pricing", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Item", + "link_to": "Item", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Item Price", + "link_to": "Item Price", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Price List", + "link_to": "Price List", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Product Bundle", + "link_to": "Product Bundle", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Item Group", + "link_to": "Item Group", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Promotional Scheme", + "link_to": "Promotional Scheme", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Pricing Rule", + "link_to": "Pricing Rule", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Settings", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Buying Settings", + "link_to": "Buying Settings", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Purchase Taxes and Charges Template", + "link_to": "Purchase Taxes and Charges Template", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Terms and Conditions Template", + "link_to": "Terms and Conditions", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Supplier", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Supplier", + "link_to": "Supplier", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Supplier Group", + "link_to": "Supplier Group", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Contact", + "link_to": "Contact", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Address", + "link_to": "Address", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Supplier Scorecard", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Supplier Scorecard", + "link_to": "Supplier Scorecard", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Supplier Scorecard Variable", + "link_to": "Supplier Scorecard Variable", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Supplier Scorecard Criteria", + "link_to": "Supplier Scorecard Criteria", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Supplier Scorecard Standing", + "link_to": "Supplier Scorecard Standing", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Key Reports", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 1, + "label": "Purchase Analytics", + "link_to": "Purchase Analytics", + "link_type": "Report", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 1, + "label": "Purchase Order Analysis", + "link_to": "Purchase Order Analysis", + "link_type": "Report", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 1, + "label": "Supplier-Wise Sales Analytics", + "link_to": "Supplier-Wise Sales Analytics", + "link_type": "Report", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 1, + "label": "Items to Order and Receive", + "link_to": "Requested Items to Order and Receive", + "link_type": "Report", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 1, + "label": "Purchase Order Trends", + "link_to": "Purchase Order Trends", + "link_type": "Report", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 1, + "label": "Procurement Tracker", + "link_to": "Procurement Tracker", + "link_type": "Report", + "onboard": 1, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Other Reports", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 1, + "label": "Items To Be Requested", + "link_to": "Items To Be Requested", + "link_type": "Report", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 1, + "label": "Item-wise Purchase History", + "link_to": "Item-wise Purchase History", + "link_type": "Report", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 1, + "label": "Purchase Receipt Trends", + "link_to": "Purchase Receipt Trends", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 1, + "label": "Purchase Invoice Trends", + "link_to": "Purchase Invoice Trends", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 1, + "label": "Subcontracted Raw Materials To Be Transferred", + "link_to": "Subcontracted Raw Materials To Be Transferred", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 1, + "label": "Subcontracted Item To Be Received", + "link_to": "Subcontracted Item To Be Received", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 1, + "label": "Supplier Quotation Comparison", + "link_to": "Supplier Quotation Comparison", + "link_type": "Report", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 1, + "label": "Material Requests for which Supplier Quotations are not created", + "link_to": "Material Requests for which Supplier Quotations are not created", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 1, + "label": "Supplier Addresses And Contacts", + "link_to": "Address And Contacts", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Regional", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Import Supplier Invoice", + "link_to": "Import Supplier Invoice", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + } + ], + "modified": "2020-12-01 13:38:38.615167", + "modified_by": "Administrator", + "module": "Buying", + "name": "Buying", + "onboarding": "Buying", + "owner": "Administrator", + "pin_to_bottom": 0, + "pin_to_top": 0, + "shortcuts": [ + { + "color": "Green", + "format": "{} Available", + "label": "Item", + "link_to": "Item", + "stats_filter": "{\n \"disabled\": 0\n}", + "type": "DocType" + }, + { + "color": "Yellow", + "format": "{} Pending", + "label": "Material Request", + "link_to": "Material Request", + "stats_filter": "{\n \"company\": [\"like\", '%' + frappe.defaults.get_global_default(\"company\") + '%'],\n \"status\": \"Pending\"\n}", + "type": "DocType" + }, + { + "color": "Yellow", + "format": "{} To Receive", + "label": "Purchase Order", + "link_to": "Purchase Order", + "stats_filter": "{\n \"company\": [\"like\", '%' + frappe.defaults.get_global_default(\"company\") + '%'],\n \"status\":[\"in\", [\"To Receive\", \"To Receive and Bill\"]]\n}", + "type": "DocType" + }, + { + "label": "Purchase Analytics", + "link_to": "Purchase Analytics", + "type": "Report" + }, + { + "label": "Purchase Order Analysis", + "link_to": "Purchase Order Analysis", + "type": "Report" + }, + { + "label": "Dashboard", + "link_to": "Buying", + "type": "Dashboard" + } + ], + "shortcuts_label": "" +} \ No newline at end of file diff --git a/erpnext/config/accounts.py b/erpnext/config/accounts.py deleted file mode 100644 index 839c4ad84a1..00000000000 --- a/erpnext/config/accounts.py +++ /dev/null @@ -1,626 +0,0 @@ -from __future__ import unicode_literals -from frappe import _ -import frappe - - -def get_data(): - config = [ - { - "label": _("Accounts Receivable"), - "items": [ - { - "type": "doctype", - "name": "Sales Invoice", - "description": _("Bills raised to Customers."), - "onboard": 1, - }, - { - "type": "doctype", - "name": "Customer", - "description": _("Customer database."), - "onboard": 1, - }, - { - "type": "doctype", - "name": "Payment Entry", - "description": _("Bank/Cash transactions against party or for internal transfer") - }, - { - "type": "doctype", - "name": "Payment Request", - "description": _("Payment Request"), - }, - { - "type": "report", - "name": "Accounts Receivable", - "doctype": "Sales Invoice", - "is_query_report": True - }, - { - "type": "report", - "name": "Accounts Receivable Summary", - "doctype": "Sales Invoice", - "is_query_report": True - }, - { - "type": "report", - "name": "Sales Register", - "doctype": "Sales Invoice", - "is_query_report": True - }, - { - "type": "report", - "name": "Item-wise Sales Register", - "is_query_report": True, - "doctype": "Sales Invoice" - }, - { - "type": "report", - "name": "Ordered Items To Be Billed", - "is_query_report": True, - "doctype": "Sales Invoice" - }, - { - "type": "report", - "name": "Delivered Items To Be Billed", - "is_query_report": True, - "doctype": "Sales Invoice" - }, - ] - }, - { - "label": _("Accounts Payable"), - "items": [ - { - "type": "doctype", - "name": "Purchase Invoice", - "description": _("Bills raised by Suppliers."), - "onboard": 1 - }, - { - "type": "doctype", - "name": "Supplier", - "description": _("Supplier database."), - "onboard": 1, - }, - { - "type": "doctype", - "name": "Payment Entry", - "description": _("Bank/Cash transactions against party or for internal transfer") - }, - { - "type": "report", - "name": "Accounts Payable", - "doctype": "Purchase Invoice", - "is_query_report": True - }, - { - "type": "report", - "name": "Accounts Payable Summary", - "doctype": "Purchase Invoice", - "is_query_report": True - }, - { - "type": "report", - "name": "Purchase Register", - "doctype": "Purchase Invoice", - "is_query_report": True - }, - { - "type": "report", - "name": "Item-wise Purchase Register", - "is_query_report": True, - "doctype": "Purchase Invoice" - }, - { - "type": "report", - "name": "Purchase Order Items To Be Billed", - "is_query_report": True, - "doctype": "Purchase Invoice" - }, - { - "type": "report", - "name": "Received Items To Be Billed", - "is_query_report": True, - "doctype": "Purchase Invoice" - }, - ] - }, - { - "label": _("Accounting Masters"), - "items": [ - { - "type": "doctype", - "name": "Company", - "description": _("Company (not Customer or Supplier) master."), - "onboard": 1, - }, - { - "type": "doctype", - "name": "Account", - "icon": "fa fa-sitemap", - "label": _("Chart of Accounts"), - "route": "#Tree/Account", - "description": _("Tree of financial accounts."), - "onboard": 1, - }, - { - "type": "doctype", - "name": "Accounts Settings", - }, - { - "type": "doctype", - "name": "Fiscal Year", - "description": _("Financial / accounting year.") - }, - { - "type": "doctype", - "name": "Accounting Dimension", - }, - { - "type": "doctype", - "name": "Finance Book", - }, - { - "type": "doctype", - "name": "Accounting Period", - }, - { - "type": "doctype", - "name": "Payment Term", - "description": _("Payment Terms based on conditions") - }, - ] - }, - { - "label": _("Banking and Payments"), - "items": [ - { - "type": "doctype", - "label": _("Match Payments with Invoices"), - "name": "Payment Reconciliation", - "description": _("Match non-linked Invoices and Payments.") - }, - { - "type": "doctype", - "label": _("Update Bank Clearance Dates"), - "name": "Bank Clearance", - "description": _("Update bank payment dates with journals.") - }, - { - "type": "doctype", - "label": _("Invoice Discounting"), - "name": "Invoice Discounting", - }, - { - "type": "report", - "name": "Bank Reconciliation Statement", - "is_query_report": True, - "doctype": "Journal Entry" - },{ - "type": "page", - "name": "bank-reconciliation", - "label": _("Bank Reconciliation"), - "icon": "fa fa-bar-chart" - }, - { - "type": "report", - "name": "Bank Clearance Summary", - "is_query_report": True, - "doctype": "Journal Entry" - }, - { - "type": "doctype", - "name": "Bank Guarantee" - }, - { - "type": "doctype", - "name": "Cheque Print Template", - "description": _("Setup cheque dimensions for printing") - }, - ] - }, - { - "label": _("General Ledger"), - "items": [ - { - "type": "doctype", - "name": "Journal Entry", - "description": _("Accounting journal entries.") - }, - { - "type": "report", - "name": "General Ledger", - "doctype": "GL Entry", - "is_query_report": True, - }, - { - "type": "report", - "name": "Customer Ledger Summary", - "doctype": "Sales Invoice", - "is_query_report": True, - }, - { - "type": "report", - "name": "Supplier Ledger Summary", - "doctype": "Sales Invoice", - "is_query_report": True, - }, - { - "type": "doctype", - "name": "Process Deferred Accounting" - } - ] - }, - { - "label": _("Taxes"), - "items": [ - { - "type": "doctype", - "name": "Sales Taxes and Charges Template", - "description": _("Tax template for selling transactions.") - }, - { - "type": "doctype", - "name": "Purchase Taxes and Charges Template", - "description": _("Tax template for buying transactions.") - }, - { - "type": "doctype", - "name": "Item Tax Template", - "description": _("Tax template for item tax rates.") - }, - { - "type": "doctype", - "name": "Tax Category", - "description": _("Tax Category for overriding tax rates.") - }, - { - "type": "doctype", - "name": "Tax Rule", - "description": _("Tax Rule for transactions.") - }, - { - "type": "doctype", - "name": "Tax Withholding Category", - "description": _("Tax Withholding rates to be applied on transactions.") - }, - ] - }, - { - "label": _("Cost Center and Budgeting"), - "items": [ - { - "type": "doctype", - "name": "Cost Center", - "icon": "fa fa-sitemap", - "label": _("Chart of Cost Centers"), - "route": "#Tree/Cost Center", - "description": _("Tree of financial Cost Centers."), - }, - { - "type": "doctype", - "name": "Budget", - "description": _("Define budget for a financial year.") - }, - { - "type": "doctype", - "name": "Accounting Dimension", - }, - { - "type": "report", - "name": "Budget Variance Report", - "is_query_report": True, - "doctype": "Cost Center" - }, - { - "type": "doctype", - "name": "Monthly Distribution", - "description": _("Seasonality for setting budgets, targets etc.") - }, - ] - }, - { - "label": _("Financial Statements"), - "items": [ - { - "type": "report", - "name": "Trial Balance", - "doctype": "GL Entry", - "is_query_report": True, - }, - { - "type": "report", - "name": "Profit and Loss Statement", - "doctype": "GL Entry", - "is_query_report": True - }, - { - "type": "report", - "name": "Balance Sheet", - "doctype": "GL Entry", - "is_query_report": True - }, - { - "type": "report", - "name": "Cash Flow", - "doctype": "GL Entry", - "is_query_report": True - }, - { - "type": "report", - "name": "Consolidated Financial Statement", - "doctype": "GL Entry", - "is_query_report": True - }, - ] - }, - { - "label": _("Opening and Closing"), - "items": [ - { - "type": "doctype", - "name": "Opening Invoice Creation Tool", - }, - { - "type": "doctype", - "name": "Chart of Accounts Importer", - }, - { - "type": "doctype", - "name": "Period Closing Voucher", - "description": _("Close Balance Sheet and book Profit or Loss.") - }, - ] - - }, - { - "label": _("Multi Currency"), - "items": [ - { - "type": "doctype", - "name": "Currency", - "description": _("Enable / disable currencies.") - }, - { - "type": "doctype", - "name": "Currency Exchange", - "description": _("Currency exchange rate master.") - }, - { - "type": "doctype", - "name": "Exchange Rate Revaluation", - "description": _("Exchange Rate Revaluation master.") - }, - ] - }, - { - "label": _("Settings"), - "icon": "fa fa-cog", - "items": [ - { - "type": "doctype", - "name": "Payment Gateway Account", - "description": _("Setup Gateway accounts.") - }, - { - "type": "doctype", - "name": "Terms and Conditions", - "label": _("Terms and Conditions Template"), - "description": _("Template of terms or contract.") - }, - { - "type": "doctype", - "name": "Mode of Payment", - "description": _("e.g. Bank, Cash, Credit Card") - }, - ] - }, - { - "label": _("Subscription Management"), - "items": [ - { - "type": "doctype", - "name": "Subscriber", - }, - { - "type": "doctype", - "name": "Subscription Plan", - }, - { - "type": "doctype", - "name": "Subscription" - }, - { - "type": "doctype", - "name": "Subscription Settings" - } - ] - }, - { - "label": _("Bank Statement"), - "items": [ - { - "type": "doctype", - "label": _("Bank"), - "name": "Bank", - }, - { - "type": "doctype", - "label": _("Bank Account"), - "name": "Bank Account", - }, - { - "type": "doctype", - "name": "Bank Statement Transaction Entry", - }, - { - "type": "doctype", - "label": _("Bank Statement Settings"), - "name": "Bank Statement Settings", - }, - ] - }, - { - "label": _("Profitability"), - "items": [ - { - "type": "report", - "name": "Gross Profit", - "doctype": "Sales Invoice", - "is_query_report": True - }, - { - "type": "report", - "name": "Profitability Analysis", - "doctype": "GL Entry", - "is_query_report": True, - }, - { - "type": "report", - "name": "Sales Invoice Trends", - "is_query_report": True, - "doctype": "Sales Invoice" - }, - { - "type": "report", - "name": "Purchase Invoice Trends", - "is_query_report": True, - "doctype": "Purchase Invoice" - }, - ] - }, - { - "label": _("Reports"), - "icon": "fa fa-table", - "items": [ - { - "type": "report", - "name": "Trial Balance for Party", - "doctype": "GL Entry", - "is_query_report": True, - }, - { - "type": "report", - "name": "Payment Period Based On Invoice Date", - "is_query_report": True, - "doctype": "Journal Entry" - }, - { - "type": "report", - "name": "Sales Partners Commission", - "is_query_report": True, - "doctype": "Sales Invoice" - }, - { - "type": "report", - "is_query_report": True, - "name": "Customer Credit Balance", - "doctype": "Customer" - }, - { - "type": "report", - "is_query_report": True, - "name": "Sales Payment Summary", - "doctype": "Sales Invoice" - }, - { - "type": "report", - "is_query_report": True, - "name": "Address And Contacts", - "doctype": "Address" - } - ] - }, - { - "label": _("Share Management"), - "icon": "fa fa-microchip ", - "items": [ - { - "type": "doctype", - "name": "Shareholder", - "description": _("List of available Shareholders with folio numbers") - }, - { - "type": "doctype", - "name": "Share Transfer", - "description": _("List of all share transactions"), - }, - { - "type": "report", - "name": "Share Ledger", - "doctype": "Share Transfer", - "is_query_report": True - }, - { - "type": "report", - "name": "Share Balance", - "doctype": "Share Transfer", - "is_query_report": True - } - ] - }, - - ] - - gst = { - "label": _("Goods and Services Tax (GST India)"), - "items": [ - { - "type": "doctype", - "name": "GST Settings", - }, - { - "type": "doctype", - "name": "GST HSN Code", - }, - { - "type": "report", - "name": "GSTR-1", - "is_query_report": True - }, - { - "type": "report", - "name": "GSTR-2", - "is_query_report": True - }, - { - "type": "doctype", - "name": "GSTR 3B Report", - }, - { - "type": "report", - "name": "GST Sales Register", - "is_query_report": True - }, - { - "type": "report", - "name": "GST Purchase Register", - "is_query_report": True - }, - { - "type": "report", - "name": "GST Itemised Sales Register", - "is_query_report": True - }, - { - "type": "report", - "name": "GST Itemised Purchase Register", - "is_query_report": True - }, - { - "type": "doctype", - "name": "C-Form", - "description": _("C-Form records"), - "country": "India" - }, - ] - } - - - countries = frappe.get_all("Company", fields="country") - countries = [country["country"] for country in countries] - if "India" in countries: - config.insert(9, gst) - domains = frappe.get_active_domains() - return config diff --git a/erpnext/config/agriculture.py b/erpnext/config/agriculture.py deleted file mode 100644 index 937d76ef7b3..00000000000 --- a/erpnext/config/agriculture.py +++ /dev/null @@ -1,70 +0,0 @@ -from __future__ import unicode_literals -from frappe import _ - -def get_data(): - return [ - { - "label": _("Crops & Lands"), - "items": [ - { - "type": "doctype", - "name": "Crop", - "onboard": 1, - }, - { - "type": "doctype", - "name": "Crop Cycle", - "onboard": 1, - }, - { - "type": "doctype", - "name": "Location", - "onboard": 1, - } - ] - }, - { - "label": _("Diseases & Fertilizers"), - "items": [ - { - "type": "doctype", - "name": "Disease", - "onboard": 1, - }, - { - "type": "doctype", - "name": "Fertilizer", - "onboard": 1, - } - ] - }, - { - "label": _("Analytics"), - "items": [ - { - "type": "doctype", - "name": "Plant Analysis", - }, - { - "type": "doctype", - "name": "Soil Analysis", - }, - { - "type": "doctype", - "name": "Water Analysis", - }, - { - "type": "doctype", - "name": "Soil Texture", - }, - { - "type": "doctype", - "name": "Weather", - }, - { - "type": "doctype", - "name": "Agriculture Analysis Criteria", - } - ] - }, - ] \ No newline at end of file diff --git a/erpnext/config/assets.py b/erpnext/config/assets.py deleted file mode 100644 index 4cf7cf08067..00000000000 --- a/erpnext/config/assets.py +++ /dev/null @@ -1,94 +0,0 @@ -from __future__ import unicode_literals -from frappe import _ - -def get_data(): - return [ - { - "label": _("Assets"), - "items": [ - { - "type": "doctype", - "name": "Asset", - "onboard": 1, - }, - { - "type": "doctype", - "name": "Location", - "onboard": 1, - }, - { - "type": "doctype", - "name": "Asset Category", - "onboard": 1, - }, - { - "type": "doctype", - "name": "Asset Movement", - "description": _("Transfer an asset from one warehouse to another") - }, - ] - }, - { - "label": _("Maintenance"), - "items": [ - { - "type": "doctype", - "name": "Asset Maintenance Team", - "onboard": 1, - }, - { - "type": "doctype", - "name": "Asset Maintenance", - "onboard": 1, - "dependencies": ["Asset Maintenance Team"], - }, - { - "type": "doctype", - "name": "Asset Maintenance Tasks", - "onboard": 1, - "dependencies": ["Asset Maintenance"], - }, - { - "type": "doctype", - "name": "Asset Maintenance Log", - "dependencies": ["Asset Maintenance"], - }, - { - "type": "doctype", - "name": "Asset Value Adjustment", - "dependencies": ["Asset"], - }, - { - "type": "doctype", - "name": "Asset Repair", - "dependencies": ["Asset"], - }, - ] - }, - { - "label": _("Reports"), - "icon": "fa fa-table", - "items": [ - { - "type": "report", - "name": "Asset Depreciation Ledger", - "doctype": "Asset", - "is_query_report": True, - "dependencies": ["Asset"], - }, - { - "type": "report", - "name": "Asset Depreciations and Balances", - "doctype": "Asset", - "is_query_report": True, - "dependencies": ["Asset"], - }, - { - "type": "report", - "name": "Asset Maintenance", - "doctype": "Asset Maintenance", - "dependencies": ["Asset Maintenance"] - }, - ] - } - ] diff --git a/erpnext/config/buying.py b/erpnext/config/buying.py deleted file mode 100644 index b06bb76ca82..00000000000 --- a/erpnext/config/buying.py +++ /dev/null @@ -1,264 +0,0 @@ -from __future__ import unicode_literals -import frappe -from frappe import _ - -def get_data(): - config = [ - { - "label": _("Purchasing"), - "icon": "fa fa-star", - "items": [ - { - "type": "doctype", - "name": "Material Request", - "onboard": 1, - "dependencies": ["Item"], - "description": _("Request for purchase."), - }, - { - "type": "doctype", - "name": "Purchase Order", - "onboard": 1, - "dependencies": ["Item", "Supplier"], - "description": _("Purchase Orders given to Suppliers."), - }, - { - "type": "doctype", - "name": "Purchase Invoice", - "onboard": 1, - "dependencies": ["Item", "Supplier"] - }, - { - "type": "doctype", - "name": "Request for Quotation", - "onboard": 1, - "dependencies": ["Item", "Supplier"], - "description": _("Request for quotation."), - }, - { - "type": "doctype", - "name": "Supplier Quotation", - "dependencies": ["Item", "Supplier"], - "description": _("Quotations received from Suppliers."), - }, - ] - }, - { - "label": _("Items and Pricing"), - "items": [ - { - "type": "doctype", - "name": "Item", - "onboard": 1, - "description": _("All Products or Services."), - }, - { - "type": "doctype", - "name": "Item Price", - "description": _("Multiple Item prices."), - "onboard": 1, - "route": "#Report/Item Price" - }, - { - "type": "doctype", - "name": "Price List", - "description": _("Price List master.") - }, - { - "type": "doctype", - "name": "Pricing Rule", - "description": _("Rules for applying pricing and discount.") - }, - { - "type": "doctype", - "name": "Product Bundle", - "description": _("Bundle items at time of sale."), - }, - { - "type": "doctype", - "name": "Item Group", - "icon": "fa fa-sitemap", - "label": _("Item Group"), - "link": "Tree/Item Group", - "description": _("Tree of Item Groups."), - }, - { - "type": "doctype", - "name": "Promotional Scheme", - "description": _("Rules for applying different promotional schemes.") - } - ] - }, - { - "label": _("Settings"), - "icon": "fa fa-cog", - "items": [ - { - "type": "doctype", - "name": "Buying Settings", - "settings": 1, - "description": _("Default settings for buying transactions.") - }, - { - "type": "doctype", - "name": "Purchase Taxes and Charges Template", - "description": _("Tax template for buying transactions.") - }, - { - "type": "doctype", - "name":"Terms and Conditions", - "label": _("Terms and Conditions Template"), - "description": _("Template of terms or contract.") - }, - ] - }, - { - "label": _("Supplier"), - "items": [ - { - "type": "doctype", - "name": "Supplier", - "onboard": 1, - "description": _("Supplier database."), - }, - { - "type": "doctype", - "name": "Supplier Group", - "description": _("Supplier Group master.") - }, - { - "type": "doctype", - "name": "Contact", - "description": _("All Contacts."), - }, - { - "type": "doctype", - "name": "Address", - "description": _("All Addresses."), - }, - - ] - }, - { - "label": _("Key Reports"), - "icon": "fa fa-table", - "items": [ - { - "type": "report", - "is_query_report": True, - "name": "Purchase Analytics", - "reference_doctype": "Purchase Order", - "onboard": 1 - }, - { - "type": "report", - "is_query_report": True, - "name": "Purchase Order Trends", - "reference_doctype": "Purchase Order", - "onboard": 1, - }, - { - "type": "report", - "is_query_report": True, - "name": "Procurement Tracker", - "reference_doctype": "Purchase Order", - "onboard": 1, - }, - { - "type": "report", - "is_query_report": True, - "name": "Requested Items To Order", - "reference_doctype": "Material Request", - "onboard": 1, - }, - { - "type": "report", - "is_query_report": True, - "name": "Address And Contacts", - "label": _("Supplier Addresses And Contacts"), - "reference_doctype": "Address", - "route_options": { - "party_type": "Supplier" - } - } - ] - }, - { - "label": _("Supplier Scorecard"), - "items": [ - { - "type": "doctype", - "name": "Supplier Scorecard", - "description": _("All Supplier scorecards."), - }, - { - "type": "doctype", - "name": "Supplier Scorecard Variable", - "description": _("Templates of supplier scorecard variables.") - }, - { - "type": "doctype", - "name": "Supplier Scorecard Criteria", - "description": _("Templates of supplier scorecard criteria."), - }, - { - "type": "doctype", - "name": "Supplier Scorecard Standing", - "description": _("Templates of supplier standings."), - }, - - ] - }, - { - "label": _("Other Reports"), - "icon": "fa fa-list", - "items": [ - { - "type": "report", - "is_query_report": True, - "name": "Items To Be Requested", - "reference_doctype": "Item", - "onboard": 1, - }, - { - "type": "report", - "is_query_report": True, - "name": "Item-wise Purchase History", - "reference_doctype": "Item", - "onboard": 1, - }, - { - "type": "report", - "is_query_report": True, - "name": "Supplier-Wise Sales Analytics", - "reference_doctype": "Stock Ledger Entry", - "onboard": 1 - }, - { - "type": "report", - "is_query_report": True, - "name": "Material Requests for which Supplier Quotations are not created", - "reference_doctype": "Material Request" - } - ] - }, - - ] - - regional = { - "label": _("Regional"), - "items": [ - { - "type": "doctype", - "name": "Import Supplier Invoice", - "description": _("Import Italian Supplier Invoice."), - "onboard": 1, - } - ] - } - - countries = frappe.get_all("Company", fields="country") - countries = [country["country"] for country in countries] - if "Italy" in countries: - config.append(regional) - return config \ No newline at end of file diff --git a/erpnext/config/crm.py b/erpnext/config/crm.py deleted file mode 100644 index 09c2a65633b..00000000000 --- a/erpnext/config/crm.py +++ /dev/null @@ -1,236 +0,0 @@ -from __future__ import unicode_literals -from frappe import _ - -def get_data(): - return [ - { - "label": _("Sales Pipeline"), - "icon": "fa fa-star", - "items": [ - { - "type": "doctype", - "name": "Lead", - "description": _("Database of potential customers."), - "onboard": 1, - }, - { - "type": "doctype", - "name": "Opportunity", - "description": _("Potential opportunities for selling."), - "onboard": 1, - }, - { - "type": "doctype", - "name": "Customer", - "description": _("Customer database."), - "onboard": 1, - }, - { - "type": "doctype", - "name": "Contact", - "description": _("All Contacts."), - "onboard": 1, - }, - { - "type": "doctype", - "name": "Communication", - "description": _("Record of all communications of type email, phone, chat, visit, etc."), - }, - { - "type": "doctype", - "name": "Lead Source", - "description": _("Track Leads by Lead Source.") - }, - { - "type": "doctype", - "name": "Contract", - "description": _("Helps you keep tracks of Contracts based on Supplier, Customer and Employee"), - }, - { - "type": "doctype", - "name": "Appointment", - "description" : _("Helps you manage appointments with your leads"), - }, - { - "type": "doctype", - "name": "Newsletter", - "label": _("Newsletter"), - } - ] - }, - { - "label": _("Reports"), - "icon": "fa fa-list", - "items": [ - { - "type": "report", - "is_query_report": True, - "name": "Lead Details", - "doctype": "Lead", - "onboard": 1, - }, - { - "type": "page", - "name": "sales-funnel", - "label": _("Sales Funnel"), - "icon": "fa fa-bar-chart", - "onboard": 1, - }, - { - "type": "report", - "name": "Prospects Engaged But Not Converted", - "doctype": "Lead", - "is_query_report": True, - "onboard": 1, - }, - { - "type": "report", - "name": "Minutes to First Response for Opportunity", - "doctype": "Opportunity", - "is_query_report": True, - "dependencies": ["Opportunity"] - }, - { - "type": "report", - "is_query_report": True, - "name": "Customer Addresses And Contacts", - "doctype": "Contact", - "dependencies": ["Customer"] - }, - { - "type": "report", - "is_query_report": True, - "name": "Inactive Customers", - "doctype": "Sales Order", - "dependencies": ["Sales Order"] - }, - { - "type": "report", - "is_query_report": True, - "name": "Campaign Efficiency", - "doctype": "Lead", - "dependencies": ["Lead"] - }, - { - "type": "report", - "is_query_report": True, - "name": "Lead Owner Efficiency", - "doctype": "Lead", - "dependencies": ["Lead"] - }, - { - "type": "report", - "is_query_report": True, - "name": "Territory-wise Sales", - "doctype": "Opportunity", - "dependencies": ["Opportunity"] - } - ] - }, - { - "label": _("Settings"), - "icon": "fa fa-cog", - "items": [ - { - "type": "doctype", - "label": _("Customer Group"), - "name": "Customer Group", - "icon": "fa fa-sitemap", - "link": "Tree/Customer Group", - "description": _("Manage Customer Group Tree."), - "onboard": 1, - }, - { - "type": "doctype", - "label": _("Territory"), - "name": "Territory", - "icon": "fa fa-sitemap", - "link": "Tree/Territory", - "description": _("Manage Territory Tree."), - "onboard": 1, - }, - { - "type": "doctype", - "label": _("Sales Person"), - "name": "Sales Person", - "icon": "fa fa-sitemap", - "link": "Tree/Sales Person", - "description": _("Manage Sales Person Tree."), - "onboard": 1, - }, - { - "type": "doctype", - "name": "Campaign", - "description": _("Sales campaigns."), - }, - { - "type": "doctype", - "name": "Email Campaign", - "description": _("Sends Mails to lead or contact based on a Campaign schedule"), - }, - { - "type": "doctype", - "name": "SMS Center", - "description":_("Send mass SMS to your contacts"), - }, - { - "type": "doctype", - "name": "SMS Log", - "description":_("Logs for maintaining sms delivery status"), - }, - { - "type": "doctype", - "name": "SMS Settings", - "description": _("Setup SMS gateway settings") - }, - { - "type": "doctype", - "label": _("Email Group"), - "name": "Email Group", - } - ] - }, - { - "label": _("Maintenance"), - "icon": "fa fa-star", - "items": [ - { - "type": "doctype", - "name": "Maintenance Schedule", - "description": _("Plan for maintenance visits."), - "onboard": 1, - }, - { - "type": "doctype", - "name": "Maintenance Visit", - "description": _("Visit report for maintenance call."), - }, - { - "type": "report", - "name": "Maintenance Schedules", - "is_query_report": True, - "doctype": "Maintenance Schedule" - }, - { - "type": "doctype", - "name": "Warranty Claim", - "description": _("Warranty Claim against Serial No."), - }, - ] - }, - # { - # "label": _("Help"), - # "items": [ - # { - # "type": "help", - # "label": _("Lead to Quotation"), - # "youtube_id": "TxYX4r4JAKA" - # }, - # { - # "type": "help", - # "label": _("Newsletters"), - # "youtube_id": "muLKsCrrDRo" - # }, - # ] - # }, - ] diff --git a/erpnext/config/desktop.py b/erpnext/config/desktop.py deleted file mode 100644 index ce7c245a631..00000000000 --- a/erpnext/config/desktop.py +++ /dev/null @@ -1,220 +0,0 @@ -# coding=utf-8 - -from __future__ import unicode_literals -from frappe import _ - -def get_data(): - return [ - # Modules - { - "module_name": "Getting Started", - "category": "Modules", - "label": _("Getting Started"), - "color": "#1abc9c", - "icon": "fa fa-check-square-o", - "type": "module", - "disable_after_onboard": 1, - "description": "Dive into the basics for your organisation's needs.", - "onboard_present": 1 - }, - { - "module_name": "Accounts", - "category": "Modules", - "label": _("Accounting"), - "color": "#3498db", - "icon": "octicon octicon-repo", - "type": "module", - "description": "Accounts, billing, payments, cost center and budgeting." - }, - { - "module_name": "Selling", - "category": "Modules", - "label": _("Selling"), - "color": "#1abc9c", - "icon": "octicon octicon-tag", - "type": "module", - "description": "Sales orders, quotations, customers and items." - }, - { - "module_name": "Buying", - "category": "Modules", - "label": _("Buying"), - "color": "#c0392b", - "icon": "octicon octicon-briefcase", - "type": "module", - "description": "Purchasing, suppliers, material requests, and items." - }, - { - "module_name": "Stock", - "category": "Modules", - "label": _("Stock"), - "color": "#f39c12", - "icon": "octicon octicon-package", - "type": "module", - "description": "Stock transactions, reports, serial numbers and batches." - }, - { - "module_name": "Assets", - "category": "Modules", - "label": _("Assets"), - "color": "#4286f4", - "icon": "octicon octicon-database", - "type": "module", - "description": "Asset movement, maintainance and tools." - }, - { - "module_name": "Projects", - "category": "Modules", - "label": _("Projects"), - "color": "#8e44ad", - "icon": "octicon octicon-rocket", - "type": "module", - "description": "Updates, Timesheets and Activities." - }, - { - "module_name": "CRM", - "category": "Modules", - "label": _("CRM"), - "color": "#EF4DB6", - "icon": "octicon octicon-broadcast", - "type": "module", - "description": "Sales pipeline, leads, opportunities and customers." - }, - { - "module_name": "Loan Management", - "category": "Modules", - "label": _("Loan Management"), - "color": "#EF4DB6", - "icon": "octicon octicon-repo", - "type": "module", - "description": "Loan Management for Customer and Employees" - }, - { - "module_name": "Support", - "category": "Modules", - "label": _("Support"), - "color": "#1abc9c", - "icon": "fa fa-check-square-o", - "type": "module", - "description": "User interactions, support issues and knowledge base." - }, - { - "module_name": "HR", - "category": "Modules", - "label": _("Human Resources"), - "color": "#2ecc71", - "icon": "octicon octicon-organization", - "type": "module", - "description": "Employees, attendance, payroll, leaves and shifts." - }, - { - "module_name": "Quality Management", - "category": "Modules", - "label": _("Quality"), - "color": "#1abc9c", - "icon": "fa fa-check-square-o", - "type": "module", - "description": "Quality goals, procedures, reviews and action." - }, - - - # Category: "Domains" - { - "module_name": "Manufacturing", - "category": "Domains", - "label": _("Manufacturing"), - "color": "#7f8c8d", - "icon": "octicon octicon-tools", - "type": "module", - "description": "BOMS, work orders, operations, and timesheets." - }, - { - "module_name": "Retail", - "category": "Domains", - "label": _("Retail"), - "color": "#7f8c8d", - "icon": "octicon octicon-credit-card", - "type": "module", - "description": "Point of Sale and cashier closing." - }, - { - "module_name": "Education", - "category": "Domains", - "label": _("Education"), - "color": "#428B46", - "icon": "octicon octicon-mortar-board", - "type": "module", - "description": "Student admissions, fees, courses and scores." - }, - - { - "module_name": "Healthcare", - "category": "Domains", - "label": _("Healthcare"), - "color": "#FF888B", - "icon": "fa fa-heartbeat", - "type": "module", - "description": "Patient appointments, procedures and tests." - }, - { - "module_name": "Agriculture", - "category": "Domains", - "label": _("Agriculture"), - "color": "#8BC34A", - "icon": "octicon octicon-globe", - "type": "module", - "description": "Crop cycles, land areas, soil and plant analysis." - }, - { - "module_name": "Hotels", - "category": "Domains", - "label": _("Hotels"), - "color": "#EA81E8", - "icon": "fa fa-bed", - "type": "module", - "description": "Hotel rooms, pricing, reservation and amenities." - }, - - { - "module_name": "Non Profit", - "category": "Domains", - "label": _("Non Profit"), - "color": "#DE2B37", - "icon": "octicon octicon-heart", - "type": "module", - "description": "Volunteers, memberships, grants and chapters." - }, - { - "module_name": "Restaurant", - "category": "Domains", - "label": _("Restaurant"), - "color": "#EA81E8", - "icon": "fa fa-cutlery", - "_doctype": "Restaurant", - "type": "module", - "link": "List/Restaurant", - "description": "Menu, Orders and Table Reservations." - }, - - { - "module_name": "Help", - "category": "Administration", - "label": _("Learn"), - "color": "#FF888B", - "icon": "octicon octicon-device-camera-video", - "type": "module", - "is_help": True, - "description": "Explore Help Articles and Videos." - }, - { - "module_name": 'Marketplace', - "category": "Places", - "label": _('Marketplace'), - "icon": "octicon octicon-star", - "type": 'link', - "link": '#marketplace/home', - "color": '#FF4136', - 'standard': 1, - "description": "Publish items to other ERPNext users." - }, - ] diff --git a/erpnext/config/docs.py b/erpnext/config/docs.py deleted file mode 100644 index 85e600687f2..00000000000 --- a/erpnext/config/docs.py +++ /dev/null @@ -1,3 +0,0 @@ -from __future__ import unicode_literals - -source_link = "https://github.com/erpnext/foundation" diff --git a/erpnext/config/education.py b/erpnext/config/education.py index 4efaaa65cdc..1c8ab10f537 100644 --- a/erpnext/config/education.py +++ b/erpnext/config/education.py @@ -173,7 +173,7 @@ def get_data(): { "type": "doctype", "name": "Course Schedule", - "route": "#List/Course Schedule/Calendar" + "route": "/app/List/Course Schedule/Calendar" }, { "type": "doctype", diff --git a/erpnext/config/getting_started.py b/erpnext/config/getting_started.py deleted file mode 100644 index dc72316d08b..00000000000 --- a/erpnext/config/getting_started.py +++ /dev/null @@ -1,268 +0,0 @@ -from __future__ import unicode_literals -import frappe -from frappe import _ - -active_domains = frappe.get_active_domains() - -def get_data(): - return [ - { - "label": _("Accounting"), - "items": [ - { - "type": "doctype", - "name": "Item", - "onboard": 1, - }, - { - "type": "doctype", - "name": "Customer", - "description": _("Customer database."), - "onboard": 1, - }, - { - "type": "doctype", - "name": "Supplier", - "description": _("Supplier database."), - "onboard": 1, - }, - { - "type": "doctype", - "name": "Company", - "description": _("Company (not Customer or Supplier) master."), - "onboard": 1, - }, - { - "type": "doctype", - "name": "Account", - "icon": "fa fa-sitemap", - "label": _("Chart of Accounts"), - "route": "#Tree/Account", - "description": _("Tree of financial accounts."), - "onboard": 1, - }, - { - "type": "doctype", - "name": "Opening Invoice Creation Tool", - "description": _("Create Opening Sales and Purchase Invoices"), - "onboard": 1, - }, - ] - }, - { - "label": _("Data Import and Settings"), - "items": [ - { - "type": "doctype", - "name": "Data Import", - "label": _("Import Data"), - "icon": "octicon octicon-cloud-upload", - "description": _("Import Data from CSV / Excel files."), - "onboard": 1, - }, - { - "type": "doctype", - "name": "Chart of Accounts Importer", - "label": _("Chart of Accounts Importer"), - "description": _("Import Chart of Accounts from CSV / Excel files"), - "onboard": 1 - }, - { - "type": "doctype", - "name": "Letter Head", - "description": _("Letter Heads for print templates."), - "onboard": 1, - }, - { - "type": "doctype", - "name": "Email Account", - "description": _("Add / Manage Email Accounts."), - "onboard": 1, - }, - - ] - }, - { - "label": _("Stock"), - "items": [ - { - "type": "doctype", - "name": "Warehouse", - "onboard": 1, - }, - { - "type": "doctype", - "name": "Brand", - "onboard": 1, - }, - { - "type": "doctype", - "name": "UOM", - "label": _("Unit of Measure") + " (UOM)", - "onboard": 1, - }, - { - "type": "doctype", - "name": "Stock Reconciliation", - "onboard": 1, - }, - ] - }, - { - "label": _("CRM"), - "items": [ - { - "type": "doctype", - "name": "Lead", - "description": _("Database of potential customers."), - "onboard": 1, - }, - { - "type": "doctype", - "label": _("Customer Group"), - "name": "Customer Group", - "icon": "fa fa-sitemap", - "link": "Tree/Customer Group", - "description": _("Manage Customer Group Tree."), - "onboard": 1, - }, - { - "type": "doctype", - "label": _("Territory"), - "name": "Territory", - "icon": "fa fa-sitemap", - "link": "Tree/Territory", - "description": _("Manage Territory Tree."), - "onboard": 1, - }, - ] - }, - { - "label": _("Human Resources"), - "items": [ - { - "type": "doctype", - "name": "Employee", - "onboard": 1, - }, - { - "type": "doctype", - "name": "Employee Attendance Tool", - "hide_count": True, - "onboard": 1, - "dependencies": ["Employee"] - }, - { - "type": "doctype", - "name": "Salary Structure", - "onboard": 1, - }, - ] - }, - { - "label": _("Education"), - "condition": "Education" in active_domains, - "items": [ - { - "type": "doctype", - "name": "Student", - "onboard": 1, - }, - { - "type": "doctype", - "name": "Course", - "onboard": 1, - }, - { - "type": "doctype", - "name": "Instructor", - "onboard": 1, - }, - { - "type": "doctype", - "name": "Room", - "onboard": 1, - }, - ] - }, - { - "label": _("Healthcare"), - "condition": "Healthcare" in active_domains, - "items": [ - { - "type": "doctype", - "name": "Patient", - "label": _("Patient"), - "onboard": 1, - }, - { - "type": "doctype", - "name": "Physician", - "label": _("Physician"), - "onboard": 1, - }, - { - "type": "doctype", - "name": "Diagnosis", - "label": _("Diagnosis"), - "onboard": 1, - } - ] - }, - { - "label": _("Agriculture"), - "condition": "Agriculture" in active_domains, - "items": [ - { - "type": "doctype", - "name": "Crop", - "onboard": 1, - }, - { - "type": "doctype", - "name": "Crop Cycle", - "onboard": 1, - }, - { - "type": "doctype", - "name": "Location", - "onboard": 1, - }, - { - "type": "doctype", - "name": "Fertilizer", - "onboard": 1, - } - ] - }, - { - "label": _("Non Profit"), - "condition": "Non Profit" in active_domains, - "items": [ - { - "type": "doctype", - "name": "Member", - "description": _("Member information."), - "onboard": 1, - }, - { - "type": "doctype", - "name": "Volunteer", - "description": _("Volunteer information."), - "onboard": 1, - }, - { - "type": "doctype", - "name": "Chapter", - "description": _("Chapter information."), - "onboard": 1, - }, - { - "type": "doctype", - "name": "Donor", - "description": _("Donor information."), - "onboard": 1, - }, - ] - } - ] \ No newline at end of file diff --git a/erpnext/config/healthcare.py b/erpnext/config/healthcare.py deleted file mode 100644 index da24d11538d..00000000000 --- a/erpnext/config/healthcare.py +++ /dev/null @@ -1,254 +0,0 @@ -from __future__ import unicode_literals -from frappe import _ - -def get_data(): - return [ - { - "label": _("Masters"), - "items": [ - { - "type": "doctype", - "name": "Patient", - "label": _("Patient"), - "onboard": 1 - }, - { - "type": "doctype", - "name": "Healthcare Practitioner", - "label": _("Healthcare Practitioner"), - "onboard": 1 - }, - { - "type": "doctype", - "name": "Practitioner Schedule", - "label": _("Practitioner Schedule"), - "onboard": 1 - }, - { - "type": "doctype", - "name": "Medical Department", - "label": _("Medical Department"), - }, - { - "type": "doctype", - "name": "Healthcare Service Unit Type", - "label": _("Healthcare Service Unit Type") - }, - { - "type": "doctype", - "name": "Healthcare Service Unit", - "label": _("Healthcare Service Unit") - }, - { - "type": "doctype", - "name": "Medical Code Standard", - "label": _("Medical Code Standard") - }, - { - "type": "doctype", - "name": "Medical Code", - "label": _("Medical Code") - } - ] - }, - { - "label": _("Consultation Setup"), - "items": [ - { - "type": "doctype", - "name": "Appointment Type", - "label": _("Appointment Type"), - }, - { - "type": "doctype", - "name": "Clinical Procedure Template", - "label": _("Clinical Procedure Template") - }, - { - "type": "doctype", - "name": "Prescription Dosage", - "label": _("Prescription Dosage") - }, - { - "type": "doctype", - "name": "Prescription Duration", - "label": _("Prescription Duration") - }, - { - "type": "doctype", - "name": "Antibiotic", - "label": _("Antibiotic") - } - ] - }, - { - "label": _("Consultation"), - "items": [ - { - "type": "doctype", - "name": "Patient Appointment", - "label": _("Patient Appointment") - }, - { - "type": "doctype", - "name": "Clinical Procedure", - "label": _("Clinical Procedure") - }, - { - "type": "doctype", - "name": "Patient Encounter", - "label": _("Patient Encounter") - }, - { - "type": "doctype", - "name": "Vital Signs", - "label": _("Vital Signs") - }, - { - "type": "doctype", - "name": "Complaint", - "label": _("Complaint") - }, - { - "type": "doctype", - "name": "Diagnosis", - "label": _("Diagnosis") - }, - { - "type": "doctype", - "name": "Fee Validity", - "label": _("Fee Validity") - } - ] - }, - { - "label": _("Settings"), - "items": [ - { - "type": "doctype", - "name": "Healthcare Settings", - "label": _("Healthcare Settings"), - "onboard": 1 - } - ] - }, - { - "label": _("Laboratory Setup"), - "items": [ - { - "type": "doctype", - "name": "Lab Test Template", - "label": _("Lab Test Template") - }, - { - "type": "doctype", - "name": "Lab Test Sample", - "label": _("Lab Test Sample") - }, - { - "type": "doctype", - "name": "Lab Test UOM", - "label": _("Lab Test UOM") - }, - { - "type": "doctype", - "name": "Sensitivity", - "label": _("Sensitivity") - } - ] - }, - { - "label": _("Laboratory"), - "items": [ - { - "type": "doctype", - "name": "Lab Test", - "label": _("Lab Test") - }, - { - "type": "doctype", - "name": "Sample Collection", - "label": _("Sample Collection") - }, - { - "type": "doctype", - "name": "Dosage Form", - "label": _("Dosage Form") - } - ] - }, - { - "label": _("Records and History"), - "items": [ - { - "type": "page", - "name": "patient_history", - "label": _("Patient History"), - }, - { - "type": "doctype", - "name": "Patient Medical Record", - "label": _("Patient Medical Record") - }, - { - "type": "doctype", - "name": "Inpatient Record", - "label": _("Inpatient Record") - } - ] - }, - { - "label": _("Reports"), - "items": [ - { - "type": "report", - "is_query_report": True, - "name": "Patient Appointment Analytics", - "doctype": "Patient Appointment" - }, - { - "type": "report", - "is_query_report": True, - "name": "Lab Test Report", - "doctype": "Lab Test", - "label": _("Lab Test Report") - } - ] - }, - { - "label": _("Rehabilitation"), - "icon": "icon-cog", - "items": [ - { - "type": "doctype", - "name": "Exercise Type", - "label": _("Exercise Type") - }, - { - "type": "doctype", - "name": "Exercise Difficulty Level", - "label": _("Exercise Difficulty Level") - }, - { - "type": "doctype", - "name": "Therapy Type", - "label": _("Therapy Type") - }, - { - "type": "doctype", - "name": "Therapy Plan", - "label": _("Therapy Plan") - }, - { - "type": "doctype", - "name": "Therapy Session", - "label": _("Therapy Session") - }, - { - "type": "doctype", - "name": "Motor Assessment Scale", - "label": _("Motor Assessment Scale") - } - ] - } - ] diff --git a/erpnext/config/help.py b/erpnext/config/help.py deleted file mode 100644 index 922afb4c495..00000000000 --- a/erpnext/config/help.py +++ /dev/null @@ -1,273 +0,0 @@ -from __future__ import unicode_literals -from frappe import _ - -def get_data(): - return [ - { - "label": _("General"), - "items": [ - { - "type": "help", - "label": _("Navigating"), - "youtube_id": "YDoI2DF4Lmc" - }, - { - "type": "help", - "label": _("Setup Wizard"), - "youtube_id": "oIOf_zCFWKQ" - }, - { - "type": "help", - "label": _("Customizing Forms"), - "youtube_id": "pJhL9mmxV_U" - }, - { - "type": "help", - "label": _("Report Builder"), - "youtube_id": "TxJGUNarcQs" - }, - ] - - }, - { - "label": _("Settings"), - "items": [ - { - "type": "help", - "label": _("Data Import and Export"), - "youtube_id": "6wiriRKPhmg" - }, - { - "type": "help", - "label": _("Opening Stock Balance"), - "youtube_id": "nlHX0ZZ84Lw" - }, - { - "type": "help", - "label": _("Setting up Email Account"), - "youtube_id": "YFYe0DrB95o" - }, - { - "type": "help", - "label": _("Printing and Branding"), - "youtube_id": "cKZHcx1znMc" - }, - { - "type": "help", - "label": _("Users and Permissions"), - "youtube_id": "8Slw1hsTmUI" - }, - { - "type": "help", - "label": _("Workflow"), - "youtube_id": "yObJUg9FxFs" - }, - { - "type": "help", - "label": _("File Manager"), - "youtube_id": "4-osLW3E_Rk" - }, - ] - }, - { - "label": _("Accounting"), - "items": [ - { - "type": "help", - "label": _("Chart of Accounts"), - "youtube_id": "DyR-DST-PyA" - }, - { - "type": "help", - "label": _("Setting up Taxes"), - "youtube_id": "nQ1zZdPgdaQ" - }, - { - "type": "help", - "label": _("Opening Accounting Balance"), - "youtube_id": "kdgM20Q-q68" - }, - { - "type": "help", - "label": _("Advance Payments"), - "youtube_id": "J46-6qtyZ9U" - }, - ] - }, - { - "label": _("CRM"), - "items": [ - { - "type": "help", - "label": _("Lead to Quotation"), - "youtube_id": "TxYX4r4JAKA" - }, - { - "type": "help", - "label": _("Newsletters"), - "youtube_id": "muLKsCrrDRo" - }, - ] - }, - { - "label": _("Selling"), - "items": [ - { - "type": "help", - "label": _("Customer and Supplier"), - "youtube_id": "anoGi_RpQ20" - }, - { - "type": "help", - "label": _("Sales Order to Payment"), - "youtube_id": "1eP90MWoDQM" - }, - { - "type": "help", - "label": _("Point-of-Sale"), - "youtube_id": "4WkelWkbP_c" - }, - { - "type": "help", - "label": _("Product Bundle"), - "youtube_id": "yk3kPrRyRRc" - }, - { - "type": "help", - "label": _("Drop Ship"), - "youtube_id": "hUc0hu_XLdo" - }, - ] - }, - { - "label": _("Stock"), - "items": [ - { - "type": "help", - "label": _("Items and Pricing"), - "youtube_id": "qXaEwld4_Ps" - }, - { - "type": "help", - "label": _("Item Variants"), - "youtube_id": "OGBETlCzU5o" - }, - { - "type": "help", - "label": _("Opening Stock Balance"), - "youtube_id": "0yPgrtfeCTs" - }, - { - "type": "help", - "label": _("Making Stock Entries"), - "youtube_id": "Njt107hlY3I" - }, - { - "type": "help", - "label": _("Serialized Inventory"), - "youtube_id": "gvOVlEwFDAk" - }, - { - "type": "help", - "label": _("Batch Inventory"), - "youtube_id": "J0QKl7ABPKM" - }, - { - "type": "help", - "label": _("Managing Subcontracting"), - "youtube_id": "ThiMCC2DtKo" - }, - { - "type": "help", - "label": _("Quality Inspection"), - "youtube_id": "WmtcF3Y40Fs" - }, - ] - }, - { - "label": _("Buying"), - "items": [ - { - "type": "help", - "label": _("Customer and Supplier"), - "youtube_id": "anoGi_RpQ20" - }, - { - "type": "help", - "label": _("Material Request to Purchase Order"), - "youtube_id": "55Gk2j7Q8Zw" - }, - { - "type": "help", - "label": _("Purchase Order to Payment"), - "youtube_id": "efFajTTQBa8" - }, - { - "type": "help", - "label": _("Managing Subcontracting"), - "youtube_id": "ThiMCC2DtKo" - }, - ] - }, - { - "label": _("Manufacturing"), - "items": [ - { - "type": "help", - "label": _("Bill of Materials"), - "youtube_id": "hDV0c1OeWLo" - }, - { - "type": "help", - "label": _("Work Order"), - "youtube_id": "ZotgLyp2YFY" - }, - - ] - }, - { - "label": _("Human Resource"), - "items": [ - { - "type": "help", - "label": _("Setting up Employees"), - "youtube_id": "USfIUdZlUhw" - }, - { - "type": "help", - "label": _("Leave Management"), - "youtube_id": "fc0p_AXebc8" - }, - { - "type": "help", - "label": _("Expense Claims"), - "youtube_id": "5SZHJF--ZFY" - } - ] - }, - { - "label": _("Projects"), - "items": [ - { - "type": "help", - "label": _("Managing Projects"), - "youtube_id": "gCzShu9Niu4" - }, - ] - }, - { - "label": _("Website"), - "items": [ - { - "type": "help", - "label": _("Publish Items on Website"), - "youtube_id": "W31LBBNzbgc" - }, - { - "type": "help", - "label": _("Shopping Cart"), - "youtube_id": "xkrYO-KFukM" - }, - ] - }, - ] diff --git a/erpnext/config/hr.py b/erpnext/config/hr.py deleted file mode 100644 index 9855a115a60..00000000000 --- a/erpnext/config/hr.py +++ /dev/null @@ -1,470 +0,0 @@ -from __future__ import unicode_literals -from frappe import _ - -def get_data(): - return [ - { - "label": _("Employee"), - "items": [ - { - "type": "doctype", - "name": "Employee", - "onboard": 1, - }, - { - "type": "doctype", - "name": "Employment Type", - }, - { - "type": "doctype", - "name": "Branch", - }, - { - "type": "doctype", - "name": "Department", - }, - { - "type": "doctype", - "name": "Designation", - }, - { - "type": "doctype", - "name": "Employee Grade", - }, - { - "type": "doctype", - "name": "Employee Group", - "dependencies": ["Employee"] - }, - { - "type": "doctype", - "name": "Employee Health Insurance" - }, - ] - }, - { - "label": _("Attendance"), - "items": [ - { - "type": "doctype", - "name": "Employee Attendance Tool", - "hide_count": True, - "onboard": 1, - "dependencies": ["Employee"] - }, - { - "type": "doctype", - "name": "Attendance", - "onboard": 1, - "dependencies": ["Employee"] - }, - { - "type": "doctype", - "name": "Attendance Request", - "dependencies": ["Employee"] - }, - { - "type": "doctype", - "name": "Upload Attendance", - "hide_count": True, - "dependencies": ["Employee"] - }, - { - "type": "doctype", - "name": "Employee Checkin", - "hide_count": True, - "dependencies": ["Employee"] - }, - { - "type": "report", - "is_query_report": True, - "name": "Monthly Attendance Sheet", - "doctype": "Attendance" - }, - ] - }, - { - "label": _("Leaves"), - "items": [ - { - "type": "doctype", - "name": "Leave Application", - "dependencies": ["Employee"] - }, - { - "type": "doctype", - "name": "Leave Allocation", - "dependencies": ["Employee"] - }, - { - "type": "doctype", - "name": "Leave Policy", - "dependencies": ["Leave Type"] - }, - { - "type": "doctype", - "name": "Leave Period", - "dependencies": ["Employee"] - }, - { - "type": "doctype", - "name":"Leave Type", - }, - { - "type": "doctype", - "name": "Holiday List", - }, - { - "type": "doctype", - "name": "Compensatory Leave Request", - "dependencies": ["Employee"] - }, - { - "type": "doctype", - "name": "Leave Encashment", - "dependencies": ["Employee"] - }, - { - "type": "doctype", - "name": "Leave Block List", - }, - { - "type": "report", - "is_query_report": True, - "name": "Employee Leave Balance", - "doctype": "Leave Application" - }, - { - "type": "report", - "is_query_report": True, - "name": "Leave Ledger Entry", - "doctype": "Leave Ledger Entry" - }, - ] - }, - { - "label": _("Payroll"), - "items": [ - { - "type": "doctype", - "name": "Salary Structure", - "onboard": 1, - }, - { - "type": "doctype", - "name": "Salary Structure Assignment", - "onboard": 1, - "dependencies": ["Salary Structure", "Employee"], - }, - { - "type": "doctype", - "name": "Payroll Entry", - "onboard": 1, - }, - { - "type": "doctype", - "name": "Salary Slip", - "onboard": 1, - }, - { - "type": "doctype", - "name": "Payroll Period", - }, - { - "type": "doctype", - "name": "Income Tax Slab", - }, - { - "type": "doctype", - "name": "Salary Component", - }, - { - "type": "doctype", - "name": "Additional Salary", - }, - { - "type": "doctype", - "name": "Retention Bonus", - "dependencies": ["Employee"] - }, - { - "type": "doctype", - "name": "Employee Incentive", - "dependencies": ["Employee"] - }, - { - "type": "report", - "is_query_report": True, - "name": "Salary Register", - "doctype": "Salary Slip" - }, - ] - }, - { - "label": _("Employee Tax and Benefits"), - "items": [ - { - "type": "doctype", - "name": "Employee Tax Exemption Declaration", - "dependencies": ["Employee"] - }, - { - "type": "doctype", - "name": "Employee Tax Exemption Proof Submission", - "dependencies": ["Employee"] - }, - { - "type": "doctype", - "name": "Employee Other Income", - }, - { - "type": "doctype", - "name": "Employee Benefit Application", - "dependencies": ["Employee"] - }, - { - "type": "doctype", - "name": "Employee Benefit Claim", - "dependencies": ["Employee"] - }, - { - "type": "doctype", - "name": "Employee Tax Exemption Category", - "dependencies": ["Employee"] - }, - { - "type": "doctype", - "name": "Employee Tax Exemption Sub Category", - "dependencies": ["Employee"] - }, - ] - }, - { - "label": _("Employee Lifecycle"), - "items": [ - { - "type": "doctype", - "name": "Employee Onboarding", - "dependencies": ["Job Applicant"], - }, - { - "type": "doctype", - "name": "Employee Skill Map", - "dependencies": ["Employee"], - }, - { - "type": "doctype", - "name": "Employee Promotion", - "dependencies": ["Employee"], - }, - { - "type": "doctype", - "name": "Employee Transfer", - "dependencies": ["Employee"], - }, - { - "type": "doctype", - "name": "Employee Separation", - "dependencies": ["Employee"], - }, - { - "type": "doctype", - "name": "Employee Onboarding Template", - "dependencies": ["Employee"] - }, - { - "type": "doctype", - "name": "Employee Separation Template", - "dependencies": ["Employee"] - }, - ] - }, - { - "label": _("Recruitment"), - "items": [ - { - "type": "doctype", - "name": "Job Opening", - "onboard": 1, - }, - { - "type": "doctype", - "name": "Job Applicant", - "onboard": 1, - }, - { - "type": "doctype", - "name": "Job Offer", - "onboard": 1, - }, - { - "type": "doctype", - "name": "Appointment Letter", - }, - { - "type": "doctype", - "name": "Staffing Plan", - }, - ] - }, - { - "label": _("Training"), - "items": [ - { - "type": "doctype", - "name": "Training Program" - }, - { - "type": "doctype", - "name": "Training Event" - }, - { - "type": "doctype", - "name": "Training Result" - }, - { - "type": "doctype", - "name": "Training Feedback" - }, - ] - }, - { - "label": _("Performance"), - "items": [ - { - "type": "doctype", - "name": "Appraisal", - }, - { - "type": "doctype", - "name": "Appraisal Template", - }, - { - "type": "doctype", - "name": "Energy Point Rule", - }, - { - "type": "doctype", - "name": "Energy Point Log", - }, - { - "type": "link", - "doctype": "Energy Point Log", - "label": _("Energy Point Leaderboard"), - "route": "#social/users" - }, - ] - }, - { - "label": _("Expense Claims"), - "items": [ - { - "type": "doctype", - "name": "Expense Claim", - "dependencies": ["Employee"] - }, - { - "type": "doctype", - "name": "Employee Advance", - "dependencies": ["Employee"] - }, - ] - }, - { - "label": _("Loans"), - "items": [ - { - "type": "doctype", - "name": "Loan Application", - "dependencies": ["Employee"] - }, - { - "type": "doctype", - "name": "Loan" - }, - { - "type": "doctype", - "name": "Loan Type", - }, - ] - }, - { - "label": _("Shift Management"), - "items": [ - { - "type": "doctype", - "name": "Shift Type", - }, - { - "type": "doctype", - "name": "Shift Request", - }, - { - "type": "doctype", - "name": "Shift Assignment", - }, - ] - }, - { - "label": _("Fleet Management"), - "items": [ - { - "type": "doctype", - "name": "Vehicle" - }, - { - "type": "doctype", - "name": "Vehicle Log" - }, - { - "type": "report", - "is_query_report": True, - "name": "Vehicle Expenses", - "doctype": "Vehicle" - }, - ] - }, - { - "label": _("Settings"), - "icon": "fa fa-cog", - "items": [ - { - "type": "doctype", - "name": "HR Settings", - }, - { - "type": "doctype", - "name": "Daily Work Summary Group" - }, - { - "type": "page", - "name": "team-updates", - "label": _("Team Updates") - }, - ] - }, - { - "label": _("Reports"), - "icon": "fa fa-list", - "items": [ - { - "type": "report", - "is_query_report": True, - "name": "Employee Birthday", - "doctype": "Employee" - }, - { - "type": "report", - "is_query_report": True, - "name": "Employees working on a holiday", - "doctype": "Employee" - }, - { - "type": "report", - "is_query_report": True, - "name": "Department Analytics", - "doctype": "Employee" - }, - ] - }, - ] diff --git a/erpnext/config/hub_node.py b/erpnext/config/hub_node.py deleted file mode 100644 index 0afdeb52b16..00000000000 --- a/erpnext/config/hub_node.py +++ /dev/null @@ -1,24 +0,0 @@ -from __future__ import unicode_literals -from frappe import _ - -def get_data(): - return [ - { - "label": _("Settings"), - "items": [ - { - "type": "doctype", - "name": "Marketplace Settings" - }, - ] - }, - { - "label": _("Marketplace"), - "items": [ - { - "type": "page", - "name": "marketplace/home" - }, - ] - }, - ] \ No newline at end of file diff --git a/erpnext/config/integrations.py b/erpnext/config/integrations.py deleted file mode 100644 index f8b3257b5c2..00000000000 --- a/erpnext/config/integrations.py +++ /dev/null @@ -1,51 +0,0 @@ -from __future__ import unicode_literals -from frappe import _ - -def get_data(): - return [ - { - "label": _("Payments"), - "icon": "fa fa-star", - "items": [ - { - "type": "doctype", - "name": "GoCardless Settings", - "description": _("GoCardless payment gateway settings"), - }, - { - "type": "doctype", - "name": "GoCardless Mandate", - "description": _("GoCardless SEPA Mandate"), - } - ] - }, - { - "label": _("Settings"), - "items": [ - { - "type": "doctype", - "name": "Woocommerce Settings" - }, - { - "type": "doctype", - "name": "Shopify Settings", - "description": _("Connect Shopify with ERPNext"), - }, - { - "type": "doctype", - "name": "Amazon MWS Settings", - "description": _("Connect Amazon with ERPNext"), - }, - { - "type": "doctype", - "name": "Plaid Settings", - "description": _("Connect your bank accounts to ERPNext"), - }, - { - "type": "doctype", - "name": "Exotel Settings", - "description": _("Connect your Exotel Account to ERPNext and track call logs"), - } - ] - } - ] diff --git a/erpnext/config/loan_management.py b/erpnext/config/loan_management.py deleted file mode 100644 index a84f13ababc..00000000000 --- a/erpnext/config/loan_management.py +++ /dev/null @@ -1,107 +0,0 @@ -from __future__ import unicode_literals -from frappe import _ -import frappe - - -def get_data(): - return [ - { - "label": _("Loan"), - "items": [ - { - "type": "doctype", - "name": "Loan Type", - "description": _("Loan Type for interest and penalty rates"), - }, - { - "type": "doctype", - "name": "Loan Application", - "description": _("Loan Applications from customers and employees."), - }, - { - "type": "doctype", - "name": "Loan", - "description": _("Loans provided to customers and employees."), - }, - - ] - }, - { - "label": _("Loan Security"), - "items": [ - { - "type": "doctype", - "name": "Loan Security Type", - }, - { - "type": "doctype", - "name": "Loan Security Price", - }, - { - "type": "doctype", - "name": "Loan Security", - }, - { - "type": "doctype", - "name": "Loan Security Pledge", - }, - { - "type": "doctype", - "name": "Loan Security Unpledge", - }, - { - "type": "doctype", - "name": "Loan Security Shortfall", - }, - ] - }, - { - "label": _("Disbursement and Repayment"), - "items": [ - { - "type": "doctype", - "name": "Loan Disbursement", - }, - { - "type": "doctype", - "name": "Loan Repayment", - }, - { - "type": "doctype", - "name": "Loan Interest Accrual" - } - ] - }, - { - "label": _("Loan Processes"), - "items": [ - { - "type": "doctype", - "name": "Process Loan Security Shortfall", - }, - { - "type": "doctype", - "name": "Process Loan Interest Accrual", - } - ] - }, - { - "label": _("Reports"), - "items": [ - { - "type": "report", - "is_query_report": True, - "name": "Loan Repayment and Closure", - "route": "#query-report/Loan Repayment and Closure", - "doctype": "Loan Repayment", - }, - { - "type": "report", - "is_query_report": True, - "name": "Loan Security Status", - "route": "#query-report/Loan Security Status", - "doctype": "Loan Security Pledge", - } - ] - } - ] \ No newline at end of file diff --git a/erpnext/config/manufacturing.py b/erpnext/config/manufacturing.py deleted file mode 100644 index 012f1cad0ad..00000000000 --- a/erpnext/config/manufacturing.py +++ /dev/null @@ -1,168 +0,0 @@ -from __future__ import unicode_literals -from frappe import _ - -def get_data(): - return [ - { - "label": _("Bill of Materials"), - "items": [ - { - "type": "doctype", - "name": "Item", - "description": _("All Products or Services."), - "onboard": 1, - }, - { - "type": "doctype", - "name": "BOM", - "description": _("Bill of Materials (BOM)"), - "label": _("Bill of Materials"), - "onboard": 1, - "dependencies": ["Item"] - }, - { - "type": "doctype", - "name": "BOM Browser", - "icon": "fa fa-sitemap", - "label": _("BOM Browser"), - "description": _("Tree of Bill of Materials"), - "link": "Tree/BOM", - "onboard": 1, - "dependencies": ["Item"] - }, - - { - "type": "doctype", - "name": "Workstation", - "description": _("Where manufacturing operations are carried."), - }, - { - "type": "doctype", - "name": "Operation", - "description": _("Details of the operations carried out."), - }, - { - "type": "doctype", - "name": "Routing" - } - - ] - }, - { - "label": _("Production"), - "icon": "fa fa-star", - "items": [ - { - "type": "doctype", - "name": "Work Order", - "description": _("Orders released for production."), - "onboard": 1, - "dependencies": ["Item", "BOM"] - }, - { - "type": "doctype", - "name": "Production Plan", - "description": _("Generate Material Requests (MRP) and Work Orders."), - "onboard": 1, - "dependencies": ["Item", "BOM"] - }, - { - "type": "doctype", - "name": "Stock Entry", - "onboard": 1, - "dependencies": ["Item"] - }, - { - "type": "doctype", - "name": "Timesheet", - "description": _("Time Sheet for manufacturing."), - "onboard": 1, - "dependencies": ["Activity Type"] - }, - { - "type": "doctype", - "name": "Job Card" - } - ] - }, - { - "label": _("Tools"), - "icon": "fa fa-wrench", - "items": [ - { - "type": "doctype", - "name": "BOM Update Tool", - "description": _("Replace BOM and update latest price in all BOMs"), - }, - { - "type": "page", - "label": _("BOM Comparison Tool"), - "name": "bom-comparison-tool", - "description": _("Compare BOMs for changes in Raw Materials and Operations"), - "data_doctype": "BOM" - }, - ] - }, - { - "label": _("Settings"), - "items": [ - { - "type": "doctype", - "name": "Manufacturing Settings", - "description": _("Global settings for all manufacturing processes."), - } - ] - }, - { - "label": _("Reports"), - "icon": "fa fa-list", - "items": [ - { - "type": "report", - "is_query_report": True, - "name": "Work Order Summary", - "doctype": "Work Order" - }, - { - "type": "report", - "is_query_report": True, - "name": "Issued Items Against Work Order", - "doctype": "Work Order" - }, - { - "type": "report", - "is_query_report": True, - "name": "Production Analytics", - "doctype": "Work Order" - }, - { - "type": "report", - "is_query_report": True, - "name": "BOM Search", - "doctype": "BOM" - }, - { - "type": "report", - "is_query_report": True, - "name": "BOM Stock Report", - "doctype": "BOM" - } - ] - }, - { - "label": _("Help"), - "icon": "fa fa-facetime-video", - "items": [ - { - "type": "help", - "label": _("Bill of Materials"), - "youtube_id": "hDV0c1OeWLo" - }, - { - "type": "help", - "label": _("Work Order"), - "youtube_id": "ZotgLyp2YFY" - }, - ] - } - ] diff --git a/erpnext/config/non_profit.py b/erpnext/config/non_profit.py deleted file mode 100644 index 42ec9d3db35..00000000000 --- a/erpnext/config/non_profit.py +++ /dev/null @@ -1,101 +0,0 @@ -from __future__ import unicode_literals -from frappe import _ - -def get_data(): - return [ - { - "label": _("Chapter"), - "icon": "fa fa-star", - "items": [ - { - "type": "doctype", - "name": "Chapter", - "description": _("Chapter information."), - "onboard": 1, - } - ] - }, - { - "label": _("Membership"), - "items": [ - { - "type": "doctype", - "name": "Member", - "description": _("Member information."), - "onboard": 1, - }, - { - "type": "doctype", - "name": "Membership", - "description": _("Memebership Details"), - "onboard": 1, - }, - { - "type": "doctype", - "name": "Membership Type", - "description": _("Memebership Type Details"), - }, - ] - }, - { - "label": _("Volunteer"), - "items": [ - { - "type": "doctype", - "name": "Volunteer", - "description": _("Volunteer information."), - "onboard": 1, - }, - { - "type": "doctype", - "name": "Volunteer Type", - "description": _("Volunteer Type information."), - } - ] - }, - { - "label": _("Donor"), - "items": [ - { - "type": "doctype", - "name": "Donor", - "description": _("Donor information."), - }, - { - "type": "doctype", - "name": "Donor Type", - "description": _("Donor Type information."), - } - ] - }, - { - "label": _("Loan Management"), - "icon": "icon-list", - "items": [ - { - "type": "doctype", - "name": "Loan Type", - "description": _("Define various loan types") - }, - { - "type": "doctype", - "name": "Loan Application", - "description": _("Loan Application") - }, - { - "type": "doctype", - "name": "Loan" - }, - ] - }, - { - "label": _("Grant Application"), - "items": [ - { - "type": "doctype", - "name": "Grant Application", - "description": _("Grant information."), - } - ] - } - ] diff --git a/erpnext/config/projects.py b/erpnext/config/projects.py index 47700d10b2a..ab4db964772 100644 --- a/erpnext/config/projects.py +++ b/erpnext/config/projects.py @@ -16,13 +16,13 @@ def get_data(): { "type": "doctype", "name": "Task", - "route": "#List/Task", + "route": "/app/List/Task", "description": _("Project activity / task."), "onboard": 1, }, { "type": "report", - "route": "#List/Task/Gantt", + "route": "/app/List/Task/Gantt", "doctype": "Task", "name": "Gantt Chart", "description": _("Gantt chart of all tasks."), @@ -97,5 +97,5 @@ def get_data(): }, ] }, - + ] diff --git a/erpnext/config/quality_management.py b/erpnext/config/quality_management.py deleted file mode 100644 index 35acdfab24f..00000000000 --- a/erpnext/config/quality_management.py +++ /dev/null @@ -1,73 +0,0 @@ -from __future__ import unicode_literals -from frappe import _ - -def get_data(): - return [ - { - "label": _("Goal and Procedure"), - "items": [ - { - "type": "doctype", - "name": "Quality Goal", - "description":_("Quality Goal."), - "onboard": 1, - }, - { - "type": "doctype", - "name": "Quality Procedure", - "description":_("Quality Procedure."), - "onboard": 1, - }, - { - "type": "doctype", - "name": "Quality Procedure", - "icon": "fa fa-sitemap", - "label": _("Tree of Procedures"), - "route": "#Tree/Quality Procedure", - "description": _("Tree of Quality Procedures."), - }, - ] - }, - { - "label": _("Review and Action"), - "items": [ - { - "type": "doctype", - "name": "Quality Review", - "description":_("Quality Review"), - "onboard": 1, - }, - { - "type": "doctype", - "name": "Quality Action", - "description":_("Quality Action"), - } - ] - }, - { - "label": _("Meeting"), - "items": [ - { - "type": "doctype", - "name": "Quality Meeting", - "description":_("Quality Meeting"), - } - ] - }, - { - "label": _("Feedback"), - "items": [ - { - "type": "doctype", - "name": "Quality Feedback", - "description":_("Quality Feedback"), - "onboard": 1, - }, - { - "type": "doctype", - "name": "Quality Feedback Template", - "description":_("Quality Feedback Template"), - } - ] - }, - ] \ No newline at end of file diff --git a/erpnext/config/retail.py b/erpnext/config/retail.py deleted file mode 100644 index 738be7eb173..00000000000 --- a/erpnext/config/retail.py +++ /dev/null @@ -1,48 +0,0 @@ -from __future__ import unicode_literals -from frappe import _ - -def get_data(): - return [ - { - "label": _("Retail Operations"), - "items": [ - { - "type": "doctype", - "name": "POS Profile", - "label": _("Point-of-Sale Profile"), - "description": _("Setup default values for POS Invoices"), - "onboard": 1, - }, - { - "type": "page", - "name": "pos", - "label": _("POS"), - "description": _("Point of Sale"), - "onboard": 1, - "dependencies": ["POS Profile"] - }, - { - "type": "doctype", - "name": "Cashier Closing", - "description": _("Cashier Closing"), - }, - { - "type": "doctype", - "name": "POS Settings", - "description": _("Setup mode of POS (Online / Offline)") - }, - { - "type": "doctype", - "name": "Loyalty Program", - "label": _("Loyalty Program"), - "description": _("To make Customer based incentive schemes.") - }, - { - "type": "doctype", - "name": "Loyalty Point Entry", - "label": _("Loyalty Point Entry"), - "description": _("To view logs of Loyalty Points assigned to a Customer.") - } - ] - } - ] \ No newline at end of file diff --git a/erpnext/config/selling.py b/erpnext/config/selling.py deleted file mode 100644 index 5db4cc27021..00000000000 --- a/erpnext/config/selling.py +++ /dev/null @@ -1,320 +0,0 @@ -from __future__ import unicode_literals -from frappe import _ - -def get_data(): - return [ - { - "label": _("Sales"), - "icon": "fa fa-star", - "items": [ - { - "type": "doctype", - "name": "Customer", - "description": _("Customer Database."), - "onboard": 1, - }, - { - "type": "doctype", - "name": "Quotation", - "description": _("Quotes to Leads or Customers."), - "onboard": 1, - "dependencies": ["Item", "Customer"], - }, - { - "type": "doctype", - "name": "Sales Order", - "description": _("Confirmed orders from Customers."), - "onboard": 1, - "dependencies": ["Item", "Customer"], - }, - { - "type": "doctype", - "name": "Sales Invoice", - "description": _("Invoices for Costumers."), - "onboard": 1, - "dependencies": ["Item", "Customer"], - }, - { - "type": "doctype", - "name": "Blanket Order", - "description": _("Blanket Orders from Costumers."), - "onboard": 1, - "dependencies": ["Item", "Customer"], - }, - { - "type": "doctype", - "name": "Sales Partner", - "description": _("Manage Sales Partners."), - "dependencies": ["Item"], - }, - { - "type": "doctype", - "label": _("Sales Person"), - "name": "Sales Person", - "icon": "fa fa-sitemap", - "link": "Tree/Sales Person", - "description": _("Manage Sales Person Tree."), - "dependencies": ["Item", "Customer"], - }, - { - "type": "report", - "is_query_report": True, - "name": "Territory Target Variance (Item Group-Wise)", - "route": "#query-report/Territory Target Variance Item Group-Wise", - "doctype": "Territory", - }, - { - "type": "report", - "is_query_report": True, - "name": "Sales Person Target Variance (Item Group-Wise)", - "route": "#query-report/Sales Person Target Variance Item Group-Wise", - "doctype": "Sales Person", - "dependencies": ["Sales Person"], - }, - ] - }, - { - "label": _("Items and Pricing"), - "items": [ - { - "type": "doctype", - "name": "Item", - "description": _("All Products or Services."), - "onboard": 1, - }, - { - "type": "doctype", - "name": "Item Price", - "description": _("Multiple Item prices."), - "route": "#Report/Item Price", - "dependencies": ["Item", "Price List"], - "onboard": 1, - }, - { - "type": "doctype", - "name": "Price List", - "description": _("Price List master."), - "onboard": 1, - }, - { - "type": "doctype", - "name": "Item Group", - "icon": "fa fa-sitemap", - "label": _("Item Group"), - "link": "Tree/Item Group", - "description": _("Tree of Item Groups."), - "onboard": 1, - }, - { - "type": "doctype", - "name": "Product Bundle", - "description": _("Bundle items at time of sale."), - "dependencies": ["Item"], - }, - { - "type": "doctype", - "name": "Promotional Scheme", - "description": _("Rules for applying different promotional schemes.") - }, - { - "type": "doctype", - "name": "Pricing Rule", - "description": _("Rules for applying pricing and discount."), - "dependencies": ["Item"], - }, - { - "type": "doctype", - "name": "Shipping Rule", - "description": _("Rules for adding shipping costs."), - }, - { - "type": "doctype", - "name": "Coupon Code", - "description": _("Define coupon codes."), - } - ] - }, - { - "label": _("Settings"), - "icon": "fa fa-cog", - "items": [ - { - "type": "doctype", - "name": "Selling Settings", - "description": _("Default settings for selling transactions."), - "settings": 1, - }, - { - "type": "doctype", - "name":"Terms and Conditions", - "label": _("Terms and Conditions Template"), - "description": _("Template of terms or contract."), - "onboard": 1, - }, - { - "type": "doctype", - "name": "Sales Taxes and Charges Template", - "description": _("Tax template for selling transactions."), - "onboard": 1, - }, - { - "type": "doctype", - "name": "Lead Source", - "description": _("Track Leads by Lead Source.") - }, - { - "type": "doctype", - "label": _("Customer Group"), - "name": "Customer Group", - "icon": "fa fa-sitemap", - "link": "Tree/Customer Group", - "description": _("Manage Customer Group Tree."), - }, - { - "type": "doctype", - "name": "Contact", - "description": _("All Contacts."), - }, - { - "type": "doctype", - "name": "Address", - "description": _("All Addresses."), - }, - { - "type": "doctype", - "label": _("Territory"), - "name": "Territory", - "icon": "fa fa-sitemap", - "link": "Tree/Territory", - "description": _("Manage Territory Tree."), - }, - { - "type": "doctype", - "name": "Campaign", - "description": _("Sales campaigns."), - }, - ] - }, - { - "label": _("Key Reports"), - "icon": "fa fa-table", - "items": [ - { - "type": "report", - "is_query_report": True, - "name": "Sales Analytics", - "doctype": "Sales Order", - "onboard": 1, - }, - { - "type": "page", - "name": "sales-funnel", - "label": _("Sales Funnel"), - "icon": "fa fa-bar-chart", - "onboard": 1, - }, - { - "type": "report", - "is_query_report": True, - "name": "Customer Acquisition and Loyalty", - "doctype": "Customer", - "icon": "fa fa-bar-chart", - }, - { - "type": "report", - "is_query_report": True, - "name": "Inactive Customers", - "doctype": "Sales Order" - }, - { - "type": "report", - "is_query_report": True, - "name": "Ordered Items To Be Delivered", - "doctype": "Sales Order" - }, - { - "type": "report", - "is_query_report": True, - "name": "Sales Person-wise Transaction Summary", - "doctype": "Sales Order" - }, - { - "type": "report", - "is_query_report": True, - "name": "Item-wise Sales History", - "doctype": "Item" - }, - { - "type": "report", - "is_query_report": True, - "name": "Quotation Trends", - "doctype": "Quotation" - }, - { - "type": "report", - "is_query_report": True, - "name": "Sales Order Trends", - "doctype": "Sales Order" - }, - ] - }, - { - "label": _("Other Reports"), - "icon": "fa fa-list", - "items": [ - { - "type": "report", - "is_query_report": True, - "name": "Lead Details", - "doctype": "Lead" - }, - { - "type": "report", - "is_query_report": True, - "name": "Address And Contacts", - "label": _("Customer Addresses And Contacts"), - "doctype": "Address", - "route_options": { - "party_type": "Customer" - } - }, - { - "type": "report", - "is_query_report": True, - "name": "BOM Search", - "doctype": "BOM" - }, - { - "type": "report", - "is_query_report": True, - "name": "Available Stock for Packing Items", - "doctype": "Item", - }, - { - "type": "report", - "is_query_report": True, - "name": "Pending SO Items For Purchase Request", - "doctype": "Sales Order" - }, - { - "type": "report", - "is_query_report": True, - "name": "Customer Credit Balance", - "doctype": "Customer" - }, - { - "type": "report", - "is_query_report": True, - "name": "Customers Without Any Sales Transactions", - "doctype": "Customer" - }, - { - "type": "report", - "is_query_report": True, - "name": "Sales Partners Commission", - "doctype": "Customer" - } - ] - }, - - ] diff --git a/erpnext/config/settings.py b/erpnext/config/settings.py deleted file mode 100644 index 323683a3e6d..00000000000 --- a/erpnext/config/settings.py +++ /dev/null @@ -1,117 +0,0 @@ -from __future__ import unicode_literals -from frappe import _ -from frappe.desk.moduleview import add_setup_section - -def get_data(): - data = [ - { - "label": _("Settings"), - "icon": "fa fa-wrench", - "items": [ - { - "type": "doctype", - "name": "Global Defaults", - "label": _("ERPNext Settings"), - "description": _("Set Default Values like Company, Currency, Current Fiscal Year, etc."), - "hide_count": True, - "settings": 1, - } - ] - }, - { - "label": _("Printing"), - "icon": "fa fa-print", - "items": [ - { - "type": "doctype", - "name": "Letter Head", - "description": _("Letter Heads for print templates."), - "onboard": 1, - }, - { - "type": "doctype", - "name": "Print Heading", - "description": _("Titles for print templates e.g. Proforma Invoice.") - }, - { - "type": "doctype", - "name": "Address Template", - "description": _("Country wise default Address Templates") - }, - { - "type": "doctype", - "name": "Terms and Conditions", - "description": _("Standard contract terms for Sales or Purchase.") - }, - ] - }, - { - "label": _("Help"), - "items": [ - { - "type": "help", - "name": _("Data Import and Export"), - "youtube_id": "6wiriRKPhmg" - }, - { - "type": "help", - "label": _("Setting up Email"), - "youtube_id": "YFYe0DrB95o" - }, - { - "type": "help", - "label": _("Printing and Branding"), - "youtube_id": "cKZHcx1znMc" - }, - { - "type": "help", - "label": _("Users and Permissions"), - "youtube_id": "8Slw1hsTmUI" - }, - { - "type": "help", - "label": _("Workflow"), - "youtube_id": "yObJUg9FxFs" - }, - ] - }, - { - "label": _("Customize"), - "icon": "fa fa-glass", - "items": [ - { - "type": "doctype", - "name": "Authorization Rule", - "description": _("Create rules to restrict transactions based on values.") - } - ] - }, - { - "label": _("Email"), - "icon": "fa fa-envelope", - "items": [ - { - "type": "doctype", - "name": "Email Digest", - "description": _("Create and manage daily, weekly and monthly email digests.") - }, - { - "type": "doctype", - "name": "SMS Settings", - "description": _("Setup SMS gateway settings") - }, - ] - } - ] - - for module, label, icon in ( - ("accounts", _("Accounting"), "fa fa-money"), - ("stock", _("Stock"), "fa fa-truck"), - ("selling", _("Selling"), "fa fa-tag"), - ("buying", _("Buying"), "fa fa-shopping-cart"), - ("hr", _("Human Resources"), "fa fa-group"), - ("support", _("Support"), "fa fa-phone")): - - add_setup_section(data, "erpnext", module, label, icon) - - return data diff --git a/erpnext/config/stock.py b/erpnext/config/stock.py deleted file mode 100644 index dd35f5ab368..00000000000 --- a/erpnext/config/stock.py +++ /dev/null @@ -1,361 +0,0 @@ -from __future__ import unicode_literals -from frappe import _ - -def get_data(): - return [ - { - "label": _("Stock Transactions"), - "items": [ - { - "type": "doctype", - "name": "Stock Entry", - "onboard": 1, - "dependencies": ["Item"], - }, - { - "type": "doctype", - "name": "Delivery Note", - "onboard": 1, - "dependencies": ["Item", "Customer"], - }, - { - "type": "doctype", - "name": "Purchase Receipt", - "onboard": 1, - "dependencies": ["Item", "Supplier"], - }, - { - "type": "doctype", - "name": "Material Request", - "onboard": 1, - "dependencies": ["Item"], - }, - { - "type": "doctype", - "name": "Pick List", - "onboard": 1, - "dependencies": ["Item"], - }, - { - "type": "doctype", - "name": "Delivery Trip" - }, - ] - }, - { - "label": _("Stock Reports"), - "items": [ - { - "type": "report", - "is_query_report": True, - "name": "Stock Ledger", - "doctype": "Stock Ledger Entry", - "onboard": 1, - "dependencies": ["Item"], - }, - { - "type": "report", - "is_query_report": True, - "name": "Stock Balance", - "doctype": "Stock Ledger Entry", - "onboard": 1, - "dependencies": ["Item"], - }, - { - "type": "report", - "is_query_report": True, - "name": "Stock Projected Qty", - "doctype": "Item", - "onboard": 1, - "dependencies": ["Item"], - }, - { - "type": "page", - "name": "stock-balance", - "label": _("Stock Summary"), - "dependencies": ["Item"], - }, - { - "type": "report", - "is_query_report": True, - "name": "Stock Ageing", - "doctype": "Item", - "dependencies": ["Item"], - }, - { - "type": "report", - "is_query_report": True, - "name": "Item Price Stock", - "doctype": "Item", - "dependencies": ["Item"], - } - ] - }, - { - "label": _("Settings"), - "icon": "fa fa-cog", - "items": [ - { - "type": "doctype", - "name": "Stock Settings", - "onboard": 1, - }, - { - "type": "doctype", - "name": "Warehouse", - "onboard": 1, - }, - { - "type": "doctype", - "name": "UOM", - "label": _("Unit of Measure") + " (UOM)", - "onboard": 1, - }, - { - "type": "doctype", - "name": "Brand", - "onboard": 1, - }, - { - "type": "doctype", - "name": "Item Attribute", - }, - { - "type": "doctype", - "name": "Item Variant Settings", - }, - ] - }, - { - "label": _("Items and Pricing"), - "items": [ - { - "type": "doctype", - "name": "Item", - "onboard": 1, - }, - { - "type": "doctype", - "name": "Product Bundle", - "onboard": 1, - }, - { - "type": "doctype", - "name": "Item Group", - "icon": "fa fa-sitemap", - "label": _("Item Group"), - "link": "Tree/Item Group", - "onboard": 1, - }, - { - "type": "doctype", - "name": "Price List", - }, - { - "type": "doctype", - "name": "Item Price", - }, - { - "type": "doctype", - "name": "Shipping Rule", - }, - { - "type": "doctype", - "name": "Pricing Rule", - }, - { - "type": "doctype", - "name": "Item Alternative", - }, - { - "type": "doctype", - "name": "Item Manufacturer", - }, - { - "type": "doctype", - "name": "Item Variant Settings", - }, - ] - }, - { - "label": _("Serial No and Batch"), - "items": [ - { - "type": "doctype", - "name": "Serial No", - "onboard": 1, - "dependencies": ["Item"], - }, - { - "type": "doctype", - "name": "Batch", - "onboard": 1, - "dependencies": ["Item"], - }, - { - "type": "doctype", - "name": "Installation Note", - "dependencies": ["Item"], - }, - { - "type": "report", - "name": "Serial No Service Contract Expiry", - "doctype": "Serial No" - }, - { - "type": "report", - "name": "Serial No Status", - "doctype": "Serial No" - }, - { - "type": "report", - "name": "Serial No Warranty Expiry", - "doctype": "Serial No" - }, - ] - }, - { - "label": _("Tools"), - "icon": "fa fa-wrench", - "items": [ - { - "type": "doctype", - "name": "Stock Reconciliation", - "onboard": 1, - }, - { - "type": "doctype", - "name": "Landed Cost Voucher", - "onboard": 1, - }, - { - "type": "doctype", - "name": "Packing Slip", - "onboard": 1, - }, - { - "type": "doctype", - "name": "Quality Inspection", - }, - { - "type": "doctype", - "name": "Quality Inspection Template", - }, - { - "type": "doctype", - "name": "Quick Stock Balance", - }, - ] - }, - { - "label": _("Key Reports"), - "icon": "fa fa-table", - "items": [ - { - "type": "report", - "is_query_report": False, - "name": "Item-wise Price List Rate", - "doctype": "Item Price", - "onboard": 1, - }, - { - "type": "report", - "is_query_report": True, - "name": "Stock Analytics", - "doctype": "Stock Entry", - "onboard": 1, - }, - { - "type": "report", - "is_query_report": True, - "name": "Delivery Note Trends", - "doctype": "Delivery Note" - }, - { - "type": "report", - "is_query_report": True, - "name": "Purchase Receipt Trends", - "doctype": "Purchase Receipt" - }, - { - "type": "report", - "is_query_report": True, - "name": "Ordered Items To Be Delivered", - "doctype": "Delivery Note" - }, - { - "type": "report", - "is_query_report": True, - "name": "Purchase Order Items To Be Received", - "doctype": "Purchase Receipt" - }, - { - "type": "report", - "is_query_report": True, - "name": "Item Shortage Report", - "doctype": "Bin" - }, - { - "type": "report", - "is_query_report": True, - "name": "Batch-Wise Balance History", - "doctype": "Batch" - }, - ] - }, - { - "label": _("Other Reports"), - "icon": "fa fa-list", - "items": [ - { - "type": "report", - "is_query_report": True, - "name": "Requested Items To Be Transferred", - "doctype": "Material Request" - }, - { - "type": "report", - "is_query_report": True, - "name": "Batch Item Expiry Status", - "doctype": "Stock Ledger Entry" - }, - { - "type": "report", - "is_query_report": True, - "name": "Item Prices", - "doctype": "Price List" - }, - { - "type": "report", - "is_query_report": True, - "name": "Itemwise Recommended Reorder Level", - "doctype": "Item" - }, - { - "type": "report", - "is_query_report": True, - "name": "Item Variant Details", - "doctype": "Item" - }, - { - "type": "report", - "is_query_report": True, - "name": "Subcontracted Raw Materials To Be Transferred", - "doctype": "Purchase Order" - }, - { - "type": "report", - "is_query_report": True, - "name": "Subcontracted Item To Be Received", - "doctype": "Purchase Order" - }, - { - "type": "report", - "is_query_report": True, - "name": "Stock and Account Value Comparison", - "doctype": "Stock Ledger Entry" - } - ] - }, - - ] diff --git a/erpnext/config/support.py b/erpnext/config/support.py deleted file mode 100644 index 151c4f743e1..00000000000 --- a/erpnext/config/support.py +++ /dev/null @@ -1,105 +0,0 @@ -from __future__ import unicode_literals -from frappe import _ - -def get_data(): - return [ - { - "label": _("Issues"), - "items": [ - { - "type": "doctype", - "name": "Issue", - "description": _("Support queries from customers."), - "onboard": 1, - }, - { - "type": "doctype", - "name": "Issue Type", - "description": _("Issue Type."), - }, - { - "type": "doctype", - "name": "Issue Priority", - "description": _("Issue Priority."), - } - ] - }, - { - "label": _("Warranty"), - "items": [ - { - "type": "doctype", - "name": "Warranty Claim", - "description": _("Warranty Claim against Serial No."), - }, - { - "type": "doctype", - "name": "Serial No", - "description": _("Single unit of an Item."), - }, - ] - }, - { - "label": _("Service Level Agreement"), - "items": [ - { - "type": "doctype", - "name": "Service Level", - "description": _("Service Level."), - }, - { - "type": "doctype", - "name": "Service Level Agreement", - "description": _("Service Level Agreement."), - } - ] - }, - { - "label": _("Maintenance"), - "items": [ - { - "type": "doctype", - "name": "Maintenance Schedule", - }, - { - "type": "doctype", - "name": "Maintenance Visit", - }, - ] - }, - { - "label": _("Reports"), - "icon": "fa fa-list", - "items": [ - { - "type": "page", - "name": "support-analytics", - "label": _("Support Analytics"), - "icon": "fa fa-bar-chart" - }, - { - "type": "report", - "name": "Minutes to First Response for Issues", - "doctype": "Issue", - "is_query_report": True - }, - { - "type": "report", - "name": "Support Hours", - "doctype": "Issue", - "is_query_report": True - }, - ] - }, - { - "label": _("Settings"), - "icon": "fa fa-list", - "items": [ - { - "type": "doctype", - "name": "Support Settings", - "label": _("Support Settings"), - }, - ] - }, - ] \ No newline at end of file diff --git a/erpnext/config/website.py b/erpnext/config/website.py deleted file mode 100644 index d31b0578812..00000000000 --- a/erpnext/config/website.py +++ /dev/null @@ -1,33 +0,0 @@ -from __future__ import unicode_literals -from frappe import _ - -def get_data(): - return [ - { - "label": _("Portal"), - "items": [ - { - "type": "doctype", - "name": "Homepage", - "description": _("Settings for website homepage"), - }, - { - "type": "doctype", - "name": "Homepage Section", - "description": _("Add cards or custom sections on homepage"), - }, - { - "type": "doctype", - "name": "Products Settings", - "description": _("Settings for website product listing"), - }, - { - "type": "doctype", - "name": "Shopping Cart Settings", - "label": _("Shopping Cart Settings"), - "description": _("Settings for online shopping cart such as shipping rules, price list etc."), - "hide_count": True - } - ] - } - ] diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index e6b14c2e408..12a81c78879 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -22,6 +22,7 @@ from six import text_type from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions from erpnext.stock.get_item_details import get_item_warehouse, _get_item_tax_template, get_item_tax_map from erpnext.stock.doctype.packed_item.packed_item import make_packing_list +from erpnext.controllers.print_settings import set_print_templates_for_item_table, set_print_templates_for_taxes class AccountMissingError(frappe.ValidationError): pass @@ -31,6 +32,19 @@ class AccountsController(TransactionBase): def __init__(self, *args, **kwargs): super(AccountsController, self).__init__(*args, **kwargs) + def get_print_settings(self): + print_setting_fields = [] + items_field = self.meta.get_field('items') + + if items_field and items_field.fieldtype == 'Table': + print_setting_fields += ['compact_item_print', 'print_uom_after_quantity'] + + taxes_field = self.meta.get_field('taxes') + if taxes_field and taxes_field.fieldtype == 'Table': + print_setting_fields += ['print_taxes_with_zero_amount'] + + return print_setting_fields + @property def company_currency(self): if not hasattr(self, "__company_currency"): @@ -157,7 +171,7 @@ class AccountsController(TransactionBase): elif self.doctype in ("Quotation", "Purchase Order", "Sales Order"): self.validate_non_invoice_documents_schedule() - def before_print(self): + def before_print(self, settings=None): if self.doctype in ['Purchase Order', 'Sales Order', 'Sales Invoice', 'Purchase Invoice', 'Supplier Quotation', 'Purchase Receipt', 'Delivery Note', 'Quotation']: if self.get("group_same_items"): @@ -170,6 +184,9 @@ class AccountsController(TransactionBase): else: df.set("print_hide", 1) + set_print_templates_for_item_table(self, settings) + set_print_templates_for_taxes(self, settings) + def calculate_paid_amount(self): if hasattr(self, "is_pos") or hasattr(self, "is_paid"): is_paid = self.get("is_pos") or self.get("is_paid") @@ -1292,45 +1309,28 @@ def add_taxes_from_tax_template(child_item, parent_doc): }) tax_row.db_insert() -def set_sales_order_defaults(parent_doctype, parent_doctype_name, child_docname, trans_item): +def set_order_defaults(parent_doctype, parent_doctype_name, child_doctype, child_docname, trans_item): """ - Returns a Sales Order Item child item containing the default values + Returns a Sales/Purchase Order Item child item containing the default values """ p_doc = frappe.get_doc(parent_doctype, parent_doctype_name) - child_item = frappe.new_doc('Sales Order Item', p_doc, child_docname) + child_item = frappe.new_doc(child_doctype, p_doc, child_docname) item = frappe.get_doc("Item", trans_item.get('item_code')) - child_item.item_code = item.item_code - child_item.item_name = item.item_name - child_item.description = item.description - child_item.delivery_date = trans_item.get('delivery_date') or p_doc.delivery_date + for field in ("item_code", "item_name", "description", "item_group"): + child_item.update({field: item.get(field)}) + date_fieldname = "delivery_date" if child_doctype == "Sales Order Item" else "schedule_date" + child_item.update({date_fieldname: trans_item.get(date_fieldname) or p_doc.get(date_fieldname)}) child_item.uom = trans_item.get("uom") or item.stock_uom conversion_factor = flt(get_conversion_factor(item.item_code, child_item.uom).get("conversion_factor")) child_item.conversion_factor = flt(trans_item.get('conversion_factor')) or conversion_factor - set_child_tax_template_and_map(item, child_item, p_doc) - add_taxes_from_tax_template(child_item, p_doc) - child_item.warehouse = get_item_warehouse(item, p_doc, overwrite_warehouse=True) - if not child_item.warehouse: - frappe.throw(_("Cannot find {} for item {}. Please set the same in Item Master or Stock Settings.") - .format(frappe.bold("default warehouse"), frappe.bold(item.item_code))) - return child_item - - -def set_purchase_order_defaults(parent_doctype, parent_doctype_name, child_docname, trans_item): - """ - Returns a Purchase Order Item child item containing the default values - """ - p_doc = frappe.get_doc(parent_doctype, parent_doctype_name) - child_item = frappe.new_doc('Purchase Order Item', p_doc, child_docname) - item = frappe.get_doc("Item", trans_item.get('item_code')) - child_item.item_code = item.item_code - child_item.item_name = item.item_name - child_item.description = item.description - child_item.schedule_date = trans_item.get('schedule_date') or p_doc.schedule_date - child_item.uom = trans_item.get("uom") or item.stock_uom - conversion_factor = flt(get_conversion_factor(item.item_code, child_item.uom).get("conversion_factor")) - child_item.conversion_factor = flt(trans_item.get('conversion_factor')) or conversion_factor - child_item.base_rate = 1 # Initiallize value will update in parent validation - child_item.base_amount = 1 # Initiallize value will update in parent validation + if child_doctype == "Purchase Order Item": + child_item.base_rate = 1 # Initiallize value will update in parent validation + child_item.base_amount = 1 # Initiallize value will update in parent validation + if child_doctype == "Sales Order Item": + child_item.warehouse = get_item_warehouse(item, p_doc, overwrite_warehouse=True) + if not child_item.warehouse: + frappe.throw(_("Cannot find {} for item {}. Please set the same in Item Master or Stock Settings.") + .format(frappe.bold("default warehouse"), frappe.bold(item.item_code))) set_child_tax_template_and_map(item, child_item, p_doc) add_taxes_from_tax_template(child_item, p_doc) return child_item @@ -1394,8 +1394,8 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil ) def get_new_child_item(item_row): - new_child_function = set_sales_order_defaults if parent_doctype == "Sales Order" else set_purchase_order_defaults - return new_child_function(parent_doctype, parent_doctype_name, child_docname, item_row) + child_doctype = "Sales Order Item" if parent_doctype == "Sales Order" else "Purchase Order Item" + return set_order_defaults(parent_doctype, parent_doctype_name, child_doctype, child_docname, item_row) def validate_quantity(child_item, d): if parent_doctype == "Sales Order" and flt(d.get("qty")) < flt(child_item.delivered_qty): diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 305a162d126..219d5295c38 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -20,16 +20,6 @@ from erpnext.controllers.sales_and_purchase_return import get_rate_for_return from erpnext.stock.utils import get_incoming_rate class BuyingController(StockController): - def __setup__(self): - if hasattr(self, "taxes"): - self.flags.print_taxes_with_zero_amount = cint(frappe.db.get_single_value("Print Settings", - "print_taxes_with_zero_amount")) - self.flags.show_inclusive_tax_in_print = self.is_inclusive_tax() - - self.print_templates = { - "total": "templates/print_formats/includes/total.html", - "taxes": "templates/print_formats/includes/taxes.html" - } def get_feed(self): if self.get("supplier_name"): diff --git a/erpnext/controllers/print_settings.py b/erpnext/controllers/print_settings.py index c41db25253f..e08c400068b 100644 --- a/erpnext/controllers/print_settings.py +++ b/erpnext/controllers/print_settings.py @@ -5,20 +5,34 @@ from __future__ import unicode_literals import frappe from frappe.utils import cint -def print_settings_for_item_table(doc): - +def set_print_templates_for_item_table(doc, settings): doc.print_templates = { - "qty": "templates/print_formats/includes/item_table_qty.html" + "items": "templates/print_formats/includes/items.html", } - doc.hide_in_print_layout = ["uom", "stock_uom"] - doc.flags.compact_item_print = cint(frappe.db.get_single_value("Print Settings", "compact_item_print")) + doc.child_print_templates = { + "items": { + "qty": "templates/print_formats/includes/item_table_qty.html", + } + } - if doc.flags.compact_item_print: - doc.print_templates["description"] = "templates/print_formats/includes/item_table_description.html" - doc.flags.compact_item_fields = ["description", "qty", "rate", "amount"] + if doc.meta.get_field("items"): + doc.meta.get_field("items").hide_in_print_layout = ["uom", "stock_uom"] + + doc.flags.compact_item_fields = ["description", "qty", "rate", "amount"] + + if settings.compact_item_print: + doc.child_print_templates["items"]["description"] =\ + "templates/print_formats/includes/item_table_description.html" doc.flags.format_columns = format_columns +def set_print_templates_for_taxes(doc, settings): + doc.flags.show_inclusive_tax_in_print = doc.is_inclusive_tax() + doc.print_templates.update({ + "total": "templates/print_formats/includes/total.html", + "taxes": "templates/print_formats/includes/taxes.html" + }) + def format_columns(display_columns, compact_fields): compact_fields = compact_fields + ["image", "item_code", "item_name"] final_columns = [] diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py index 0e1829a7676..de61b35316e 100644 --- a/erpnext/controllers/sales_and_purchase_return.py +++ b/erpnext/controllers/sales_and_purchase_return.py @@ -204,8 +204,6 @@ def get_already_returned_items(doc): return items def get_returned_qty_map_for_row(row_name, doctype): - if doctype == "POS Invoice": return {} - child_doctype = doctype + " Item" reference_field = "dn_detail" if doctype == "Delivery Note" else frappe.scrub(child_doctype) @@ -354,7 +352,12 @@ def make_return_doc(doctype, source_name, target_doc=None): target_doc.so_detail = source_doc.so_detail target_doc.dn_detail = source_doc.dn_detail target_doc.expense_account = source_doc.expense_account - target_doc.sales_invoice_item = source_doc.name + + if doctype == "Sales Invoice": + target_doc.sales_invoice_item = source_doc.name + else: + target_doc.pos_invoice_item = source_doc.name + target_doc.price_list_rate = 0 if default_warehouse_for_sales_return: target_doc.warehouse = default_warehouse_for_sales_return diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index 3e82d46c2de..fb52c1f6caa 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -16,16 +16,6 @@ from erpnext.controllers.stock_controller import StockController from erpnext.controllers.sales_and_purchase_return import get_rate_for_return class SellingController(StockController): - def __setup__(self): - if hasattr(self, "taxes"): - self.flags.print_taxes_with_zero_amount = cint(frappe.db.get_single_value("Print Settings", - "print_taxes_with_zero_amount")) - self.flags.show_inclusive_tax_in_print = self.is_inclusive_tax() - - self.print_templates = { - "total": "templates/print_formats/includes/total.html", - "taxes": "templates/print_formats/includes/taxes.html" - } def get_feed(self): return _("To {0} | {1} {2}").format(self.customer_name, self.currency, @@ -152,6 +142,11 @@ class SellingController(StockController): self.base_net_total * sales_person.allocated_percentage / 100.0, self.precision("allocated_amount", sales_person)) + if sales_person.commission_rate: + sales_person.incentives = flt( + sales_person.allocated_amount * flt(sales_person.commission_rate) / 100.0, + self.precision("incentives", sales_person)) + total += sales_person.allocated_percentage if sales_team and total != 100.0: diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index cb44b73caf1..11ac703311b 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -320,7 +320,7 @@ class StockController(AccountsController): return serialized_items def validate_warehouse(self): - from erpnext.stock.utils import validate_warehouse_company + from erpnext.stock.utils import validate_warehouse_company, validate_disabled_warehouse warehouses = list(set([d.warehouse for d in self.get("items") if getattr(d, "warehouse", None)])) @@ -336,6 +336,7 @@ class StockController(AccountsController): warehouses.extend(from_warehouse) for w in warehouses: + validate_disabled_warehouse(w) validate_warehouse_company(w, self.company) def update_billing_percentage(self, update_modified=True): @@ -405,7 +406,8 @@ class StockController(AccountsController): def set_rate_of_stock_uom(self): if self.doctype in ["Purchase Receipt", "Purchase Invoice", "Purchase Order", "Sales Invoice", "Sales Order", "Delivery Note", "Quotation"]: for d in self.get("items"): - d.stock_uom_rate = d.rate / d.conversion_factor + if d.conversion_factor: + d.stock_uom_rate = d.rate / d.conversion_factor def validate_internal_transfer(self): if self.doctype in ('Sales Invoice', 'Delivery Note', 'Purchase Invoice', 'Purchase Receipt') \ diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index 6c7eb922210..10271cbcc9d 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -15,6 +15,8 @@ from erpnext.accounts.doctype.journal_entry.journal_entry import get_exchange_ra class calculate_taxes_and_totals(object): def __init__(self, doc): self.doc = doc + frappe.flags.round_off_applicable_accounts = [] + get_round_off_applicable_accounts(self.doc.company, frappe.flags.round_off_applicable_accounts) self.calculate() def calculate(self): @@ -239,9 +241,6 @@ class calculate_taxes_and_totals(object): self.doc.round_floats_in(self.doc, ["total", "base_total", "net_total", "base_net_total"]) - if self.doc.doctype == 'Sales Invoice' and self.doc.is_pos: - self.doc.pos_total_qty = self.doc.total_qty - def calculate_taxes(self): self.doc.rounding_adjustment = 0 # maintain actual tax rate based on idx @@ -335,10 +334,18 @@ class calculate_taxes_and_totals(object): elif tax.charge_type == "On Item Quantity": current_tax_amount = tax_rate * item.qty + current_tax_amount = self.get_final_current_tax_amount(tax, current_tax_amount) self.set_item_wise_tax(item, tax, tax_rate, current_tax_amount) return current_tax_amount + def get_final_current_tax_amount(self, tax, current_tax_amount): + # Some countries need individual tax components to be rounded + # Handeled via regional doctypess + if tax.account_head in frappe.flags.round_off_applicable_accounts: + current_tax_amount = round(current_tax_amount, 0) + return current_tax_amount + def set_item_wise_tax(self, item, tax, tax_rate, current_tax_amount): # store tax breakup for each item key = item.item_code or item.item_name @@ -696,6 +703,15 @@ def get_itemised_tax_breakup_html(doc): ) ) +@frappe.whitelist() +def get_round_off_applicable_accounts(company, account_list): + account_list = get_regional_round_off_accounts(company, account_list) + + return account_list + +@erpnext.allow_regional +def get_regional_round_off_accounts(company, account_list): + pass @erpnext.allow_regional def update_itemised_tax_data(doc): diff --git a/erpnext/crm/desk_page/crm/crm.json b/erpnext/crm/desk_page/crm/crm.json deleted file mode 100644 index d974beb2de8..00000000000 --- a/erpnext/crm/desk_page/crm/crm.json +++ /dev/null @@ -1,86 +0,0 @@ -{ - "cards": [ - { - "hidden": 0, - "label": "Sales Pipeline", - "links": "[\n {\n \"description\": \"Database of potential customers.\",\n \"label\": \"Lead\",\n \"name\": \"Lead\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Potential opportunities for selling.\",\n \"label\": \"Opportunity\",\n \"name\": \"Opportunity\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Customer database.\",\n \"label\": \"Customer\",\n \"name\": \"Customer\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"All Contacts.\",\n \"label\": \"Contact\",\n \"name\": \"Contact\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Record of all communications of type email, phone, chat, visit, etc.\",\n \"label\": \"Communication\",\n \"name\": \"Communication\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Track Leads by Lead Source.\",\n \"label\": \"Lead Source\",\n \"name\": \"Lead Source\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Helps you keep tracks of Contracts based on Supplier, Customer and Employee\",\n \"label\": \"Contract\",\n \"name\": \"Contract\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Helps you manage appointments with your leads\",\n \"label\": \"Appointment\",\n \"name\": \"Appointment\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Newsletter\",\n \"name\": \"Newsletter\",\n \"type\": \"doctype\"\n }\n]" - }, - { - "hidden": 0, - "label": "Reports", - "links": "[\n {\n \"dependencies\": [\n \"Lead\"\n ],\n \"doctype\": \"Lead\",\n \"is_query_report\": true,\n \"label\": \"Lead Details\",\n \"name\": \"Lead Details\",\n \"onboard\": 1,\n \"type\": \"report\"\n },\n {\n \"icon\": \"fa fa-bar-chart\",\n \"label\": \"Sales Funnel\",\n \"name\": \"sales-funnel\",\n \"onboard\": 1,\n \"type\": \"page\"\n },\n {\n \"dependencies\": [\n \"Lead\"\n ],\n \"doctype\": \"Lead\",\n \"is_query_report\": true,\n \"label\": \"Prospects Engaged But Not Converted\",\n \"name\": \"Prospects Engaged But Not Converted\",\n \"onboard\": 1,\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Opportunity\"\n ],\n \"doctype\": \"Opportunity\",\n \"is_query_report\": true,\n \"label\": \"First Response Time for Opportunity\",\n \"name\": \"First Response Time for Opportunity\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Order\"\n ],\n \"doctype\": \"Sales Order\",\n \"is_query_report\": true,\n \"label\": \"Inactive Customers\",\n \"name\": \"Inactive Customers\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Lead\"\n ],\n \"doctype\": \"Lead\",\n \"is_query_report\": true,\n \"label\": \"Campaign Efficiency\",\n \"name\": \"Campaign Efficiency\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Lead\"\n ],\n \"doctype\": \"Lead\",\n \"is_query_report\": true,\n \"label\": \"Lead Owner Efficiency\",\n \"name\": \"Lead Owner Efficiency\",\n \"type\": \"report\"\n }\n]" - }, - { - "hidden": 0, - "label": "Maintenance", - "links": "[\n {\n \"description\": \"Plan for maintenance visits.\",\n \"label\": \"Maintenance Schedule\",\n \"name\": \"Maintenance Schedule\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Visit report for maintenance call.\",\n \"label\": \"Maintenance Visit\",\n \"name\": \"Maintenance Visit\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Warranty Claim against Serial No.\",\n \"label\": \"Warranty Claim\",\n \"name\": \"Warranty Claim\",\n \"type\": \"doctype\"\n }\n]" - }, - { - "hidden": 0, - "label": "Campaign", - "links": "[\n {\n \"description\": \"Sales campaigns.\",\n \"label\": \"Campaign\",\n \"name\": \"Campaign\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Sends Mails to lead or contact based on a Campaign schedule\",\n \"label\": \"Email Campaign\",\n \"name\": \"Email Campaign\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Create and Schedule social media posts\",\n \"label\": \"Social Media Post\",\n \"name\": \"Social Media Post\",\n \"type\": \"doctype\"\n }\n]" - }, - { - "hidden": 0, - "label": "Settings", - "links": "[\n {\n \"description\": \"Manage Customer Group Tree.\",\n \"icon\": \"fa fa-sitemap\",\n \"label\": \"Customer Group\",\n \"link\": \"Tree/Customer Group\",\n \"name\": \"Customer Group\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Manage Territory Tree.\",\n \"icon\": \"fa fa-sitemap\",\n \"label\": \"Territory\",\n \"link\": \"Tree/Territory\",\n \"name\": \"Territory\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Manage Sales Person Tree.\",\n \"icon\": \"fa fa-sitemap\",\n \"label\": \"Sales Person\",\n \"link\": \"Tree/Sales Person\",\n \"name\": \"Sales Person\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Send mass SMS to your contacts\",\n \"label\": \"SMS Center\",\n \"name\": \"SMS Center\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Logs for maintaining sms delivery status\",\n \"label\": \"SMS Log\",\n \"name\": \"SMS Log\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Setup SMS gateway settings\",\n \"label\": \"SMS Settings\",\n \"name\": \"SMS Settings\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Email Group\",\n \"name\": \"Email Group\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Twitter Settings\",\n \"name\": \"Twitter Settings\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"LinkedIn Settings\",\n \"name\": \"LinkedIn Settings\",\n \"type\": \"doctype\"\n }\n]" - } - ], - "category": "Modules", - "charts": [ - { - "chart_name": "Territory Wise Sales" - } - ], - "creation": "2020-01-23 14:48:30.183272", - "developer_mode_only": 0, - "disable_user_customization": 0, - "docstatus": 0, - "doctype": "Desk Page", - "extends_another_page": 0, - "hide_custom": 0, - "idx": 0, - "is_standard": 1, - "label": "CRM", - "modified": "2020-08-11 18:55:18.238900", - "modified_by": "Administrator", - "module": "CRM", - "name": "CRM", - "onboarding": "CRM", - "owner": "Administrator", - "pin_to_bottom": 0, - "pin_to_top": 0, - "shortcuts": [ - { - "color": "#ffe8cd", - "format": "{} Open", - "label": "Lead", - "link_to": "Lead", - "stats_filter": "{\"status\":\"Open\"}", - "type": "DocType" - }, - { - "color": "#cef6d1", - "format": "{} Assigned", - "label": "Opportunity", - "link_to": "Opportunity", - "stats_filter": "{\"_assign\": [\"like\", '%' + frappe.session.user + '%']}", - "type": "DocType" - }, - { - "label": "Customer", - "link_to": "Customer", - "type": "DocType" - }, - { - "label": "Sales Analytics", - "link_to": "Sales Analytics", - "type": "Report" - }, - { - "label": "Dashboard", - "link_to": "CRM", - "type": "Dashboard" - } - ] -} \ No newline at end of file diff --git a/erpnext/crm/doctype/contract/contract_list.js b/erpnext/crm/doctype/contract/contract_list.js index 2ef59007f4a..26a2907c7cc 100644 --- a/erpnext/crm/doctype/contract/contract_list.js +++ b/erpnext/crm/doctype/contract/contract_list.js @@ -1,12 +1,12 @@ frappe.listview_settings['Contract'] = { - add_fields: ["status"], - get_indicator: function (doc) { - if (doc.status == "Unsigned") { - return [__(doc.status), "red", "status,=," + doc.status]; - } else if (doc.status == "Active") { - return [__(doc.status), "green", "status,=," + doc.status]; - } else if (doc.status == "Inactive") { - return [__(doc.status), "darkgrey", "status,=," + doc.status]; - } - }, + add_fields: ["status"], + get_indicator: function (doc) { + if (doc.status == "Unsigned") { + return [__(doc.status), "red", "status,=," + doc.status]; + } else if (doc.status == "Active") { + return [__(doc.status), "green", "status,=," + doc.status]; + } else if (doc.status == "Inactive") { + return [__(doc.status), "gray", "status,=," + doc.status]; + } + }, }; \ No newline at end of file diff --git a/erpnext/crm/doctype/lead/lead.json b/erpnext/crm/doctype/lead/lead.json index 2df1793fdbe..1b33fd73acf 100644 --- a/erpnext/crm/doctype/lead/lead.json +++ b/erpnext/crm/doctype/lead/lead.json @@ -49,6 +49,7 @@ "phone", "mobile_no", "fax", + "website", "more_info", "type", "market_segment", @@ -56,8 +57,8 @@ "request_type", "column_break3", "company", - "website", "territory", + "language", "unsubscribed", "blog_subscriber", "title" @@ -447,13 +448,19 @@ "fieldtype": "Select", "label": "Address Type", "options": "Billing\nShipping\nOffice\nPersonal\nPlant\nPostal\nShop\nSubsidiary\nWarehouse\nCurrent\nPermanent\nOther" + }, + { + "fieldname": "language", + "fieldtype": "Link", + "label": "Print Language", + "options": "Language" } ], "icon": "fa fa-user", "idx": 5, "image_field": "image", "links": [], - "modified": "2020-10-13 15:24:00.094811", + "modified": "2021-01-06 19:39:58.748978", "modified_by": "Administrator", "module": "CRM", "name": "Lead", diff --git a/erpnext/crm/doctype/lead/lead.py b/erpnext/crm/doctype/lead/lead.py index 938cbfdc852..d1d096843bf 100644 --- a/erpnext/crm/doctype/lead/lead.py +++ b/erpnext/crm/doctype/lead/lead.py @@ -352,7 +352,7 @@ def get_lead_with_phone_number(number): leads = frappe.get_all('Lead', or_filters={ 'phone': ['like', '%{}'.format(number)], 'mobile_no': ['like', '%{}'.format(number)] - }, limit=1) + }, limit=1, order_by="creation DESC") lead = leads[0].name if leads else None @@ -361,4 +361,4 @@ def get_lead_with_phone_number(number): def daily_open_lead(): leads = frappe.get_all("Lead", filters = [["contact_date", "Between", [nowdate(), nowdate()]]]) for lead in leads: - frappe.db.set_value("Lead", lead.name, "status", "Open") \ No newline at end of file + frappe.db.set_value("Lead", lead.name, "status", "Open") diff --git a/erpnext/crm/doctype/opportunity/opportunity.js b/erpnext/crm/doctype/opportunity/opportunity.js index 08958b7dd65..ac374a95f4e 100644 --- a/erpnext/crm/doctype/opportunity/opportunity.js +++ b/erpnext/crm/doctype/opportunity/opportunity.js @@ -24,6 +24,12 @@ frappe.ui.form.on("Opportunity", { frm.trigger('set_contact_link'); } }, + contact_date: function(frm) { + if(frm.doc.contact_date < frappe.datetime.now_datetime()){ + frm.set_value("contact_date", ""); + frappe.throw(__("Next follow up date should be greater than now.")) + } + }, onload_post_render: function(frm) { frm.get_field("items").grid.set_multiple_add("item_code", "qty"); diff --git a/erpnext/crm/doctype/opportunity/opportunity.json b/erpnext/crm/doctype/opportunity/opportunity.json index eee13f7e799..2e09a76c0f6 100644 --- a/erpnext/crm/doctype/opportunity/opportunity.json +++ b/erpnext/crm/doctype/opportunity/opportunity.json @@ -54,6 +54,7 @@ "campaign", "column_break1", "transaction_date", + "language", "amended_from", "lost_reasons" ], @@ -419,12 +420,18 @@ "fieldtype": "Duration", "label": "First Response Time", "read_only": 1 + }, + { + "fieldname": "language", + "fieldtype": "Link", + "label": "Print Language", + "options": "Language" } ], "icon": "fa fa-info-sign", "idx": 195, "links": [], - "modified": "2020-08-12 17:34:35.066961", + "modified": "2021-01-06 19:42:46.190051", "modified_by": "Administrator", "module": "CRM", "name": "Opportunity", diff --git a/erpnext/crm/doctype/utils.py b/erpnext/crm/doctype/utils.py index 885ef0584d2..f244daffea3 100644 --- a/erpnext/crm/doctype/utils.py +++ b/erpnext/crm/doctype/utils.py @@ -78,7 +78,9 @@ def get_scheduled_employees_for_popup(communication_medium): def strip_number(number): if not number: return - # strip 0 from the start of the number for proper number comparisions + # strip + and 0 from the start of the number for proper number comparisions + # eg. +7888383332 should match with 7888383332 # eg. 07888383332 should match with 7888383332 + number = number.lstrip('+') number = number.lstrip('0') - return number \ No newline at end of file + return number diff --git a/erpnext/crm/report/prospects_engaged_but_not_converted/prospects_engaged_but_not_converted.py b/erpnext/crm/report/prospects_engaged_but_not_converted/prospects_engaged_but_not_converted.py index b538a581891..3a9d57d6075 100644 --- a/erpnext/crm/report/prospects_engaged_but_not_converted/prospects_engaged_but_not_converted.py +++ b/erpnext/crm/report/prospects_engaged_but_not_converted/prospects_engaged_but_not_converted.py @@ -19,15 +19,50 @@ def set_defaut_value_for_filters(filters): if not filters.get('lead_age'): filters["lead_age"] = 60 def get_columns(): - return [ - _("Lead") + ":Link/Lead:100", - _("Name") + "::100", - _("Organization") + "::100", - _("Reference Document") + "::150", - _("Reference Name") + ":Dynamic Link/"+_("Reference Document")+":120", - _("Last Communication") + ":Data:200", - _("Last Communication Date") + ":Date:180" - ] + columns = [{ + "label": _("Lead"), + "fieldname": "lead", + "fieldtype": "Link", + "options": "Lead", + "width": 130 + }, + { + "label": _("Name"), + "fieldname": "name", + "width": 120 + }, + { + "label": _("Organization"), + "fieldname": "organization", + "width": 120 + }, + { + "label": _("Reference Document Type"), + "fieldname": "reference_document_type", + "fieldtype": "Link", + "options": "Doctype", + "width": 100 + }, + { + "label": _("Reference Name"), + "fieldname": "reference_name", + "fieldtype": "Dynamic Link", + "options": "reference_document_type", + "width": 140 + }, + { + "label": _("Last Communication"), + "fieldname": "last_communication", + "fieldtype": "Data", + "width": 200 + }, + { + "label": _("Last Communication Date"), + "fieldname": "last_communication_date", + "fieldtype": "Date", + "width": 100 + }] + return columns def get_data(filters): lead_details = [] diff --git a/erpnext/crm/workspace/crm/crm.json b/erpnext/crm/workspace/crm/crm.json new file mode 100644 index 00000000000..b4fb7d8abe9 --- /dev/null +++ b/erpnext/crm/workspace/crm/crm.json @@ -0,0 +1,407 @@ +{ + "category": "Modules", + "charts": [ + { + "chart_name": "Territory Wise Sales" + } + ], + "creation": "2020-01-23 14:48:30.183272", + "developer_mode_only": 0, + "disable_user_customization": 0, + "docstatus": 0, + "doctype": "Workspace", + "extends_another_page": 0, + "hide_custom": 0, + "icon": "crm", + "idx": 0, + "is_standard": 1, + "label": "CRM", + "links": [ + { + "hidden": 0, + "is_query_report": 0, + "label": "Sales Pipeline", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Lead", + "link_to": "Lead", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Opportunity", + "link_to": "Opportunity", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Customer", + "link_to": "Customer", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Contact", + "link_to": "Contact", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Communication", + "link_to": "Communication", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Lead Source", + "link_to": "Lead Source", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Contract", + "link_to": "Contract", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Appointment", + "link_to": "Appointment", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Newsletter", + "link_to": "Newsletter", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Reports", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "Lead", + "hidden": 0, + "is_query_report": 1, + "label": "Lead Details", + "link_to": "Lead Details", + "link_type": "Report", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Sales Funnel", + "link_to": "sales-funnel", + "link_type": "Page", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "Lead", + "hidden": 0, + "is_query_report": 1, + "label": "Prospects Engaged But Not Converted", + "link_to": "Prospects Engaged But Not Converted", + "link_type": "Report", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "Opportunity", + "hidden": 0, + "is_query_report": 1, + "label": "First Response Time for Opportunity", + "link_to": "First Response Time for Opportunity", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Sales Order", + "hidden": 0, + "is_query_report": 1, + "label": "Inactive Customers", + "link_to": "Inactive Customers", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Lead", + "hidden": 0, + "is_query_report": 1, + "label": "Campaign Efficiency", + "link_to": "Campaign Efficiency", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Lead", + "hidden": 0, + "is_query_report": 1, + "label": "Lead Owner Efficiency", + "link_to": "Lead Owner Efficiency", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Maintenance", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Maintenance Schedule", + "link_to": "Maintenance Schedule", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Maintenance Visit", + "link_to": "Maintenance Visit", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Warranty Claim", + "link_to": "Warranty Claim", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Campaign", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Campaign", + "link_to": "Campaign", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Email Campaign", + "link_to": "Email Campaign", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Social Media Post", + "link_to": "Social Media Post", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Settings", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Customer Group", + "link_to": "Customer Group", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Territory", + "link_to": "Territory", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Sales Person", + "link_to": "Sales Person", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "SMS Center", + "link_to": "SMS Center", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "SMS Log", + "link_to": "SMS Log", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "SMS Settings", + "link_to": "SMS Settings", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Email Group", + "link_to": "Email Group", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Twitter Settings", + "link_to": "Twitter Settings", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "LinkedIn Settings", + "link_to": "LinkedIn Settings", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + } + ], + "modified": "2020-12-01 13:38:36.871352", + "modified_by": "Administrator", + "module": "CRM", + "name": "CRM", + "onboarding": "CRM", + "owner": "Administrator", + "pin_to_bottom": 0, + "pin_to_top": 0, + "shortcuts": [ + { + "color": "Blue", + "format": "{} Open", + "label": "Lead", + "link_to": "Lead", + "stats_filter": "{\"status\":\"Open\"}", + "type": "DocType" + }, + { + "color": "Blue", + "format": "{} Assigned", + "label": "Opportunity", + "link_to": "Opportunity", + "stats_filter": "{\"_assign\": [\"like\", '%' + frappe.session.user + '%']}", + "type": "DocType" + }, + { + "label": "Customer", + "link_to": "Customer", + "type": "DocType" + }, + { + "label": "Sales Analytics", + "link_to": "Sales Analytics", + "type": "Report" + }, + { + "label": "Dashboard", + "link_to": "CRM", + "type": "Dashboard" + } + ] +} \ No newline at end of file diff --git a/erpnext/education/api.py b/erpnext/education/api.py index 948e7cc1aed..afa0be9b9f3 100644 --- a/erpnext/education/api.py +++ b/erpnext/education/api.py @@ -36,6 +36,7 @@ def enroll_student(source_name): student.save() program_enrollment = frappe.new_doc("Program Enrollment") program_enrollment.student = student.name + program_enrollment.student_category = student.student_category program_enrollment.student_name = student.title program_enrollment.program = frappe.db.get_value("Student Applicant", source_name, "program") frappe.publish_realtime('enroll_student_progress', {"progress": [2, 4]}, user=frappe.session.user) diff --git a/erpnext/education/desk_page/education/education.json b/erpnext/education/desk_page/education/education.json deleted file mode 100644 index 77ee8ecaf6d..00000000000 --- a/erpnext/education/desk_page/education/education.json +++ /dev/null @@ -1,154 +0,0 @@ -{ - "cards": [ - { - "hidden": 0, - "label": "Student and Instructor", - "links": "[\n {\n \"label\": \"Student\",\n \"name\": \"Student\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Instructor\",\n \"name\": \"Instructor\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Guardian\",\n \"name\": \"Guardian\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Student Group\",\n \"name\": \"Student Group\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Student Log\",\n \"name\": \"Student Log\",\n \"type\": \"doctype\"\n }\n]" - }, - { - "hidden": 0, - "label": "Masters", - "links": "[\n {\n \"label\": \"Program\",\n \"name\": \"Program\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Course\",\n \"name\": \"Course\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Topic\",\n \"name\": \"Topic\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Room\",\n \"name\": \"Room\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n }\n]" - }, - { - "hidden": 0, - "label": "Content Masters", - "links": "[\n {\n \"label\": \"Article\",\n \"name\": \"Article\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Video\",\n \"name\": \"Video\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Quiz\",\n \"name\": \"Quiz\",\n \"type\": \"doctype\"\n }\n]" - }, - { - "hidden": 0, - "label": "Settings", - "links": "[\n {\n \"label\": \"Education Settings\",\n \"name\": \"Education Settings\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Student Category\",\n \"name\": \"Student Category\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Student Batch Name\",\n \"name\": \"Student Batch Name\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Grading Scale\",\n \"name\": \"Grading Scale\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Academic Term\",\n \"name\": \"Academic Term\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Academic Year\",\n \"name\": \"Academic Year\",\n \"type\": \"doctype\"\n }\n]" - }, - { - "hidden": 0, - "label": "Admission", - "links": "[\n {\n \"label\": \"Student Applicant\",\n \"name\": \"Student Applicant\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Student Admission\",\n \"name\": \"Student Admission\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Program Enrollment\",\n \"name\": \"Program Enrollment\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Course Enrollment\",\n \"name\": \"Course Enrollment\",\n \"type\": \"doctype\"\n }\n]" - }, - { - "hidden": 0, - "label": "Fees", - "links": "[\n {\n \"label\": \"Fee Structure\",\n \"name\": \"Fee Structure\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Fee Category\",\n \"name\": \"Fee Category\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Fee Schedule\",\n \"name\": \"Fee Schedule\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Fees\",\n \"name\": \"Fees\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Fees\"\n ],\n \"doctype\": \"Fees\",\n \"is_query_report\": true,\n \"label\": \"Student Fee Collection Report\",\n \"name\": \"Student Fee Collection\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Fees\"\n ],\n \"doctype\": \"Fees\",\n \"is_query_report\": true,\n \"label\": \"Program wise Fee Collection Report\",\n \"name\": \"Program wise Fee Collection\",\n \"type\": \"report\"\n }\n]" - }, - { - "hidden": 0, - "label": "Schedule", - "links": "[\n {\n \"label\": \"Course Schedule\",\n \"name\": \"Course Schedule\",\n \"route\": \"#List/Course Schedule/Calendar\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Course Scheduling Tool\",\n \"name\": \"Course Scheduling Tool\",\n \"type\": \"doctype\"\n }\n]" - }, - { - "hidden": 0, - "label": "Attendance", - "links": "[\n {\n \"label\": \"Student Attendance\",\n \"name\": \"Student Attendance\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Student Leave Application\",\n \"name\": \"Student Leave Application\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Student Attendance\"\n ],\n \"doctype\": \"Student Attendance\",\n \"is_query_report\": true,\n \"label\": \"Student Monthly Attendance Sheet\",\n \"name\": \"Student Monthly Attendance Sheet\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Student Attendance\"\n ],\n \"doctype\": \"Student Attendance\",\n \"is_query_report\": true,\n \"label\": \"Absent Student Report\",\n \"name\": \"Absent Student Report\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Student Attendance\"\n ],\n \"doctype\": \"Student Attendance\",\n \"is_query_report\": true,\n \"label\": \"Student Batch-Wise Attendance\",\n \"name\": \"Student Batch-Wise Attendance\",\n \"type\": \"report\"\n }\n]" - }, - { - "hidden": 0, - "label": "LMS Activity", - "links": "[\n {\n \"label\": \"Course Enrollment\",\n \"name\": \"Course Enrollment\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Course Activity\",\n \"name\": \"Course Activity\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Quiz Activity\",\n \"name\": \"Quiz Activity\",\n \"type\": \"doctype\"\n }\n]" - }, - { - "hidden": 0, - "label": "Assessment", - "links": "[\n {\n \"label\": \"Assessment Plan\",\n \"name\": \"Assessment Plan\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Assessment Group\",\n \"link\": \"Tree/Assessment Group\",\n \"name\": \"Assessment Group\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Assessment Result\",\n \"name\": \"Assessment Result\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Assessment Criteria\",\n \"name\": \"Assessment Criteria\",\n \"type\": \"doctype\"\n }\n]" - }, - { - "hidden": 0, - "label": "Assessment Reports", - "links": "[\n {\n \"dependencies\": [\n \"Assessment Result\"\n ],\n \"doctype\": \"Assessment Result\",\n \"is_query_report\": true,\n \"label\": \"Course wise Assessment Report\",\n \"name\": \"Course wise Assessment Report\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Assessment Result\"\n ],\n \"doctype\": \"Assessment Result\",\n \"is_query_report\": true,\n \"label\": \"Final Assessment Grades\",\n \"name\": \"Final Assessment Grades\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Assessment Plan\"\n ],\n \"doctype\": \"Assessment Plan\",\n \"is_query_report\": true,\n \"label\": \"Assessment Plan Status\",\n \"name\": \"Assessment Plan Status\",\n \"type\": \"report\"\n },\n {\n \"label\": \"Student Report Generation Tool\",\n \"name\": \"Student Report Generation Tool\",\n \"type\": \"doctype\"\n }\n]" - }, - { - "hidden": 0, - "label": "Tools", - "links": "[\n {\n \"label\": \"Student Attendance Tool\",\n \"name\": \"Student Attendance Tool\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Assessment Result Tool\",\n \"name\": \"Assessment Result Tool\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Student Group Creation Tool\",\n \"name\": \"Student Group Creation Tool\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Program Enrollment Tool\",\n \"name\": \"Program Enrollment Tool\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Course Scheduling Tool\",\n \"name\": \"Course Scheduling Tool\",\n \"type\": \"doctype\"\n }\n]" - }, - { - "hidden": 0, - "label": "Other Reports", - "links": "[\n {\n \"dependencies\": [\n \"Program Enrollment\"\n ],\n \"doctype\": \"Program Enrollment\",\n \"is_query_report\": true,\n \"label\": \"Student and Guardian Contact Details\",\n \"name\": \"Student and Guardian Contact Details\",\n \"type\": \"report\"\n }\n]" - } - ], - "category": "Domains", - "charts": [ - { - "chart_name": "Program Enrollments", - "label": "Program Enrollments" - } - ], - "creation": "2020-03-02 17:22:57.066401", - "developer_mode_only": 0, - "disable_user_customization": 0, - "docstatus": 0, - "doctype": "Desk Page", - "extends_another_page": 0, - "hide_custom": 0, - "idx": 0, - "is_standard": 1, - "label": "Education", - "modified": "2020-07-27 19:35:18.832694", - "modified_by": "Administrator", - "module": "Education", - "name": "Education", - "onboarding": "Education", - "owner": "Administrator", - "pin_to_bottom": 0, - "pin_to_top": 0, - "restrict_to_domain": "Education", - "shortcuts": [ - { - "color": "#cef6d1", - "format": "{} Active", - "label": "Student", - "link_to": "Student", - "stats_filter": "{\n \"enabled\": 1\n}", - "type": "DocType" - }, - { - "color": "#cef6d1", - "format": "{} Active", - "label": "Instructor", - "link_to": "Instructor", - "stats_filter": "{\n \"status\": \"Active\"\n}", - "type": "DocType" - }, - { - "color": "", - "format": "", - "label": "Program", - "link_to": "Program", - "stats_filter": "", - "type": "DocType" - }, - { - "label": "Course", - "link_to": "Course", - "type": "DocType" - }, - { - "color": "#ffe8cd", - "format": "{} Unpaid", - "label": "Fees", - "link_to": "Fees", - "stats_filter": "{\n \"outstanding_amount\": [\"!=\", 0.0]\n}", - "type": "DocType" - }, - { - "label": "Student Monthly Attendance Sheet", - "link_to": "Student Monthly Attendance Sheet", - "type": "Report" - }, - { - "label": "Course Scheduling Tool", - "link_to": "Course Scheduling Tool", - "type": "DocType" - }, - { - "label": "Student Attendance Tool", - "link_to": "Student Attendance Tool", - "type": "DocType" - }, - { - "label": "Dashboard", - "link_to": "Education", - "type": "Dashboard" - } - ] -} \ No newline at end of file diff --git a/erpnext/education/doctype/assessment_result_tool/assessment_result_tool.js b/erpnext/education/doctype/assessment_result_tool/assessment_result_tool.js index 3cd451209f1..053f0c2f1b8 100644 --- a/erpnext/education/doctype/assessment_result_tool/assessment_result_tool.js +++ b/erpnext/education/doctype/assessment_result_tool/assessment_result_tool.js @@ -128,7 +128,7 @@ frappe.ui.form.on('Assessment Result Tool', { result_table.find(`span[data-student=${assessment_result.student}].total-score-grade`).html(assessment_result.grade); let link_span = result_table.find(`span[data-student=${assessment_result.student}].total-result-link`); $(link_span).css("display", "block"); - $(link_span).find("a").attr("href", "#Form/Assessment Result/"+assessment_result.name); + $(link_span).find("a").attr("href", "/app/assessment-result/"+assessment_result.name); } }); } diff --git a/erpnext/education/doctype/course_scheduling_tool/course_scheduling_tool.js b/erpnext/education/doctype/course_scheduling_tool/course_scheduling_tool.js index 20503f919cc..d57f46ab98e 100644 --- a/erpnext/education/doctype/course_scheduling_tool/course_scheduling_tool.js +++ b/erpnext/education/doctype/course_scheduling_tool/course_scheduling_tool.js @@ -19,17 +19,22 @@ frappe.ui.form.on('Course Scheduling Tool', { } const { course_schedules } = r.message; if (course_schedules) { + const course_schedules_html = course_schedules.map(c => ` +
    ${c.name}${c.schedule_date}
    - - - - ${course_schedules.map( - c => ` - ` - ).join('')} - -
    ${__('Following course schedules were created')}
    ${__("Course")}${__("Date")}
    ${c.name}${c.schedule_date}
    ` + + + + + ${course_schedules_html} + +
    ${__('Following course schedules were created')}
    ${__("Course")}${__("Date")}
    + `; frappe.msgprint(html); } diff --git a/erpnext/education/doctype/fee_schedule/fee_schedule.js b/erpnext/education/doctype/fee_schedule/fee_schedule.js index 65b5fa6cf23..0089957df40 100644 --- a/erpnext/education/doctype/fee_schedule/fee_schedule.js +++ b/erpnext/education/doctype/fee_schedule/fee_schedule.js @@ -48,7 +48,7 @@ frappe.ui.form.on('Fee Schedule', { frm.reload_doc(); } if (data.progress) { - let progress_bar = $(cur_frm.dashboard.progress_area).find('.progress-bar'); + let progress_bar = $(cur_frm.dashboard.progress_area.body).find('.progress-bar'); if (progress_bar) { $(progress_bar).removeClass('progress-bar-danger').addClass('progress-bar-success progress-bar-striped'); $(progress_bar).css('width', data.progress+'%'); diff --git a/erpnext/education/doctype/program_enrollment/program_enrollment.py b/erpnext/education/doctype/program_enrollment/program_enrollment.py index 886a7d85d8b..d18c0f9625c 100644 --- a/erpnext/education/doctype/program_enrollment/program_enrollment.py +++ b/erpnext/education/doctype/program_enrollment/program_enrollment.py @@ -87,7 +87,7 @@ class ProgramEnrollment(Document): fees.submit() fee_list.append(fees.name) if fee_list: - fee_list = ["""%s""" % \ + fee_list = ["""%s""" % \ (fee, fee) for fee in fee_list] msgprint(_("Fee Records Created - {0}").format(comma_and(fee_list))) diff --git a/erpnext/education/doctype/program_enrollment_tool/program_enrollment_tool.py b/erpnext/education/doctype/program_enrollment_tool/program_enrollment_tool.py index 9f8f9f4dc00..8180102c582 100644 --- a/erpnext/education/doctype/program_enrollment_tool/program_enrollment_tool.py +++ b/erpnext/education/doctype/program_enrollment_tool/program_enrollment_tool.py @@ -30,7 +30,7 @@ class ProgramEnrollmentTool(Document): .format(condition), self.as_dict(), as_dict=1) elif self.get_students_from == "Program Enrollment": condition2 = 'and student_batch_name=%(student_batch)s' if self.student_batch else " " - students = frappe.db.sql('''select student, student_name, student_batch_name from `tabProgram Enrollment` + students = frappe.db.sql('''select student, student_name, student_batch_name, student_category from `tabProgram Enrollment` where program=%(program)s and academic_year=%(academic_year)s {0} {1} and docstatus != 2''' .format(condition, condition2), self.as_dict(), as_dict=1) @@ -57,6 +57,7 @@ class ProgramEnrollmentTool(Document): prog_enrollment = frappe.new_doc("Program Enrollment") prog_enrollment.student = stud.student prog_enrollment.student_name = stud.student_name + prog_enrollment.student_category = stud.student_category prog_enrollment.program = self.new_program prog_enrollment.academic_year = self.new_academic_year prog_enrollment.academic_term = self.new_academic_term diff --git a/erpnext/education/doctype/student_admission/templates/student_admission.html b/erpnext/education/doctype/student_admission/templates/student_admission.html index 7ff3906b341..f9ddac086b1 100644 --- a/erpnext/education/doctype/student_admission/templates/student_admission.html +++ b/erpnext/education/doctype/student_admission/templates/student_admission.html @@ -21,7 +21,7 @@ {% elif frappe.utils.getdate(doc.admission_start_date) > today %} blue"> Application will open {% else %} - darkgrey + gray {% endif %}
    diff --git a/erpnext/education/doctype/student_admission/templates/student_admission_row.html b/erpnext/education/doctype/student_admission/templates/student_admission_row.html index cf22436ae66..99868d5f020 100644 --- a/erpnext/education/doctype/student_admission/templates/student_admission_row.html +++ b/erpnext/education/doctype/student_admission/templates/student_admission_row.html @@ -11,7 +11,7 @@ {% elif frappe.utils.getdate(doc.admission_start_date) > today %} blue {% else %} - darkgrey + gray {% endif %} ">{{ doc.title }}
    diff --git a/erpnext/education/doctype/student_applicant/student_applicant.json b/erpnext/education/doctype/student_applicant/student_applicant.json index 6df9b9a84f9..95f9224a73c 100644 --- a/erpnext/education/doctype/student_applicant/student_applicant.json +++ b/erpnext/education/doctype/student_applicant/student_applicant.json @@ -11,6 +11,7 @@ "middle_name", "last_name", "program", + "student_category", "lms_only", "paid", "column_break_8", @@ -257,12 +258,18 @@ "options": "Student Applicant", "print_hide": 1, "read_only": 1 + }, + { + "fieldname": "student_category", + "fieldtype": "Link", + "label": "Student Category", + "options": "Student Category" } ], "image_field": "image", "is_submittable": 1, "links": [], - "modified": "2020-10-05 13:59:45.631647", + "modified": "2021-03-01 23:00:25.119241", "modified_by": "Administrator", "module": "Education", "name": "Student Applicant", diff --git a/erpnext/education/workspace/education/education.json b/erpnext/education/workspace/education/education.json new file mode 100644 index 00000000000..bf7496146d9 --- /dev/null +++ b/erpnext/education/workspace/education/education.json @@ -0,0 +1,701 @@ +{ + "category": "Domains", + "charts": [ + { + "chart_name": "Program Enrollments", + "label": "Program Enrollments" + } + ], + "creation": "2020-03-02 17:22:57.066401", + "developer_mode_only": 0, + "disable_user_customization": 0, + "docstatus": 0, + "doctype": "Workspace", + "extends_another_page": 0, + "hide_custom": 0, + "icon": "education", + "idx": 0, + "is_standard": 1, + "label": "Education", + "links": [ + { + "hidden": 0, + "is_query_report": 0, + "label": "Student and Instructor", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Student", + "link_to": "Student", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Instructor", + "link_to": "Instructor", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Guardian", + "link_to": "Guardian", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Student Group", + "link_to": "Student Group", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Student Log", + "link_to": "Student Log", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Masters", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Program", + "link_to": "Program", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Course", + "link_to": "Course", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Topic", + "link_to": "Topic", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Room", + "link_to": "Room", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Content Masters", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Article", + "link_to": "Article", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Video", + "link_to": "Video", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Quiz", + "link_to": "Quiz", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Settings", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Education Settings", + "link_to": "Education Settings", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Student Category", + "link_to": "Student Category", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Student Batch Name", + "link_to": "Student Batch Name", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Grading Scale", + "link_to": "Grading Scale", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Academic Term", + "link_to": "Academic Term", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Academic Year", + "link_to": "Academic Year", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Admission", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Student Applicant", + "link_to": "Student Applicant", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Student Admission", + "link_to": "Student Admission", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Program Enrollment", + "link_to": "Program Enrollment", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Course Enrollment", + "link_to": "Course Enrollment", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Fees", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Fee Structure", + "link_to": "Fee Structure", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Fee Category", + "link_to": "Fee Category", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Fee Schedule", + "link_to": "Fee Schedule", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Fees", + "link_to": "Fees", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Fees", + "hidden": 0, + "is_query_report": 1, + "label": "Student Fee Collection Report", + "link_to": "Student Fee Collection", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Fees", + "hidden": 0, + "is_query_report": 1, + "label": "Program wise Fee Collection Report", + "link_to": "Program wise Fee Collection", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Schedule", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Course Schedule", + "link_to": "Course Schedule", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Course Scheduling Tool", + "link_to": "Course Scheduling Tool", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Attendance", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Student Attendance", + "link_to": "Student Attendance", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Student Leave Application", + "link_to": "Student Leave Application", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Student Attendance", + "hidden": 0, + "is_query_report": 1, + "label": "Student Monthly Attendance Sheet", + "link_to": "Student Monthly Attendance Sheet", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Student Attendance", + "hidden": 0, + "is_query_report": 1, + "label": "Absent Student Report", + "link_to": "Absent Student Report", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Student Attendance", + "hidden": 0, + "is_query_report": 1, + "label": "Student Batch-Wise Attendance", + "link_to": "Student Batch-Wise Attendance", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "LMS Activity", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Course Enrollment", + "link_to": "Course Enrollment", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Course Activity", + "link_to": "Course Activity", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Quiz Activity", + "link_to": "Quiz Activity", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Assessment", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Assessment Plan", + "link_to": "Assessment Plan", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Assessment Group", + "link_to": "Assessment Group", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Assessment Result", + "link_to": "Assessment Result", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Assessment Criteria", + "link_to": "Assessment Criteria", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Assessment Reports", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "Assessment Result", + "hidden": 0, + "is_query_report": 1, + "label": "Course wise Assessment Report", + "link_to": "Course wise Assessment Report", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Assessment Result", + "hidden": 0, + "is_query_report": 1, + "label": "Final Assessment Grades", + "link_to": "Final Assessment Grades", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Assessment Plan", + "hidden": 0, + "is_query_report": 1, + "label": "Assessment Plan Status", + "link_to": "Assessment Plan Status", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Student Report Generation Tool", + "link_to": "Student Report Generation Tool", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Tools", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Student Attendance Tool", + "link_to": "Student Attendance Tool", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Assessment Result Tool", + "link_to": "Assessment Result Tool", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Student Group Creation Tool", + "link_to": "Student Group Creation Tool", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Program Enrollment Tool", + "link_to": "Program Enrollment Tool", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Course Scheduling Tool", + "link_to": "Course Scheduling Tool", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Other Reports", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "Program Enrollment", + "hidden": 0, + "is_query_report": 1, + "label": "Student and Guardian Contact Details", + "link_to": "Student and Guardian Contact Details", + "link_type": "Report", + "onboard": 0, + "type": "Link" + } + ], + "modified": "2020-12-01 13:38:37.448989", + "modified_by": "Administrator", + "module": "Education", + "name": "Education", + "onboarding": "Education", + "owner": "Administrator", + "pin_to_bottom": 0, + "pin_to_top": 0, + "restrict_to_domain": "Education", + "shortcuts": [ + { + "color": "Grey", + "format": "{} Active", + "label": "Student", + "link_to": "Student", + "stats_filter": "{\n \"enabled\": 1\n}", + "type": "DocType" + }, + { + "color": "Grey", + "format": "{} Active", + "label": "Instructor", + "link_to": "Instructor", + "stats_filter": "{\n \"status\": \"Active\"\n}", + "type": "DocType" + }, + { + "color": "", + "format": "", + "label": "Program", + "link_to": "Program", + "stats_filter": "", + "type": "DocType" + }, + { + "label": "Course", + "link_to": "Course", + "type": "DocType" + }, + { + "color": "Grey", + "format": "{} Unpaid", + "label": "Fees", + "link_to": "Fees", + "stats_filter": "{\n \"outstanding_amount\": [\"!=\", 0.0]\n}", + "type": "DocType" + }, + { + "label": "Student Monthly Attendance Sheet", + "link_to": "Student Monthly Attendance Sheet", + "type": "Report" + }, + { + "label": "Course Scheduling Tool", + "link_to": "Course Scheduling Tool", + "type": "DocType" + }, + { + "label": "Student Attendance Tool", + "link_to": "Student Attendance Tool", + "type": "DocType" + }, + { + "label": "Dashboard", + "link_to": "Education", + "type": "Dashboard" + } + ] +} \ No newline at end of file diff --git a/erpnext/erpnext_integrations/desk_page/erpnext_integrations/erpnext_integrations.json b/erpnext/erpnext_integrations/desk_page/erpnext_integrations/erpnext_integrations.json deleted file mode 100644 index ea3b1291b72..00000000000 --- a/erpnext/erpnext_integrations/desk_page/erpnext_integrations/erpnext_integrations.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "cards": [ - { - "hidden": 0, - "label": "Marketplace", - "links": "[\n {\n \"description\": \"Woocommerce marketplace settings\",\n \"label\": \"Woocommerce Settings\",\n \"name\": \"Woocommerce Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Amazon MWS settings\",\n \"label\": \"Amazon MWS Settings\",\n \"name\": \"Amazon MWS Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Shopify settings\",\n \"label\": \"Shopify Settings\",\n \"name\": \"Shopify Settings\",\n \"type\": \"doctype\"\n }\n]" - }, - { - "hidden": 0, - "label": "Payments", - "links": "[\n {\n \"description\": \"GoCardless payment gateway settings\",\n \"label\": \"GoCardless Settings\",\n \"name\": \"GoCardless Settings\",\n \"type\": \"doctype\"\n }, {\n \"description\": \"M-Pesa payment gateway settings\",\n \"label\": \"M-Pesa Settings\",\n \"name\": \"Mpesa Settings\",\n \"type\": \"doctype\"\n }\n]" - }, - { - "hidden": 0, - "label": "Settings", - "links": "[\n {\n \"description\": \"Plaid settings\",\n \"label\": \"Plaid Settings\",\n \"name\": \"Plaid Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Exotel settings\",\n \"label\": \"Exotel Settings\",\n \"name\": \"Exotel Settings\",\n \"type\": \"doctype\"\n }\n]" - } - ], - "category": "Modules", - "charts": [], - "creation": "2020-08-20 19:30:48.138801", - "developer_mode_only": 0, - "disable_user_customization": 0, - "docstatus": 0, - "doctype": "Desk Page", - "extends": "Integrations", - "extends_another_page": 1, - "hide_custom": 1, - "idx": 0, - "is_standard": 1, - "label": "ERPNext Integrations", - "modified": "2020-10-29 19:54:46.228222", - "modified_by": "Administrator", - "module": "ERPNext Integrations", - "name": "ERPNext Integrations", - "owner": "Administrator", - "pin_to_bottom": 0, - "pin_to_top": 0, - "shortcuts": [] -} \ No newline at end of file diff --git a/erpnext/erpnext_integrations/desk_page/erpnext_integrations_settings/erpnext_integrations_settings.json b/erpnext/erpnext_integrations/desk_page/erpnext_integrations_settings/erpnext_integrations_settings.json deleted file mode 100644 index 3bbc36ad940..00000000000 --- a/erpnext/erpnext_integrations/desk_page/erpnext_integrations_settings/erpnext_integrations_settings.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "cards": [ - { - "hidden": 0, - "label": "Integrations Settings", - "links": "[\n\t{\n\t \"type\": \"doctype\",\n\t\t\"name\": \"Woocommerce Settings\"\n\t},\n\t{\n\t \"type\": \"doctype\",\n\t\t\"name\": \"Shopify Settings\",\n\t\t\"description\": \"Connect Shopify with ERPNext\"\n\t},\n\t{\n\t \"type\": \"doctype\",\n\t\t\"name\": \"Amazon MWS Settings\",\n\t\t\"description\": \"Connect Amazon with ERPNext\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Plaid Settings\",\n\t\t\"description\": \"Connect your bank accounts to ERPNext\"\n\t},\n {\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Exotel Settings\",\n\t\t\"description\": \"Connect your Exotel Account to ERPNext and track call logs\"\n }\n]" - } - ], - "category": "Modules", - "charts": [], - "creation": "2020-07-31 10:38:54.021237", - "developer_mode_only": 0, - "disable_user_customization": 0, - "docstatus": 0, - "doctype": "Desk Page", - "extends": "Settings", - "extends_another_page": 1, - "hide_custom": 0, - "idx": 0, - "is_standard": 1, - "label": "ERPNext Integrations Settings", - "modified": "2020-07-31 10:44:39.374297", - "modified_by": "Administrator", - "module": "ERPNext Integrations", - "name": "ERPNext Integrations Settings", - "owner": "Administrator", - "pin_to_bottom": 0, - "pin_to_top": 0, - "shortcuts": [] -} \ No newline at end of file diff --git a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_methods.py b/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_methods.py index cc75a0afbe0..148c1a6a166 100644 --- a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_methods.py +++ b/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_methods.py @@ -117,7 +117,7 @@ def call_mws_method(mws_method, *args, **kwargs): return response except Exception as e: delay = math.pow(4, x) * 125 - frappe.log_error(message=e, title=str(mws_method)) + frappe.log_error(message=e, title=f'Method "{mws_method.__name__}" failed') time.sleep(delay) continue diff --git a/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.json b/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.json index 407f82616ff..8f3b4271c18 100644 --- a/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.json +++ b/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.json @@ -103,7 +103,7 @@ } ], "links": [], - "modified": "2021-01-29 12:02:16.106942", + "modified": "2021-03-02 17:35:14.084342", "modified_by": "Administrator", "module": "ERPNext Integrations", "name": "Mpesa Settings", @@ -147,5 +147,6 @@ } ], "sort_field": "modified", - "sort_order": "DESC" + "sort_order": "DESC", + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py b/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py index 08b2bc2fa83..29487962f69 100644 --- a/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py +++ b/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py @@ -196,8 +196,6 @@ class TestMpesaSettings(unittest.TestCase): pr.delete() pos_invoice.delete() - frappe.db.set_value("Customer", "_Test Customer", "default_currency", "") - def create_mpesa_settings(payment_gateway_name="Express"): if frappe.db.exists("Mpesa Settings", payment_gateway_name): return frappe.get_doc("Mpesa Settings", payment_gateway_name) diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_connector.py b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_connector.py index 66d0e5f77db..5f990cdd034 100644 --- a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_connector.py +++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_connector.py @@ -20,7 +20,7 @@ class PlaidConnector(): client_id=self.settings.plaid_client_id, secret=self.settings.get_password("plaid_secret"), environment=self.settings.plaid_env, - api_version="2019-05-29" + api_version="2020-09-14" ) def get_access_token(self, public_token): @@ -29,7 +29,7 @@ class PlaidConnector(): response = self.client.Item.public_token.exchange(public_token) access_token = response["access_token"] return access_token - + def get_token_request(self, update_mode=False): country_codes = ["US", "CA", "FR", "IE", "NL", "ES", "GB"] if self.settings.enable_european_access else ["US", "CA"] args = { diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.json b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.json index 122aa41f4b9..e7176ea945c 100644 --- a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.json +++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.json @@ -70,7 +70,7 @@ ], "issingle": 1, "links": [], - "modified": "2020-10-29 20:24:56.916104", + "modified": "2021-03-02 17:35:27.544259", "modified_by": "Administrator", "module": "ERPNext Integrations", "name": "Plaid Settings", @@ -88,5 +88,6 @@ } ], "sort_field": "modified", - "sort_order": "DESC" + "sort_order": "DESC", + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py index 70c7f3fe5d7..21f6fee79c8 100644 --- a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py +++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py @@ -204,8 +204,8 @@ def new_bank_transaction(transaction): "date": getdate(transaction["date"]), "status": status, "bank_account": bank_account, - "debit": debit, - "credit": credit, + "deposit": debit, + "withdrawal": credit, "currency": transaction["iso_currency_code"], "transaction_id": transaction["transaction_id"], "reference_number": transaction["payment_meta"]["reference_number"], diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.json b/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.json index 20ec06373e7..308e7d163f3 100644 --- a/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.json +++ b/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.json @@ -330,7 +330,7 @@ ], "issingle": 1, "links": [], - "modified": "2020-11-05 20:44:03.664891", + "modified": "2021-03-02 17:35:41.953317", "modified_by": "Administrator", "module": "ERPNext Integrations", "name": "Shopify Settings", @@ -348,5 +348,6 @@ } ], "sort_field": "modified", - "sort_order": "DESC" + "sort_order": "DESC", + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.js b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.js index fd16d1e84aa..5482b9cc695 100644 --- a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.js +++ b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.js @@ -23,10 +23,10 @@ frappe.ui.form.on("Tally Migration", { frappe.msgprint({ message: __("An error has occurred during {0}. Check {1} for more details", [ - repl("%(tally_document)s", { + repl("%(tally_document)s", { tally_document: frm.docname }), - "Error Log" + "Error Log" ] ), title: __("Tally Migration Error"), diff --git a/erpnext/erpnext_integrations/workspace/erpnext_integrations/erpnext_integrations.json b/erpnext/erpnext_integrations/workspace/erpnext_integrations/erpnext_integrations.json new file mode 100644 index 00000000000..4a5e54edd2f --- /dev/null +++ b/erpnext/erpnext_integrations/workspace/erpnext_integrations/erpnext_integrations.json @@ -0,0 +1,116 @@ +{ + "category": "Modules", + "charts": [], + "creation": "2020-08-20 19:30:48.138801", + "developer_mode_only": 0, + "disable_user_customization": 0, + "docstatus": 0, + "doctype": "Workspace", + "extends": "Integrations", + "extends_another_page": 1, + "hide_custom": 1, + "idx": 0, + "is_standard": 1, + "label": "ERPNext Integrations", + "links": [ + { + "hidden": 0, + "is_query_report": 0, + "label": "Marketplace", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Woocommerce Settings", + "link_to": "Woocommerce Settings", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Amazon MWS Settings", + "link_to": "Amazon MWS Settings", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Shopify Settings", + "link_to": "Shopify Settings", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Payments", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "GoCardless Settings", + "link_to": "GoCardless Settings", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "M-Pesa Settings", + "link_to": "Mpesa Settings", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Settings", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Plaid Settings", + "link_to": "Plaid Settings", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Exotel Settings", + "link_to": "Exotel Settings", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + } + ], + "modified": "2020-12-01 13:38:35.846528", + "modified_by": "Administrator", + "module": "ERPNext Integrations", + "name": "ERPNext Integrations", + "owner": "Administrator", + "pin_to_bottom": 0, + "pin_to_top": 0, + "shortcuts": [] +} \ No newline at end of file diff --git a/erpnext/erpnext_integrations/workspace/erpnext_integrations_settings/erpnext_integrations_settings.json b/erpnext/erpnext_integrations/workspace/erpnext_integrations_settings/erpnext_integrations_settings.json new file mode 100644 index 00000000000..d258d571318 --- /dev/null +++ b/erpnext/erpnext_integrations/workspace/erpnext_integrations_settings/erpnext_integrations_settings.json @@ -0,0 +1,82 @@ +{ + "category": "Modules", + "charts": [], + "creation": "2020-07-31 10:38:54.021237", + "developer_mode_only": 0, + "disable_user_customization": 0, + "docstatus": 0, + "doctype": "Workspace", + "extends": "Settings", + "extends_another_page": 1, + "hide_custom": 0, + "idx": 0, + "is_standard": 1, + "label": "ERPNext Integrations Settings", + "links": [ + { + "hidden": 0, + "is_query_report": 0, + "label": "Integrations Settings", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Woocommerce Settings", + "link_to": "Woocommerce Settings", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Shopify Settings", + "link_to": "Shopify Settings", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Amazon MWS Settings", + "link_to": "Amazon MWS Settings", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Plaid Settings", + "link_to": "Plaid Settings", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Exotel Settings", + "link_to": "Exotel Settings", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + } + ], + "modified": "2020-12-01 13:38:34.732552", + "modified_by": "Administrator", + "module": "ERPNext Integrations", + "name": "ERPNext Integrations Settings", + "owner": "Administrator", + "pin_to_bottom": 0, + "pin_to_top": 0, + "shortcuts": [] +} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.js b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.js index 1d4411d73de..b55d5d6f633 100644 --- a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.js +++ b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.js @@ -85,7 +85,7 @@ frappe.ui.form.on('Clinical Procedure', { callback: function(r) { if (r.message) { frappe.show_alert({ - message: __('Stock Entry {0} created', ['' + r.message + '']), + message: __('Stock Entry {0} created', ['' + r.message + '']), indicator: 'green' }); } @@ -364,7 +364,7 @@ let calculate_age = function(birth) { let age = new Date(); age.setTime(ageMS); let years = age.getFullYear() - 1970; - return years + ' Year(s) ' + age.getMonth() + ' Month(s) ' + age.getDate() + ' Day(s)'; + return `${years} ${__('Years(s)')} ${age.getMonth()} ${__('Month(s)')} ${age.getDate()} ${__('Day(s)')}`; }; // List Stock items diff --git a/erpnext/healthcare/doctype/exercise_type/exercise_type.js b/erpnext/healthcare/doctype/exercise_type/exercise_type.js index 68db0477c2d..b49b00e219b 100644 --- a/erpnext/healthcare/doctype/exercise_type/exercise_type.js +++ b/erpnext/healthcare/doctype/exercise_type/exercise_type.js @@ -71,7 +71,7 @@ erpnext.ExerciseEditor = Class.extend({ $('.btn-del').on('click', function() { let id = $(this).attr('data-id'); - $('#card-'+id).addClass("zoomOutDelete"); + $('#card-'+id).addClass("zoom-out"); setTimeout(() => { // not using grid_rows[id].remove because diff --git a/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit_tree.js b/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit_tree.js index a03b579c507..b75f2718271 100644 --- a/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit_tree.js +++ b/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit_tree.js @@ -12,20 +12,20 @@ frappe.treeview_settings["Healthcare Service Unit"] = { get_tree_nodes: 'erpnext.healthcare.utils.get_children', ignore_fields:["parent_healthcare_service_unit"], onrender: function(node) { - if (node.data.occupied_out_of_vacant!==undefined){ - $('' + if (node.data.occupied_out_of_vacant!==undefined) { + $('' + " " + node.data.occupied_out_of_vacant + '').insertBefore(node.$ul); } if (node.data && node.data.inpatient_occupancy!==undefined) { - if (node.data.inpatient_occupancy == 1){ - if (node.data.occupancy_status == "Occupied"){ - $('' + if (node.data.inpatient_occupancy == 1) { + if (node.data.occupancy_status == "Occupied") { + $('' + " " + node.data.occupancy_status + '').insertBefore(node.$ul); } - if (node.data.occupancy_status == "Vacant"){ - $('' + if (node.data.occupancy_status == "Vacant") { + $('' + " " + node.data.occupancy_status + '').insertBefore(node.$ul); } diff --git a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py index dc549a65db6..88d7f0b2332 100644 --- a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py +++ b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py @@ -50,7 +50,7 @@ class InpatientRecord(Document): if ip_record: msg = _(("Already {0} Patient {1} with Inpatient Record ").format(ip_record[0].status, self.patient) \ - + """ {0}""".format(ip_record[0].name)) + + """ {0}""".format(ip_record[0].name)) frappe.throw(msg) def admit(self, service_unit, check_in, expected_discharge=None): diff --git a/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py b/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py index ea0d1e982d9..a8c7720a0a4 100644 --- a/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py +++ b/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py @@ -145,7 +145,7 @@ def create_inpatient(patient): def get_healthcare_service_unit(unit_name=None): if not unit_name: - service_unit = get_random("Healthcare Service Unit", filters={"inpatient_occupancy": 1}) + service_unit = get_random("Healthcare Service Unit", filters={"inpatient_occupancy": 1, "company": "_Test Company"}) else: service_unit = frappe.db.exists("Healthcare Service Unit", {"healthcare_service_unit_name": unit_name}) diff --git a/erpnext/healthcare/doctype/lab_test/lab_test.js b/erpnext/healthcare/doctype/lab_test/lab_test.js index f1634c12949..bb7976ccfac 100644 --- a/erpnext/healthcare/doctype/lab_test/lab_test.js +++ b/erpnext/healthcare/doctype/lab_test/lab_test.js @@ -258,5 +258,5 @@ var calculate_age = function (dob) { var age = new Date(); age.setTime(ageMS); var years = age.getFullYear() - 1970; - return years + ' Year(s) ' + age.getMonth() + ' Month(s) ' + age.getDate() + ' Day(s)'; + return `${years} ${__('Years(s)')} ${age.getMonth()} ${__('Month(s)')} ${age.getDate()} ${__('Day(s)')}`; }; diff --git a/erpnext/healthcare/doctype/patient/patient.js b/erpnext/healthcare/doctype/patient/patient.js index 490f2475001..bce42e51d07 100644 --- a/erpnext/healthcare/doctype/patient/patient.js +++ b/erpnext/healthcare/doctype/patient/patient.js @@ -46,11 +46,11 @@ frappe.ui.form.on('Patient', { } }, onload: function (frm) { - if(!frm.doc.dob){ + if (!frm.doc.dob) { $(frm.fields_dict['age_html'].wrapper).html(''); } - if(frm.doc.dob){ - $(frm.fields_dict['age_html'].wrapper).html('AGE : ' + get_age(frm.doc.dob)); + if (frm.doc.dob) { + $(frm.fields_dict['age_html'].wrapper).html(`${__('AGE')} : ${get_age(frm.doc.dob)}`); } } }); @@ -65,7 +65,7 @@ frappe.ui.form.on('Patient', 'dob', function(frm) { } else { let age_str = get_age(frm.doc.dob); - $(frm.fields_dict['age_html'].wrapper).html('AGE : ' + age_str); + $(frm.fields_dict['age_html'].wrapper).html(`${__('AGE')} : ${age_str}`); } } else { diff --git a/erpnext/healthcare/doctype/patient/patient.py b/erpnext/healthcare/doctype/patient/patient.py index 63dd8d4793a..8603f974c39 100644 --- a/erpnext/healthcare/doctype/patient/patient.py +++ b/erpnext/healthcare/doctype/patient/patient.py @@ -108,7 +108,7 @@ class Patient(Document): if self.dob: dob = getdate(self.dob) age = dateutil.relativedelta.relativedelta(getdate(), dob) - age_str = str(age.years) + ' year(s) ' + str(age.months) + ' month(s) ' + str(age.days) + ' day(s)' + age_str = str(age.years) + ' ' + _("Years(s)") + ' ' + str(age.months) + ' ' + _("Month(s)") + ' ' + str(age.days) + ' ' + _("Day(s)") return age_str def invoice_patient_registration(self): diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js index 0354733dfb7..2976ef13a1d 100644 --- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js +++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js @@ -596,5 +596,5 @@ let calculate_age = function(birth) { let age = new Date(); age.setTime(ageMS); let years = age.getFullYear() - 1970; - return years + ' Year(s) ' + age.getMonth() + ' Month(s) ' + age.getDate() + ' Day(s)'; + return `${years} ${__('Years(s)')} ${age.getMonth()} ${__('Month(s)')} ${age.getDate()} ${__('Day(s)')}`; }; diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py index 649f16dbf15..1f76cd624cd 100755 --- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py +++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py @@ -65,7 +65,7 @@ class PatientAppointment(Document): if overlaps: overlapping_details = _('Appointment overlaps with ') - overlapping_details += "{0}
    ".format(overlaps[0][0]) + overlapping_details += "{0}
    ".format(overlaps[0][0]) overlapping_details += _('{0} has appointment scheduled with {1} at {2} having {3} minute(s) duration.').format( overlaps[0][1], overlaps[0][2], overlaps[0][3], overlaps[0][4]) frappe.throw(overlapping_details, title=_('Appointments Overlapping')) @@ -97,7 +97,7 @@ class PatientAppointment(Document): if frappe.db.get_single_value('Healthcare Settings', 'automate_appointment_invoicing'): if not frappe.db.get_value('Patient', self.patient, 'customer'): msg = _("Please set a Customer linked to the Patient") - msg += " {0}".format(self.patient) + msg += " {0}".format(self.patient) frappe.throw(msg, title=_('Customer Not Found')) def update_prescription_details(self): diff --git a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js index e960f0a9c40..aaeaa692e63 100644 --- a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js +++ b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js @@ -358,5 +358,5 @@ let calculate_age = function(birth) { let age = new Date(); age.setTime(ageMS); let years = age.getFullYear() - 1970; - return years + ' Year(s) ' + age.getMonth() + ' Month(s) ' + age.getDate() + ' Day(s)'; + return `${years} ${__('Years(s)')} ${age.getMonth()} ${__('Month(s)')} ${age.getDate()} ${__('Day(s)')}`; }; diff --git a/erpnext/healthcare/doctype/sample_collection/sample_collection.js b/erpnext/healthcare/doctype/sample_collection/sample_collection.js index 03903912358..ddf8285bc6d 100644 --- a/erpnext/healthcare/doctype/sample_collection/sample_collection.js +++ b/erpnext/healthcare/doctype/sample_collection/sample_collection.js @@ -36,5 +36,5 @@ var calculate_age = function(birth) { var age = new Date(); age.setTime(ageMS); var years = age.getFullYear() - 1970; - return years + ' Year(s) ' + age.getMonth() + ' Month(s) ' + age.getDate() + ' Day(s)'; + return `${years} ${__('Years(s)')} ${age.getMonth()} ${__('Month(s)')} ${age.getDate()} ${__('Day(s)')}`; }; diff --git a/erpnext/healthcare/report/inpatient_medication_orders/test_inpatient_medication_orders.py b/erpnext/healthcare/report/inpatient_medication_orders/test_inpatient_medication_orders.py index 0d3f45f5000..4b461f1a97d 100644 --- a/erpnext/healthcare/report/inpatient_medication_orders/test_inpatient_medication_orders.py +++ b/erpnext/healthcare/report/inpatient_medication_orders/test_inpatient_medication_orders.py @@ -119,7 +119,7 @@ def create_records(patient): ip_record.expected_length_of_stay = 0 ip_record.save() ip_record.reload() - service_unit = get_healthcare_service_unit() + service_unit = get_healthcare_service_unit('Test Service Unit Ip Occupancy') admit_patient(ip_record, service_unit, now_datetime()) ipmo = create_ipmo(patient) diff --git a/erpnext/healthcare/utils.py b/erpnext/healthcare/utils.py index 510ac9e475a..d3d22c80b67 100644 --- a/erpnext/healthcare/utils.py +++ b/erpnext/healthcare/utils.py @@ -35,7 +35,7 @@ def get_healthcare_services_to_invoice(patient, company): def validate_customer_created(patient): if not frappe.db.get_value('Patient', patient.name, 'customer'): msg = _("Please set a Customer linked to the Patient") - msg += " {0}".format(patient.name) + msg += " {0}".format(patient.name) frappe.throw(msg, title=_('Customer Not Found')) @@ -182,7 +182,7 @@ def get_clinical_procedures_to_invoice(patient, company): service_item = frappe.db.get_single_value('Healthcare Settings', 'clinical_procedure_consumable_item') if not service_item: msg = _('Please Configure Clinical Procedure Consumable Item in ') - msg += '''Healthcare Settings''' + msg += '''Healthcare Settings''' frappe.throw(msg, title=_('Missing Configuration')) clinical_procedures_to_invoice.append({ @@ -363,7 +363,7 @@ def throw_config_service_item(is_inpatient): service_item_label = _('Inpatient Visit Charge Item') msg = _(('Please Configure {0} in ').format(service_item_label) \ - + '''Healthcare Settings''') + + '''Healthcare Settings''') frappe.throw(msg, title=_('Missing Configuration')) @@ -373,7 +373,7 @@ def throw_config_practitioner_charge(is_inpatient, practitioner): charge_name = _('Inpatient Visit Charge') msg = _(('Please Configure {0} for Healthcare Practitioner').format(charge_name) \ - + ''' {0}'''.format(practitioner)) + + ''' {0}'''.format(practitioner)) frappe.throw(msg, title=_('Missing Configuration')) @@ -714,6 +714,6 @@ def render_doc_as_html(doctype, docname, exclude_fields = []): >
    " \ + section_html + html +'
    ' if doc_html: - doc_html = "
    " %(doctype, docname) + doc_html + '
    ' + doc_html = "
    " %(doctype, docname) + doc_html + '
    ' return {'html': doc_html} diff --git a/erpnext/healthcare/workspace/healthcare/healthcare.json b/erpnext/healthcare/workspace/healthcare/healthcare.json new file mode 100644 index 00000000000..b93dda2e879 --- /dev/null +++ b/erpnext/healthcare/workspace/healthcare/healthcare.json @@ -0,0 +1,536 @@ +{ + "category": "Domains", + "charts": [ + { + "chart_name": "Patient Appointments", + "label": "Patient Appointments" + } + ], + "charts_label": "", + "creation": "2020-03-02 17:23:17.919682", + "developer_mode_only": 0, + "disable_user_customization": 0, + "docstatus": 0, + "doctype": "Workspace", + "extends_another_page": 0, + "hide_custom": 0, + "icon": "healthcare", + "idx": 0, + "is_standard": 1, + "label": "Healthcare", + "links": [ + { + "hidden": 0, + "is_query_report": 0, + "label": "Masters", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Patient", + "link_to": "Patient", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Healthcare Practitioner", + "link_to": "Healthcare Practitioner", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Practitioner Schedule", + "link_to": "Practitioner Schedule", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Medical Department", + "link_to": "Medical Department", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Healthcare Service Unit Type", + "link_to": "Healthcare Service Unit Type", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Healthcare Service Unit", + "link_to": "Healthcare Service Unit", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Medical Code Standard", + "link_to": "Medical Code Standard", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Medical Code", + "link_to": "Medical Code", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Consultation Setup", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Appointment Type", + "link_to": "Appointment Type", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Clinical Procedure Template", + "link_to": "Clinical Procedure Template", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Prescription Dosage", + "link_to": "Prescription Dosage", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Prescription Duration", + "link_to": "Prescription Duration", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Antibiotic", + "link_to": "Antibiotic", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Consultation", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Patient Appointment", + "link_to": "Patient Appointment", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Clinical Procedure", + "link_to": "Clinical Procedure", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Patient Encounter", + "link_to": "Patient Encounter", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Vital Signs", + "link_to": "Vital Signs", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Complaint", + "link_to": "Complaint", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Diagnosis", + "link_to": "Diagnosis", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Fee Validity", + "link_to": "Fee Validity", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Settings", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Healthcare Settings", + "link_to": "Healthcare Settings", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Laboratory Setup", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Lab Test Template", + "link_to": "Lab Test Template", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Lab Test Sample", + "link_to": "Lab Test Sample", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Lab Test UOM", + "link_to": "Lab Test UOM", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Sensitivity", + "link_to": "Sensitivity", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Laboratory", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Lab Test", + "link_to": "Lab Test", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Sample Collection", + "link_to": "Sample Collection", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Dosage Form", + "link_to": "Dosage Form", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Rehabilitation and Physiotherapy", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Exercise Type", + "link_to": "Exercise Type", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Therapy Type", + "link_to": "Therapy Type", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Therapy Plan", + "link_to": "Therapy Plan", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Therapy Session", + "link_to": "Therapy Session", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Patient Assessment Template", + "link_to": "Patient Assessment Template", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Patient Assessment", + "link_to": "Patient Assessment", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Records and History", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Patient History", + "link_to": "patient_history", + "link_type": "Page", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Patient Progress", + "link_to": "patient-progress", + "link_type": "Page", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Patient Medical Record", + "link_to": "Patient Medical Record", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Inpatient Record", + "link_to": "Inpatient Record", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Reports", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 1, + "label": "Patient Appointment Analytics", + "link_to": "Patient Appointment Analytics", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 1, + "label": "Lab Test Report", + "link_to": "Lab Test Report", + "link_type": "Report", + "onboard": 0, + "type": "Link" + } + ], + "modified": "2020-12-01 13:38:34.841396", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Healthcare", + "onboarding": "Healthcare", + "owner": "Administrator", + "pin_to_bottom": 0, + "pin_to_top": 0, + "restrict_to_domain": "Healthcare", + "shortcuts": [ + { + "color": "Orange", + "format": "{} Open", + "label": "Patient Appointment", + "link_to": "Patient Appointment", + "stats_filter": "{\n \"status\": \"Open\",\n \"company\": [\"like\", '%' + frappe.defaults.get_global_default(\"company\") + '%']\n}", + "type": "DocType" + }, + { + "color": "Orange", + "format": "{} Active", + "label": "Patient", + "link_to": "Patient", + "stats_filter": "{\n \"status\": \"Active\"\n}", + "type": "DocType" + }, + { + "color": "Green", + "format": "{} Vacant", + "label": "Healthcare Service Unit", + "link_to": "Healthcare Service Unit", + "stats_filter": "{\n \"occupancy_status\": \"Vacant\",\n \"is_group\": 0,\n \"company\": [\"like\", \"%\" + frappe.defaults.get_global_default(\"company\") + \"%\"]\n}", + "type": "DocType" + }, + { + "label": "Healthcare Practitioner", + "link_to": "Healthcare Practitioner", + "type": "DocType" + }, + { + "label": "Patient History", + "link_to": "patient_history", + "type": "Page" + }, + { + "label": "Dashboard", + "link_to": "Healthcare", + "type": "Dashboard" + } + ] +} \ No newline at end of file diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 1c20555b827..f87769c182c 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -10,7 +10,7 @@ app_color = "#e74c3c" app_email = "info@erpnext.com" app_license = "GNU General Public License (v3)" source_link = "https://github.com/frappe/erpnext" -app_logo_url = '/assets/erpnext/images/erp-icon.svg' +app_logo_url = "/assets/erpnext/images/erpnext-logo.svg" develop_version = '13.x.x-develop' @@ -46,6 +46,7 @@ notification_config = "erpnext.startup.notifications.get_notification_config" get_help_messages = "erpnext.utilities.activation.get_help_messages" leaderboards = "erpnext.startup.leaderboard.get_leaderboards" filters_config = "erpnext.startup.filters.get_filters_config" +additional_print_settings = "erpnext.controllers.print_settings.get_print_settings" on_session_creation = [ "erpnext.portal.utils.create_customer_or_supplier", @@ -77,8 +78,8 @@ website_generators = ["Item Group", "Item", "BOM", "Sales Partner", "Job Opening", "Student Admission"] website_context = { - "favicon": "/assets/erpnext/images/favicon.png", - "splash_image": "/assets/erpnext/images/erp-icon.svg" + "favicon": "/assets/erpnext/images/erpnext-favicon.svg", + "splash_image": "/assets/erpnext/images/erpnext-logo.svg" } website_route_rules = [ @@ -271,17 +272,20 @@ doc_events = { 'Address': { 'validate': ['erpnext.regional.india.utils.validate_gstin_for_india', 'erpnext.regional.italy.utils.set_state_code', 'erpnext.regional.india.utils.update_gst_category'] }, + 'Supplier': { + 'validate': 'erpnext.regional.india.utils.validate_pan_for_india' + }, ('Sales Invoice', 'Sales Order', 'Delivery Note', 'Purchase Invoice', 'Purchase Order', 'Purchase Receipt'): { 'validate': ['erpnext.regional.india.utils.set_place_of_supply'] }, + ('Sales Invoice', 'Purchase Invoice'): { + 'validate': ['erpnext.regional.india.utils.validate_document_name'] + }, "Contact": { "on_trash": "erpnext.support.doctype.issue.issue.update_issue", - "after_insert": "erpnext.telephony.doctype.call_log.call_log.set_caller_information", + "after_insert": "erpnext.telephony.doctype.call_log.call_log.link_existing_conversations", "validate": "erpnext.crm.utils.update_lead_phone_numbers" }, - "Lead": { - "after_insert": "erpnext.telephony.doctype.call_log.call_log.set_caller_information" - }, "Email Unsubscribe": { "after_insert": "erpnext.crm.doctype.email_campaign.email_campaign.unsubscribe_recipient" }, @@ -392,6 +396,15 @@ payment_gateway_enabled = "erpnext.accounts.utils.create_payment_gateway_account communication_doctypes = ["Customer", "Supplier"] +accounting_dimension_doctypes = ["GL Entry", "Sales Invoice", "Purchase Invoice", "Payment Entry", "Asset", + "Expense Claim", "Expense Claim Detail", "Expense Taxes and Charges", "Stock Entry", "Budget", "Payroll Entry", "Delivery Note", + "Sales Invoice Item", "Purchase Invoice Item", "Purchase Order Item", "Journal Entry Account", "Material Request Item", "Delivery Note Item", + "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" +] + regional_overrides = { 'France': { 'erpnext.tests.test_regional.test_method': 'erpnext.regional.france.utils.test_method' @@ -401,6 +414,7 @@ regional_overrides = { 'erpnext.controllers.taxes_and_totals.get_itemised_tax_breakup_header': 'erpnext.regional.india.utils.get_itemised_tax_breakup_header', 'erpnext.controllers.taxes_and_totals.get_itemised_tax_breakup_data': 'erpnext.regional.india.utils.get_itemised_tax_breakup_data', 'erpnext.accounts.party.get_regional_address_details': 'erpnext.regional.india.utils.get_regional_address_details', + 'erpnext.controllers.taxes_and_totals.get_regional_round_off_accounts': 'erpnext.regional.india.utils.get_regional_round_off_accounts', 'erpnext.hr.utils.calculate_annual_eligible_hra_exemption': 'erpnext.regional.india.utils.calculate_annual_eligible_hra_exemption', 'erpnext.hr.utils.calculate_hra_exemption_for_period': 'erpnext.regional.india.utils.calculate_hra_exemption_for_period', 'erpnext.accounts.doctype.purchase_invoice.purchase_invoice.make_regional_gl_entries': 'erpnext.regional.india.utils.make_regional_gl_entries', @@ -584,3 +598,7 @@ global_search_doctypes = { {'doctype': 'Hotel Room Type', 'index': 4} ] } + +additional_timeline_content = { + '*': ['erpnext.telephony.doctype.call_log.call_log.get_linked_call_logs'] +} diff --git a/erpnext/hr/desk_page/hr/hr.json b/erpnext/hr/desk_page/hr/hr.json deleted file mode 100644 index 895cf7290c9..00000000000 --- a/erpnext/hr/desk_page/hr/hr.json +++ /dev/null @@ -1,130 +0,0 @@ -{ - "cards": [ - { - "hidden": 0, - "label": "Employee", - "links": "[\n {\n \"label\": \"Employee\",\n \"name\": \"Employee\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Employment Type\",\n \"name\": \"Employment Type\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Branch\",\n \"name\": \"Branch\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Department\",\n \"name\": \"Department\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Designation\",\n \"name\": \"Designation\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Employee Grade\",\n \"name\": \"Employee Grade\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Employee Group\",\n \"name\": \"Employee Group\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Employee Health Insurance\",\n \"name\": \"Employee Health Insurance\",\n \"type\": \"doctype\"\n }\n]" - }, - { - "hidden": 0, - "label": "Employee Lifecycle", - "links": "[\n {\n \"dependencies\": [\n \"Job Applicant\"\n ],\n \"label\": \"Employee Onboarding\",\n \"name\": \"Employee Onboarding\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Employee Skill Map\",\n \"name\": \"Employee Skill Map\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Employee Promotion\",\n \"name\": \"Employee Promotion\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Employee Transfer\",\n \"name\": \"Employee Transfer\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Employee Separation\",\n \"name\": \"Employee Separation\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Employee Onboarding Template\",\n \"name\": \"Employee Onboarding Template\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Employee Separation Template\",\n \"name\": \"Employee Separation Template\",\n \"type\": \"doctype\"\n }\n]" - }, - { - "hidden": 0, - "label": "Shift Management", - "links": "[\n {\n \"label\": \"Shift Type\",\n \"name\": \"Shift Type\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Shift Request\",\n \"name\": \"Shift Request\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Shift Assignment\",\n \"name\": \"Shift Assignment\",\n \"type\": \"doctype\"\n }\n]" - }, - { - "hidden": 0, - "label": "Leaves", - "links": "[\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Leave Application\",\n \"name\": \"Leave Application\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Leave Allocation\",\n \"name\": \"Leave Allocation\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Leave Type\"\n ],\n \"label\": \"Leave Policy\",\n \"name\": \"Leave Policy\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Leave Period\",\n \"name\": \"Leave Period\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Leave Type\",\n \"name\": \"Leave Type\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Holiday List\",\n \"name\": \"Holiday List\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Compensatory Leave Request\",\n \"name\": \"Compensatory Leave Request\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Leave Encashment\",\n \"name\": \"Leave Encashment\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Leave Block List\",\n \"name\": \"Leave Block List\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Leave Application\"\n ],\n \"doctype\": \"Leave Application\",\n \"is_query_report\": true,\n \"label\": \"Employee Leave Balance\",\n \"name\": \"Employee Leave Balance\",\n \"type\": \"report\"\n }\n]" - }, - { - "hidden": 0, - "label": "Attendance", - "links": "[\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"hide_count\": true,\n \"label\": \"Employee Attendance Tool\",\n \"name\": \"Employee Attendance Tool\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Attendance\",\n \"name\": \"Attendance\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Attendance Request\",\n \"name\": \"Attendance Request\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"hide_count\": true,\n \"label\": \"Upload Attendance\",\n \"name\": \"Upload Attendance\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"hide_count\": true,\n \"label\": \"Employee Checkin\",\n \"name\": \"Employee Checkin\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Attendance\"\n ],\n \"doctype\": \"Attendance\",\n \"is_query_report\": true,\n \"label\": \"Monthly Attendance Sheet\",\n \"name\": \"Monthly Attendance Sheet\",\n \"type\": \"report\"\n }\n]" - }, - { - "hidden": 0, - "label": "Expense Claims", - "links": "[\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Expense Claim\",\n \"name\": \"Expense Claim\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Employee Advance\",\n \"name\": \"Employee Advance\",\n \"type\": \"doctype\"\n }\n]" - }, - { - "hidden": 0, - "label": "Settings", - "links": "[\n {\n \"label\": \"HR Settings\",\n \"name\": \"HR Settings\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Daily Work Summary Group\",\n \"name\": \"Daily Work Summary Group\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Team Updates\",\n \"name\": \"team-updates\",\n \"type\": \"page\"\n }\n]" - }, - { - "hidden": 0, - "label": "Fleet Management", - "links": "[\n {\n \"label\": \"Vehicle\",\n \"name\": \"Vehicle\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Vehicle Log\",\n \"name\": \"Vehicle Log\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Vehicle\"\n ],\n \"doctype\": \"Vehicle\",\n \"is_query_report\": true,\n \"label\": \"Vehicle Expenses\",\n \"name\": \"Vehicle Expenses\",\n \"type\": \"report\"\n }\n]" - }, - { - "hidden": 0, - "label": "Recruitment", - "links": "[\n {\n \"label\": \"Job Opening\",\n \"name\": \"Job Opening\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Job Applicant\",\n \"name\": \"Job Applicant\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Job Offer\",\n \"name\": \"Job Offer\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Staffing Plan\",\n \"name\": \"Staffing Plan\",\n \"type\": \"doctype\"\n }\n]" - }, - { - "hidden": 0, - "label": "Training", - "links": "[\n {\n \"label\": \"Training Program\",\n \"name\": \"Training Program\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Training Event\",\n \"name\": \"Training Event\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Training Result\",\n \"name\": \"Training Result\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Training Feedback\",\n \"name\": \"Training Feedback\",\n \"type\": \"doctype\"\n }\n]" - }, - { - "hidden": 0, - "label": "Reports", - "links": "[\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"doctype\": \"Employee\",\n \"is_query_report\": true,\n \"label\": \"Employee Birthday\",\n \"name\": \"Employee Birthday\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"doctype\": \"Employee\",\n \"is_query_report\": true,\n \"label\": \"Employees working on a holiday\",\n \"name\": \"Employees working on a holiday\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"doctype\": \"Employee\",\n \"is_query_report\": true,\n \"label\": \"Department Analytics\",\n \"name\": \"Department Analytics\",\n \"type\": \"report\"\n }\n]" - }, - { - "hidden": 0, - "label": "Performance", - "links": "[\n {\n \"label\": \"Appraisal\",\n \"name\": \"Appraisal\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Appraisal Template\",\n \"name\": \"Appraisal Template\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Energy Point Rule\",\n \"name\": \"Energy Point Rule\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Energy Point Log\",\n \"name\": \"Energy Point Log\",\n \"type\": \"doctype\"\n }\n]" - } - ], - "category": "Modules", - "charts": [ - { - "chart_name": "Attendance Count", - "label": "Attendance Count" - } - ], - "creation": "2020-03-02 15:48:58.322521", - "developer_mode_only": 0, - "disable_user_customization": 0, - "docstatus": 0, - "doctype": "Desk Page", - "extends_another_page": 0, - "hide_custom": 0, - "idx": 0, - "is_standard": 1, - "label": "HR", - "modified": "2020-08-11 17:04:38.655417", - "modified_by": "Administrator", - "module": "HR", - "name": "HR", - "onboarding": "Human Resource", - "owner": "Administrator", - "pin_to_bottom": 0, - "pin_to_top": 0, - "shortcuts": [ - { - "color": "#cef6d1", - "format": "{} Active", - "label": "Employee", - "link_to": "Employee", - "stats_filter": "{\"status\":\"Active\"}", - "type": "DocType" - }, - { - "color": "#ffe8cd", - "format": "{} Open", - "label": "Leave Application", - "link_to": "Leave Application", - "stats_filter": "{\"status\":\"Open\"}", - "type": "DocType" - }, - { - "label": "Attendance", - "link_to": "Attendance", - "stats_filter": "", - "type": "DocType" - }, - { - "label": "Job Applicant", - "link_to": "Job Applicant", - "type": "DocType" - }, - { - "label": "Monthly Attendance Sheet", - "link_to": "Monthly Attendance Sheet", - "type": "Report" - }, - { - "format": "{} Open", - "label": "Dashboard", - "link_to": "Human Resource", - "stats_filter": "{\n \"status\": \"Open\"\n}", - "type": "Dashboard" - } - ] -} \ No newline at end of file diff --git a/erpnext/hr/doctype/attendance/attendance.py b/erpnext/hr/doctype/attendance/attendance.py index 9577a464964..f3b8a799b3c 100644 --- a/erpnext/hr/doctype/attendance/attendance.py +++ b/erpnext/hr/doctype/attendance/attendance.py @@ -132,6 +132,10 @@ def mark_bulk_attendance(data): data = json.loads(data) data = frappe._dict(data) company = frappe.get_value('Employee', data.employee, 'company') + if not data.unmarked_days: + frappe.throw(_("Please select a date.")) + return + for date in data.unmarked_days: doc_dict = { 'doctype': 'Attendance', diff --git a/erpnext/hr/doctype/attendance/attendance_list.js b/erpnext/hr/doctype/attendance/attendance_list.js index 6df3dbd7845..0c7eafe9c61 100644 --- a/erpnext/hr/doctype/attendance/attendance_list.js +++ b/erpnext/hr/doctype/attendance/attendance_list.js @@ -12,7 +12,7 @@ frappe.listview_settings['Attendance'] = { onload: function(list_view) { let me = this; const months = moment.months() - list_view.page.add_inner_button( __("Mark Attendance"), function(){ + list_view.page.add_inner_button( __("Mark Attendance"), function() { let dialog = new frappe.ui.Dialog({ title: __("Mark Attendance"), fields: [ @@ -22,11 +22,12 @@ frappe.listview_settings['Attendance'] = { fieldtype: 'Link', options: 'Employee', reqd: 1, - onchange: function(){ + onchange: function() { dialog.set_df_property("unmarked_days", "hidden", 1); dialog.set_df_property("status", "hidden", 1); dialog.set_df_property("month", "value", ''); dialog.set_df_property("unmarked_days", "options", []); + dialog.no_unmarked_days_left = false; } }, { @@ -35,13 +36,18 @@ frappe.listview_settings['Attendance'] = { fieldname: "month", options: months, reqd: 1, - onchange: function(){ + onchange: function() { if(dialog.fields_dict.employee.value && dialog.fields_dict.month.value) { dialog.set_df_property("status", "hidden", 0); dialog.set_df_property("unmarked_days", "options", []); + dialog.no_unmarked_days_left = false; me.get_multi_select_options(dialog.fields_dict.employee.value, dialog.fields_dict.month.value).then(options =>{ - dialog.set_df_property("unmarked_days", "hidden", 0); - dialog.set_df_property("unmarked_days", "options", options); + if (options.length > 0) { + dialog.set_df_property("unmarked_days", "hidden", 0); + dialog.set_df_property("unmarked_days", "options", options); + } else { + dialog.no_unmarked_days_left = true; + } }); } } @@ -64,21 +70,25 @@ frappe.listview_settings['Attendance'] = { hidden: 1 }, ], - primary_action(data){ - frappe.confirm(__('Mark attendance as ' + data.status + ' for ' + data.month +'' + ' on selected dates?'), () => { - frappe.call({ - method: "erpnext.hr.doctype.attendance.attendance.mark_bulk_attendance", - args: { - data : data - }, - callback: function(r) { - if(r.message === 1) { - frappe.show_alert({message:__("Attendance Marked"), indicator:'blue'}); - cur_dialog.hide(); + primary_action(data) { + if (cur_dialog.no_unmarked_days_left) { + frappe.msgprint(__("Attendance for the month of {0} , has already been marked for the Employee {1}",[dialog.fields_dict.month.value, dialog.fields_dict.employee.value])); + } else { + frappe.confirm(__('Mark attendance as {0} for {1} on selected dates?', [data.status,data.month]), () => { + frappe.call({ + method: "erpnext.hr.doctype.attendance.attendance.mark_bulk_attendance", + args: { + data: data + }, + callback: function(r) { + if (r.message === 1) { + frappe.show_alert({message: __("Attendance Marked"), indicator: 'blue'}); + cur_dialog.hide(); + } } - } + }); }); - }); + } dialog.hide(); list_view.refresh(); }, diff --git a/erpnext/hr/doctype/employee/employee.json b/erpnext/hr/doctype/employee/employee.json index dc2aaa4a067..5123d6a5a78 100644 --- a/erpnext/hr/doctype/employee/employee.json +++ b/erpnext/hr/doctype/employee/employee.json @@ -813,7 +813,7 @@ "idx": 24, "image_field": "image", "links": [], - "modified": "2021-01-01 16:54:33.477439", + "modified": "2021-01-02 16:54:33.477439", "modified_by": "Administrator", "module": "HR", "name": "Employee", diff --git a/erpnext/hr/doctype/employee/employee.py b/erpnext/hr/doctype/employee/employee.py index 0fde3a12ac8..d0e7d0537b2 100755 --- a/erpnext/hr/doctype/employee/employee.py +++ b/erpnext/hr/doctype/employee/employee.py @@ -278,63 +278,89 @@ def send_birthday_reminders(): if int(frappe.db.get_single_value("HR Settings", "stop_birthday_reminders") or 0): return - birthdays = get_employees_who_are_born_today() + employees_born_today = get_employees_who_are_born_today() - if birthdays: - employee_list = frappe.get_all('Employee', - fields=['name','employee_name'], - filters={'status': 'Active', - 'company': birthdays[0]['company'] - } - ) - employee_emails = get_employee_emails(employee_list) - birthday_names = [name["employee_name"] for name in birthdays] - birthday_emails = [email["user_id"] or email["personal_email"] or email["company_email"] for email in birthdays] + for company, birthday_persons in employees_born_today.items(): + employee_emails = get_all_employee_emails(company) + birthday_person_emails = [get_employee_email(doc) for doc in birthday_persons] + recipients = list(set(employee_emails) - set(birthday_person_emails)) - birthdays.append({'company_email': '','employee_name': '','personal_email': '','user_id': ''}) + reminder_text, message = get_birthday_reminder_text_and_message(birthday_persons) + send_birthday_reminder(recipients, reminder_text, birthday_persons, message) - for e in birthdays: - if e['company_email'] or e['personal_email'] or e['user_id']: - if len(birthday_names) == 1: - continue - recipients = e['company_email'] or e['personal_email'] or e['user_id'] + if len(birthday_persons) > 1: + # special email for people sharing birthdays + for person in birthday_persons: + person_email = person["user_id"] or person["personal_email"] or person["company_email"] + others = [d for d in birthday_persons if d != person] + reminder_text, message = get_birthday_reminder_text_and_message(others) + send_birthday_reminder(person_email, reminder_text, others, message) +def get_employee_email(employee_doc): + return employee_doc["user_id"] or employee_doc["personal_email"] or employee_doc["company_email"] - else: - recipients = list(set(employee_emails) - set(birthday_emails)) - - frappe.sendmail(recipients=recipients, - subject=_("Birthday Reminder"), - message=get_birthday_reminder_message(e, birthday_names), - header=['Birthday Reminder', 'green'], - ) - -def get_birthday_reminder_message(employee, employee_names): - """Get employee birthday reminder message""" - pattern = "
  • " - message = pattern.join(filter(lambda u: u not in (employee['employee_name']), employee_names)) - message = message.title() - - if pattern not in message: - message = "Today is {0}'s birthday \U0001F603".format(message) - +def get_birthday_reminder_text_and_message(birthday_persons): + if len(birthday_persons) == 1: + birthday_person_text = birthday_persons[0]['name'] else: - message = "Today your colleagues are celebrating their birthdays \U0001F382
    • " + message +"
    " + # converts ["Jim", "Rim", "Dim"] to Jim, Rim & Dim + person_names = [d['name'] for d in birthday_persons] + last_person = person_names[-1] + birthday_person_text = ", ".join(person_names[:-1]) + birthday_person_text = _("{} & {}").format(birthday_person_text, last_person) - return message + reminder_text = _("Today is {0}'s birthday 🎉").format(birthday_person_text) + message = _("A friendly reminder of an important date for our team.") + message += "
    " + message += _("Everyone, let’s congratulate {0} on their birthday.").format(birthday_person_text) + return reminder_text, message -def get_employees_who_are_born_today(): - """Get Employee properties whose birthday is today.""" - return frappe.db.get_values("Employee", - fieldname=["name", "personal_email", "company", "company_email", "user_id", "employee_name"], - filters={ - "date_of_birth": ("like", "%{}".format(format_datetime(getdate(), "-MM-dd"))), - "status": "Active", - }, - as_dict=True +def send_birthday_reminder(recipients, reminder_text, birthday_persons, message): + frappe.sendmail( + recipients=recipients, + subject=_("Birthday Reminder"), + template="birthday_reminder", + args=dict( + reminder_text=reminder_text, + birthday_persons=birthday_persons, + message=message, + ), + header=_("Birthday Reminder 🎂") ) +def get_employees_who_are_born_today(): + """Get all employee born today & group them based on their company""" + from collections import defaultdict + employees_born_today = frappe.db.multisql({ + "mariadb": """ + SELECT `personal_email`, `company`, `company_email`, `user_id`, `employee_name` AS 'name', `image` + FROM `tabEmployee` + WHERE + DAY(date_of_birth) = DAY(%(today)s) + AND + MONTH(date_of_birth) = MONTH(%(today)s) + AND + `status` = 'Active' + """, + "postgres": """ + SELECT "personal_email", "company", "company_email", "user_id", "employee_name" AS 'name', "image" + FROM "tabEmployee" + WHERE + DATE_PART('day', "date_of_birth") = date_part('day', %(today)s) + AND + DATE_PART('month', "date_of_birth") = date_part('month', %(today)s) + AND + "status" = 'Active' + """, + }, dict(today=today()), as_dict=1) + + grouped_employees = defaultdict(lambda: []) + + for employee_doc in employees_born_today: + grouped_employees[employee_doc.get('company')].append(employee_doc) + + return grouped_employees def get_holiday_list_for_employee(employee, raise_exception=True): if employee: @@ -404,6 +430,26 @@ def create_user(employee, user = None, email=None): user.insert() return user.name +def get_all_employee_emails(company): + '''Returns list of employee emails either based on user_id or company_email''' + employee_list = frappe.get_all('Employee', + fields=['name','employee_name'], + filters={ + 'status': 'Active', + 'company': company + } + ) + employee_emails = [] + for employee in employee_list: + if not employee: + continue + user, company_email, personal_email = frappe.db.get_value('Employee', + employee, ['user_id', 'company_email', 'personal_email']) + email = user or company_email or personal_email + if email: + employee_emails.append(email) + return employee_emails + def get_employee_emails(employee_list): '''Returns list of employee emails either based on user_id or company_email''' employee_emails = [] diff --git a/erpnext/hr/doctype/employee/employee_list.js b/erpnext/hr/doctype/employee/employee_list.js index 7a66d12bf5e..44837030be8 100644 --- a/erpnext/hr/doctype/employee/employee_list.js +++ b/erpnext/hr/doctype/employee/employee_list.js @@ -3,7 +3,7 @@ frappe.listview_settings['Employee'] = { filters: [["status","=", "Active"]], get_indicator: function(doc) { var indicator = [__(doc.status), frappe.utils.guess_colour(doc.status), "status,=," + doc.status]; - indicator[1] = {"Active": "green", "Temporary Leave": "red", "Left": "darkgrey"}[doc.status]; + indicator[1] = {"Active": "green", "Temporary Leave": "red", "Left": "gray"}[doc.status]; return indicator; } }; diff --git a/erpnext/hr/doctype/employee/test_employee.py b/erpnext/hr/doctype/employee/test_employee.py index b88daaa34f9..7d652a7366a 100644 --- a/erpnext/hr/doctype/employee/test_employee.py +++ b/erpnext/hr/doctype/employee/test_employee.py @@ -21,7 +21,8 @@ class TestEmployee(unittest.TestCase): from erpnext.hr.doctype.employee.employee import get_employees_who_are_born_today, send_birthday_reminders - self.assertTrue(employee.name in [e.name for e in get_employees_who_are_born_today()]) + employees_born_today = get_employees_who_are_born_today() + self.assertTrue(employees_born_today.get("_Test Company")) frappe.db.sql("delete from `tabEmail Queue`") @@ -47,6 +48,7 @@ class TestEmployee(unittest.TestCase): self.assertRaises(EmployeeLeftValidationError, employee1_doc.save) def make_employee(user, company=None, **kwargs): + "" if not frappe.db.get_value("User", user): frappe.get_doc({ "doctype": "User", diff --git a/erpnext/hr/doctype/employee_transfer/employee_transfer.py b/erpnext/hr/doctype/employee_transfer/employee_transfer.py index c730e022a50..3539970a32a 100644 --- a/erpnext/hr/doctype/employee_transfer/employee_transfer.py +++ b/erpnext/hr/doctype/employee_transfer/employee_transfer.py @@ -50,8 +50,9 @@ class EmployeeTransfer(Document): employee = frappe.get_doc("Employee", self.employee) if self.create_new_employee_id: if self.new_employee_id: - frappe.throw(_("Please delete the Employee {0}\ - to cancel this document").format(self.new_employee_id)) + frappe.throw(_("Please delete the Employee {0} to cancel this document").format( + "{0}".format(self.new_employee_id) + )) #mark the employee as active employee.status = "Active" employee.relieving_date = '' diff --git a/erpnext/hr/doctype/hr_settings/hr_settings.json b/erpnext/hr/doctype/hr_settings/hr_settings.json index f99963504ab..d8aae667960 100644 --- a/erpnext/hr/doctype/hr_settings/hr_settings.json +++ b/erpnext/hr/doctype/hr_settings/hr_settings.json @@ -138,7 +138,7 @@ "idx": 1, "issingle": 1, "links": [], - "modified": "2020-08-27 14:30:28.995324", + "modified": "2021-02-25 12:31:14.947865", "modified_by": "Administrator", "module": "HR", "name": "HR Settings", @@ -155,5 +155,6 @@ } ], "sort_field": "modified", - "sort_order": "ASC" -} + "sort_order": "ASC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/hr/doctype/job_offer/job_offer.py b/erpnext/hr/doctype/job_offer/job_offer.py index c397a3f5cad..7e650f76917 100644 --- a/erpnext/hr/doctype/job_offer/job_offer.py +++ b/erpnext/hr/doctype/job_offer/job_offer.py @@ -16,7 +16,7 @@ class JobOffer(Document): def validate(self): self.validate_vacancies() - job_offer = frappe.db.exists("Job Offer",{"job_applicant": self.job_applicant}) + job_offer = frappe.db.exists("Job Offer",{"job_applicant": self.job_applicant, "docstatus": ["!=", 2]}) if job_offer and job_offer != self.name: frappe.throw(_("Job Offer: {0} is already for Job Applicant: {1}").format(frappe.bold(job_offer), frappe.bold(self.job_applicant))) diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.py b/erpnext/hr/doctype/leave_allocation/leave_allocation.py index a4f4f1ace95..69d605d0633 100755 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation.py +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.py @@ -86,7 +86,7 @@ class LeaveAllocation(Document): frappe.msgprint(_("{0} already allocated for Employee {1} for period {2} to {3}") .format(self.leave_type, self.employee, formatdate(self.from_date), formatdate(self.to_date))) - frappe.throw(_('Reference') + ': {0}' + frappe.throw(_('Reference') + ': {0}' .format(leave_allocation[0][0]), OverlapError) def validate_back_dated_allocation(self): diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation_list.js b/erpnext/hr/doctype/leave_allocation/leave_allocation_list.js index 93f7b8356b3..3ab176f8099 100644 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation_list.js +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation_list.js @@ -5,7 +5,7 @@ frappe.listview_settings['Leave Allocation'] = { get_indicator: function(doc) { if(doc.status==="Expired") { - return [__("Expired"), "darkgrey", "expired, =, 1"]; + return [__("Expired"), "gray", "expired, =, 1"]; } }, }; diff --git a/erpnext/hr/doctype/leave_application/leave_application.js b/erpnext/hr/doctype/leave_application/leave_application.js index d62e418b17e..9ccb915908f 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.js +++ b/erpnext/hr/doctype/leave_application/leave_application.js @@ -75,7 +75,8 @@ frappe.ui.form.on("Leave Application", { frm.dashboard.add_section( frappe.render_template('leave_application_dashboard', { data: leave_details - }) + }), + __("Allocated Leaves") ); frm.dashboard.show(); let allowed_leave_types = Object.keys(leave_details); diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py index 4f3e4623909..132c3bd3b92 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.py +++ b/erpnext/hr/doctype/leave_application/leave_application.py @@ -245,7 +245,7 @@ class LeaveApplication(Document): def throw_overlap_error(self, d): msg = _("Employee {0} has already applied for {1} between {2} and {3} : ").format(self.employee, d['leave_type'], formatdate(d['from_date']), formatdate(d['to_date'])) \ - + """ {0}""".format(d["name"]) + + """ {0}""".format(d["name"]) frappe.throw(msg, OverlapError) def get_total_leaves_on_half_day(self): diff --git a/erpnext/hr/doctype/leave_application/leave_application_dashboard.html b/erpnext/hr/doctype/leave_application/leave_application_dashboard.html index d30e3b9f9c6..9f667a68356 100644 --- a/erpnext/hr/doctype/leave_application/leave_application_dashboard.html +++ b/erpnext/hr/doctype/leave_application/leave_application_dashboard.html @@ -1,15 +1,14 @@ {% if not jQuery.isEmptyObject(data) %} -
    {{ __("Allocated Leaves") }}
    - - - - - + + + + + @@ -26,5 +25,5 @@
    {{ __("Leave Type") }}{{ __("Total Allocated Leaves") }}{{ __("Expired Leaves") }}{{ __("Used Leaves") }}{{ __("Pending Leaves") }}{{ __("Available Leaves") }}{{ __("Total Allocated Leave") }}{{ __("Expired Leave") }}{{ __("Used Leave") }}{{ __("Pending Leave") }}{{ __("Available Leave") }}
    {% else %} -

    No Leaves have been allocated.

    -{% endif %} \ No newline at end of file +

    No Leave has been allocated.

    +{% endif %} diff --git a/erpnext/hr/doctype/leave_application/leave_application_list.js b/erpnext/hr/doctype/leave_application/leave_application_list.js index cbb4b73227e..a3c03b1bec7 100644 --- a/erpnext/hr/doctype/leave_application/leave_application_list.js +++ b/erpnext/hr/doctype/leave_application/leave_application_list.js @@ -1,5 +1,6 @@ frappe.listview_settings['Leave Application'] = { add_fields: ["leave_type", "employee", "employee_name", "total_leave_days", "from_date", "to_date"], + has_indicator_for_draft: 1, get_indicator: function (doc) { if (doc.status === "Approved") { return [__("Approved"), "green", "status,=,Approved"]; diff --git a/erpnext/hr/doctype/shift_request/shift_request.py b/erpnext/hr/doctype/shift_request/shift_request.py index 1c2801bf08f..473193d5ac4 100644 --- a/erpnext/hr/doctype/shift_request/shift_request.py +++ b/erpnext/hr/doctype/shift_request/shift_request.py @@ -87,5 +87,5 @@ class ShiftRequest(Document): def throw_overlap_error(self, d): msg = _("Employee {0} has already applied for {1} between {2} and {3} : ").format(self.employee, d['shift_type'], formatdate(d['from_date']), formatdate(d['to_date'])) \ - + """ {0}""".format(d["name"]) + + """ {0}""".format(d["name"]) frappe.throw(msg, OverlapError) \ No newline at end of file diff --git a/erpnext/hr/doctype/skill/skill.json b/erpnext/hr/doctype/skill/skill.json index 518297395bd..4c8a8c92c10 100644 --- a/erpnext/hr/doctype/skill/skill.json +++ b/erpnext/hr/doctype/skill/skill.json @@ -3,7 +3,7 @@ "allow_events_in_timeline": 0, "allow_guest_to_view": 0, "allow_import": 0, - "allow_rename": 0, + "allow_rename": 1, "autoname": "field:skill_name", "beta": 0, "creation": "2019-04-16 09:54:39.486915", @@ -16,7 +16,7 @@ "fields": [ { "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, + "allow_in_quick_entry": 1, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -46,6 +46,12 @@ "set_only_once": 0, "translatable": 0, "unique": 1 + }, + { + "allow_in_quick_entry": 1, + "fieldname": "description", + "fieldtype": "Text", + "label": "Description" } ], "has_web_view": 0, @@ -56,7 +62,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2019-04-16 09:55:00.536328", + "modified": "2021-02-26 10:55:00.536328", "modified_by": "Administrator", "module": "HR", "name": "Skill", @@ -110,4 +116,4 @@ "track_changes": 1, "track_seen": 0, "track_views": 0 -} \ No newline at end of file +} diff --git a/erpnext/hr/page/team_updates/team_updates.js b/erpnext/hr/page/team_updates/team_updates.js index da1f5316a0f..358329748e6 100644 --- a/erpnext/hr/page/team_updates/team_updates.js +++ b/erpnext/hr/page/team_updates/team_updates.js @@ -36,12 +36,12 @@ frappe.team_updates = { start: me.start }, callback: function(r) { - if(r.message) { + if (r.message && r.message.length > 0) { r.message.forEach(function(d) { me.add_row(d); }); } else { - frappe.show_alert({message:__('No more updates'), indicator:'darkgrey'}); + frappe.show_alert({message: __('No more updates'), indicator: 'gray'}); me.more.parent().addClass('hidden'); } } @@ -75,6 +75,6 @@ frappe.team_updates = { } me.last_feed_date = date; - $(frappe.render_template('team_update_row', data)).appendTo(me.body) + $(frappe.render_template('team_update_row', data)).appendTo(me.body); } -} \ No newline at end of file +} diff --git a/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py b/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py index 1b923581841..06f9160363c 100644 --- a/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py +++ b/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py @@ -40,17 +40,17 @@ def get_columns(): 'fieldname': 'opening_balance', 'width': 130, }, { - 'label': _('Leaves Allocated'), + 'label': _('Leave Allocated'), 'fieldtype': 'float', 'fieldname': 'leaves_allocated', 'width': 130, }, { - 'label': _('Leaves Taken'), + 'label': _('Leave Taken'), 'fieldtype': 'float', 'fieldname': 'leaves_taken', 'width': 130, }, { - 'label': _('Leaves Expired'), + 'label': _('Leave Expired'), 'fieldtype': 'float', 'fieldname': 'leaves_expired', 'width': 130, diff --git a/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py b/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py index 46082129e24..c5929c6bf99 100644 --- a/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py +++ b/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py @@ -36,6 +36,8 @@ def execute(filters=None): conditions, filters = get_conditions(filters) columns, days = get_columns(filters) att_map = get_attendance_list(conditions, filters) + if not att_map: + return columns, [], None, None if filters.group_by: emp_map, group_by_parameters = get_employee_details(filters.group_by, filters.company) @@ -65,10 +67,14 @@ def execute(filters=None): if filters.group_by: emp_att_map = {} for parameter in group_by_parameters: - data.append([ ""+ parameter + ""]) - record, aaa = add_data(emp_map[parameter], att_map, filters, holiday_map, conditions, default_holiday_list, leave_list=leave_list) - emp_att_map.update(aaa) - data += record + emp_map_set = set([key for key in emp_map[parameter].keys()]) + att_map_set = set([key for key in att_map.keys()]) + if (att_map_set & emp_map_set): + parameter_row = [""+ parameter + ""] + ['' for day in range(filters["total_days_in_month"] + 2)] + data.append(parameter_row) + record, emp_att_data = add_data(emp_map[parameter], att_map, filters, holiday_map, conditions, default_holiday_list, leave_list=leave_list) + emp_att_map.update(emp_att_data) + data += record else: record, emp_att_map = add_data(emp_map, att_map, filters, holiday_map, conditions, default_holiday_list, leave_list=leave_list) data += record @@ -237,6 +243,9 @@ def get_attendance_list(conditions, filters): status from tabAttendance where docstatus = 1 %s order by employee, attendance_date""" % conditions, filters, as_dict=1) + if not attendance_list: + msgprint(_("No attendance record found"), alert=True, indicator="orange") + att_map = {} for d in attendance_list: att_map.setdefault(d.employee, frappe._dict()).setdefault(d.day_of_month, "") diff --git a/erpnext/hr/utils.py b/erpnext/hr/utils.py index 3f8d61d4146..0c4c1cafb07 100644 --- a/erpnext/hr/utils.py +++ b/erpnext/hr/utils.py @@ -1,16 +1,19 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals -import frappe, erpnext -from frappe import _ -from frappe.utils import formatdate, format_datetime, getdate, get_datetime, nowdate, flt, cstr, add_days, today -from frappe.model.document import Document -from frappe.desk.form import assign_to +import erpnext +import frappe from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee +from frappe import _ +from frappe.desk.form import assign_to +from frappe.model.document import Document +from frappe.utils import (add_days, cstr, flt, format_datetime, formatdate, + get_datetime, getdate, nowdate, today, unique) + class DuplicateDeclarationError(frappe.ValidationError): pass + class EmployeeBoardingController(Document): ''' Create the project and the task for the boarding process @@ -48,27 +51,38 @@ class EmployeeBoardingController(Document): continue task = frappe.get_doc({ - "doctype": "Task", - "project": self.project, - "subject": activity.activity_name + " : " + self.employee_name, - "description": activity.description, - "department": self.department, - "company": self.company, - "task_weight": activity.task_weight - }).insert(ignore_permissions=True) + "doctype": "Task", + "project": self.project, + "subject": activity.activity_name + " : " + self.employee_name, + "description": activity.description, + "department": self.department, + "company": self.company, + "task_weight": activity.task_weight + }).insert(ignore_permissions=True) activity.db_set("task", task.name) + users = [activity.user] if activity.user else [] if activity.role: - user_list = frappe.db.sql_list('''select distinct(parent) from `tabHas Role` - where parenttype='User' and role=%s''', activity.role) - users = users + user_list + user_list = frappe.db.sql_list(''' + SELECT + DISTINCT(has_role.parent) + FROM + `tabHas Role` has_role + LEFT JOIN `tabUser` user + ON has_role.parent = user.name + WHERE + has_role.parenttype = 'User' + AND user.enabled = 1 + AND has_role.role = %s + ''', activity.role) + users = unique(users + user_list) if "Administrator" in users: users.remove("Administrator") # assign the task the users if users: - self.assign_task_to_users(task, set(users)) + self.assign_task_to_users(task, users) def assign_task_to_users(self, task, users): for user in users: @@ -211,7 +225,7 @@ def get_doc_condition(doctype): def throw_overlap_error(doc, exists_for, overlap_doc, from_date, to_date): msg = _("A {0} exists between {1} and {2} (").format(doc.doctype, formatdate(from_date), formatdate(to_date)) \ - + """ {1}""".format(doc.doctype, overlap_doc) \ + + """ {1}""".format(doc.doctype, overlap_doc) \ + _(") for {0}").format(exists_for) frappe.throw(msg) diff --git a/erpnext/hr/workspace/hr/hr.json b/erpnext/hr/workspace/hr/hr.json new file mode 100644 index 00000000000..f650b24d861 --- /dev/null +++ b/erpnext/hr/workspace/hr/hr.json @@ -0,0 +1,829 @@ +{ + "category": "Modules", + "charts": [ + { + "chart_name": "Outgoing Salary", + "label": "Outgoing Salary" + } + ], + "creation": "2020-03-02 15:48:58.322521", + "developer_mode_only": 0, + "disable_user_customization": 0, + "docstatus": 0, + "doctype": "Workspace", + "extends_another_page": 0, + "hide_custom": 0, + "icon": "hr", + "idx": 0, + "is_standard": 1, + "label": "HR", + "links": [ + { + "hidden": 0, + "is_query_report": 0, + "label": "Employee", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Employee", + "link_to": "Employee", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Employment Type", + "link_to": "Employment Type", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Branch", + "link_to": "Branch", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Department", + "link_to": "Department", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Designation", + "link_to": "Designation", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Employee Grade", + "link_to": "Employee Grade", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Employee", + "hidden": 0, + "is_query_report": 0, + "label": "Employee Group", + "link_to": "Employee Group", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Employee Health Insurance", + "link_to": "Employee Health Insurance", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Employee Lifecycle", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "Job Applicant", + "hidden": 0, + "is_query_report": 0, + "label": "Employee Onboarding", + "link_to": "Employee Onboarding", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Employee", + "hidden": 0, + "is_query_report": 0, + "label": "Employee Skill Map", + "link_to": "Employee Skill Map", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Employee", + "hidden": 0, + "is_query_report": 0, + "label": "Employee Promotion", + "link_to": "Employee Promotion", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Employee", + "hidden": 0, + "is_query_report": 0, + "label": "Employee Transfer", + "link_to": "Employee Transfer", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Employee", + "hidden": 0, + "is_query_report": 0, + "label": "Employee Separation", + "link_to": "Employee Separation", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Employee", + "hidden": 0, + "is_query_report": 0, + "label": "Employee Onboarding Template", + "link_to": "Employee Onboarding Template", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Employee", + "hidden": 0, + "is_query_report": 0, + "label": "Employee Separation Template", + "link_to": "Employee Separation Template", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Shift Management", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Shift Type", + "link_to": "Shift Type", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Shift Request", + "link_to": "Shift Request", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Shift Assignment", + "link_to": "Shift Assignment", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Leaves", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "Employee", + "hidden": 0, + "is_query_report": 0, + "label": "Leave Application", + "link_to": "Leave Application", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Employee", + "hidden": 0, + "is_query_report": 0, + "label": "Leave Allocation", + "link_to": "Leave Allocation", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Leave Type", + "hidden": 0, + "is_query_report": 0, + "label": "Leave Policy", + "link_to": "Leave Policy", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Leave Period", + "link_to": "Leave Period", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Leave Type", + "link_to": "Leave Type", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Holiday List", + "link_to": "Holiday List", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Employee", + "hidden": 0, + "is_query_report": 0, + "label": "Compensatory Leave Request", + "link_to": "Compensatory Leave Request", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Employee", + "hidden": 0, + "is_query_report": 0, + "label": "Leave Encashment", + "link_to": "Leave Encashment", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Leave Block List", + "link_to": "Leave Block List", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Leave Application", + "hidden": 0, + "is_query_report": 1, + "label": "Employee Leave Balance", + "link_to": "Employee Leave Balance", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Attendance", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "Employee", + "hidden": 0, + "is_query_report": 0, + "label": "Employee Attendance Tool", + "link_to": "Employee Attendance Tool", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "Employee", + "hidden": 0, + "is_query_report": 0, + "label": "Attendance", + "link_to": "Attendance", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "Employee", + "hidden": 0, + "is_query_report": 0, + "label": "Attendance Request", + "link_to": "Attendance Request", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Employee", + "hidden": 0, + "is_query_report": 0, + "label": "Upload Attendance", + "link_to": "Upload Attendance", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Employee", + "hidden": 0, + "is_query_report": 0, + "label": "Employee Checkin", + "link_to": "Employee Checkin", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Attendance", + "hidden": 0, + "is_query_report": 1, + "label": "Monthly Attendance Sheet", + "link_to": "Monthly Attendance Sheet", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Expense Claims", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "Employee", + "hidden": 0, + "is_query_report": 0, + "label": "Expense Claim", + "link_to": "Expense Claim", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Employee", + "hidden": 0, + "is_query_report": 0, + "label": "Employee Advance", + "link_to": "Employee Advance", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Settings", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "HR Settings", + "link_to": "HR Settings", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Daily Work Summary Group", + "link_to": "Daily Work Summary Group", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Team Updates", + "link_to": "team-updates", + "link_type": "Page", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Fleet Management", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Vehicle", + "link_to": "Vehicle", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Vehicle Log", + "link_to": "Vehicle Log", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Vehicle", + "hidden": 0, + "is_query_report": 1, + "label": "Vehicle Expenses", + "link_to": "Vehicle Expenses", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Recruitment", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Job Opening", + "link_to": "Job Opening", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Job Applicant", + "link_to": "Job Applicant", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Job Offer", + "link_to": "Job Offer", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Staffing Plan", + "link_to": "Staffing Plan", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Loans", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "Employee", + "hidden": 0, + "is_query_report": 0, + "label": "Loan Application", + "link_to": "Loan Application", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Loan", + "link_to": "Loan", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Loan Type", + "link_to": "Loan Type", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Training", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Training Program", + "link_to": "Training Program", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Training Event", + "link_to": "Training Event", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Training Result", + "link_to": "Training Result", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Training Feedback", + "link_to": "Training Feedback", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Reports", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "Employee", + "hidden": 0, + "is_query_report": 1, + "label": "Employee Birthday", + "link_to": "Employee Birthday", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Employee", + "hidden": 0, + "is_query_report": 1, + "label": "Employees working on a holiday", + "link_to": "Employees working on a holiday", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Performance", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Appraisal", + "link_to": "Appraisal", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Appraisal Template", + "link_to": "Appraisal Template", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Energy Point Rule", + "link_to": "Energy Point Rule", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Energy Point Log", + "link_to": "Energy Point Log", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Employee Tax and Benefits", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "Employee", + "hidden": 0, + "is_query_report": 0, + "label": "Employee Tax Exemption Declaration", + "link_to": "Employee Tax Exemption Declaration", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Employee", + "hidden": 0, + "is_query_report": 0, + "label": "Employee Tax Exemption Proof Submission", + "link_to": "Employee Tax Exemption Proof Submission", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Employee, Payroll Period", + "hidden": 0, + "is_query_report": 0, + "label": "Employee Other Income", + "link_to": "Employee Other Income", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Employee", + "hidden": 0, + "is_query_report": 0, + "label": "Employee Benefit Application", + "link_to": "Employee Benefit Application", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Employee", + "hidden": 0, + "is_query_report": 0, + "label": "Employee Benefit Claim", + "link_to": "Employee Benefit Claim", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Employee", + "hidden": 0, + "is_query_report": 0, + "label": "Employee Tax Exemption Category", + "link_to": "Employee Tax Exemption Category", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Employee", + "hidden": 0, + "is_query_report": 0, + "label": "Employee Tax Exemption Sub Category", + "link_to": "Employee Tax Exemption Sub Category", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + } + ], + "modified": "2021-01-21 13:38:38.941001", + "modified_by": "Administrator", + "module": "HR", + "name": "HR", + "onboarding": "Human Resource", + "owner": "Administrator", + "pin_to_bottom": 0, + "pin_to_top": 0, + "shortcuts": [ + { + "color": "Green", + "format": "{} Active", + "label": "Employee", + "link_to": "Employee", + "stats_filter": "{\"status\":\"Active\"}", + "type": "DocType" + }, + { + "color": "Yellow", + "format": "{} Open", + "label": "Leave Application", + "link_to": "Leave Application", + "stats_filter": "{\"status\":\"Open\"}", + "type": "DocType" + }, + { + "label": "Attendance", + "link_to": "Attendance", + "stats_filter": "", + "type": "DocType" + }, + { + "label": "Job Applicant", + "link_to": "Job Applicant", + "type": "DocType" + }, + { + "label": "Monthly Attendance Sheet", + "link_to": "Monthly Attendance Sheet", + "type": "Report" + }, + { + "format": "{} Open", + "label": "Dashboard", + "link_to": "Human Resource", + "stats_filter": "{\n \"status\": \"Open\"\n}", + "type": "Dashboard" + } + ] +} \ No newline at end of file diff --git a/erpnext/loan_management/dashboard_chart/loan_disbursements/loan_disbursements.json b/erpnext/loan_management/dashboard_chart/loan_disbursements/loan_disbursements.json new file mode 100644 index 00000000000..b8abf210f81 --- /dev/null +++ b/erpnext/loan_management/dashboard_chart/loan_disbursements/loan_disbursements.json @@ -0,0 +1,29 @@ +{ + "based_on": "disbursement_date", + "chart_name": "Loan Disbursements", + "chart_type": "Sum", + "creation": "2021-02-06 18:40:36.148470", + "docstatus": 0, + "doctype": "Dashboard Chart", + "document_type": "Loan Disbursement", + "dynamic_filters_json": "[]", + "filters_json": "[[\"Loan Disbursement\",\"docstatus\",\"=\",\"1\",false]]", + "group_by_type": "Count", + "idx": 0, + "is_public": 0, + "is_standard": 1, + "modified": "2021-02-06 18:40:49.308663", + "modified_by": "Administrator", + "module": "Loan Management", + "name": "Loan Disbursements", + "number_of_groups": 0, + "owner": "Administrator", + "source": "", + "time_interval": "Daily", + "timeseries": 1, + "timespan": "Last Month", + "type": "Line", + "use_report_chart": 0, + "value_based_on": "disbursed_amount", + "y_axis": [] +} \ No newline at end of file diff --git a/erpnext/loan_management/dashboard_chart/loan_interest_accrual/loan_interest_accrual.json b/erpnext/loan_management/dashboard_chart/loan_interest_accrual/loan_interest_accrual.json new file mode 100644 index 00000000000..aa0f78a2f6e --- /dev/null +++ b/erpnext/loan_management/dashboard_chart/loan_interest_accrual/loan_interest_accrual.json @@ -0,0 +1,31 @@ +{ + "based_on": "posting_date", + "chart_name": "Loan Interest Accrual", + "chart_type": "Sum", + "color": "#39E4A5", + "creation": "2021-02-18 20:07:04.843876", + "docstatus": 0, + "doctype": "Dashboard Chart", + "document_type": "Loan Interest Accrual", + "dynamic_filters_json": "[]", + "filters_json": "[[\"Loan Interest Accrual\",\"docstatus\",\"=\",\"1\",false]]", + "group_by_type": "Count", + "idx": 0, + "is_public": 0, + "is_standard": 1, + "last_synced_on": "2021-02-21 21:01:26.022634", + "modified": "2021-02-21 21:01:44.930712", + "modified_by": "Administrator", + "module": "Loan Management", + "name": "Loan Interest Accrual", + "number_of_groups": 0, + "owner": "Administrator", + "source": "", + "time_interval": "Monthly", + "timeseries": 1, + "timespan": "Last Year", + "type": "Line", + "use_report_chart": 0, + "value_based_on": "interest_amount", + "y_axis": [] +} \ No newline at end of file diff --git a/erpnext/loan_management/dashboard_chart/new_loans/new_loans.json b/erpnext/loan_management/dashboard_chart/new_loans/new_loans.json new file mode 100644 index 00000000000..35bd43b994f --- /dev/null +++ b/erpnext/loan_management/dashboard_chart/new_loans/new_loans.json @@ -0,0 +1,31 @@ +{ + "based_on": "creation", + "chart_name": "New Loans", + "chart_type": "Count", + "color": "#449CF0", + "creation": "2021-02-06 16:59:27.509170", + "docstatus": 0, + "doctype": "Dashboard Chart", + "document_type": "Loan", + "dynamic_filters_json": "[]", + "filters_json": "[[\"Loan\",\"docstatus\",\"=\",\"1\",false]]", + "group_by_type": "Count", + "idx": 0, + "is_public": 0, + "is_standard": 1, + "last_synced_on": "2021-02-21 20:55:33.515025", + "modified": "2021-02-21 21:00:33.900821", + "modified_by": "Administrator", + "module": "Loan Management", + "name": "New Loans", + "number_of_groups": 0, + "owner": "Administrator", + "source": "", + "time_interval": "Daily", + "timeseries": 1, + "timespan": "Last Month", + "type": "Bar", + "use_report_chart": 0, + "value_based_on": "", + "y_axis": [] +} \ No newline at end of file diff --git a/erpnext/loan_management/dashboard_chart/top_10_pledged_loan_securities/top_10_pledged_loan_securities.json b/erpnext/loan_management/dashboard_chart/top_10_pledged_loan_securities/top_10_pledged_loan_securities.json new file mode 100644 index 00000000000..76c27b062d6 --- /dev/null +++ b/erpnext/loan_management/dashboard_chart/top_10_pledged_loan_securities/top_10_pledged_loan_securities.json @@ -0,0 +1,31 @@ +{ + "based_on": "", + "chart_name": "Top 10 Pledged Loan Securities", + "chart_type": "Custom", + "color": "#EC864B", + "creation": "2021-02-06 22:02:46.284479", + "docstatus": 0, + "doctype": "Dashboard Chart", + "document_type": "", + "dynamic_filters_json": "[]", + "filters_json": "[]", + "group_by_type": "Count", + "idx": 0, + "is_public": 0, + "is_standard": 1, + "last_synced_on": "2021-02-21 21:00:57.043034", + "modified": "2021-02-21 21:01:10.048623", + "modified_by": "Administrator", + "module": "Loan Management", + "name": "Top 10 Pledged Loan Securities", + "number_of_groups": 0, + "owner": "Administrator", + "source": "Top 10 Pledged Loan Securities", + "time_interval": "Yearly", + "timeseries": 0, + "timespan": "Last Year", + "type": "Bar", + "use_report_chart": 0, + "value_based_on": "", + "y_axis": [] +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/bank_statement_transaction_entry/__init__.py b/erpnext/loan_management/dashboard_chart_source/__init__.py similarity index 100% rename from erpnext/accounts/doctype/bank_statement_transaction_entry/__init__.py rename to erpnext/loan_management/dashboard_chart_source/__init__.py diff --git a/erpnext/accounts/doctype/bank_statement_transaction_invoice_item/__init__.py b/erpnext/loan_management/dashboard_chart_source/top_10_pledged_loan_securities/__init__.py similarity index 100% rename from erpnext/accounts/doctype/bank_statement_transaction_invoice_item/__init__.py rename to erpnext/loan_management/dashboard_chart_source/top_10_pledged_loan_securities/__init__.py diff --git a/erpnext/loan_management/dashboard_chart_source/top_10_pledged_loan_securities/top_10_pledged_loan_securities.js b/erpnext/loan_management/dashboard_chart_source/top_10_pledged_loan_securities/top_10_pledged_loan_securities.js new file mode 100644 index 00000000000..cf75cc8e41a --- /dev/null +++ b/erpnext/loan_management/dashboard_chart_source/top_10_pledged_loan_securities/top_10_pledged_loan_securities.js @@ -0,0 +1,14 @@ +frappe.provide('frappe.dashboards.chart_sources'); + +frappe.dashboards.chart_sources["Top 10 Pledged Loan Securities"] = { + method: "erpnext.loan_management.dashboard_chart_source.top_10_pledged_loan_securities.top_10_pledged_loan_securities.get_data", + filters: [ + { + 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/loan_management/dashboard_chart_source/top_10_pledged_loan_securities/top_10_pledged_loan_securities.json b/erpnext/loan_management/dashboard_chart_source/top_10_pledged_loan_securities/top_10_pledged_loan_securities.json new file mode 100644 index 00000000000..42c9b1c335a --- /dev/null +++ b/erpnext/loan_management/dashboard_chart_source/top_10_pledged_loan_securities/top_10_pledged_loan_securities.json @@ -0,0 +1,13 @@ +{ + "creation": "2021-02-06 22:01:01.332628", + "docstatus": 0, + "doctype": "Dashboard Chart Source", + "idx": 0, + "modified": "2021-02-06 22:01:01.332628", + "modified_by": "Administrator", + "module": "Loan Management", + "name": "Top 10 Pledged Loan Securities", + "owner": "Administrator", + "source_name": "Top 10 Pledged Loan Securities ", + "timeseries": 0 +} \ No newline at end of file diff --git a/erpnext/loan_management/dashboard_chart_source/top_10_pledged_loan_securities/top_10_pledged_loan_securities.py b/erpnext/loan_management/dashboard_chart_source/top_10_pledged_loan_securities/top_10_pledged_loan_securities.py new file mode 100644 index 00000000000..6bb04401bed --- /dev/null +++ b/erpnext/loan_management/dashboard_chart_source/top_10_pledged_loan_securities/top_10_pledged_loan_securities.py @@ -0,0 +1,76 @@ +# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals +import frappe +from frappe.utils.dashboard import cache_source +from erpnext.loan_management.report.applicant_wise_loan_security_exposure.applicant_wise_loan_security_exposure \ + import get_loan_security_details +from six import iteritems + +@frappe.whitelist() +@cache_source +def get_data(chart_name = None, chart = None, no_cache = None, filters = None, from_date = None, + to_date = None, timespan = None, time_interval = None, heatmap_year = None): + if chart_name: + chart = frappe.get_doc('Dashboard Chart', chart_name) + else: + chart = frappe._dict(frappe.parse_json(chart)) + + filters = {} + current_pledges = {} + + if filters: + filters = frappe.parse_json(filters)[0] + + conditions = "" + labels = [] + values = [] + + if filters.get('company'): + conditions = "AND company = %(company)s" + + loan_security_details = get_loan_security_details() + + unpledges = frappe._dict(frappe.db.sql(""" + SELECT u.loan_security, sum(u.qty) as qty + FROM `tabLoan Security Unpledge` up, `tabUnpledge` u + WHERE u.parent = up.name + AND up.status = 'Approved' + {conditions} + GROUP BY u.loan_security + """.format(conditions=conditions), filters, as_list=1)) + + pledges = frappe._dict(frappe.db.sql(""" + SELECT p.loan_security, sum(p.qty) as qty + FROM `tabLoan Security Pledge` lp, `tabPledge`p + WHERE p.parent = lp.name + AND lp.status = 'Pledged' + {conditions} + GROUP BY p.loan_security + """.format(conditions=conditions), filters, as_list=1)) + + for security, qty in iteritems(pledges): + current_pledges.setdefault(security, qty) + current_pledges[security] -= unpledges.get(security, 0.0) + + sorted_pledges = dict(sorted(current_pledges.items(), key=lambda item: item[1], reverse=True)) + + count = 0 + for security, qty in iteritems(sorted_pledges): + values.append(qty * loan_security_details.get(security, {}).get('latest_price', 0)) + labels.append(security) + count +=1 + + ## Just need top 10 securities + if count == 10: + break + + return { + 'labels': labels, + 'datasets': [{ + 'name': 'Top 10 Securities', + 'chartType': 'bar', + 'values': values + }] + } \ No newline at end of file diff --git a/erpnext/loan_management/desk_page/loan/loan.json b/erpnext/loan_management/desk_page/loan/loan.json deleted file mode 100644 index 75036bd0972..00000000000 --- a/erpnext/loan_management/desk_page/loan/loan.json +++ /dev/null @@ -1,63 +0,0 @@ -{ - "cards": [ - { - "hidden": 0, - "label": "Loan", - "links": "[\n {\n \"description\": \"Loan Type for interest and penalty rates\",\n \"label\": \"Loan Type\",\n \"name\": \"Loan Type\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Loan Applications from customers and employees.\",\n \"label\": \"Loan Application\",\n \"name\": \"Loan Application\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Loans provided to customers and employees.\",\n \"label\": \"Loan\",\n \"name\": \"Loan\",\n \"type\": \"doctype\"\n }\n]" - }, - { - "hidden": 0, - "label": "Loan Processes", - "links": "[\n {\n \"label\": \"Process Loan Security Shortfall\",\n \"name\": \"Process Loan Security Shortfall\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Process Loan Interest Accrual\",\n \"name\": \"Process Loan Interest Accrual\",\n \"type\": \"doctype\"\n }\n]" - }, - { - "hidden": 0, - "label": "Disbursement and Repayment", - "links": "[\n {\n \"label\": \"Loan Disbursement\",\n \"name\": \"Loan Disbursement\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Loan Repayment\",\n \"name\": \"Loan Repayment\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Loan Write Off\",\n \"name\": \"Loan Write Off\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Loan Interest Accrual\",\n \"name\": \"Loan Interest Accrual\",\n \"type\": \"doctype\"\n }\n]" - }, - { - "hidden": 0, - "label": "Loan Security", - "links": "[\n {\n \"label\": \"Loan Security Type\",\n \"name\": \"Loan Security Type\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Loan Security Price\",\n \"name\": \"Loan Security Price\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Loan Security\",\n \"name\": \"Loan Security\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Loan Security Pledge\",\n \"name\": \"Loan Security Pledge\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Loan Security Unpledge\",\n \"name\": \"Loan Security Unpledge\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Loan Security Shortfall\",\n \"name\": \"Loan Security Shortfall\",\n \"type\": \"doctype\"\n }\n]" - }, - { - "hidden": 0, - "label": "Reports", - "links": "[\n {\n \"doctype\": \"Loan Repayment\",\n \"is_query_report\": true,\n \"label\": \"Loan Repayment and Closure\",\n \"name\": \"Loan Repayment and Closure\",\n \"route\": \"#query-report/Loan Repayment and Closure\",\n \"type\": \"report\"\n },\n {\n \"doctype\": \"Loan Security Pledge\",\n \"is_query_report\": true,\n \"label\": \"Loan Security Status\",\n \"name\": \"Loan Security Status\",\n \"route\": \"#query-report/Loan Security Status\",\n \"type\": \"report\"\n },\n {\n \"doctype\": \"Loan Interest Accrual\",\n \"is_query_report\": true,\n \"label\": \"Loan Interest Report\",\n \"name\": \"Loan Interest Report\",\n \"route\": \"#query-report/Loan Interest Report\",\n \"type\": \"report\"\n },\n {\n \"doctype\": \"Loan Security\",\n \"is_query_report\": true,\n \"label\": \"Loan Security Exposure\",\n \"name\": \"Loan Security Exposure\",\n \"route\": \"#query-report/Loan Security Exposure\",\n \"type\": \"report\"\n },\n {\n \"doctype\": \"Loan Security\",\n \"is_query_report\": true,\n \"label\": \"Applicant-Wise Loan Security Exposure\",\n \"name\": \"Applicant-Wise Loan Security Exposure\",\n \"route\": \"#query-report/Applicant-Wise Loan Security Exposure\",\n \"type\": \"report\"\n }\n]" - } - ], - "category": "Modules", - "charts": [], - "creation": "2020-03-12 16:35:55.299820", - "developer_mode_only": 0, - "disable_user_customization": 0, - "docstatus": 0, - "doctype": "Desk Page", - "extends_another_page": 0, - "hide_custom": 0, - "idx": 0, - "is_standard": 1, - "label": "Loan", - "modified": "2021-01-17 07:21:22.092184", - "modified_by": "Administrator", - "module": "Loan Management", - "name": "Loan", - "owner": "Administrator", - "pin_to_bottom": 0, - "pin_to_top": 0, - "shortcuts": [ - { - "color": "#ffe8cd", - "format": "{} Open", - "label": "Loan Application", - "link_to": "Loan Application", - "stats_filter": "{ \"status\": \"Open\" }", - "type": "DocType" - }, - { - "label": "Loan", - "link_to": "Loan", - "type": "DocType" - } - ] -} \ No newline at end of file diff --git a/erpnext/loan_management/doctype/loan/loan.py b/erpnext/loan_management/doctype/loan/loan.py index e607d4f3cbf..83a813f947b 100644 --- a/erpnext/loan_management/doctype/loan/loan.py +++ b/erpnext/loan_management/doctype/loan/loan.py @@ -201,7 +201,9 @@ def request_loan_closure(loan, posting_date=None): write_off_limit = frappe.get_value('Loan Type', loan_type, 'write_off_amount') # checking greater than 0 as there may be some minor precision error - if pending_amount < write_off_limit: + if not pending_amount: + frappe.db.set_value('Loan', loan, 'status', 'Loan Closure Requested') + elif pending_amount < write_off_limit: # Auto create loan write off and update status as loan closure requested write_off = make_loan_write_off(loan) write_off.submit() @@ -348,3 +350,13 @@ def validate_employee_currency_with_company_currency(applicant, company): if employee_currency != company_currency: frappe.throw(_("Loan cannot be repayed from salary for Employee {0} because salary is processed in currency {1}") .format(applicant, employee_currency)) + +@frappe.whitelist() +def get_shortfall_applicants(): + loans = frappe.get_all('Loan Security Shortfall', {'status': 'Pending'}, pluck='loan') + applicants = set(frappe.get_all('Loan', {'name': ('in', loans)}, pluck='name')) + + return { + "value": len(applicants), + "fieldtype": "Int" + } \ No newline at end of file diff --git a/erpnext/loan_management/doctype/loan/test_loan.py b/erpnext/loan_management/doctype/loan/test_loan.py index f3c9db62338..13a209418d1 100644 --- a/erpnext/loan_management/doctype/loan/test_loan.py +++ b/erpnext/loan_management/doctype/loan/test_loan.py @@ -547,7 +547,7 @@ class TestLoan(unittest.TestCase): # 30 days - grace period penalty_days = 30 - 4 - penalty_applicable_amount = flt(amounts['interest_amount']/2, 2) + penalty_applicable_amount = flt(amounts['interest_amount']/2) penalty_amount = flt((((penalty_applicable_amount * 25) / 100) * penalty_days), 2) process = process_loan_interest_accrual_for_demand_loans(posting_date = '2019-11-30') diff --git a/erpnext/loan_management/doctype/loan_application/loan_application.py b/erpnext/loan_management/doctype/loan_application/loan_application.py index e59db4c12dc..9c0147e55ba 100644 --- a/erpnext/loan_management/doctype/loan_application/loan_application.py +++ b/erpnext/loan_management/doctype/loan_application/loan_application.py @@ -197,7 +197,7 @@ def get_proposed_pledge(securities): security.qty = cint(security.amount/security.loan_security_price) security.amount = security.qty * security.loan_security_price - security.post_haircut_amount = security.amount - (security.amount * security.haircut/100) + security.post_haircut_amount = cint(security.amount - (security.amount * security.haircut/100)) maximum_loan_amount += security.post_haircut_amount diff --git a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py index 7d7992d40ae..7978350adf8 100644 --- a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py +++ b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py @@ -246,7 +246,5 @@ def get_per_day_interest(principal_amount, rate_of_interest, posting_date=None): if not posting_date: posting_date = getdate() - precision = cint(frappe.db.get_default("currency_precision")) or 2 - - return flt((principal_amount * rate_of_interest) / (days_in_year(get_datetime(posting_date).year) * 100), precision) + return flt((principal_amount * rate_of_interest) / (days_in_year(get_datetime(posting_date).year) * 100)) diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py index ac30c91b670..bac06c4e9e6 100644 --- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py +++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py @@ -81,8 +81,8 @@ class LoanRepayment(AccountsController): last_accrual_date = get_last_accrual_date(self.against_loan) # get posting date upto which interest has to be accrued - per_day_interest = flt(get_per_day_interest(self.pending_principal_amount, - self.rate_of_interest, self.posting_date), 2) + per_day_interest = get_per_day_interest(self.pending_principal_amount, + self.rate_of_interest, self.posting_date) no_of_days = flt(flt(self.total_interest_paid - self.interest_payable, precision)/per_day_interest, 0) - 1 @@ -105,8 +105,6 @@ class LoanRepayment(AccountsController): }) def update_paid_amount(self): - precision = cint(frappe.db.get_default("currency_precision")) or 2 - loan = frappe.get_doc("Loan", self.against_loan) for payment in self.repayment_details: @@ -114,7 +112,7 @@ class LoanRepayment(AccountsController): SET paid_principal_amount = `paid_principal_amount` + %s, paid_interest_amount = `paid_interest_amount` + %s WHERE name = %s""", - (flt(payment.paid_principal_amount, precision), flt(payment.paid_interest_amount, precision), payment.loan_interest_accrual)) + (flt(payment.paid_principal_amount), flt(payment.paid_interest_amount), payment.loan_interest_accrual)) frappe.db.sql(""" UPDATE `tabLoan` SET total_amount_paid = %s, total_principal_paid = %s WHERE name = %s """, (loan.total_amount_paid + self.amount_paid, @@ -148,8 +146,6 @@ class LoanRepayment(AccountsController): frappe.db.set_value("Loan", self.against_loan, "status", "Disbursed") def allocate_amounts(self, repayment_details): - precision = cint(frappe.db.get_default("currency_precision")) or 2 - self.set('repayment_details', []) self.principal_amount_paid = 0 total_interest_paid = 0 @@ -185,21 +181,18 @@ class LoanRepayment(AccountsController): # no of days for which to accrue interest # Interest can only be accrued for an entire day and not partial if interest_paid > repayment_details['unaccrued_interest']: - per_day_interest = flt(get_per_day_interest(self.pending_principal_amount, - self.rate_of_interest, self.posting_date), precision) interest_paid -= repayment_details['unaccrued_interest'] total_interest_paid += repayment_details['unaccrued_interest'] else: # get no of days for which interest can be paid - per_day_interest = flt(get_per_day_interest(self.pending_principal_amount, - self.rate_of_interest, self.posting_date), precision) + per_day_interest = get_per_day_interest(self.pending_principal_amount, + self.rate_of_interest, self.posting_date) no_of_days = cint(interest_paid/per_day_interest) total_interest_paid += no_of_days * per_day_interest interest_paid -= no_of_days * per_day_interest self.total_interest_paid = total_interest_paid - if interest_paid: self.principal_amount_paid += interest_paid @@ -369,7 +362,7 @@ def get_amounts(amounts, against_loan, posting_date): if pending_days > 0: principal_amount = flt(pending_principal_amount, precision) per_day_interest = get_per_day_interest(principal_amount, loan_type_details.rate_of_interest, posting_date) - unaccrued_interest += (pending_days * flt(per_day_interest, precision)) + unaccrued_interest += (pending_days * per_day_interest) amounts["pending_principal_amount"] = flt(pending_principal_amount, precision) amounts["payable_principal_amount"] = flt(payable_principal_amount, precision) diff --git a/erpnext/loan_management/loan_management_dashboard/loan_dashboard/loan_dashboard.json b/erpnext/loan_management/loan_management_dashboard/loan_dashboard/loan_dashboard.json new file mode 100644 index 00000000000..e060253d34c --- /dev/null +++ b/erpnext/loan_management/loan_management_dashboard/loan_dashboard/loan_dashboard.json @@ -0,0 +1,70 @@ +{ + "cards": [ + { + "card": "New Loans" + }, + { + "card": "Active Loans" + }, + { + "card": "Closed Loans" + }, + { + "card": "Total Disbursed" + }, + { + "card": "Open Loan Applications" + }, + { + "card": "New Loan Applications" + }, + { + "card": "Total Sanctioned Amount" + }, + { + "card": "Active Securities" + }, + { + "card": "Applicants With Unpaid Shortfall" + }, + { + "card": "Total Shortfall Amount" + }, + { + "card": "Total Repayment" + }, + { + "card": "Total Write Off" + } + ], + "charts": [ + { + "chart": "New Loans", + "width": "Half" + }, + { + "chart": "Loan Disbursements", + "width": "Half" + }, + { + "chart": "Top 10 Pledged Loan Securities", + "width": "Half" + }, + { + "chart": "Loan Interest Accrual", + "width": "Half" + } + ], + "creation": "2021-02-06 16:52:43.484752", + "dashboard_name": "Loan Dashboard", + "docstatus": 0, + "doctype": "Dashboard", + "idx": 0, + "is_default": 0, + "is_standard": 1, + "modified": "2021-02-21 20:53:47.531699", + "modified_by": "Administrator", + "module": "Loan Management", + "name": "Loan Dashboard", + "owner": "Administrator" +} \ No newline at end of file diff --git a/erpnext/loan_management/number_card/active_loans/active_loans.json b/erpnext/loan_management/number_card/active_loans/active_loans.json new file mode 100644 index 00000000000..7e0db472882 --- /dev/null +++ b/erpnext/loan_management/number_card/active_loans/active_loans.json @@ -0,0 +1,23 @@ +{ + "aggregate_function_based_on": "", + "creation": "2021-02-06 17:10:26.132493", + "docstatus": 0, + "doctype": "Number Card", + "document_type": "Loan", + "dynamic_filters_json": "[]", + "filters_json": "[[\"Loan\",\"docstatus\",\"=\",\"1\",false],[\"Loan\",\"status\",\"in\",[\"Disbursed\",\"Partially Disbursed\",null],false]]", + "function": "Count", + "idx": 0, + "is_public": 0, + "is_standard": 1, + "label": "Active Loans", + "modified": "2021-02-06 17:29:20.304087", + "modified_by": "Administrator", + "module": "Loan Management", + "name": "Active Loans", + "owner": "Administrator", + "report_function": "Sum", + "show_percentage_stats": 1, + "stats_time_interval": "Monthly", + "type": "Document Type" +} \ No newline at end of file diff --git a/erpnext/loan_management/number_card/active_securities/active_securities.json b/erpnext/loan_management/number_card/active_securities/active_securities.json new file mode 100644 index 00000000000..298e41061a8 --- /dev/null +++ b/erpnext/loan_management/number_card/active_securities/active_securities.json @@ -0,0 +1,23 @@ +{ + "aggregate_function_based_on": "", + "creation": "2021-02-06 19:07:21.344199", + "docstatus": 0, + "doctype": "Number Card", + "document_type": "Loan Security", + "dynamic_filters_json": "[]", + "filters_json": "[[\"Loan Security\",\"disabled\",\"=\",0,false]]", + "function": "Count", + "idx": 0, + "is_public": 0, + "is_standard": 1, + "label": "Active Securities", + "modified": "2021-02-06 19:07:26.671516", + "modified_by": "Administrator", + "module": "Loan Management", + "name": "Active Securities", + "owner": "Administrator", + "report_function": "Sum", + "show_percentage_stats": 1, + "stats_time_interval": "Daily", + "type": "Document Type" +} \ No newline at end of file diff --git a/erpnext/loan_management/number_card/applicants_with_unpaid_shortfall/applicants_with_unpaid_shortfall.json b/erpnext/loan_management/number_card/applicants_with_unpaid_shortfall/applicants_with_unpaid_shortfall.json new file mode 100644 index 00000000000..3b9eba15536 --- /dev/null +++ b/erpnext/loan_management/number_card/applicants_with_unpaid_shortfall/applicants_with_unpaid_shortfall.json @@ -0,0 +1,21 @@ +{ + "creation": "2021-02-07 18:55:12.632616", + "docstatus": 0, + "doctype": "Number Card", + "filters_json": "null", + "function": "Count", + "idx": 0, + "is_public": 0, + "is_standard": 1, + "label": "Applicants With Unpaid Shortfall", + "method": "erpnext.loan_management.doctype.loan.loan.get_shortfall_applicants", + "modified": "2021-02-07 21:46:27.369795", + "modified_by": "Administrator", + "module": "Loan Management", + "name": "Applicants With Unpaid Shortfall", + "owner": "Administrator", + "report_function": "Sum", + "show_percentage_stats": 1, + "stats_time_interval": "Daily", + "type": "Custom" +} \ No newline at end of file diff --git a/erpnext/loan_management/number_card/closed_loans/closed_loans.json b/erpnext/loan_management/number_card/closed_loans/closed_loans.json new file mode 100644 index 00000000000..c2f22442653 --- /dev/null +++ b/erpnext/loan_management/number_card/closed_loans/closed_loans.json @@ -0,0 +1,23 @@ +{ + "aggregate_function_based_on": "", + "creation": "2021-02-21 19:51:49.261813", + "docstatus": 0, + "doctype": "Number Card", + "document_type": "Loan", + "dynamic_filters_json": "[]", + "filters_json": "[[\"Loan\",\"docstatus\",\"=\",\"1\",false],[\"Loan\",\"status\",\"=\",\"Closed\",false]]", + "function": "Count", + "idx": 0, + "is_public": 0, + "is_standard": 1, + "label": "Closed Loans", + "modified": "2021-02-21 19:51:54.087903", + "modified_by": "Administrator", + "module": "Loan Management", + "name": "Closed Loans", + "owner": "Administrator", + "report_function": "Sum", + "show_percentage_stats": 1, + "stats_time_interval": "Daily", + "type": "Document Type" +} \ No newline at end of file diff --git a/erpnext/loan_management/number_card/last_interest_accrual/last_interest_accrual.json b/erpnext/loan_management/number_card/last_interest_accrual/last_interest_accrual.json new file mode 100644 index 00000000000..65c8ce67d21 --- /dev/null +++ b/erpnext/loan_management/number_card/last_interest_accrual/last_interest_accrual.json @@ -0,0 +1,21 @@ +{ + "creation": "2021-02-07 21:57:14.758007", + "docstatus": 0, + "doctype": "Number Card", + "filters_json": "null", + "function": "Count", + "idx": 0, + "is_public": 0, + "is_standard": 1, + "label": "Last Interest Accrual", + "method": "erpnext.loan_management.doctype.loan.loan.get_last_accrual_date", + "modified": "2021-02-07 21:59:47.525197", + "modified_by": "Administrator", + "module": "Loan Management", + "name": "Last Interest Accrual", + "owner": "Administrator", + "report_function": "Sum", + "show_percentage_stats": 1, + "stats_time_interval": "Daily", + "type": "Custom" +} \ No newline at end of file diff --git a/erpnext/loan_management/number_card/new_loan_applications/new_loan_applications.json b/erpnext/loan_management/number_card/new_loan_applications/new_loan_applications.json new file mode 100644 index 00000000000..7e655ff35c7 --- /dev/null +++ b/erpnext/loan_management/number_card/new_loan_applications/new_loan_applications.json @@ -0,0 +1,23 @@ +{ + "aggregate_function_based_on": "", + "creation": "2021-02-06 17:59:10.051269", + "docstatus": 0, + "doctype": "Number Card", + "document_type": "Loan Application", + "dynamic_filters_json": "[]", + "filters_json": "[[\"Loan Application\",\"docstatus\",\"=\",\"1\",false],[\"Loan Application\",\"creation\",\"Timespan\",\"today\",false]]", + "function": "Count", + "idx": 0, + "is_public": 0, + "is_standard": 1, + "label": "New Loan Applications", + "modified": "2021-02-06 17:59:21.880979", + "modified_by": "Administrator", + "module": "Loan Management", + "name": "New Loan Applications", + "owner": "Administrator", + "report_function": "Sum", + "show_percentage_stats": 1, + "stats_time_interval": "Daily", + "type": "Document Type" +} \ No newline at end of file diff --git a/erpnext/loan_management/number_card/new_loans/new_loans.json b/erpnext/loan_management/number_card/new_loans/new_loans.json new file mode 100644 index 00000000000..424f0f14958 --- /dev/null +++ b/erpnext/loan_management/number_card/new_loans/new_loans.json @@ -0,0 +1,23 @@ +{ + "aggregate_function_based_on": "", + "creation": "2021-02-06 17:56:34.624031", + "docstatus": 0, + "doctype": "Number Card", + "document_type": "Loan", + "dynamic_filters_json": "[]", + "filters_json": "[[\"Loan\",\"docstatus\",\"=\",\"1\",false],[\"Loan\",\"creation\",\"Timespan\",\"today\",false]]", + "function": "Count", + "idx": 0, + "is_public": 0, + "is_standard": 1, + "label": "New Loans", + "modified": "2021-02-06 17:58:20.209166", + "modified_by": "Administrator", + "module": "Loan Management", + "name": "New Loans", + "owner": "Administrator", + "report_function": "Sum", + "show_percentage_stats": 1, + "stats_time_interval": "Daily", + "type": "Document Type" +} \ No newline at end of file diff --git a/erpnext/loan_management/number_card/open_loan_applications/open_loan_applications.json b/erpnext/loan_management/number_card/open_loan_applications/open_loan_applications.json new file mode 100644 index 00000000000..1d5e84ed7f0 --- /dev/null +++ b/erpnext/loan_management/number_card/open_loan_applications/open_loan_applications.json @@ -0,0 +1,23 @@ +{ + "aggregate_function_based_on": "", + "creation": "2021-02-06 17:23:32.509899", + "docstatus": 0, + "doctype": "Number Card", + "document_type": "Loan Application", + "dynamic_filters_json": "[]", + "filters_json": "[[\"Loan Application\",\"docstatus\",\"=\",\"1\",false],[\"Loan Application\",\"status\",\"=\",\"Open\",false]]", + "function": "Count", + "idx": 0, + "is_public": 0, + "is_standard": 1, + "label": "Open Loan Applications", + "modified": "2021-02-06 17:29:09.761011", + "modified_by": "Administrator", + "module": "Loan Management", + "name": "Open Loan Applications", + "owner": "Administrator", + "report_function": "Sum", + "show_percentage_stats": 1, + "stats_time_interval": "Monthly", + "type": "Document Type" +} \ No newline at end of file diff --git a/erpnext/loan_management/number_card/total_disbursed/total_disbursed.json b/erpnext/loan_management/number_card/total_disbursed/total_disbursed.json new file mode 100644 index 00000000000..4a3f8699a04 --- /dev/null +++ b/erpnext/loan_management/number_card/total_disbursed/total_disbursed.json @@ -0,0 +1,23 @@ +{ + "aggregate_function_based_on": "disbursed_amount", + "creation": "2021-02-06 16:52:19.505462", + "docstatus": 0, + "doctype": "Number Card", + "document_type": "Loan Disbursement", + "dynamic_filters_json": "[]", + "filters_json": "[[\"Loan Disbursement\",\"docstatus\",\"=\",\"1\",false]]", + "function": "Sum", + "idx": 0, + "is_public": 0, + "is_standard": 1, + "label": "Total Disbursed Amount", + "modified": "2021-02-06 17:29:38.453870", + "modified_by": "Administrator", + "module": "Loan Management", + "name": "Total Disbursed", + "owner": "Administrator", + "report_function": "Sum", + "show_percentage_stats": 1, + "stats_time_interval": "Monthly", + "type": "Document Type" +} \ No newline at end of file diff --git a/erpnext/loan_management/number_card/total_repayment/total_repayment.json b/erpnext/loan_management/number_card/total_repayment/total_repayment.json new file mode 100644 index 00000000000..38de42b89c8 --- /dev/null +++ b/erpnext/loan_management/number_card/total_repayment/total_repayment.json @@ -0,0 +1,24 @@ +{ + "aggregate_function_based_on": "amount_paid", + "color": "#29CD42", + "creation": "2021-02-21 19:27:45.989222", + "docstatus": 0, + "doctype": "Number Card", + "document_type": "Loan Repayment", + "dynamic_filters_json": "[]", + "filters_json": "[[\"Loan Repayment\",\"docstatus\",\"=\",\"1\",false]]", + "function": "Sum", + "idx": 0, + "is_public": 0, + "is_standard": 1, + "label": "Total Repayment", + "modified": "2021-02-21 19:34:59.656546", + "modified_by": "Administrator", + "module": "Loan Management", + "name": "Total Repayment", + "owner": "Administrator", + "report_function": "Sum", + "show_percentage_stats": 1, + "stats_time_interval": "Daily", + "type": "Document Type" +} \ No newline at end of file diff --git a/erpnext/loan_management/number_card/total_sanctioned_amount/total_sanctioned_amount.json b/erpnext/loan_management/number_card/total_sanctioned_amount/total_sanctioned_amount.json new file mode 100644 index 00000000000..dfb9d24e925 --- /dev/null +++ b/erpnext/loan_management/number_card/total_sanctioned_amount/total_sanctioned_amount.json @@ -0,0 +1,23 @@ +{ + "aggregate_function_based_on": "loan_amount", + "creation": "2021-02-06 17:05:04.704162", + "docstatus": 0, + "doctype": "Number Card", + "document_type": "Loan", + "dynamic_filters_json": "[]", + "filters_json": "[[\"Loan\",\"docstatus\",\"=\",\"1\",false],[\"Loan\",\"status\",\"=\",\"Sanctioned\",false]]", + "function": "Sum", + "idx": 0, + "is_public": 0, + "is_standard": 1, + "label": "Total Sanctioned Amount", + "modified": "2021-02-06 17:29:29.930557", + "modified_by": "Administrator", + "module": "Loan Management", + "name": "Total Sanctioned Amount", + "owner": "Administrator", + "report_function": "Sum", + "show_percentage_stats": 1, + "stats_time_interval": "Monthly", + "type": "Document Type" +} \ No newline at end of file diff --git a/erpnext/loan_management/number_card/total_shortfall_amount/total_shortfall_amount.json b/erpnext/loan_management/number_card/total_shortfall_amount/total_shortfall_amount.json new file mode 100644 index 00000000000..aa6b0937323 --- /dev/null +++ b/erpnext/loan_management/number_card/total_shortfall_amount/total_shortfall_amount.json @@ -0,0 +1,23 @@ +{ + "aggregate_function_based_on": "shortfall_amount", + "creation": "2021-02-09 08:07:20.096995", + "docstatus": 0, + "doctype": "Number Card", + "document_type": "Loan Security Shortfall", + "dynamic_filters_json": "[]", + "filters_json": "[]", + "function": "Sum", + "idx": 0, + "is_public": 0, + "is_standard": 1, + "label": "Total Unpaid Shortfall Amount", + "modified": "2021-02-09 08:09:00.355547", + "modified_by": "Administrator", + "module": "Loan Management", + "name": "Total Shortfall Amount", + "owner": "Administrator", + "report_function": "Sum", + "show_percentage_stats": 1, + "stats_time_interval": "Daily", + "type": "Document Type" +} \ No newline at end of file diff --git a/erpnext/loan_management/number_card/total_write_off/total_write_off.json b/erpnext/loan_management/number_card/total_write_off/total_write_off.json new file mode 100644 index 00000000000..c85169acf8d --- /dev/null +++ b/erpnext/loan_management/number_card/total_write_off/total_write_off.json @@ -0,0 +1,24 @@ +{ + "aggregate_function_based_on": "write_off_amount", + "color": "#CB2929", + "creation": "2021-02-21 19:48:29.004429", + "docstatus": 0, + "doctype": "Number Card", + "document_type": "Loan Write Off", + "dynamic_filters_json": "[]", + "filters_json": "[[\"Loan Write Off\",\"docstatus\",\"=\",\"1\",false]]", + "function": "Sum", + "idx": 0, + "is_public": 0, + "is_standard": 1, + "label": "Total Write Off", + "modified": "2021-02-21 19:48:58.604159", + "modified_by": "Administrator", + "module": "Loan Management", + "name": "Total Write Off", + "owner": "Administrator", + "report_function": "Sum", + "show_percentage_stats": 1, + "stats_time_interval": "Daily", + "type": "Document Type" +} \ No newline at end of file diff --git a/erpnext/loan_management/report/applicant_wise_loan_security_exposure/applicant_wise_loan_security_exposure.py b/erpnext/loan_management/report/applicant_wise_loan_security_exposure/applicant_wise_loan_security_exposure.py index ab586bc09c4..0ccd149e5fb 100644 --- a/erpnext/loan_management/report/applicant_wise_loan_security_exposure/applicant_wise_loan_security_exposure.py +++ b/erpnext/loan_management/report/applicant_wise_loan_security_exposure/applicant_wise_loan_security_exposure.py @@ -36,7 +36,7 @@ def get_columns(filters): def get_data(filters): data = [] - loan_security_details = get_loan_security_details(filters) + loan_security_details = get_loan_security_details() pledge_values, total_value_map, applicant_type_map = get_applicant_wise_total_loan_security_qty(filters, loan_security_details) @@ -64,7 +64,7 @@ def get_data(filters): return data -def get_loan_security_details(filters): +def get_loan_security_details(): security_detail_map = {} loan_security_price_map = {} lsp_validity_map = {} diff --git a/erpnext/loan_management/report/loan_interest_report/loan_interest_report.py b/erpnext/loan_management/report/loan_interest_report/loan_interest_report.py index 2bfe6d3c33e..0f72c3cce7c 100644 --- a/erpnext/loan_management/report/loan_interest_report/loan_interest_report.py +++ b/erpnext/loan_management/report/loan_interest_report/loan_interest_report.py @@ -81,7 +81,7 @@ def get_active_loan_details(filters): + loan['penalty'] if loan_wise_security_value.get(loan.loan): - loan['loan_to_value'] = (loan['principal_outstanding'] * 100) / loan_wise_security_value.get(loan.loan) + loan['loan_to_value'] = flt((loan['principal_outstanding'] * 100) / loan_wise_security_value.get(loan.loan)) return loan_details @@ -148,7 +148,7 @@ def get_loan_wise_pledges(filters): WHERE u.parent = up.name AND up.status = 'Approved' {conditions} - GROUP BY up.loan + GROUP BY up.loan, u.loan_security """.format(conditions=conditions), filters, as_dict=1) for unpledge in unpledges: @@ -160,7 +160,7 @@ def get_loan_wise_pledges(filters): WHERE p.parent = lp.name AND lp.status = 'Pledged' {conditions} - GROUP BY lp.loan + GROUP BY lp.loan, p.loan_security """.format(conditions=conditions), filters, as_dict=1) for security in pledges: @@ -171,7 +171,7 @@ def get_loan_wise_pledges(filters): return current_pledges def get_loan_wise_security_value(filters, current_pledges): - loan_security_details = get_loan_security_details(filters) + loan_security_details = get_loan_security_details() loan_wise_security_value = {} for key in current_pledges: diff --git a/erpnext/loan_management/report/loan_security_exposure/loan_security_exposure.py b/erpnext/loan_management/report/loan_security_exposure/loan_security_exposure.py index adc8013c686..887a86a46c5 100644 --- a/erpnext/loan_management/report/loan_security_exposure/loan_security_exposure.py +++ b/erpnext/loan_management/report/loan_security_exposure/loan_security_exposure.py @@ -35,7 +35,7 @@ def get_columns(filters): def get_data(filters): data = [] - loan_security_details = get_loan_security_details(filters) + loan_security_details = get_loan_security_details() current_pledges, total_portfolio_value = get_company_wise_loan_security_details(filters, loan_security_details) currency = erpnext.get_company_currency(filters.get('company')) @@ -76,7 +76,7 @@ def get_company_wise_loan_security_details(filters, loan_security_details): if qty: security_wise_map[key[1]]['applicant_count'] += 1 - total_portfolio_value += flt(qty * loan_security_details.get(key[1])['latest_price']) + total_portfolio_value += flt(qty * loan_security_details.get(key[1], {}).get('latest_price', 0)) return security_wise_map, total_portfolio_value diff --git a/erpnext/loan_management/workspace/loan_management/loan_management.json b/erpnext/loan_management/workspace/loan_management/loan_management.json new file mode 100644 index 00000000000..18559dceef7 --- /dev/null +++ b/erpnext/loan_management/workspace/loan_management/loan_management.json @@ -0,0 +1,251 @@ +{ + "category": "Modules", + "charts": [], + "creation": "2020-03-12 16:35:55.299820", + "developer_mode_only": 0, + "disable_user_customization": 0, + "docstatus": 0, + "doctype": "Workspace", + "extends_another_page": 0, + "hide_custom": 0, + "icon": "loan", + "idx": 0, + "is_default": 0, + "is_standard": 1, + "label": "Loan Management", + "links": [ + { + "hidden": 0, + "is_query_report": 0, + "label": "Loan", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Loan Type", + "link_to": "Loan Type", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Loan Application", + "link_to": "Loan Application", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Loan", + "link_to": "Loan", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Loan Processes", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Process Loan Security Shortfall", + "link_to": "Process Loan Security Shortfall", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Process Loan Interest Accrual", + "link_to": "Process Loan Interest Accrual", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Disbursement and Repayment", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Loan Disbursement", + "link_to": "Loan Disbursement", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Loan Repayment", + "link_to": "Loan Repayment", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Loan Write Off", + "link_to": "Loan Write Off", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Loan Interest Accrual", + "link_to": "Loan Interest Accrual", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Loan Security", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Loan Security Type", + "link_to": "Loan Security Type", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Loan Security Price", + "link_to": "Loan Security Price", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Loan Security", + "link_to": "Loan Security", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Loan Security Pledge", + "link_to": "Loan Security Pledge", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Loan Security Unpledge", + "link_to": "Loan Security Unpledge", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Loan Security Shortfall", + "link_to": "Loan Security Shortfall", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Reports", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 1, + "label": "Loan Repayment and Closure", + "link_to": "Loan Repayment and Closure", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 1, + "label": "Loan Security Status", + "link_to": "Loan Security Status", + "link_type": "Report", + "onboard": 0, + "type": "Link" + } + ], + "modified": "2021-02-18 17:31:53.586508", + "modified_by": "Administrator", + "module": "Loan Management", + "name": "Loan Management", + "owner": "Administrator", + "pin_to_bottom": 0, + "pin_to_top": 0, + "shortcuts": [ + { + "color": "Green", + "format": "{} Open", + "label": "Loan Application", + "link_to": "Loan Application", + "stats_filter": "{ \"status\": \"Open\" }", + "type": "DocType" + }, + { + "label": "Loan", + "link_to": "Loan", + "type": "DocType" + }, + { + "doc_view": "", + "label": "Dashboard", + "link_to": "Loan Dashboard", + "type": "Dashboard" + } + ] +} \ No newline at end of file diff --git a/erpnext/manufacturing/desk_page/manufacturing/manufacturing.json b/erpnext/manufacturing/desk_page/manufacturing/manufacturing.json deleted file mode 100644 index 8d11294164f..00000000000 --- a/erpnext/manufacturing/desk_page/manufacturing/manufacturing.json +++ /dev/null @@ -1,124 +0,0 @@ -{ - "cards": [ - { - "hidden": 0, - "label": "Production", - "links": "[\n {\n \"dependencies\": [\n \"Item\",\n \"BOM\"\n ],\n \"description\": \"Orders released for production.\",\n \"label\": \"Work Order\",\n \"name\": \"Work Order\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\",\n \"BOM\"\n ],\n \"description\": \"Generate Material Requests (MRP) and Work Orders.\",\n \"label\": \"Production Plan\",\n \"name\": \"Production Plan\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"label\": \"Stock Entry\",\n \"name\": \"Stock Entry\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Job Card\",\n \"name\": \"Job Card\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Downtime Entry\",\n \"name\": \"Downtime Entry\",\n \"type\": \"doctype\"\n }\n]" - }, - { - "hidden": 0, - "label": "Bill of Materials", - "links": "[\n {\n \"description\": \"All Products or Services.\",\n \"label\": \"Item\",\n \"name\": \"Item\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"description\": \"Bill of Materials (BOM)\",\n \"label\": \"Bill of Materials\",\n \"name\": \"BOM\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Where manufacturing operations are carried.\",\n \"label\": \"Workstation\",\n \"name\": \"Workstation\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Details of the operations carried out.\",\n \"label\": \"Operation\",\n \"name\": \"Operation\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Routing\",\n \"name\": \"Routing\",\n \"type\": \"doctype\"\n }\n]" - }, - { - "hidden": 0, - "label": "Reports", - "links": "[{\n\t\"dependencies\": [\"Work Order\"],\n\t\"name\": \"Production Planning Report\",\n\t\"is_query_report\": true,\n\t\"type\": \"report\",\n\t\"doctype\": \"Work Order\",\n\t\"label\": \"Production Planning Report\"\n}, {\n\t\"dependencies\": [\"Work Order\"],\n\t\"name\": \"Work Order Summary\",\n\t\"is_query_report\": true,\n\t\"type\": \"report\",\n\t\"doctype\": \"Work Order\",\n\t\"label\": \"Work Order Summary\"\n}, {\n\t\"dependencies\": [\"Quality Inspection\"],\n\t\"name\": \"Quality Inspection Summary\",\n\t\"is_query_report\": true,\n\t\"type\": \"report\",\n\t\"doctype\": \"Quality Inspection\",\n\t\"label\": \"Quality Inspection Summary\"\n}, {\n\t\"dependencies\": [\"Downtime Entry\"],\n\t\"name\": \"Downtime Analysis\",\n\t\"is_query_report\": true,\n\t\"type\": \"report\",\n\t\"doctype\": \"Downtime Entry\",\n\t\"label\": \"Downtime Analysis\"\n}, {\n\t\"dependencies\": [\"Job Card\"],\n\t\"name\": \"Job Card Summary\",\n\t\"is_query_report\": true,\n\t\"type\": \"report\",\n\t\"doctype\": \"Job Card\",\n\t\"label\": \"Job Card Summary\"\n}, {\n\t\"dependencies\": [\"BOM\"],\n\t\"name\": \"BOM Search\",\n\t\"is_query_report\": true,\n\t\"type\": \"report\",\n\t\"doctype\": \"BOM\",\n\t\"label\": \"BOM Search\"\n}, {\n\t\"dependencies\": [\"BOM\"],\n\t\"name\": \"BOM Stock Report\",\n\t\"is_query_report\": true,\n\t\"type\": \"report\",\n\t\"doctype\": \"BOM\",\n\t\"label\": \"BOM Stock Report\"\n}, {\n\t\"dependencies\": [\"Work Order\"],\n\t\"name\": \"Production Analytics\",\n\t\"is_query_report\": true,\n\t\"type\": \"report\",\n\t\"doctype\": \"Work Order\",\n\t\"label\": \"Production Analytics\"\n}, {\n\t\"dependencies\": [\"BOM\"],\n\t\"name\": \"BOM Operations Time\",\n\t\"is_query_report\": true,\n\t\"type\": \"report\",\n\t\"doctype\": \"BOM\",\n\t\"label\": \"BOM Operations Time\"\n}]" - }, - { - "hidden": 0, - "label": "Tools", - "links": "[\n {\n \"description\": \"Replace BOM and update latest price in all BOMs\",\n \"label\": \"BOM Update Tool\",\n \"name\": \"BOM Update Tool\",\n \"type\": \"doctype\"\n },\n {\n \"data_doctype\": \"BOM\",\n \"description\": \"Compare BOMs for changes in Raw Materials and Operations\",\n \"label\": \"BOM Comparison Tool\",\n \"name\": \"bom-comparison-tool\",\n \"type\": \"page\"\n }\n]" - }, - { - "hidden": 0, - "label": "Settings", - "links": "[\n {\n \"description\": \"Global settings for all manufacturing processes.\",\n \"label\": \"Manufacturing Settings\",\n \"name\": \"Manufacturing Settings\",\n \"type\": \"doctype\"\n }\n]" - }, - { - "hidden": 0, - "label": "Help", - "links": "[\n {\n \"label\": \"Work Order\",\n \"name\": \"Work Order\",\n \"type\": \"help\",\n \"youtube_id\": \"ZotgLyp2YFY\"\n }\n]" - } - ], - "category": "Domains", - "charts": [ - { - "chart_name": "Produced Quantity" - } - ], - "creation": "2020-03-02 17:11:37.032604", - "developer_mode_only": 0, - "disable_user_customization": 0, - "docstatus": 0, - "doctype": "Desk Page", - "extends_another_page": 0, - "hide_custom": 0, - "idx": 0, - "is_standard": 1, - "label": "Manufacturing", - "modified": "2020-05-28 13:54:02.048419", - "modified_by": "Administrator", - "module": "Manufacturing", - "name": "Manufacturing", - "onboarding": "Manufacturing", - "owner": "Administrator", - "pin_to_bottom": 0, - "pin_to_top": 0, - "restrict_to_domain": "Manufacturing", - "shortcuts": [ - { - "color": "#cef6d1", - "format": "{} Active", - "label": "Item", - "link_to": "Item", - "restrict_to_domain": "Manufacturing", - "stats_filter": "{\n \"disabled\": 0\n}", - "type": "DocType" - }, - { - "color": "#cef6d1", - "format": "{} Active", - "label": "BOM", - "link_to": "BOM", - "restrict_to_domain": "Manufacturing", - "stats_filter": "{\n \"is_active\": 1\n}", - "type": "DocType" - }, - { - "color": "#ffe8cd", - "format": "{} Open", - "label": "Work Order", - "link_to": "Work Order", - "restrict_to_domain": "Manufacturing", - "stats_filter": "{ \n \"status\": [\"in\", \n [\"Draft\", \"Not Started\", \"In Process\"]\n ]\n}", - "type": "DocType" - }, - { - "color": "#ffe8cd", - "format": "{} Open", - "label": "Production Plan", - "link_to": "Production Plan", - "restrict_to_domain": "Manufacturing", - "stats_filter": "{ \n \"status\": [\"not in\", [\"Completed\"]]\n}", - "type": "DocType" - }, - { - "label": "Forecasting", - "link_to": "Exponential Smoothing Forecasting", - "type": "Report" - }, - { - "label": "Work Order Summary", - "link_to": "Work Order Summary", - "restrict_to_domain": "Manufacturing", - "type": "Report" - }, - { - "label": "BOM Stock Report", - "link_to": "BOM Stock Report", - "type": "Report" - }, - { - "label": "Production Planning Report", - "link_to": "Production Planning Report", - "type": "Report" - }, - { - "label": "Dashboard", - "link_to": "Manufacturing", - "restrict_to_domain": "Manufacturing", - "type": "Dashboard" - } - ] -} \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js index 15affd84e10..fbfd801a114 100644 --- a/erpnext/manufacturing/doctype/bom/bom.js +++ b/erpnext/manufacturing/doctype/bom/bom.js @@ -134,7 +134,7 @@ frappe.ui.form.on("BOM", { frm.set_intro(__('This is a Template BOM and will be used to make the work order for {0} of the item {1}', [ `variants`, - `${frm.doc.item}`, + `${frm.doc.item}`, ]), true); frm.$wrapper.find(".variants-intro").on("click", () => { diff --git a/erpnext/manufacturing/doctype/bom/bom_item_preview.html b/erpnext/manufacturing/doctype/bom/bom_item_preview.html index c782f7bf0ee..6cd5f8cb3cf 100644 --- a/erpnext/manufacturing/doctype/bom/bom_item_preview.html +++ b/erpnext/manufacturing/doctype/bom/bom_item_preview.html @@ -12,11 +12,11 @@

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

    diff --git a/erpnext/manufacturing/doctype/bom/bom_list.js b/erpnext/manufacturing/doctype/bom/bom_list.js index 94cb466bd8a..4b5887f180c 100644 --- a/erpnext/manufacturing/doctype/bom/bom_list.js +++ b/erpnext/manufacturing/doctype/bom/bom_list.js @@ -8,7 +8,7 @@ frappe.listview_settings['BOM'] = { } else if(doc.is_active) { return [__("Active"), "blue", "is_active,=,Yes"]; } else if(!doc.is_active) { - return [__("Not active"), "darkgrey", "is_active,=,No"]; + return [__("Not active"), "gray", "is_active,=,No"]; } } }; diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index ec28eb7795c..662a06b1ee2 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -267,6 +267,17 @@ class JobCard(Document): fields = ["sum(total_time_in_mins) as time_in_mins", "sum(total_completed_qty) as completed_qty"], filters = {"docstatus": 1, "work_order": self.work_order, "operation_id": self.operation_id}) + def set_transferred_qty_in_job_card(self, ste_doc): + for row in ste_doc.items: + if not row.job_card_item: continue + + qty = frappe.db.sql(""" SELECT SUM(qty) from `tabStock Entry Detail` sed, `tabStock Entry` se + WHERE sed.job_card_item = %s and se.docstatus = 1 and sed.parent = se.name and + se.purpose = 'Material Transfer for Manufacture' + """, (row.job_card_item))[0][0] + + frappe.db.set_value('Job Card Item', row.job_card_item, 'transferred_qty', flt(qty)) + def set_transferred_qty(self, update_status=False): if not self.items: self.transferred_qty = self.for_quantity if self.docstatus == 1 else 0 @@ -279,7 +290,8 @@ class JobCard(Document): self.transferred_qty = frappe.db.get_value('Stock Entry', { 'job_card': self.name, 'work_order': self.work_order, - 'docstatus': 1 + 'docstatus': 1, + 'purpose': 'Material Transfer for Manufacture' }, 'sum(fg_completed_qty)') or 0 self.db_set("transferred_qty", self.transferred_qty) @@ -420,6 +432,7 @@ def make_stock_entry(source_name, target_doc=None): target.purpose = "Material Transfer for Manufacture" target.from_bom = 1 target.fg_completed_qty = source.get('for_quantity', 0) - source.get('transferred_qty', 0) + target.set_transfer_qty() target.calculate_rate_and_amount() target.set_missing_values() target.set_stock_entry_type() @@ -437,9 +450,10 @@ def make_stock_entry(source_name, target_doc=None): "field_map": { "source_warehouse": "s_warehouse", "required_qty": "qty", - "uom": "stock_uom" + "name": "job_card_item" }, "postprocess": update_item, + "condition": lambda doc: doc.required_qty > 0 } }, target_doc, set_missing_values) diff --git a/erpnext/manufacturing/doctype/job_card_item/job_card_item.json b/erpnext/manufacturing/doctype/job_card_item/job_card_item.json index bc9fe108ca6..100ef4ca3a3 100644 --- a/erpnext/manufacturing/doctype/job_card_item/job_card_item.json +++ b/erpnext/manufacturing/doctype/job_card_item/job_card_item.json @@ -1,363 +1,120 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2018-07-09 17:20:44.737289", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", + "actions": [], + "creation": "2018-07-09 17:20:44.737289", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "item_code", + "source_warehouse", + "uom", + "item_group", + "column_break_3", + "stock_uom", + "item_name", + "description", + "qty_section", + "required_qty", + "column_break_9", + "transferred_qty", + "allow_alternative_item" + ], "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, - "options": "Item", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "item_code", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Item Code", + "options": "Item", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "source_warehouse", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 1, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Source Warehouse", - "length": 0, - "no_copy": 0, - "options": "Warehouse", - "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": "source_warehouse", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "in_list_view": 1, + "label": "Source Warehouse", + "options": "Warehouse" + }, { - "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, - "options": "UOM", - "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": "uom", + "fieldtype": "Link", + "label": "UOM", + "options": "UOM" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_3", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 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": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Item Name", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "item_name", + "fieldtype": "Data", + "label": "Item Name", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "description", - "fieldtype": "Text", - "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, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "description", + "fieldtype": "Text", + "label": "Description", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "qty_section", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Qty", - "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": "qty_section", + "fieldtype": "Section Break", + "label": "Qty" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "required_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": "Required Qty", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "required_qty", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Required Qty", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_9", - "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_9", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "allow_alternative_item", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Allow Alternative Item", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "default": "0", + "fieldname": "allow_alternative_item", + "fieldtype": "Check", + "label": "Allow Alternative Item" + }, + { + "fetch_from": "item_code.item_group", + "fieldname": "item_group", + "fieldtype": "Link", + "label": "Item Group", + "options": "Item Group", + "read_only": 1 + }, + { + "fetch_from": "item_code.stock_uom", + "fieldname": "stock_uom", + "fieldtype": "Link", + "label": "Stock UOM", + "options": "UOM" + }, + { + "fieldname": "transferred_qty", + "fieldtype": "Float", + "label": "Transferred Qty", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2018-08-28 15:23:48.099459", - "modified_by": "Administrator", - "module": "Manufacturing", - "name": "Job Card Item", - "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-02-11 13:50:13.804108", + "modified_by": "Administrator", + "module": "Manufacturing", + "name": "Job Card Item", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 3833e86d276..8f9dd052175 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -319,7 +319,7 @@ class ProductionPlan(Document): frappe.flags.mute_messages = False if wo_list: - wo_list = ["""%s""" % \ + wo_list = ["""%s""" % \ (p, p) for p in wo_list] msgprint(_("{0} created").format(comma_and(wo_list))) else : @@ -423,7 +423,7 @@ class ProductionPlan(Document): frappe.flags.mute_messages = False if material_request_list: - material_request_list = ["""{1}""".format(m.name, m.name) \ + material_request_list = ["""{1}""".format(m.name, m.name) \ for m in material_request_list] msgprint(_("{0} created").format(comma_and(material_request_list))) else : diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan_list.js b/erpnext/manufacturing/doctype/production_plan/production_plan_list.js index 165b66ff5d6..c2e3e6d7124 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan_list.js +++ b/erpnext/manufacturing/doctype/production_plan/production_plan_list.js @@ -1,16 +1,16 @@ frappe.listview_settings['Production Plan'] = { add_fields: ["status"], filters: [["status", "!=", "Closed"]], - get_indicator: function(doc) { - if(doc.status==="Submitted") { + get_indicator: function (doc) { + if (doc.status === "Submitted") { return [__("Not Started"), "orange", "status,=,Submitted"]; } else { return [__(doc.status), { "Draft": "red", "In Process": "orange", "Completed": "green", - "Material Requested": "darkgrey", - "Cancelled": "darkgrey", + "Material Requested": "yellow", + "Cancelled": "gray", "Closed": "grey" }[doc.status], "status,=," + doc.status]; } diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index ca530bbaddc..3d64ad4318d 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -528,6 +528,10 @@ class WorkOrder(Document): if not reset_only_qty: self.required_items = [] + operation = None + if self.get('operations') and len(self.operations) == 1: + operation = self.operations[0].operation + if self.bom_no and self.qty: item_dict = get_bom_items_as_dict(self.bom_no, self.company, qty=self.qty, fetch_exploded = self.use_multi_level_bom) @@ -536,6 +540,9 @@ class WorkOrder(Document): for d in self.get("required_items"): if item_dict.get(d.item_code): d.required_qty = item_dict.get(d.item_code).get("qty") + + if not d.operation: + d.operation = operation else: # Attribute a big number (999) to idx for sorting putpose in case idx is NULL # For instance in BOM Explosion Item child table, the items coming from sub assembly items @@ -543,7 +550,7 @@ class WorkOrder(Document): self.append('required_items', { 'rate': item.rate, 'amount': item.amount, - 'operation': item.operation, + 'operation': item.operation or operation, 'item_code': item.item_code, 'item_name': item.item_name, 'description': item.description, @@ -879,7 +886,7 @@ def create_job_card(work_order, row, qty=0, enable_capacity_planning=False, auto doc.schedule_time_logs(row) doc.insert() - frappe.msgprint(_("Job card {0} created").format(get_link_to_form("Job Card", doc.name))) + frappe.msgprint(_("Job card {0} created").format(get_link_to_form("Job Card", doc.name)), alert=True) return doc diff --git a/erpnext/manufacturing/doctype/work_order/work_order_list.js b/erpnext/manufacturing/doctype/work_order/work_order_list.js index 8d18395acd4..81c23bb7104 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order_list.js +++ b/erpnext/manufacturing/doctype/work_order/work_order_list.js @@ -12,7 +12,7 @@ frappe.listview_settings['Work Order'] = { "Not Started": "red", "In Process": "orange", "Completed": "green", - "Cancelled": "darkgrey" + "Cancelled": "gray" }[doc.status], "status,=," + doc.status]; } } diff --git a/erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.py b/erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.py index f7b407b7922..ffd9242e1b8 100644 --- a/erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.py +++ b/erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.py @@ -88,11 +88,11 @@ def get_bom_stock(filters): GROUP BY bom_item.item_code""".format(qty_field=qty_field, table=table, conditions=conditions, bom=bom), as_dict=1) def get_manufacturer_records(): - details = frappe.get_list('Item Manufacturer', fields = ["manufacturer", "manufacturer_part_no, parent"]) + details = frappe.get_list('Item Manufacturer', fields = ["manufacturer", "manufacturer_part_no", "parent"]) manufacture_details = frappe._dict() for detail in details: dic = manufacture_details.setdefault(detail.get('parent'), {}) dic.setdefault('manufacturer', []).append(detail.get('manufacturer')) dic.setdefault('manufacturer_part', []).append(detail.get('manufacturer_part_no')) - return manufacture_details \ No newline at end of file + return manufacture_details diff --git a/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.js b/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.js index 8cd016461cc..7beecaceedf 100644 --- a/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.js +++ b/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.js @@ -27,9 +27,9 @@ frappe.query_reports["BOM Stock Report"] = { value = default_formatter(value, row, column, data); if (column.id == "item") { if (data["enough_parts_to_build"] > 0) { - value = `${data['item']}`; + value = `${data['item']}`; } else { - value = `${data['item']}`; + value = `${data['item']}`; } } return value diff --git a/erpnext/manufacturing/workspace/manufacturing/manufacturing.json b/erpnext/manufacturing/workspace/manufacturing/manufacturing.json new file mode 100644 index 00000000000..a355203e4d7 --- /dev/null +++ b/erpnext/manufacturing/workspace/manufacturing/manufacturing.json @@ -0,0 +1,350 @@ +{ + "category": "Domains", + "charts": [ + { + "chart_name": "Produced Quantity" + } + ], + "creation": "2020-03-02 17:11:37.032604", + "developer_mode_only": 0, + "disable_user_customization": 0, + "docstatus": 0, + "doctype": "Workspace", + "extends_another_page": 0, + "hide_custom": 0, + "icon": "organization", + "idx": 0, + "is_standard": 1, + "label": "Manufacturing", + "links": [ + { + "hidden": 0, + "is_query_report": 0, + "label": "Production", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "Item, BOM", + "hidden": 0, + "is_query_report": 0, + "label": "Work Order", + "link_to": "Work Order", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "Item, BOM", + "hidden": 0, + "is_query_report": 0, + "label": "Production Plan", + "link_to": "Production Plan", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "Item", + "hidden": 0, + "is_query_report": 0, + "label": "Stock Entry", + "link_to": "Stock Entry", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Job Card", + "link_to": "Job Card", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Downtime Entry", + "link_to": "Downtime Entry", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Bill of Materials", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Item", + "link_to": "Item", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "Item", + "hidden": 0, + "is_query_report": 0, + "label": "Bill of Materials", + "link_to": "BOM", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Workstation", + "link_to": "Workstation", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Operation", + "link_to": "Operation", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Routing", + "link_to": "Routing", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Reports", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "Work Order", + "hidden": 0, + "is_query_report": 1, + "label": "Production Planning Report", + "link_to": "Production Planning Report", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Work Order", + "hidden": 0, + "is_query_report": 1, + "label": "Work Order Summary", + "link_to": "Work Order Summary", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Quality Inspection", + "hidden": 0, + "is_query_report": 1, + "label": "Quality Inspection Summary", + "link_to": "Quality Inspection Summary", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Downtime Entry", + "hidden": 0, + "is_query_report": 1, + "label": "Downtime Analysis", + "link_to": "Downtime Analysis", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Job Card", + "hidden": 0, + "is_query_report": 1, + "label": "Job Card Summary", + "link_to": "Job Card Summary", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "BOM", + "hidden": 0, + "is_query_report": 1, + "label": "BOM Search", + "link_to": "BOM Search", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "BOM", + "hidden": 0, + "is_query_report": 1, + "label": "BOM Stock Report", + "link_to": "BOM Stock Report", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Work Order", + "hidden": 0, + "is_query_report": 1, + "label": "Production Analytics", + "link_to": "Production Analytics", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "BOM", + "hidden": 0, + "is_query_report": 1, + "label": "BOM Operations Time", + "link_to": "BOM Operations Time", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Tools", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "BOM Update Tool", + "link_to": "BOM Update Tool", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "BOM Comparison Tool", + "link_to": "bom-comparison-tool", + "link_type": "Page", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Settings", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Manufacturing Settings", + "link_to": "Manufacturing Settings", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + } + ], + "modified": "2020-12-01 13:38:39.365928", + "modified_by": "Administrator", + "module": "Manufacturing", + "name": "Manufacturing", + "onboarding": "Manufacturing", + "owner": "Administrator", + "pin_to_bottom": 0, + "pin_to_top": 0, + "restrict_to_domain": "Manufacturing", + "shortcuts": [ + { + "color": "Green", + "format": "{} Active", + "label": "Item", + "link_to": "Item", + "restrict_to_domain": "Manufacturing", + "stats_filter": "{\n \"disabled\": 0\n}", + "type": "DocType" + }, + { + "color": "Green", + "format": "{} Active", + "label": "BOM", + "link_to": "BOM", + "restrict_to_domain": "Manufacturing", + "stats_filter": "{\n \"is_active\": 1\n}", + "type": "DocType" + }, + { + "color": "Yellow", + "format": "{} Open", + "label": "Work Order", + "link_to": "Work Order", + "restrict_to_domain": "Manufacturing", + "stats_filter": "{ \n \"status\": [\"in\", \n [\"Draft\", \"Not Started\", \"In Process\"]\n ]\n}", + "type": "DocType" + }, + { + "color": "Yellow", + "format": "{} Open", + "label": "Production Plan", + "link_to": "Production Plan", + "restrict_to_domain": "Manufacturing", + "stats_filter": "{ \n \"status\": [\"not in\", [\"Completed\"]]\n}", + "type": "DocType" + }, + { + "label": "Forecasting", + "link_to": "Exponential Smoothing Forecasting", + "type": "Report" + }, + { + "label": "Work Order Summary", + "link_to": "Work Order Summary", + "restrict_to_domain": "Manufacturing", + "type": "Report" + }, + { + "label": "BOM Stock Report", + "link_to": "BOM Stock Report", + "type": "Report" + }, + { + "label": "Production Planning Report", + "link_to": "Production Planning Report", + "type": "Report" + }, + { + "label": "Dashboard", + "link_to": "Manufacturing", + "restrict_to_domain": "Manufacturing", + "type": "Dashboard" + } + ] +} \ No newline at end of file diff --git a/erpnext/non_profit/desk_page/non_profit/non_profit.json b/erpnext/non_profit/desk_page/non_profit/non_profit.json deleted file mode 100644 index ebe61948935..00000000000 --- a/erpnext/non_profit/desk_page/non_profit/non_profit.json +++ /dev/null @@ -1,80 +0,0 @@ -{ - "cards": [ - { - "hidden": 0, - "label": "Loan Management", - "links": "[\n {\n \"description\": \"Define various loan types\",\n \"label\": \"Loan Type\",\n \"name\": \"Loan Type\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Loan Application\",\n \"label\": \"Loan Application\",\n \"name\": \"Loan Application\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Loan\",\n \"name\": \"Loan\",\n \"type\": \"doctype\"\n }\n]" - }, - { - "hidden": 0, - "label": "Grant Application", - "links": "[\n {\n \"description\": \"Grant information.\",\n \"label\": \"Grant Application\",\n \"name\": \"Grant Application\",\n \"type\": \"doctype\"\n }\n]" - }, - { - "hidden": 0, - "label": "Membership", - "links": "[\n {\n \"description\": \"Member information.\",\n \"label\": \"Member\",\n \"name\": \"Member\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Membership Details\",\n \"label\": \"Membership\",\n \"name\": \"Membership\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Membership Type Details\",\n \"label\": \"Membership Type\",\n \"name\": \"Membership Type\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Billing and Gateway Settings\",\n \"label\": \"Membership Settings\",\n \"name\": \"Membership Settings\",\n \"type\": \"doctype\"\n }\n]" - }, - { - "hidden": 0, - "label": "Volunteer", - "links": "[\n {\n \"description\": \"Volunteer information.\",\n \"label\": \"Volunteer\",\n \"name\": \"Volunteer\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Volunteer Type information.\",\n \"label\": \"Volunteer Type\",\n \"name\": \"Volunteer Type\",\n \"type\": \"doctype\"\n }\n]" - }, - { - "hidden": 0, - "label": "Chapter", - "links": "[\n {\n \"description\": \"Chapter information.\",\n \"label\": \"Chapter\",\n \"name\": \"Chapter\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n }\n]" - }, - { - "hidden": 0, - "label": "Donor", - "links": "[\n {\n \"description\": \"Donor information.\",\n \"label\": \"Donor\",\n \"name\": \"Donor\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Donor Type information.\",\n \"label\": \"Donor Type\",\n \"name\": \"Donor Type\",\n \"type\": \"doctype\"\n }\n]" - } - ], - "category": "Domains", - "charts": [], - "creation": "2020-03-02 17:23:47.811421", - "developer_mode_only": 0, - "disable_user_customization": 0, - "docstatus": 0, - "doctype": "Desk Page", - "extends_another_page": 0, - "idx": 0, - "is_standard": 1, - "label": "Non Profit", - "modified": "2020-04-13 13:41:52.373705", - "modified_by": "Administrator", - "module": "Non Profit", - "name": "Non Profit", - "owner": "Administrator", - "pin_to_bottom": 0, - "pin_to_top": 0, - "restrict_to_domain": "Non Profit", - "shortcuts": [ - { - "label": "Member", - "link_to": "Member", - "type": "DocType" - }, - { - "label": "Membership Settings", - "link_to": "Membership Settings", - "type": "DocType" - }, - { - "label": "Membership", - "link_to": "Membership", - "type": "DocType" - }, - { - "label": "Chapter", - "link_to": "Chapter", - "type": "DocType" - }, - { - "label": "Chapter Member", - "link_to": "Chapter Member", - "type": "DocType" - } - ] -} \ No newline at end of file diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 20ea5097bfb..59b12f319eb 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -730,6 +730,7 @@ erpnext.patches.v13_0.setting_custom_roles_for_some_regional_reports erpnext.patches.v13_0.rename_issue_doctype_fields erpnext.patches.v13_0.change_default_pos_print_format erpnext.patches.v13_0.set_youtube_video_id +erpnext.patches.v13_0.set_app_name erpnext.patches.v13_0.print_uom_after_quantity_patch erpnext.patches.v13_0.set_payment_channel_in_payment_gateway_account erpnext.patches.v13_0.create_healthcare_custom_fields_in_stock_entry_detail @@ -744,14 +745,17 @@ erpnext.patches.v13_0.create_leave_policy_assignment_based_on_employee_current_l erpnext.patches.v13_0.update_pos_closing_entry_in_merge_log erpnext.patches.v13_0.add_po_to_global_search erpnext.patches.v13_0.update_returned_qty_in_pr_dn +execute:frappe.rename_doc("Workspace", "Loan", "Loan Management", ignore_if_exists=True, force=True) erpnext.patches.v13_0.create_uae_pos_invoice_fields erpnext.patches.v13_0.update_project_template_tasks erpnext.patches.v13_0.set_company_in_leave_ledger_entry erpnext.patches.v13_0.convert_qi_parameter_to_link_field erpnext.patches.v13_0.setup_patient_history_settings_for_standard_doctypes erpnext.patches.v13_0.add_naming_series_to_old_projects # 1-02-2021 -erpnext.patches.v13_0.item_reposting_for_incorrect_sl_and_gl erpnext.patches.v12_0.add_state_code_for_ladakh +erpnext.patches.v13_0.item_reposting_for_incorrect_sl_and_gl +erpnext.patches.v13_0.delete_old_bank_reconciliation_doctypes erpnext.patches.v13_0.update_vehicle_no_reqd_condition erpnext.patches.v13_0.setup_fields_for_80g_certificate_and_donation -erpnext.patches.v13_0.rename_membership_settings_to_non_profit_settings \ No newline at end of file +erpnext.patches.v13_0.rename_membership_settings_to_non_profit_settings +erpnext.patches.v13_0.setup_gratuity_rule_for_india_and_uae diff --git a/erpnext/patches/v11_0/refactor_autoname_naming.py b/erpnext/patches/v11_0/refactor_autoname_naming.py index 5dc5d3bf0cf..b997ba2db22 100644 --- a/erpnext/patches/v11_0/refactor_autoname_naming.py +++ b/erpnext/patches/v11_0/refactor_autoname_naming.py @@ -20,7 +20,7 @@ doctype_series_map = { 'Certified Consultant': 'NPO-CONS-.YYYY.-.#####', 'Chat Room': 'CHAT-ROOM-.#####', 'Compensatory Leave Request': 'HR-CMP-.YY.-.MM.-.#####', - 'Custom Script': 'SYS-SCR-.#####', + 'Client Script': 'SYS-SCR-.#####', 'Employee Benefit Application': 'HR-BEN-APP-.YY.-.MM.-.#####', 'Employee Benefit Application Detail': '', 'Employee Benefit Claim': 'HR-BEN-CLM-.YY.-.MM.-.#####', diff --git a/erpnext/patches/v11_1/update_bank_transaction_status.py b/erpnext/patches/v11_1/update_bank_transaction_status.py index 1acdfcccf9f..544bc5e6911 100644 --- a/erpnext/patches/v11_1/update_bank_transaction_status.py +++ b/erpnext/patches/v11_1/update_bank_transaction_status.py @@ -7,9 +7,20 @@ import frappe def execute(): frappe.reload_doc("accounts", "doctype", "bank_transaction") - frappe.db.sql(""" UPDATE `tabBank Transaction` - SET status = 'Reconciled' - WHERE - status = 'Settled' and (debit = allocated_amount or credit = allocated_amount) - and ifnull(allocated_amount, 0) > 0 - """) \ No newline at end of file + bank_transaction_fields = frappe.get_meta("Bank Transaction").get_valid_columns() + + if 'debit' in bank_transaction_fields: + frappe.db.sql(""" UPDATE `tabBank Transaction` + SET status = 'Reconciled' + WHERE + status = 'Settled' and (debit = allocated_amount or credit = allocated_amount) + and ifnull(allocated_amount, 0) > 0 + """) + + elif 'deposit' in bank_transaction_fields: + frappe.db.sql(""" UPDATE `tabBank Transaction` + SET status = 'Reconciled' + WHERE + status = 'Settled' and (deposit = allocated_amount or withdrawal = allocated_amount) + and ifnull(allocated_amount, 0) > 0 + """) \ No newline at end of file diff --git a/erpnext/patches/v13_0/delete_old_bank_reconciliation_doctypes.py b/erpnext/patches/v13_0/delete_old_bank_reconciliation_doctypes.py new file mode 100644 index 00000000000..af1f6e7ec17 --- /dev/null +++ b/erpnext/patches/v13_0/delete_old_bank_reconciliation_doctypes.py @@ -0,0 +1,26 @@ +# Copyright (c) 2019, Frappe and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals + +import frappe +from frappe.model.utils.rename_field import rename_field + +def execute(): + doctypes = [ + "Bank Statement Settings", + "Bank Statement Settings Item", + "Bank Statement Transaction Entry", + "Bank Statement Transaction Invoice Item", + "Bank Statement Transaction Payment Item", + "Bank Statement Transaction Settings Item", + "Bank Statement Transaction Settings", + ] + + for doctype in doctypes: + frappe.delete_doc("DocType", doctype, force=1) + + frappe.delete_doc("Page", "bank-reconciliation", force=1) + + rename_field("Bank Transaction", "debit", "deposit") + rename_field("Bank Transaction", "credit", "withdrawal") diff --git a/erpnext/patches/v13_0/set_app_name.py b/erpnext/patches/v13_0/set_app_name.py new file mode 100644 index 00000000000..3f886f1d159 --- /dev/null +++ b/erpnext/patches/v13_0/set_app_name.py @@ -0,0 +1,7 @@ +import frappe +from frappe import _ + +def execute(): + frappe.reload_doctype("System Settings") + settings = frappe.get_doc("System Settings") + settings.db_set("app_name", "ERPNext", commit=True) diff --git a/erpnext/patches/v13_0/setup_gratuity_rule_for_india_and_uae.py b/erpnext/patches/v13_0/setup_gratuity_rule_for_india_and_uae.py new file mode 100644 index 00000000000..01fd6a158e9 --- /dev/null +++ b/erpnext/patches/v13_0/setup_gratuity_rule_for_india_and_uae.py @@ -0,0 +1,16 @@ +# Copyright (c) 2019, Frappe and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals +import frappe + +def execute(): + frappe.reload_doc('payroll', 'doctype', 'gratuity_rule') + frappe.reload_doc('payroll', 'doctype', 'gratuity_rule_slab') + frappe.reload_doc('payroll', 'doctype', 'gratuity_applicable_component') + if frappe.db.exists("Company", {"country": "India"}): + from erpnext.regional.india.setup import create_gratuity_rule + create_gratuity_rule() + if frappe.db.exists("Company", {"country": "United Arab Emirates"}): + from erpnext.regional.united_arab_emirates.setup import create_gratuity_rule + create_gratuity_rule() diff --git a/erpnext/patches/v5_0/replace_renamed_fields_in_custom_scripts_and_print_formats.py b/erpnext/patches/v5_0/replace_renamed_fields_in_custom_scripts_and_print_formats.py index ef3f1d6c0a0..c564f8b02ab 100644 --- a/erpnext/patches/v5_0/replace_renamed_fields_in_custom_scripts_and_print_formats.py +++ b/erpnext/patches/v5_0/replace_renamed_fields_in_custom_scripts_and_print_formats.py @@ -9,7 +9,7 @@ def execute(): # NOTE: sequence is important renamed_fields = get_all_renamed_fields() - for dt, script_field, ref_dt_field in (("Custom Script", "script", "dt"), ("Print Format", "html", "doc_type")): + for dt, script_field, ref_dt_field in (("Client Script", "script", "dt"), ("Print Format", "html", "doc_type")): cond1 = " or ".join("""{0} like "%%{1}%%" """.format(script_field, d[0].replace("_", "\\_")) for d in renamed_fields) cond2 = " and standard = 'No'" if dt == "Print Format" else "" diff --git a/erpnext/patches/v7_0/remove_doctypes_and_reports.py b/erpnext/patches/v7_0/remove_doctypes_and_reports.py index 746cae0e1ca..2356e2f6ee4 100644 --- a/erpnext/patches/v7_0/remove_doctypes_and_reports.py +++ b/erpnext/patches/v7_0/remove_doctypes_and_reports.py @@ -7,7 +7,7 @@ def execute(): where name in('Time Log Batch', 'Time Log Batch Detail', 'Time Log')""") frappe.db.sql("""delete from `tabDocField` where parent in ('Time Log', 'Time Log Batch')""") - frappe.db.sql("""update `tabCustom Script` set dt = 'Timesheet' where dt = 'Time Log'""") + frappe.db.sql("""update `tabClient Script` set dt = 'Timesheet' where dt = 'Time Log'""") for data in frappe.db.sql(""" select label, fieldname from `tabCustom Field` where dt = 'Time Log'""", as_dict=1): custom_field = frappe.get_doc({ diff --git a/erpnext/payroll/desk_page/payroll/payroll.json b/erpnext/payroll/desk_page/payroll/payroll.json deleted file mode 100644 index 285e3b3a135..00000000000 --- a/erpnext/payroll/desk_page/payroll/payroll.json +++ /dev/null @@ -1,84 +0,0 @@ -{ - "cards": [ - { - "hidden": 0, - "label": "Payroll", - "links": "[\n {\n \"label\": \"Salary Component\",\n \"name\": \"Salary Component\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n \n },\n {\n \"label\": \"Salary Structure\",\n \"name\": \"Salary Structure\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Salary Structure Assignment\",\n \"name\": \"Salary Structure Assignment\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Payroll Entry\",\n \"name\": \"Payroll Entry\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Salary Slip\",\n \"name\": \"Salary Slip\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n }\n]" - }, - { - "hidden": 0, - "label": "Taxation", - "links": "[\n {\n \"label\": \"Payroll Period\",\n \"name\": \"Payroll Period\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n \n },\n {\n \"label\": \"Income Tax Slab\",\n \"name\": \"Income Tax Slab\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n \n },\n {\n \"label\": \"Employee Other Income\",\n \"name\": \"Employee Other Income\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n \n },\n {\n \"label\": \"Employee Tax Exemption Declaration\",\n \"name\": \"Employee Tax Exemption Declaration\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n \n },\n {\n \"label\": \"Employee Tax Exemption Proof Submission\",\n \"name\": \"Employee Tax Exemption Proof Submission\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n \n },\n {\n \"label\": \"Employee Tax Exemption Category\",\n \"name\": \"Employee Tax Exemption Category\",\n \"type\": \"doctype\"\n \n },\n {\n \"label\": \"Employee Tax Exemption Sub Category\",\n \"name\": \"Employee Tax Exemption Sub Category\",\n \"type\": \"doctype\"\n \n }\n]" - }, - { - "hidden": 0, - "label": "Compensations", - "links": "[\n {\n \"label\": \"Additional Salary\",\n \"name\": \"Additional Salary\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n \n },\n {\n \"label\": \"Retention Bonus\",\n \"name\": \"Retention Bonus\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Employee Incentive\",\n \"name\": \"Employee Incentive\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Employee Benefit Application\",\n \"name\": \"Employee Benefit Application\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Employee Benefit Claim\",\n \"name\": \"Employee Benefit Claim\",\n \"type\": \"doctype\"\n }\n]" - }, - { - "hidden": 0, - "label": "Reports", - "links": "[\n {\n \"dependencies\": [\n \"Salary Slip\"\n ],\n \"doctype\": \"Salary Slip\",\n \"is_query_report\": true,\n \"label\": \"Salary Register\",\n \"name\": \"Salary Register\",\n \"type\": \"report\"\n \n },\n {\n \"dependencies\": [\n \"Salary Slip\"\n ],\n \"doctype\": \"Salary Slip\",\n \"label\": \"Salary Payments Based On Payment Mode\",\n \"is_query_report\": true,\n \"name\": \"Salary Payments Based On Payment Mode\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Salary Slip\"\n ],\n \"doctype\": \"Salary Slip\",\n \"label\": \"Salary Payments via ECS\",\n \"is_query_report\": true,\n \"name\": \"Salary Payments via ECS\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Salary Slip\"\n ],\n \"doctype\": \"Salary Slip\",\n \"label\": \"Income Tax Deductions\",\n \"is_query_report\": true,\n \"name\": \"Income Tax Deductions\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Salary Slip\"\n ],\n \"doctype\": \"Salary Slip\",\n \"label\": \"Professional Tax Deductions\",\n \"is_query_report\": true,\n \"name\": \"Professional Tax Deductions\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Salary Slip\"\n ],\n \"doctype\": \"Salary Slip\",\n \"label\": \"Provident Fund Deductions\",\n \"is_query_report\": true,\n \"name\": \"Provident Fund Deductions\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Payroll Entry\"\n ],\n \"doctype\": \"Payroll Entry\",\n \"is_query_report\": true,\n \"label\": \"Bank Remittance\",\n \"name\": \"Bank Remittance\",\n \"type\": \"report\"\n \n }\n]" - } - ], - "category": "Modules", - "charts": [ - { - "chart_name": "Outgoing Salary", - "label": "Outgoing Salary" - } - ], - "creation": "2020-05-27 19:54:23.405607", - "developer_mode_only": 0, - "disable_user_customization": 0, - "docstatus": 0, - "doctype": "Desk Page", - "extends_another_page": 0, - "hide_custom": 0, - "idx": 0, - "is_standard": 1, - "label": "Payroll", - "modified": "2020-08-10 19:38:45.976209", - "modified_by": "Administrator", - "module": "Payroll", - "name": "Payroll", - "onboarding": "Payroll", - "owner": "Administrator", - "pin_to_bottom": 0, - "pin_to_top": 0, - "shortcuts": [ - { - "label": "Salary Structure", - "link_to": "Salary Structure", - "type": "DocType" - }, - { - "label": "Payroll Entry", - "link_to": "Payroll Entry", - "type": "DocType" - }, - { - "color": "", - "format": "{} Pending", - "label": "Salary Slip", - "link_to": "Salary Slip", - "stats_filter": "{\"status\": \"Draft\"}", - "type": "DocType" - }, - { - "label": "Income Tax Slab", - "link_to": "Income Tax Slab", - "type": "DocType" - }, - { - "label": "Salary Register", - "link_to": "Salary Register", - "type": "Report" - }, - { - "label": "Dashboard", - "link_to": "Payroll", - "type": "Dashboard" - } - ] -} \ No newline at end of file diff --git a/erpnext/accounts/doctype/bank_statement_transaction_payment_item/__init__.py b/erpnext/payroll/doctype/gratuity/__init__.py similarity index 100% rename from erpnext/accounts/doctype/bank_statement_transaction_payment_item/__init__.py rename to erpnext/payroll/doctype/gratuity/__init__.py diff --git a/erpnext/payroll/doctype/gratuity/gratuity.js b/erpnext/payroll/doctype/gratuity/gratuity.js new file mode 100644 index 00000000000..565d2c49f94 --- /dev/null +++ b/erpnext/payroll/doctype/gratuity/gratuity.js @@ -0,0 +1,72 @@ +// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Gratuity', { + setup: function (frm) { + frm.set_query('salary_component', function () { + return { + filters: { + type: "Earning" + } + }; + }); + frm.set_query("expense_account", function () { + return { + filters: { + "root_type": "Expense", + "is_group": 0, + "company": frm.doc.company + } + }; + }); + + frm.set_query("payable_account", function () { + return { + filters: { + "root_type": "Liability", + "is_group": 0, + "company": frm.doc.company + } + }; + }); + }, + refresh: function (frm) { + if (frm.doc.docstatus === 1 && frm.doc.pay_via_salary_slip === 0 && frm.doc.status === "Unpaid") { + frm.add_custom_button(__("Create Payment Entry"), function () { + return frappe.call({ + method: 'erpnext.accounts.doctype.payment_entry.payment_entry.get_payment_entry', + args: { + "dt": frm.doc.doctype, + "dn": frm.doc.name + }, + callback: function (r) { + var doclist = frappe.model.sync(r.message); + frappe.set_route("Form", doclist[0].doctype, doclist[0].name); + } + }); + }); + } + }, + employee: function (frm) { + frm.events.calculate_work_experience_and_amount(frm); + }, + gratuity_rule: function (frm) { + frm.events.calculate_work_experience_and_amount(frm); + }, + calculate_work_experience_and_amount: function (frm) { + + if (frm.doc.employee && frm.doc.gratuity_rule) { + frappe.call({ + method: "erpnext.payroll.doctype.gratuity.gratuity.calculate_work_experience_and_amount", + args: { + employee: frm.doc.employee, + gratuity_rule: frm.doc.gratuity_rule + } + }).then((r) => { + frm.set_value("current_work_experience", r.message['current_work_experience']); + frm.set_value("amount", r.message['amount']); + }); + } + } + +}); \ No newline at end of file diff --git a/erpnext/payroll/doctype/gratuity/gratuity.json b/erpnext/payroll/doctype/gratuity/gratuity.json new file mode 100644 index 00000000000..5cffd7eebf9 --- /dev/null +++ b/erpnext/payroll/doctype/gratuity/gratuity.json @@ -0,0 +1,232 @@ +{ + "actions": [], + "autoname": "HR-GRA-PAY-.#####", + "creation": "2020-08-05 20:52:13.024683", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "employee", + "employee_name", + "department", + "designation", + "column_break_3", + "posting_date", + "status", + "company", + "gratuity_rule", + "section_break_5", + "pay_via_salary_slip", + "payroll_date", + "salary_component", + "payable_account", + "expense_account", + "mode_of_payment", + "cost_center", + "column_break_15", + "current_work_experience", + "amount", + "paid_amount", + "amended_from" + ], + "fields": [ + { + "fieldname": "employee", + "fieldtype": "Link", + "in_global_search": 1, + "in_list_view": 1, + "label": "Employee", + "options": "Employee", + "reqd": 1, + "search_index": 1 + }, + { + "fetch_from": "employee.company", + "fieldname": "company", + "fieldtype": "Link", + "label": "Company", + "options": "Company", + "read_only": 1, + "reqd": 1 + }, + { + "default": "1", + "fieldname": "pay_via_salary_slip", + "fieldtype": "Check", + "label": "Pay via Salary Slip" + }, + { + "fieldname": "posting_date", + "fieldtype": "Date", + "label": "Posting date", + "reqd": 1 + }, + { + "depends_on": "eval: doc.pay_via_salary_slip == 1", + "fieldname": "salary_component", + "fieldtype": "Link", + "label": "Salary Component", + "mandatory_depends_on": "eval: doc.pay_via_salary_slip == 1", + "options": "Salary Component" + }, + { + "default": "0", + "fieldname": "current_work_experience", + "fieldtype": "Int", + "label": "Current Work Experience", + "read_only": 1 + }, + { + "default": "0", + "fieldname": "amount", + "fieldtype": "Currency", + "label": "Total Amount", + "read_only": 1, + "reqd": 1 + }, + { + "default": "Draft", + "fieldname": "status", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Status", + "options": "Draft\nUnpaid\nPaid", + "read_only": 1, + "reqd": 1 + }, + { + "depends_on": "eval: doc.pay_via_salary_slip == 0", + "fieldname": "expense_account", + "fieldtype": "Link", + "label": "Expense Account", + "mandatory_depends_on": "eval: doc.pay_via_salary_slip == 0", + "options": "Account" + }, + { + "depends_on": "eval: doc.pay_via_salary_slip == 0", + "fieldname": "mode_of_payment", + "fieldtype": "Link", + "label": "Mode of Payment", + "mandatory_depends_on": "eval: doc.pay_via_salary_slip == 0", + "options": "Mode of Payment" + }, + { + "fieldname": "gratuity_rule", + "fieldtype": "Link", + "label": "Gratuity Rule", + "options": "Gratuity Rule", + "reqd": 1 + }, + { + "fieldname": "section_break_5", + "fieldtype": "Section Break", + "label": "Payment Configuration" + }, + { + "fetch_from": "employee.employee_name", + "fieldname": "employee_name", + "fieldtype": "Data", + "label": "Employee Name", + "read_only": 1 + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "fetch_from": "employee.department", + "fieldname": "department", + "fieldtype": "Link", + "label": "Department", + "options": "Department", + "read_only": 1 + }, + { + "fetch_from": "employee.designation", + "fieldname": "designation", + "fieldtype": "Data", + "label": "Designation", + "read_only": 1 + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Gratuity", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "column_break_15", + "fieldtype": "Column Break" + }, + { + "depends_on": "eval: doc.pay_via_salary_slip == 1", + "fieldname": "payroll_date", + "fieldtype": "Date", + "label": "Payroll Date", + "mandatory_depends_on": "eval: doc.pay_via_salary_slip == 1" + }, + { + "default": "0", + "depends_on": "eval:doc.pay_via_salary_slip == 0", + "fieldname": "paid_amount", + "fieldtype": "Currency", + "label": "Paid Amount", + "read_only": 1 + }, + { + "depends_on": "eval: doc.pay_via_salary_slip == 0", + "fieldname": "payable_account", + "fieldtype": "Link", + "label": "Payable Account", + "mandatory_depends_on": "eval: doc.pay_via_salary_slip == 0", + "options": "Account" + }, + { + "depends_on": "eval: doc.pay_via_salary_slip == 0", + "fieldname": "cost_center", + "fieldtype": "Link", + "label": "Cost Center", + "mandatory_depends_on": "eval: doc.pay_via_salary_slip == 0", + "options": "Cost Center" + } + ], + "index_web_pages_for_search": 1, + "is_submittable": 1, + "links": [], + "modified": "2020-11-02 18:21:11.971488", + "modified_by": "Administrator", + "module": "Payroll", + "name": "Gratuity", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "HR Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "HR User", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC" +} \ No newline at end of file diff --git a/erpnext/payroll/doctype/gratuity/gratuity.py b/erpnext/payroll/doctype/gratuity/gratuity.py new file mode 100644 index 00000000000..1acd6e342fd --- /dev/null +++ b/erpnext/payroll/doctype/gratuity/gratuity.py @@ -0,0 +1,249 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe import _, bold +from frappe.utils import flt, get_datetime, get_link_to_form +from erpnext.accounts.general_ledger import make_gl_entries +from erpnext.controllers.accounts_controller import AccountsController +from math import floor + +class Gratuity(AccountsController): + def validate(self): + data = calculate_work_experience_and_amount(self.employee, self.gratuity_rule) + self.current_work_experience = data["current_work_experience"] + self.amount = data["amount"] + if self.docstatus == 1: + self.status = "Unpaid" + + def on_submit(self): + if self.pay_via_salary_slip: + self.create_additional_salary() + else: + self.create_gl_entries() + + def on_cancel(self): + self.ignore_linked_doctypes = ['GL Entry'] + self.create_gl_entries(cancel=True) + + def create_gl_entries(self, cancel=False): + gl_entries = self.get_gl_entries() + make_gl_entries(gl_entries, cancel) + + def get_gl_entries(self): + gl_entry = [] + # payable entry + if self.amount: + gl_entry.append( + self.get_gl_dict({ + "account": self.payable_account, + "credit": self.amount, + "credit_in_account_currency": self.amount, + "against": self.expense_account, + "party_type": "Employee", + "party": self.employee, + "against_voucher_type": self.doctype, + "against_voucher": self.name, + "cost_center": self.cost_center + }, item=self) + ) + + # expense entries + gl_entry.append( + self.get_gl_dict({ + "account": self.expense_account, + "debit": self.amount, + "debit_in_account_currency": self.amount, + "against": self.payable_account, + "cost_center": self.cost_center + }, item=self) + ) + else: + frappe.throw(_("Total Amount can not be zero")) + + return gl_entry + + def create_additional_salary(self): + if self.pay_via_salary_slip: + additional_salary = frappe.new_doc('Additional Salary') + additional_salary.employee = self.employee + additional_salary.salary_component = self.salary_component + additional_salary.overwrite_salary_structure_amount = 0 + additional_salary.amount = self.amount + additional_salary.payroll_date = self.payroll_date + additional_salary.company = self.company + additional_salary.ref_doctype = self.doctype + additional_salary.ref_docname = self.name + additional_salary.submit() + + def set_total_advance_paid(self): + paid_amount = frappe.db.sql(""" + select ifnull(sum(debit_in_account_currency), 0) as paid_amount + from `tabGL Entry` + where against_voucher_type = 'Gratuity' + and against_voucher = %s + and party_type = 'Employee' + and party = %s + """, (self.name, self.employee), as_dict=1)[0].paid_amount + + if flt(paid_amount) > self.amount: + frappe.throw(_("Row {0}# Paid Amount cannot be greater than Total amount")) + + + self.db_set("paid_amount", paid_amount) + if self.amount == self.paid_amount: + self.db_set("status", "Paid") + + +@frappe.whitelist() +def calculate_work_experience_and_amount(employee, gratuity_rule): + current_work_experience = calculate_work_experience(employee, gratuity_rule) or 0 + gratuity_amount = calculate_gratuity_amount(employee, gratuity_rule, current_work_experience) or 0 + + return {'current_work_experience': current_work_experience, "amount": gratuity_amount} + +def calculate_work_experience(employee, gratuity_rule): + + total_working_days_per_year, minimum_year_for_gratuity = frappe.db.get_value("Gratuity Rule", gratuity_rule, ["total_working_days_per_year", "minimum_year_for_gratuity"]) + + date_of_joining, relieving_date = frappe.db.get_value('Employee', employee, ['date_of_joining', 'relieving_date']) + if not relieving_date: + frappe.throw(_("Please set Relieving Date for employee: {0}").format(bold(get_link_to_form("Employee", employee)))) + + method = frappe.db.get_value("Gratuity Rule", gratuity_rule, "work_experience_calculation_function") + employee_total_workings_days = calculate_employee_total_workings_days(employee, date_of_joining, relieving_date) + + current_work_experience = employee_total_workings_days/total_working_days_per_year or 1 + current_work_experience = get_work_experience_using_method(method, current_work_experience, minimum_year_for_gratuity, employee) + return current_work_experience + +def calculate_employee_total_workings_days(employee, date_of_joining, relieving_date ): + employee_total_workings_days = (get_datetime(relieving_date) - get_datetime(date_of_joining)).days + + payroll_based_on = frappe.db.get_value("Payroll Settings", None, "payroll_based_on") or "Leave" + if payroll_based_on == "Leave": + total_lwp = get_non_working_days(employee, relieving_date, "On Leave") + employee_total_workings_days -= total_lwp + elif payroll_based_on == "Attendance": + total_absents = get_non_working_days(employee, relieving_date, "Absent") + employee_total_workings_days -= total_absents + + return employee_total_workings_days + +def get_work_experience_using_method(method, current_work_experience, minimum_year_for_gratuity, employee): + if method == "Round off Work Experience": + current_work_experience = round(current_work_experience) + else: + current_work_experience = floor(current_work_experience) + + if current_work_experience < minimum_year_for_gratuity: + frappe.throw(_("Employee: {0} have to complete minimum {1} years for gratuity").format(bold(employee), minimum_year_for_gratuity)) + return current_work_experience + +def get_non_working_days(employee, relieving_date, status): + + filters={ + "docstatus": 1, + "status": status, + "employee": employee, + "attendance_date": ("<=", get_datetime(relieving_date)) + } + + if status == "On Leave": + lwp_leave_types = frappe.get_list("Leave Type", filters = {"is_lwp":1}) + lwp_leave_types = [leave_type.name for leave_type in lwp_leave_types] + filters["leave_type"] = ("IN", lwp_leave_types) + + + record = frappe.get_all("Attendance", filters=filters, fields = ["COUNT(name) as total_lwp"]) + return record[0].total_lwp if len(record) else 0 + +def calculate_gratuity_amount(employee, gratuity_rule, experience): + applicable_earnings_component = get_applicable_components(gratuity_rule) + total_applicable_components_amount = get_total_applicable_component_amount(employee, applicable_earnings_component, gratuity_rule) + + calculate_gratuity_amount_based_on = frappe.db.get_value("Gratuity Rule", gratuity_rule, "calculate_gratuity_amount_based_on") + gratuity_amount = 0 + slabs = get_gratuity_rule_slabs(gratuity_rule) + slab_found = False + year_left = experience + + for slab in slabs: + if calculate_gratuity_amount_based_on == "Current Slab": + slab_found, gratuity_amount = calculate_amount_based_on_current_slab(slab.from_year, slab.to_year, + experience, total_applicable_components_amount, slab.fraction_of_applicable_earnings) + if slab_found: + break + + elif calculate_gratuity_amount_based_on == "Sum of all previous slabs": + if slab.to_year == 0 and slab.from_year == 0: + gratuity_amount += year_left * total_applicable_components_amount * slab.fraction_of_applicable_earnings + slab_found = True + break + + if experience > slab.to_year and experience > slab.from_year and slab.to_year !=0: + gratuity_amount += (slab.to_year - slab.from_year) * total_applicable_components_amount * slab.fraction_of_applicable_earnings + year_left -= (slab.to_year - slab.from_year) + slab_found = True + elif slab.from_year <= experience and (experience < slab.to_year or slab.to_year == 0): + gratuity_amount += year_left * total_applicable_components_amount * slab.fraction_of_applicable_earnings + slab_found = True + + if not slab_found: + frappe.throw(_("No Suitable Slab found for Calculation of gratuity amount in Gratuity Rule: {0}").format(bold(gratuity_rule))) + return gratuity_amount + +def get_applicable_components(gratuity_rule): + applicable_earnings_component = frappe.get_all("Gratuity Applicable Component", filters= {'parent': gratuity_rule}, fields=["salary_component"]) + if len(applicable_earnings_component) == 0: + frappe.throw(_("No Applicable Earnings Component found for Gratuity Rule: {0}").format(bold(get_link_to_form("Gratuity Rule",gratuity_rule)))) + applicable_earnings_component = [component.salary_component for component in applicable_earnings_component] + + return applicable_earnings_component + +def get_total_applicable_component_amount(employee, applicable_earnings_component, gratuity_rule): + sal_slip = get_last_salary_slip(employee) + if not sal_slip: + frappe.throw(_("No Salary Slip is found for Employee: {0}").format(bold(employee))) + component_and_amounts = frappe.get_list("Salary Detail", + filters={ + "docstatus": 1, + 'parent': sal_slip, + "parentfield": "earnings", + 'salary_component': ('in', applicable_earnings_component) + }, + fields=["amount"]) + total_applicable_components_amount = 0 + if not len(component_and_amounts): + frappe.throw(_("No Applicable Component is present in last month salary slip")) + for data in component_and_amounts: + total_applicable_components_amount += data.amount + return total_applicable_components_amount + +def calculate_amount_based_on_current_slab(from_year, to_year, experience, total_applicable_components_amount, fraction_of_applicable_earnings): + slab_found = False; gratuity_amount = 0 + if experience >= from_year and (to_year == 0 or experience < to_year): + gratuity_amount = total_applicable_components_amount * experience * fraction_of_applicable_earnings + if fraction_of_applicable_earnings: + slab_found = True + + return slab_found, gratuity_amount + +def get_gratuity_rule_slabs(gratuity_rule): + return frappe.get_all("Gratuity Rule Slab", filters= {'parent': gratuity_rule}, fields = ["*"], order_by="idx") + +def get_salary_structure(employee): + return frappe.get_list("Salary Structure Assignment", filters = { + "employee": employee, 'docstatus': 1 + }, + fields=["from_date", "salary_structure"], + order_by = "from_date desc")[0].salary_structure + +def get_last_salary_slip(employee): + return frappe.get_list("Salary Slip", filters = { + "employee": employee, 'docstatus': 1 + }, + order_by = "start_date desc")[0].name + diff --git a/erpnext/payroll/doctype/gratuity/gratuity_dashboard.py b/erpnext/payroll/doctype/gratuity/gratuity_dashboard.py new file mode 100644 index 00000000000..5b2489f22cd --- /dev/null +++ b/erpnext/payroll/doctype/gratuity/gratuity_dashboard.py @@ -0,0 +1,20 @@ +from __future__ import unicode_literals +from frappe import _ + +def get_data(): + return { + 'fieldname': 'reference_name', + 'non_standard_fieldnames': { + 'Additional Salary': 'ref_docname', + }, + 'transactions': [ + { + 'label': _('Payment'), + 'items': ['Payment Entry'] + }, + { + 'label': _('Additional Salary'), + 'items': ['Additional Salary'] + } + ] + } \ No newline at end of file diff --git a/erpnext/payroll/doctype/gratuity/test_gratuity.py b/erpnext/payroll/doctype/gratuity/test_gratuity.py new file mode 100644 index 00000000000..e89e3dd077a --- /dev/null +++ b/erpnext/payroll/doctype/gratuity/test_gratuity.py @@ -0,0 +1,192 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +import frappe +import unittest +from erpnext.hr.doctype.employee.test_employee import make_employee +from erpnext.payroll.doctype.salary_slip.test_salary_slip import make_employee_salary_slip, make_earning_salary_component, \ + make_deduction_salary_component +from erpnext.payroll.doctype.gratuity.gratuity import get_last_salary_slip +from erpnext.regional.united_arab_emirates.setup import create_gratuity_rule +from erpnext.hr.doctype.expense_claim.test_expense_claim import get_payable_account +from frappe.utils import getdate, add_days, get_datetime, flt + +test_dependencies = ["Salary Component", "Salary Slip", "Account"] +class TestGratuity(unittest.TestCase): + def setUp(self): + make_earning_salary_component(setup=True, test_tax=True, company_list=['_Test Company']) + make_deduction_salary_component(setup=True, test_tax=True, company_list=['_Test Company']) + frappe.db.sql("DELETE FROM `tabGratuity`") + frappe.db.sql("DELETE FROM `tabAdditional Salary` WHERE ref_doctype = 'Gratuity'") + + def test_check_gratuity_amount_based_on_current_slab_and_additional_salary_creation(self): + employee, sal_slip = create_employee_and_get_last_salary_slip() + + rule = get_gratuity_rule("Rule Under Unlimited Contract on termination (UAE)") + + gratuity = create_gratuity(pay_via_salary_slip = 1, employee=employee, rule=rule.name) + + #work experience calculation + date_of_joining, relieving_date = frappe.db.get_value('Employee', employee, ['date_of_joining', 'relieving_date']) + employee_total_workings_days = (get_datetime(relieving_date) - get_datetime(date_of_joining)).days + + experience = employee_total_workings_days/rule.total_working_days_per_year + gratuity.reload() + from math import floor + self.assertEqual(floor(experience), gratuity.current_work_experience) + + #amount Calculation + component_amount = frappe.get_list("Salary Detail", + filters={ + "docstatus": 1, + 'parent': sal_slip, + "parentfield": "earnings", + 'salary_component': "Basic Salary" + }, + fields=["amount"]) + + ''' 5 - 0 fraction is 1 ''' + + gratuity_amount = component_amount[0].amount * experience + gratuity.reload() + + self.assertEqual(flt(gratuity_amount, 2), flt(gratuity.amount, 2)) + + #additional salary creation (Pay via salary slip) + self.assertTrue(frappe.db.exists("Additional Salary", {"ref_docname": gratuity.name})) + + def test_check_gratuity_amount_based_on_all_previous_slabs(self): + employee, sal_slip = create_employee_and_get_last_salary_slip() + rule = get_gratuity_rule("Rule Under Limited Contract (UAE)") + set_mode_of_payment_account() + + gratuity = create_gratuity(expense_account = 'Payment Account - _TC', mode_of_payment='Cash', employee=employee) + + #work experience calculation + date_of_joining, relieving_date = frappe.db.get_value('Employee', employee, ['date_of_joining', 'relieving_date']) + employee_total_workings_days = (get_datetime(relieving_date) - get_datetime(date_of_joining)).days + + experience = employee_total_workings_days/rule.total_working_days_per_year + + gratuity.reload() + + from math import floor + + self.assertEqual(floor(experience), gratuity.current_work_experience) + + #amount Calculation + component_amount = frappe.get_list("Salary Detail", + filters={ + "docstatus": 1, + 'parent': sal_slip, + "parentfield": "earnings", + 'salary_component': "Basic Salary" + }, + fields=["amount"]) + + ''' range | Fraction + 0-1 | 0 + 1-5 | 0.7 + 5-0 | 1 + ''' + + gratuity_amount = ((0 * 1) + (4 * 0.7) + (1 * 1)) * component_amount[0].amount + gratuity.reload() + + self.assertEqual(flt(gratuity_amount, 2), flt(gratuity.amount, 2)) + self.assertEqual(gratuity.status, "Unpaid") + + from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry + pay_entry = get_payment_entry("Gratuity", gratuity.name) + pay_entry.reference_no = "123467" + pay_entry.reference_date = getdate() + pay_entry.save() + pay_entry.submit() + gratuity.reload() + + self.assertEqual(gratuity.status, "Paid") + self.assertEqual(flt(gratuity.paid_amount,2), flt(gratuity.amount, 2)) + + def tearDown(self): + frappe.db.sql("DELETE FROM `tabGratuity`") + frappe.db.sql("DELETE FROM `tabAdditional Salary` WHERE ref_doctype = 'Gratuity'") + +def get_gratuity_rule(name): + rule = frappe.db.exists("Gratuity Rule", name) + if not rule: + create_gratuity_rule() + rule = frappe.get_doc("Gratuity Rule", name) + rule.applicable_earnings_component = [] + rule.append("applicable_earnings_component", { + "salary_component": "Basic Salary" + }) + rule.save() + rule.reload() + + return rule + +def create_gratuity(**args): + if args: + args = frappe._dict(args) + gratuity = frappe.new_doc("Gratuity") + gratuity.employee = args.employee + gratuity.posting_date = getdate() + gratuity.gratuity_rule = args.rule or "Rule Under Limited Contract (UAE)" + gratuity.pay_via_salary_slip = args.pay_via_salary_slip or 0 + if gratuity.pay_via_salary_slip: + gratuity.payroll_date = getdate() + gratuity.salary_component = "Performance Bonus" + else: + gratuity.expense_account = args.expense_account or 'Payment Account - _TC' + gratuity.payable_account = args.payable_account or get_payable_account("_Test Company") + gratuity.mode_of_payment = args.mode_of_payment or 'Cash' + + gratuity.save() + gratuity.submit() + + return gratuity + +def set_mode_of_payment_account(): + if not frappe.db.exists("Account", "Payment Account - _TC"): + mode_of_payment = create_account() + + mode_of_payment = frappe.get_doc("Mode of Payment", "Cash") + + mode_of_payment.accounts = [] + mode_of_payment.append("accounts", { + "company": "_Test Company", + "default_account": "_Test Bank - _TC" + }) + mode_of_payment.save() + +def create_account(): + return frappe.get_doc({ + "doctype": "Account", + "company": "_Test Company", + "account_name": "Payment Account", + "root_type": "Asset", + "report_type": "Balance Sheet", + "currency": "INR", + "parent_account": "Bank Accounts - _TC", + "account_type": "Bank", + }).insert(ignore_permissions=True) + +def create_employee_and_get_last_salary_slip(): + employee = make_employee("test_employee@salary.com", company='_Test Company') + frappe.db.set_value("Employee", employee, "relieving_date", getdate()) + frappe.db.set_value("Employee", employee, "date_of_joining", add_days(getdate(), - (6*365))) + if not frappe.db.exists("Salary Slip", {"employee":employee}): + salary_slip = make_employee_salary_slip("test_employee@salary.com", "Monthly") + salary_slip.submit() + salary_slip = salary_slip.name + else: + salary_slip = get_last_salary_slip(employee) + + if not frappe.db.get_value("Employee", "test_employee@salary.com", "holiday_list"): + from erpnext.payroll.doctype.salary_slip.test_salary_slip import make_holiday_list + make_holiday_list() + frappe.db.set_value("Company", '_Test Company', "default_holiday_list", "Salary Slip Test Holiday List") + + return employee, salary_slip diff --git a/erpnext/accounts/doctype/bank_statement_transaction_settings/__init__.py b/erpnext/payroll/doctype/gratuity_applicable_component/__init__.py similarity index 100% rename from erpnext/accounts/doctype/bank_statement_transaction_settings/__init__.py rename to erpnext/payroll/doctype/gratuity_applicable_component/__init__.py diff --git a/erpnext/payroll/doctype/gratuity_applicable_component/gratuity_applicable_component.json b/erpnext/payroll/doctype/gratuity_applicable_component/gratuity_applicable_component.json new file mode 100644 index 00000000000..eea0e852b17 --- /dev/null +++ b/erpnext/payroll/doctype/gratuity_applicable_component/gratuity_applicable_component.json @@ -0,0 +1,32 @@ +{ + "actions": [], + "creation": "2020-08-05 19:00:28.097265", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "salary_component" + ], + "fields": [ + { + "fieldname": "salary_component", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Salary Component ", + "options": "Salary Component", + "reqd": 1 + } + ], + "istable": 1, + "links": [], + "modified": "2020-08-05 20:17:13.855035", + "modified_by": "Administrator", + "module": "Payroll", + "name": "Gratuity Applicable Component", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/bank_statement_transaction_invoice_item/bank_statement_transaction_invoice_item.py b/erpnext/payroll/doctype/gratuity_applicable_component/gratuity_applicable_component.py similarity index 55% rename from erpnext/accounts/doctype/bank_statement_transaction_invoice_item/bank_statement_transaction_invoice_item.py rename to erpnext/payroll/doctype/gratuity_applicable_component/gratuity_applicable_component.py index cb1b15815fb..23e4340b04f 100644 --- a/erpnext/accounts/doctype/bank_statement_transaction_invoice_item/bank_statement_transaction_invoice_item.py +++ b/erpnext/payroll/doctype/gratuity_applicable_component/gratuity_applicable_component.py @@ -1,10 +1,10 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2017, sathishpy@gmail.com and contributors +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt from __future__ import unicode_literals -import frappe +# import frappe from frappe.model.document import Document -class BankStatementTransactionInvoiceItem(Document): +class GratuityApplicableComponent(Document): pass diff --git a/erpnext/accounts/doctype/bank_statement_transaction_settings_item/__init__.py b/erpnext/payroll/doctype/gratuity_rule/__init__.py similarity index 100% rename from erpnext/accounts/doctype/bank_statement_transaction_settings_item/__init__.py rename to erpnext/payroll/doctype/gratuity_rule/__init__.py diff --git a/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.js b/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.js new file mode 100644 index 00000000000..ee6c5df7371 --- /dev/null +++ b/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.js @@ -0,0 +1,40 @@ +// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Gratuity Rule', { + // refresh: function(frm) { + + // } +}); + +frappe.ui.form.on('Gratuity Rule Slab', { + + /* + Slabs should be in order like + + from | to | fraction + 0 | 4 | 0.5 + 4 | 6 | 0.7 + + So, on row addition setting current_row.from = previous row.to. + On to_year insert we have to check that it is not less than from_year + + Wrong order may lead to Wrong Calculation + */ + + gratuity_rule_slabs_add(frm, cdt, cdn) { + let row = locals[cdt][cdn]; + let array_idx = row.idx - 1; + if (array_idx > 0) { + row.from_year = cur_frm.doc.gratuity_rule_slabs[array_idx - 1].to_year; + frm.refresh(); + } + }, + + to_year(frm, cdt, cdn) { + let row = locals[cdt][cdn]; + if (row.to_year <= row.from_year && row.to_year === 0) { + frappe.throw(__("To(Year) year can not be less than From(year) ")); + } + } +}); \ No newline at end of file diff --git a/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.json b/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.json new file mode 100644 index 00000000000..84cdcf50386 --- /dev/null +++ b/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.json @@ -0,0 +1,114 @@ +{ + "actions": [], + "autoname": "Prompt", + "creation": "2020-08-05 19:00:36.103500", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "applicable_earnings_component", + "work_experience_calculation_function", + "total_working_days_per_year", + "column_break_3", + "disable", + "calculate_gratuity_amount_based_on", + "minimum_year_for_gratuity", + "gratuity_rules_section", + "gratuity_rule_slabs" + ], + "fields": [ + { + "default": "0", + "fieldname": "disable", + "fieldtype": "Check", + "label": "Disable" + }, + { + "fieldname": "calculate_gratuity_amount_based_on", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Calculate Gratuity Amount Based On", + "options": "Current Slab\nSum of all previous slabs", + "reqd": 1 + }, + { + "description": "Salary components should be part of the Salary Structure.", + "fieldname": "applicable_earnings_component", + "fieldtype": "Table MultiSelect", + "label": "Applicable Earnings Component", + "options": "Gratuity Applicable Component", + "reqd": 1 + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "fieldname": "gratuity_rules_section", + "fieldtype": "Section Break", + "label": "Gratuity Rules" + }, + { + "description": "Leave From and To 0 for no upper and lower limit.", + "fieldname": "gratuity_rule_slabs", + "fieldtype": "Table", + "label": "Current Work Experience", + "options": "Gratuity Rule Slab", + "reqd": 1 + }, + { + "default": "Round off Work Experience", + "fieldname": "work_experience_calculation_function", + "fieldtype": "Select", + "label": "Work Experience Calculation method", + "options": "Round off Work Experience\nTake Exact Completed Years" + }, + { + "default": "365", + "fieldname": "total_working_days_per_year", + "fieldtype": "Int", + "label": "Total working Days Per Year" + }, + { + "fieldname": "minimum_year_for_gratuity", + "fieldtype": "Int", + "label": "Minimum Year for Gratuity" + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2020-12-03 17:08:27.891535", + "modified_by": "Administrator", + "module": "Payroll", + "name": "Gratuity Rule", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "HR Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "HR User", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.py b/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.py new file mode 100644 index 00000000000..29a6ebe1a6a --- /dev/null +++ b/erpnext/payroll/doctype/gratuity_rule/gratuity_rule.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.model.document import Document +from frappe import _ + +class GratuityRule(Document): + + def validate(self): + for current_slab in self.gratuity_rule_slabs: + if (current_slab.from_year > current_slab.to_year) and current_slab.to_year != 0: + frappe(_("Row {0}: From (Year) can not be greater than To (Year)").format(current_slab.idx)) + + if current_slab.to_year == 0 and current_slab.from_year == 0 and len(self.gratuity_rule_slabs) > 1: + frappe.throw(_("You can not define multiple slabs if you have a slab with no lower and upper limits.")) + +def get_gratuity_rule(name, slabs, **args): + args = frappe._dict(args) + + rule = frappe.new_doc("Gratuity Rule") + rule.name = name + rule.calculate_gratuity_amount_based_on = args.calculate_gratuity_amount_based_on or "Current Slab" + rule.work_experience_calculation_method = args.work_experience_calculation_method or "Take Exact Completed Years" + rule.minimum_year_for_gratuity = 1 + + + for slab in slabs: + slab = frappe._dict(slab) + rule.append("gratuity_rule_slabs", slab) + return rule diff --git a/erpnext/accounts/doctype/pos_profile/pos_profile_dashboard.py b/erpnext/payroll/doctype/gratuity_rule/gratuity_rule_dashboard.py similarity index 53% rename from erpnext/accounts/doctype/pos_profile/pos_profile_dashboard.py rename to erpnext/payroll/doctype/gratuity_rule/gratuity_rule_dashboard.py index 2e4632a8d57..0d70163495a 100644 --- a/erpnext/accounts/doctype/pos_profile/pos_profile_dashboard.py +++ b/erpnext/payroll/doctype/gratuity_rule/gratuity_rule_dashboard.py @@ -1,14 +1,13 @@ from __future__ import unicode_literals - from frappe import _ - def get_data(): return { - 'fieldname': 'pos_profile', + 'fieldname': 'gratuity_rule', 'transactions': [ { - 'items': ['Sales Invoice', 'POS Closing Entry', 'POS Opening Entry'] + 'label': _('Gratuity'), + 'items': ['Gratuity'] } ] - } + } \ No newline at end of file diff --git a/erpnext/payroll/doctype/gratuity_rule/test_gratuity_rule.py b/erpnext/payroll/doctype/gratuity_rule/test_gratuity_rule.py new file mode 100644 index 00000000000..1f5dc4e571e --- /dev/null +++ b/erpnext/payroll/doctype/gratuity_rule/test_gratuity_rule.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestGratuityRule(unittest.TestCase): + pass diff --git a/erpnext/accounts/page/bank_reconciliation/__init__.py b/erpnext/payroll/doctype/gratuity_rule_slab/__init__.py similarity index 100% rename from erpnext/accounts/page/bank_reconciliation/__init__.py rename to erpnext/payroll/doctype/gratuity_rule_slab/__init__.py diff --git a/erpnext/payroll/doctype/gratuity_rule_slab/gratuity_rule_slab.json b/erpnext/payroll/doctype/gratuity_rule_slab/gratuity_rule_slab.json new file mode 100644 index 00000000000..bc37b0f51ed --- /dev/null +++ b/erpnext/payroll/doctype/gratuity_rule_slab/gratuity_rule_slab.json @@ -0,0 +1,50 @@ +{ + "actions": [], + "creation": "2020-08-05 19:12:49.423500", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "from_year", + "to_year", + "fraction_of_applicable_earnings" + ], + "fields": [ + { + "fieldname": "fraction_of_applicable_earnings", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Fraction of Applicable Earnings ", + "reqd": 1 + }, + { + "default": "0", + "fieldname": "from_year", + "fieldtype": "Int", + "in_list_view": 1, + "label": "From(Year)", + "read_only": 1, + "reqd": 1 + }, + { + "default": "0", + "fieldname": "to_year", + "fieldtype": "Int", + "in_list_view": 1, + "label": "To(Year)", + "reqd": 1 + } + ], + "istable": 1, + "links": [], + "modified": "2020-08-17 14:09:56.781712", + "modified_by": "Administrator", + "module": "Payroll", + "name": "Gratuity Rule Slab", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/bank_statement_settings_item/bank_statement_settings_item.py b/erpnext/payroll/doctype/gratuity_rule_slab/gratuity_rule_slab.py similarity index 58% rename from erpnext/accounts/doctype/bank_statement_settings_item/bank_statement_settings_item.py rename to erpnext/payroll/doctype/gratuity_rule_slab/gratuity_rule_slab.py index 9438e9a63f0..fa468e77beb 100644 --- a/erpnext/accounts/doctype/bank_statement_settings_item/bank_statement_settings_item.py +++ b/erpnext/payroll/doctype/gratuity_rule_slab/gratuity_rule_slab.py @@ -1,10 +1,10 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2018, sathishpy@gmail.com and contributors +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt from __future__ import unicode_literals -import frappe +# import frappe from frappe.model.document import Document -class BankStatementSettingsItem(Document): +class GratuityRuleSlab(Document): pass diff --git a/erpnext/payroll/doctype/payroll_period/payroll_period.py b/erpnext/payroll/doctype/payroll_period/payroll_period.py index 46f6cd842c3..ef3a6cc0061 100644 --- a/erpnext/payroll/doctype/payroll_period/payroll_period.py +++ b/erpnext/payroll/doctype/payroll_period/payroll_period.py @@ -41,7 +41,7 @@ class PayrollPeriod(Document): if overlap_doc: msg = _("A {0} exists between {1} and {2} (").format(self.doctype, formatdate(self.start_date), formatdate(self.end_date)) \ - + """ {1}""".format(self.doctype, overlap_doc[0].name) \ + + """ {1}""".format(self.doctype, overlap_doc[0].name) \ + _(") for {0}").format(self.company) frappe.throw(msg) diff --git a/erpnext/payroll/doctype/payroll_settings/payroll_settings.json b/erpnext/payroll/doctype/payroll_settings/payroll_settings.json index 680e518ca04..54377e94b30 100644 --- a/erpnext/payroll/doctype/payroll_settings/payroll_settings.json +++ b/erpnext/payroll/doctype/payroll_settings/payroll_settings.json @@ -97,7 +97,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2021-02-19 11:07:55.873991", + "modified": "2021-03-03 17:49:59.579723", "modified_by": "Administrator", "module": "Payroll", "name": "Payroll Settings", @@ -114,5 +114,6 @@ } ], "sort_field": "modified", - "sort_order": "ASC" + "sort_order": "ASC", + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py index 5c5eccd7e5d..595d6974fd5 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py @@ -80,9 +80,26 @@ class SalarySlip(TransactionBase): if (frappe.db.get_single_value("Payroll Settings", "email_salary_slip_to_employee")) and not frappe.flags.via_payroll_entry: self.email_salary_slip() + self.update_payment_status_for_gratuity() + + def update_payment_status_for_gratuity(self): + add_salary = frappe.db.get_all("Additional Salary", + filters = { + "payroll_date": ("BETWEEN", [self.start_date, self.end_date]), + "employee": self.employee, + "ref_doctype": "Gratuity", + "docstatus": 1, + }, fields = ["ref_docname", "name"], limit=1) + + if len(add_salary): + status = "Paid" if self.docstatus == 1 else "Unpaid" + if add_salary[0].name in [data.additional_salary for data in self.earnings]: + frappe.db.set_value("Gratuity", add_salary.ref_docname, "status", status) + def on_cancel(self): self.set_status() self.update_status() + self.update_payment_status_for_gratuity() self.cancel_loan_repayment_entry() def on_trash(self): @@ -506,7 +523,8 @@ class SalarySlip(TransactionBase): return amount except NameError as err: - frappe.throw(_("Name error: {0}").format(err)) + frappe.throw(_("{0}
    This error can be due to missing or deleted field.").format(err), + title=_("Name error")) except SyntaxError as err: frappe.throw(_("Syntax error in formula or condition: {0}").format(err)) except Exception as e: @@ -573,6 +591,7 @@ class SalarySlip(TransactionBase): for d in self.get(key): if d.salary_component == struct_row.salary_component: component_row = d + if not component_row or (struct_row.get("is_additional_component") and not overwrite): if amount: self.append(key, { @@ -930,7 +949,8 @@ class SalarySlip(TransactionBase): if condition: return frappe.safe_eval(condition, self.whitelisted_globals, data) except NameError as err: - frappe.throw(_("Name error: {0}").format(err)) + frappe.throw(_("{0}
    This error can be due to missing or deleted field.").format(err), + title=_("Name error")) except SyntaxError as err: frappe.throw(_("Syntax error in condition: {0}").format(err)) except Exception as e: @@ -1125,6 +1145,7 @@ class SalarySlip(TransactionBase): #calculate total working hours, earnings based on hourly wages and totals def calculate_total_for_salary_slip_based_on_timesheet(self): if self.timesheets: + self.total_working_hours = 0 for timesheet in self.timesheets: if timesheet.working_hours: self.total_working_hours += timesheet.working_hours @@ -1241,4 +1262,4 @@ def unlink_ref_doc_from_salary_slip(ref_no): def generate_password_for_pdf(policy_template, employee): employee = frappe.get_doc("Employee", employee) - return policy_template.format(**employee.as_dict()) \ No newline at end of file + return policy_template.format(**employee.as_dict()) diff --git a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py index f58a8e58c20..7289933d99e 100644 --- a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py @@ -21,6 +21,7 @@ from erpnext.payroll.doctype.employee_tax_exemption_declaration.test_employee_ta class TestSalarySlip(unittest.TestCase): def setUp(self): setup_test() + def tearDown(self): frappe.db.set_value("Payroll Settings", None, "include_holidays_in_total_working_days", 0) frappe.set_user("Administrator") diff --git a/erpnext/payroll/doctype/salary_structure/salary_structure.js b/erpnext/payroll/doctype/salary_structure/salary_structure.js index 1378bf0b913..6aa13873633 100755 --- a/erpnext/payroll/doctype/salary_structure/salary_structure.js +++ b/erpnext/payroll/doctype/salary_structure/salary_structure.js @@ -142,6 +142,8 @@ frappe.ui.form.on('Salary Structure', { ], primary_action: function() { var data = d.get_values(); + delete data.company + delete data.currency frappe.call({ doc: frm.doc, method: "assign_salary_structure", diff --git a/erpnext/payroll/workspace/payroll/payroll.json b/erpnext/payroll/workspace/payroll/payroll.json new file mode 100644 index 00000000000..814973063da --- /dev/null +++ b/erpnext/payroll/workspace/payroll/payroll.json @@ -0,0 +1,333 @@ +{ + "category": "Modules", + "charts": [ + { + "chart_name": "Outgoing Salary", + "label": "Outgoing Salary" + } + ], + "creation": "2020-05-27 19:54:23.405607", + "developer_mode_only": 0, + "disable_user_customization": 0, + "docstatus": 0, + "doctype": "Workspace", + "extends_another_page": 0, + "hide_custom": 0, + "icon": "money-coins-1", + "idx": 0, + "is_standard": 1, + "label": "Payroll", + "links": [ + { + "hidden": 0, + "is_query_report": 0, + "label": "Payroll", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Salary Component", + "link_to": "Salary Component", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Salary Structure", + "link_to": "Salary Structure", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Salary Structure Assignment", + "link_to": "Salary Structure Assignment", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Payroll Entry", + "link_to": "Payroll Entry", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Salary Slip", + "link_to": "Salary Slip", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Taxation", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Payroll Period", + "link_to": "Payroll Period", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Income Tax Slab", + "link_to": "Income Tax Slab", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Employee Other Income", + "link_to": "Employee Other Income", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Employee Tax Exemption Declaration", + "link_to": "Employee Tax Exemption Declaration", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Employee Tax Exemption Proof Submission", + "link_to": "Employee Tax Exemption Proof Submission", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Employee Tax Exemption Category", + "link_to": "Employee Tax Exemption Category", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Employee Tax Exemption Sub Category", + "link_to": "Employee Tax Exemption Sub Category", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Compensations", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Additional Salary", + "link_to": "Additional Salary", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Retention Bonus", + "link_to": "Retention Bonus", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Employee Incentive", + "link_to": "Employee Incentive", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Employee Benefit Application", + "link_to": "Employee Benefit Application", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Employee Benefit Claim", + "link_to": "Employee Benefit Claim", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Reports", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "Salary Slip", + "hidden": 0, + "is_query_report": 1, + "label": "Salary Register", + "link_to": "Salary Register", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Salary Slip", + "hidden": 0, + "is_query_report": 1, + "label": "Salary Payments Based On Payment Mode", + "link_to": "Salary Payments Based On Payment Mode", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Salary Slip", + "hidden": 0, + "is_query_report": 1, + "label": "Salary Payments via ECS", + "link_to": "Salary Payments via ECS", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Salary Slip", + "hidden": 0, + "is_query_report": 1, + "label": "Income Tax Deductions", + "link_to": "Income Tax Deductions", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Salary Slip", + "hidden": 0, + "is_query_report": 1, + "label": "Professional Tax Deductions", + "link_to": "Professional Tax Deductions", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Salary Slip", + "hidden": 0, + "is_query_report": 1, + "label": "Provident Fund Deductions", + "link_to": "Provident Fund Deductions", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Payroll Entry", + "hidden": 0, + "is_query_report": 1, + "label": "Bank Remittance", + "link_to": "Bank Remittance", + "link_type": "Report", + "onboard": 0, + "type": "Link" + } + ], + "modified": "2020-12-01 13:38:37.205628", + "modified_by": "Administrator", + "module": "Payroll", + "name": "Payroll", + "onboarding": "Payroll", + "owner": "Administrator", + "pin_to_bottom": 0, + "pin_to_top": 0, + "shortcuts": [ + { + "label": "Salary Structure", + "link_to": "Salary Structure", + "type": "DocType" + }, + { + "label": "Payroll Entry", + "link_to": "Payroll Entry", + "type": "DocType" + }, + { + "color": "", + "format": "{} Pending", + "label": "Salary Slip", + "link_to": "Salary Slip", + "stats_filter": "{\"status\": \"Draft\"}", + "type": "DocType" + }, + { + "label": "Income Tax Slab", + "link_to": "Income Tax Slab", + "type": "DocType" + }, + { + "label": "Salary Register", + "link_to": "Salary Register", + "type": "Report" + }, + { + "label": "Dashboard", + "link_to": "Payroll", + "type": "Dashboard" + } + ] +} \ No newline at end of file diff --git a/erpnext/portal/doctype/products_settings/products_settings.py b/erpnext/portal/doctype/products_settings/products_settings.py index ae7dc680208..9a708924ae0 100644 --- a/erpnext/portal/doctype/products_settings/products_settings.py +++ b/erpnext/portal/doctype/products_settings/products_settings.py @@ -17,6 +17,7 @@ class ProductsSettings(Document): self.validate_field_filters() self.validate_attribute_filters() + frappe.clear_document_cache("Product Settings", "Product Settings") def validate_field_filters(self): if not (self.enable_field_filters and self.filter_fields): return diff --git a/erpnext/portal/product_configurator/utils.py b/erpnext/portal/product_configurator/utils.py index 9ba4cdc5145..21fd7c28789 100644 --- a/erpnext/portal/product_configurator/utils.py +++ b/erpnext/portal/product_configurator/utils.py @@ -1,6 +1,7 @@ import frappe from frappe.utils import cint from erpnext.portal.product_configurator.item_variants_cache import ItemVariantsCacheManager +from erpnext.shopping_cart.product_info import get_product_info_for_website def get_field_filter_data(): product_settings = get_product_settings() @@ -356,10 +357,10 @@ def get_items(filters=None, search=None): results = frappe.db.sql(''' SELECT - `tabItem`.`name`, `tabItem`.`item_name`, + `tabItem`.`name`, `tabItem`.`item_name`, `tabItem`.`item_code`, `tabItem`.`website_image`, `tabItem`.`image`, `tabItem`.`web_long_description`, `tabItem`.`description`, - `tabItem`.`route` + `tabItem`.`route`, `tabItem`.`item_group` FROM `tabItem` {left_join} @@ -384,6 +385,9 @@ def get_items(filters=None, search=None): for r in results: r.description = r.web_long_description or r.description r.image = r.website_image or r.image + product_info = get_product_info_for_website(r.item_code, skip_quotation_creation=True).get('product_info') + if product_info: + r.formatted_price = product_info['price'].get('formatted_price') if product_info['price'] else None return results diff --git a/erpnext/projects/desk_page/projects/projects.json b/erpnext/projects/desk_page/projects/projects.json deleted file mode 100644 index e24cf3081cb..00000000000 --- a/erpnext/projects/desk_page/projects/projects.json +++ /dev/null @@ -1,76 +0,0 @@ -{ - "cards": [ - { - "hidden": 0, - "label": "Projects", - "links": "[\n {\n \"description\": \"Project master.\",\n \"label\": \"Project\",\n \"name\": \"Project\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Project activity / task.\",\n \"label\": \"Task\",\n \"name\": \"Task\",\n \"onboard\": 1,\n \"route\": \"#List/Task\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Make project from a template.\",\n \"label\": \"Project Template\",\n \"name\": \"Project Template\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Define Project type.\",\n \"label\": \"Project Type\",\n \"name\": \"Project Type\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Project\"\n ],\n \"description\": \"Project Update.\",\n \"label\": \"Project Update\",\n \"name\": \"Project Update\",\n \"type\": \"doctype\"\n }\n]" - }, - { - "hidden": 0, - "label": "Time Tracking", - "links": "[\n {\n \"description\": \"Timesheet for tasks.\",\n \"label\": \"Timesheet\",\n \"name\": \"Timesheet\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Types of activities for Time Logs\",\n \"label\": \"Activity Type\",\n \"name\": \"Activity Type\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Activity Type\"\n ],\n \"description\": \"Cost of various activities\",\n \"label\": \"Activity Cost\",\n \"name\": \"Activity Cost\",\n \"type\": \"doctype\"\n }\n]" - }, - { - "hidden": 0, - "label": "Reports", - "links": "[\n {\n \"dependencies\": [\n \"Timesheet\"\n ],\n \"doctype\": \"Timesheet\",\n \"is_query_report\": true,\n \"label\": \"Daily Timesheet Summary\",\n \"name\": \"Daily Timesheet Summary\",\n \"onboard\": 1,\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Project\"\n ],\n \"doctype\": \"Project\",\n \"is_query_report\": true,\n \"label\": \"Project wise Stock Tracking\",\n \"name\": \"Project wise Stock Tracking\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Project\"\n ],\n \"doctype\": \"Project\",\n \"is_query_report\": true,\n \"label\": \"Project Billing Summary\",\n \"name\": \"Project Billing Summary\",\n \"type\": \"report\"\n }\n]" - } - ], - "category": "Modules", - "charts": [ - { - "chart_name": "Project Summary", - "label": "Open Projects" - } - ], - "creation": "2020-03-02 15:46:04.874669", - "developer_mode_only": 0, - "disable_user_customization": 0, - "docstatus": 0, - "doctype": "Desk Page", - "extends_another_page": 0, - "hide_custom": 0, - "idx": 0, - "is_standard": 1, - "label": "Projects", - "modified": "2020-05-28 13:38:19.934937", - "modified_by": "Administrator", - "module": "Projects", - "name": "Projects", - "owner": "Administrator", - "pin_to_bottom": 0, - "pin_to_top": 0, - "shortcuts": [ - { - "color": "#cef6d1", - "format": "{} Assigned", - "label": "Task", - "link_to": "Task", - "stats_filter": "{\n \"_assign\": [\"like\", '%' + frappe.session.user + '%'],\n \"status\": \"Open\"\n}", - "type": "DocType" - }, - { - "color": "#ffe8cd", - "format": "{} Open", - "label": "Project", - "link_to": "Project", - "stats_filter": "{\n \"status\": \"Open\"\n}", - "type": "DocType" - }, - { - "label": "Timesheet", - "link_to": "Timesheet", - "type": "DocType" - }, - { - "label": "Project Billing Summary", - "link_to": "Project Billing Summary", - "type": "Report" - }, - { - "label": "Dashboard", - "link_to": "Project", - "type": "Dashboard" - } - ] -} \ No newline at end of file diff --git a/erpnext/projects/doctype/project/project.js b/erpnext/projects/doctype/project/project.js index 3570a0f2be4..077011ace07 100644 --- a/erpnext/projects/doctype/project/project.js +++ b/erpnext/projects/doctype/project/project.js @@ -75,24 +75,27 @@ frappe.ui.form.on("Project", { frm.add_custom_button(__('Cancelled'), () => { frm.events.set_status(frm, 'Cancelled'); }, __('Set Status')); - } - if (frappe.model.can_read("Task")) { - frm.add_custom_button(__("Gantt Chart"), function () { - frappe.route_options = { - "project": frm.doc.name - }; - frappe.set_route("List", "Task", "Gantt"); - }); - frm.add_custom_button(__("Kanban Board"), () => { - frappe.call('erpnext.projects.doctype.project.project.create_kanban_board_if_not_exists', { - project: frm.doc.project_name - }).then(() => { - frappe.set_route('List', 'Task', 'Kanban', frm.doc.project_name); + if (frappe.model.can_read("Task")) { + frm.add_custom_button(__("Gantt Chart"), function () { + frappe.route_options = { + "project": frm.doc.name + }; + frappe.set_route("List", "Task", "Gantt"); }); - }); + + frm.add_custom_button(__("Kanban Board"), () => { + frappe.call('erpnext.projects.doctype.project.project.create_kanban_board_if_not_exists', { + project: frm.doc.project_name + }).then(() => { + frappe.set_route('List', 'Task', 'Kanban', frm.doc.project_name); + }); + }); + } } + + }, create_duplicate: function(frm) { @@ -135,4 +138,4 @@ function open_form(frm, doctype, child_doctype, parentfield) { frappe.ui.form.make_quick_entry(doctype, null, null, new_doc); }); -} \ No newline at end of file +} diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py index 60f85b0e7a6..8ba0b6cb54c 100644 --- a/erpnext/projects/doctype/project/project.py +++ b/erpnext/projects/doctype/project/project.py @@ -27,7 +27,7 @@ class Project(Document): self.update_costing() - def before_print(self): + def before_print(self, settings=None): self.onload() diff --git a/erpnext/projects/doctype/project/test_project.py b/erpnext/projects/doctype/project/test_project.py index d85c82612a2..62905385a31 100644 --- a/erpnext/projects/doctype/project/test_project.py +++ b/erpnext/projects/doctype/project/test_project.py @@ -37,7 +37,7 @@ class TestProject(unittest.TestCase): task1 = task_exists("Test Template Task Parent") if not task1: - task1 = create_task(subject="Test Template Task Parent", is_group=1, is_template=1, begin=1, duration=1) + task1 = create_task(subject="Test Template Task Parent", is_group=1, is_template=1, begin=1, duration=4) task2 = task_exists("Test Template Task Child 1") if not task2: @@ -52,7 +52,7 @@ class TestProject(unittest.TestCase): tasks = frappe.get_all('Task', ['subject','exp_end_date','depends_on_tasks', 'name', 'parent_task'], dict(project=project.name), order_by='creation asc') self.assertEqual(tasks[0].subject, 'Test Template Task Parent') - self.assertEqual(getdate(tasks[0].exp_end_date), calculate_end_date(project, 1, 1)) + self.assertEqual(getdate(tasks[0].exp_end_date), calculate_end_date(project, 1, 4)) self.assertEqual(tasks[1].subject, 'Test Template Task Child 1') self.assertEqual(getdate(tasks[1].exp_end_date), calculate_end_date(project, 1, 3)) diff --git a/erpnext/projects/doctype/project_template_task/project_template_task.json b/erpnext/projects/doctype/project_template_task/project_template_task.json index 69530b15b40..16caaa20ae4 100644 --- a/erpnext/projects/doctype/project_template_task/project_template_task.json +++ b/erpnext/projects/doctype/project_template_task/project_template_task.json @@ -20,6 +20,7 @@ }, { "columns": 6, + "fetch_from": "task.subject", "fieldname": "subject", "fieldtype": "Read Only", "in_list_view": 1, @@ -28,7 +29,7 @@ ], "istable": 1, "links": [], - "modified": "2021-01-07 15:13:40.995071", + "modified": "2021-02-24 15:18:49.095071", "modified_by": "Administrator", "module": "Projects", "name": "Project Template Task", diff --git a/erpnext/projects/doctype/task/task.py b/erpnext/projects/doctype/task/task.py index a99603329b4..855ff5f83e8 100755 --- a/erpnext/projects/doctype/task/task.py +++ b/erpnext/projects/doctype/task/task.py @@ -17,305 +17,326 @@ class CircularReferenceError(frappe.ValidationError): pass class EndDateCannotBeGreaterThanProjectEndDateError(frappe.ValidationError): pass class Task(NestedSet): - nsm_parent_field = 'parent_task' + nsm_parent_field = 'parent_task' - def get_feed(self): - return '{0}: {1}'.format(_(self.status), self.subject) + def get_feed(self): + return '{0}: {1}'.format(_(self.status), self.subject) - def get_customer_details(self): - cust = frappe.db.sql("select customer_name from `tabCustomer` where name=%s", self.customer) - if cust: - ret = {'customer_name': cust and cust[0][0] or ''} - return ret + def get_customer_details(self): + cust = frappe.db.sql("select customer_name from `tabCustomer` where name=%s", self.customer) + if cust: + ret = {'customer_name': cust and cust[0][0] or ''} + return ret - def validate(self): - self.validate_dates() - self.validate_parent_project_dates() - self.validate_progress() - self.validate_status() - self.update_depends_on() - self.validate_dependencies_for_template_task() + def validate(self): + self.validate_dates() + self.validate_parent_expected_end_date() + self.validate_parent_project_dates() + self.validate_progress() + self.validate_status() + self.update_depends_on() + self.validate_dependencies_for_template_task() - def validate_dates(self): - if self.exp_start_date and self.exp_end_date and getdate(self.exp_start_date) > getdate(self.exp_end_date): - frappe.throw(_("{0} can not be greater than {1}").format(frappe.bold("Expected Start Date"), \ - frappe.bold("Expected End Date"))) + def validate_dates(self): + if self.exp_start_date and self.exp_end_date and getdate(self.exp_start_date) > getdate(self.exp_end_date): + frappe.throw(_("{0} can not be greater than {1}").format(frappe.bold("Expected Start Date"), \ + frappe.bold("Expected End Date"))) - if self.act_start_date and self.act_end_date and getdate(self.act_start_date) > getdate(self.act_end_date): - frappe.throw(_("{0} can not be greater than {1}").format(frappe.bold("Actual Start Date"), \ - frappe.bold("Actual End Date"))) + if self.act_start_date and self.act_end_date and getdate(self.act_start_date) > getdate(self.act_end_date): + frappe.throw(_("{0} can not be greater than {1}").format(frappe.bold("Actual Start Date"), \ + frappe.bold("Actual End Date"))) - def validate_parent_project_dates(self): - if not self.project or frappe.flags.in_test: - return + def validate_parent_expected_end_date(self): + if self.parent_task: + parent_exp_end_date = frappe.db.get_value("Task", self.parent_task, "exp_end_date") + if parent_exp_end_date and getdate(self.get("exp_end_date")) > getdate(parent_exp_end_date): + frappe.throw(_("Expected End Date should be less than or equal to parent task's Expected End Date {0}.").format(getdate(parent_exp_end_date))) - expected_end_date = frappe.db.get_value("Project", self.project, "expected_end_date") + def validate_parent_project_dates(self): + if not self.project or frappe.flags.in_test: + return - if expected_end_date: - validate_project_dates(getdate(expected_end_date), self, "exp_start_date", "exp_end_date", "Expected") - validate_project_dates(getdate(expected_end_date), self, "act_start_date", "act_end_date", "Actual") + expected_end_date = frappe.db.get_value("Project", self.project, "expected_end_date") - def validate_status(self): - if self.is_template and self.status != "Template": - self.status = "Template" - if self.status!=self.get_db_value("status") and self.status == "Completed": - for d in self.depends_on: - if frappe.db.get_value("Task", d.task, "status") not in ("Completed", "Cancelled"): - frappe.throw(_("Cannot complete task {0} as its dependant task {1} are not ccompleted / cancelled.").format(frappe.bold(self.name), frappe.bold(d.task))) + if expected_end_date: + validate_project_dates(getdate(expected_end_date), self, "exp_start_date", "exp_end_date", "Expected") + validate_project_dates(getdate(expected_end_date), self, "act_start_date", "act_end_date", "Actual") - close_all_assignments(self.doctype, self.name) + def validate_status(self): + if self.is_template and self.status != "Template": + self.status = "Template" + if self.status!=self.get_db_value("status") and self.status == "Completed": + for d in self.depends_on: + if frappe.db.get_value("Task", d.task, "status") not in ("Completed", "Cancelled"): + frappe.throw(_("Cannot complete task {0} as its dependant task {1} are not ccompleted / cancelled.").format(frappe.bold(self.name), frappe.bold(d.task))) - def validate_progress(self): - if flt(self.progress or 0) > 100: - frappe.throw(_("Progress % for a task cannot be more than 100.")) + close_all_assignments(self.doctype, self.name) - if flt(self.progress) == 100: - self.status = 'Completed' + def validate_progress(self): + if flt(self.progress or 0) > 100: + frappe.throw(_("Progress % for a task cannot be more than 100.")) - if self.status == 'Completed': - self.progress = 100 + if flt(self.progress) == 100: + self.status = 'Completed' - def validate_dependencies_for_template_task(self): - if self.is_template: - self.validate_depends_on_tasks() - - def validate_depends_on_tasks(self): - if self.depends_on: - for task in self.depends_on: - if not frappe.db.get_value("Task", task.task, "is_template"): - dependent_task_format = """{0}""".format(task.task) - frappe.throw(_("Dependent Task {0} is not a Template Task").format(dependent_task_format)) + if self.status == 'Completed': + self.progress = 100 - def update_depends_on(self): - depends_on_tasks = self.depends_on_tasks or "" - for d in self.depends_on: - if d.task and d.task not in depends_on_tasks: - depends_on_tasks += d.task + "," - self.depends_on_tasks = depends_on_tasks + def validate_dependencies_for_template_task(self): + if self.is_template: + self.validate_parent_template_task() + self.validate_depends_on_tasks() - def update_nsm_model(self): - frappe.utils.nestedset.update_nsm(self) + def validate_parent_template_task(self): + if self.parent_task: + if not frappe.db.get_value("Task", self.parent_task, "is_template"): + parent_task_format = """{0}""".format(self.parent_task) + frappe.throw(_("Parent Task {0} is not a Template Task").format(parent_task_format)) - def on_update(self): - self.update_nsm_model() - self.check_recursion() - self.reschedule_dependent_tasks() - self.update_project() - self.unassign_todo() - self.populate_depends_on() + def validate_depends_on_tasks(self): + if self.depends_on: + for task in self.depends_on: + if not frappe.db.get_value("Task", task.task, "is_template"): + dependent_task_format = """{0}""".format(task.task) + frappe.throw(_("Dependent Task {0} is not a Template Task").format(dependent_task_format)) - def unassign_todo(self): - if self.status == "Completed": - close_all_assignments(self.doctype, self.name) - if self.status == "Cancelled": - clear(self.doctype, self.name) + def update_depends_on(self): + depends_on_tasks = self.depends_on_tasks or "" + for d in self.depends_on: + if d.task and d.task not in depends_on_tasks: + depends_on_tasks += d.task + "," + self.depends_on_tasks = depends_on_tasks - def update_total_expense_claim(self): - self.total_expense_claim = frappe.db.sql("""select sum(total_sanctioned_amount) from `tabExpense Claim` - where project = %s and task = %s and docstatus=1""",(self.project, self.name))[0][0] + def update_nsm_model(self): + frappe.utils.nestedset.update_nsm(self) - def update_time_and_costing(self): - tl = frappe.db.sql("""select min(from_time) as start_date, max(to_time) as end_date, - sum(billing_amount) as total_billing_amount, sum(costing_amount) as total_costing_amount, - sum(hours) as time from `tabTimesheet Detail` where task = %s and docstatus=1""" - ,self.name, as_dict=1)[0] - if self.status == "Open": - self.status = "Working" - self.total_costing_amount= tl.total_costing_amount - self.total_billing_amount= tl.total_billing_amount - self.actual_time= tl.time - self.act_start_date= tl.start_date - self.act_end_date= tl.end_date + def on_update(self): + self.update_nsm_model() + self.check_recursion() + self.reschedule_dependent_tasks() + self.update_project() + self.unassign_todo() + self.populate_depends_on() - def update_project(self): - if self.project and not self.flags.from_project: - frappe.get_cached_doc("Project", self.project).update_project() + def unassign_todo(self): + if self.status == "Completed": + close_all_assignments(self.doctype, self.name) + if self.status == "Cancelled": + clear(self.doctype, self.name) - def check_recursion(self): - if self.flags.ignore_recursion_check: return - check_list = [['task', 'parent'], ['parent', 'task']] - for d in check_list: - task_list, count = [self.name], 0 - while (len(task_list) > count ): - tasks = frappe.db.sql(" select %s from `tabTask Depends On` where %s = %s " % - (d[0], d[1], '%s'), cstr(task_list[count])) - count = count + 1 - for b in tasks: - if b[0] == self.name: - frappe.throw(_("Circular Reference Error"), CircularReferenceError) - if b[0]: - task_list.append(b[0]) + def update_total_expense_claim(self): + self.total_expense_claim = frappe.db.sql("""select sum(total_sanctioned_amount) from `tabExpense Claim` + where project = %s and task = %s and docstatus=1""",(self.project, self.name))[0][0] - if count == 15: - break + def update_time_and_costing(self): + tl = frappe.db.sql("""select min(from_time) as start_date, max(to_time) as end_date, + sum(billing_amount) as total_billing_amount, sum(costing_amount) as total_costing_amount, + sum(hours) as time from `tabTimesheet Detail` where task = %s and docstatus=1""" + ,self.name, as_dict=1)[0] + if self.status == "Open": + self.status = "Working" + self.total_costing_amount= tl.total_costing_amount + self.total_billing_amount= tl.total_billing_amount + self.actual_time= tl.time + self.act_start_date= tl.start_date + self.act_end_date= tl.end_date - def reschedule_dependent_tasks(self): - end_date = self.exp_end_date or self.act_end_date - if end_date: - for task_name in frappe.db.sql(""" - select name from `tabTask` as parent - where parent.project = %(project)s - and parent.name in ( - select parent from `tabTask Depends On` as child - where child.task = %(task)s and child.project = %(project)s) - """, {'project': self.project, 'task':self.name }, as_dict=1): - task = frappe.get_doc("Task", task_name.name) - if task.exp_start_date and task.exp_end_date and task.exp_start_date < getdate(end_date) and task.status == "Open": - task_duration = date_diff(task.exp_end_date, task.exp_start_date) - task.exp_start_date = add_days(end_date, 1) - task.exp_end_date = add_days(task.exp_start_date, task_duration) - task.flags.ignore_recursion_check = True - task.save() + def update_project(self): + if self.project and not self.flags.from_project: + frappe.get_cached_doc("Project", self.project).update_project() - def has_webform_permission(self): - project_user = frappe.db.get_value("Project User", {"parent": self.project, "user":frappe.session.user} , "user") - if project_user: - return True + def check_recursion(self): + if self.flags.ignore_recursion_check: return + check_list = [['task', 'parent'], ['parent', 'task']] + for d in check_list: + task_list, count = [self.name], 0 + while (len(task_list) > count ): + tasks = frappe.db.sql(" select %s from `tabTask Depends On` where %s = %s " % + (d[0], d[1], '%s'), cstr(task_list[count])) + count = count + 1 + for b in tasks: + if b[0] == self.name: + frappe.throw(_("Circular Reference Error"), CircularReferenceError) + if b[0]: + task_list.append(b[0]) - def populate_depends_on(self): - if self.parent_task: - parent = frappe.get_doc('Task', self.parent_task) - if self.name not in [row.task for row in parent.depends_on]: - parent.append("depends_on", { - "doctype": "Task Depends On", - "task": self.name, - "subject": self.subject - }) - parent.save() + if count == 15: + break - def on_trash(self): - if check_if_child_exists(self.name): - throw(_("Child Task exists for this Task. You can not delete this Task.")) + def reschedule_dependent_tasks(self): + end_date = self.exp_end_date or self.act_end_date + if end_date: + for task_name in frappe.db.sql(""" + select name from `tabTask` as parent + where parent.project = %(project)s + and parent.name in ( + select parent from `tabTask Depends On` as child + where child.task = %(task)s and child.project = %(project)s) + """, {'project': self.project, 'task':self.name }, as_dict=1): + task = frappe.get_doc("Task", task_name.name) + if task.exp_start_date and task.exp_end_date and task.exp_start_date < getdate(end_date) and task.status == "Open": + task_duration = date_diff(task.exp_end_date, task.exp_start_date) + task.exp_start_date = add_days(end_date, 1) + task.exp_end_date = add_days(task.exp_start_date, task_duration) + task.flags.ignore_recursion_check = True + task.save() - self.update_nsm_model() + def has_webform_permission(self): + project_user = frappe.db.get_value("Project User", {"parent": self.project, "user":frappe.session.user} , "user") + if project_user: + return True - def after_delete(self): - self.update_project() + def populate_depends_on(self): + if self.parent_task: + parent = frappe.get_doc('Task', self.parent_task) + if self.name not in [row.task for row in parent.depends_on]: + parent.append("depends_on", { + "doctype": "Task Depends On", + "task": self.name, + "subject": self.subject + }) + parent.save() - def update_status(self): - if self.status not in ('Cancelled', 'Completed') and self.exp_end_date: - from datetime import datetime - if self.exp_end_date < datetime.now().date(): - self.db_set('status', 'Overdue', update_modified=False) - self.update_project() + def on_trash(self): + if check_if_child_exists(self.name): + throw(_("Child Task exists for this Task. You can not delete this Task.")) + + self.update_nsm_model() + + def after_delete(self): + self.update_project() + + def update_status(self): + if self.status not in ('Cancelled', 'Completed') and self.exp_end_date: + from datetime import datetime + if self.exp_end_date < datetime.now().date(): + self.db_set('status', 'Overdue', update_modified=False) + self.update_project() @frappe.whitelist() def check_if_child_exists(name): - child_tasks = frappe.get_all("Task", filters={"parent_task": name}) - child_tasks = [get_link_to_form("Task", task.name) for task in child_tasks] - return child_tasks + child_tasks = frappe.get_all("Task", filters={"parent_task": name}) + child_tasks = [get_link_to_form("Task", task.name) for task in child_tasks] + return child_tasks @frappe.whitelist() @frappe.validate_and_sanitize_search_inputs def get_project(doctype, txt, searchfield, start, page_len, filters): - from erpnext.controllers.queries import get_match_cond - return frappe.db.sql(""" select name from `tabProject` - where %(key)s like %(txt)s - %(mcond)s - order by name - limit %(start)s, %(page_len)s""" % { - 'key': searchfield, - 'txt': frappe.db.escape('%' + txt + '%'), - 'mcond':get_match_cond(doctype), - 'start': start, - 'page_len': page_len - }) + from erpnext.controllers.queries import get_match_cond + meta = frappe.get_meta(doctype) + searchfields = meta.get_search_fields() + search_columns = ", " + ", ".join(searchfields) if searchfields else '' + search_cond = " or " + " or ".join([field + " like %(txt)s" for field in searchfields]) + + return frappe.db.sql(""" select name {search_columns} from `tabProject` + where %(key)s like %(txt)s + %(mcond)s + {search_condition} + order by name + limit %(start)s, %(page_len)s""".format(search_columns = search_columns, + search_condition=search_cond), { + 'key': searchfield, + 'txt': '%' + txt + '%', + 'mcond':get_match_cond(doctype), + 'start': start, + 'page_len': page_len + }) @frappe.whitelist() def set_multiple_status(names, status): - names = json.loads(names) - for name in names: - task = frappe.get_doc("Task", name) - task.status = status - task.save() + names = json.loads(names) + for name in names: + task = frappe.get_doc("Task", name) + task.status = status + task.save() def set_tasks_as_overdue(): - tasks = frappe.get_all("Task", filters={"status": ["not in", ["Cancelled", "Completed"]]}, fields=["name", "status", "review_date"]) - for task in tasks: - if task.status == "Pending Review": - if getdate(task.review_date) > getdate(today()): - continue - frappe.get_doc("Task", task.name).update_status() + tasks = frappe.get_all("Task", filters={"status": ["not in", ["Cancelled", "Completed"]]}, fields=["name", "status", "review_date"]) + for task in tasks: + if task.status == "Pending Review": + if getdate(task.review_date) > getdate(today()): + continue + frappe.get_doc("Task", task.name).update_status() @frappe.whitelist() def make_timesheet(source_name, target_doc=None, ignore_permissions=False): - def set_missing_values(source, target): - target.append("time_logs", { - "hours": source.actual_time, - "completed": source.status == "Completed", - "project": source.project, - "task": source.name - }) + def set_missing_values(source, target): + target.append("time_logs", { + "hours": source.actual_time, + "completed": source.status == "Completed", + "project": source.project, + "task": source.name + }) - doclist = get_mapped_doc("Task", source_name, { - "Task": { - "doctype": "Timesheet" - } - }, target_doc, postprocess=set_missing_values, ignore_permissions=ignore_permissions) + doclist = get_mapped_doc("Task", source_name, { + "Task": { + "doctype": "Timesheet" + } + }, target_doc, postprocess=set_missing_values, ignore_permissions=ignore_permissions) - return doclist + return doclist @frappe.whitelist() def get_children(doctype, parent, task=None, project=None, is_root=False): - filters = [['docstatus', '<', '2']] + filters = [['docstatus', '<', '2']] - if task: - filters.append(['parent_task', '=', task]) - elif parent and not is_root: - # via expand child - filters.append(['parent_task', '=', parent]) - else: - filters.append(['ifnull(`parent_task`, "")', '=', '']) + if task: + filters.append(['parent_task', '=', task]) + elif parent and not is_root: + # via expand child + filters.append(['parent_task', '=', parent]) + else: + filters.append(['ifnull(`parent_task`, "")', '=', '']) - if project: - filters.append(['project', '=', project]) + if project: + filters.append(['project', '=', project]) - tasks = frappe.get_list(doctype, fields=[ - 'name as value', - 'subject as title', - 'is_group as expandable' - ], filters=filters, order_by='name') + tasks = frappe.get_list(doctype, fields=[ + 'name as value', + 'subject as title', + 'is_group as expandable' + ], filters=filters, order_by='name') - # return tasks - return tasks + # return tasks + return tasks @frappe.whitelist() def add_node(): - from frappe.desk.treeview import make_tree_args - args = frappe.form_dict - args.update({ - "name_field": "subject" - }) - args = make_tree_args(**args) + from frappe.desk.treeview import make_tree_args + args = frappe.form_dict + args.update({ + "name_field": "subject" + }) + args = make_tree_args(**args) - if args.parent_task == 'All Tasks' or args.parent_task == args.project: - args.parent_task = None + if args.parent_task == 'All Tasks' or args.parent_task == args.project: + args.parent_task = None - frappe.get_doc(args).insert() + frappe.get_doc(args).insert() @frappe.whitelist() def add_multiple_tasks(data, parent): - data = json.loads(data) - new_doc = {'doctype': 'Task', 'parent_task': parent if parent!="All Tasks" else ""} - new_doc['project'] = frappe.db.get_value('Task', {"name": parent}, 'project') or "" + data = json.loads(data) + new_doc = {'doctype': 'Task', 'parent_task': parent if parent!="All Tasks" else ""} + new_doc['project'] = frappe.db.get_value('Task', {"name": parent}, 'project') or "" - for d in data: - if not d.get("subject"): continue - new_doc['subject'] = d.get("subject") - new_task = frappe.get_doc(new_doc) - new_task.insert() + for d in data: + if not d.get("subject"): continue + new_doc['subject'] = d.get("subject") + new_task = frappe.get_doc(new_doc) + new_task.insert() def on_doctype_update(): - frappe.db.add_index("Task", ["lft", "rgt"]) + frappe.db.add_index("Task", ["lft", "rgt"]) def validate_project_dates(project_end_date, task, task_start, task_end, actual_or_expected_date): - if task.get(task_start) and date_diff(project_end_date, getdate(task.get(task_start))) < 0: - frappe.throw(_("Task's {0} Start Date cannot be after Project's End Date.").format(actual_or_expected_date)) + if task.get(task_start) and date_diff(project_end_date, getdate(task.get(task_start))) < 0: + frappe.throw(_("Task's {0} Start Date cannot be after Project's End Date.").format(actual_or_expected_date)) - if task.get(task_end) and date_diff(project_end_date, getdate(task.get(task_end))) < 0: - frappe.throw(_("Task's {0} End Date cannot be after Project's End Date.").format(actual_or_expected_date)) + if task.get(task_end) and date_diff(project_end_date, getdate(task.get(task_end))) < 0: + frappe.throw(_("Task's {0} End Date cannot be after Project's End Date.").format(actual_or_expected_date)) diff --git a/erpnext/projects/doctype/task/task_list.js b/erpnext/projects/doctype/task/task_list.js index 39734ee8b1c..98d2bbc81a4 100644 --- a/erpnext/projects/doctype/task/task_list.js +++ b/erpnext/projects/doctype/task/task_list.js @@ -27,7 +27,7 @@ frappe.listview_settings['Task'] = { }, gantt_custom_popup_html: function(ganttobj, task) { var html = `
    ${ganttobj.name}
    `; + href="/app/task/${ganttobj.id}""> ${ganttobj.name} `; if(task.project) html += `

    Project: ${task.project}

    `; html += `

    Progress: ${ganttobj.progress}

    `; diff --git a/erpnext/projects/doctype/timesheet/timesheet.py b/erpnext/projects/doctype/timesheet/timesheet.py index 9e807f728ec..ed02f79c2dd 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.py +++ b/erpnext/projects/doctype/timesheet/timesheet.py @@ -204,14 +204,16 @@ class Timesheet(Document): ts_detail.billing_rate = 0.0 @frappe.whitelist() -def get_projectwise_timesheet_data(project, parent=None): - cond = '' +def get_projectwise_timesheet_data(project, parent=None, from_time=None, to_time=None): + condition = '' if parent: - cond = "and parent = %(parent)s" + condition = "AND parent = %(parent)s" + if from_time and to_time: + condition += "AND from_time BETWEEN %(from_time)s AND %(to_time)s" return frappe.db.sql("""select name, parent, billing_hours, billing_amount as billing_amt from `tabTimesheet Detail` where parenttype = 'Timesheet' and docstatus=1 and project = %(project)s {0} and billable = 1 - and sales_invoice is null""".format(cond), {'project': project, 'parent': parent}, as_dict=1) + and sales_invoice is null""".format(condition), {'project': project, 'parent': parent, 'from_time': from_time, 'to_time': to_time}, as_dict=1) @frappe.whitelist() @frappe.validate_and_sanitize_search_inputs @@ -288,7 +290,7 @@ def make_sales_invoice(source_name, item_code=None, customer=None): def make_salary_slip(source_name, target_doc=None): target = frappe.new_doc("Salary Slip") set_missing_values(source_name, target) - target.run_method("get_emp_and_leave_details") + target.run_method("get_emp_and_working_day_details") return target diff --git a/erpnext/projects/workspace/projects/projects.json b/erpnext/projects/workspace/projects/projects.json new file mode 100644 index 00000000000..dbbd7e1458e --- /dev/null +++ b/erpnext/projects/workspace/projects/projects.json @@ -0,0 +1,193 @@ +{ + "category": "Modules", + "charts": [ + { + "chart_name": "Project Summary", + "label": "Open Projects" + } + ], + "creation": "2020-03-02 15:46:04.874669", + "developer_mode_only": 0, + "disable_user_customization": 0, + "docstatus": 0, + "doctype": "Workspace", + "extends_another_page": 0, + "hide_custom": 0, + "icon": "project", + "idx": 0, + "is_standard": 1, + "label": "Projects", + "links": [ + { + "hidden": 0, + "is_query_report": 0, + "label": "Projects", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Project", + "link_to": "Project", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Task", + "link_to": "Task", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Project Template", + "link_to": "Project Template", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Project Type", + "link_to": "Project Type", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Project", + "hidden": 0, + "is_query_report": 0, + "label": "Project Update", + "link_to": "Project Update", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Time Tracking", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Timesheet", + "link_to": "Timesheet", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Activity Type", + "link_to": "Activity Type", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "Activity Type", + "hidden": 0, + "is_query_report": 0, + "label": "Activity Cost", + "link_to": "Activity Cost", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Reports", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "Timesheet", + "hidden": 0, + "is_query_report": 1, + "label": "Daily Timesheet Summary", + "link_to": "Daily Timesheet Summary", + "link_type": "Report", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "Project", + "hidden": 0, + "is_query_report": 1, + "label": "Project wise Stock Tracking", + "link_to": "Project wise Stock Tracking", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Project", + "hidden": 0, + "is_query_report": 1, + "label": "Project Billing Summary", + "link_to": "Project Billing Summary", + "link_type": "Report", + "onboard": 0, + "type": "Link" + } + ], + "modified": "2020-12-01 13:38:37.856224", + "modified_by": "Administrator", + "module": "Projects", + "name": "Projects", + "owner": "Administrator", + "pin_to_bottom": 0, + "pin_to_top": 0, + "shortcuts": [ + { + "color": "Blue", + "format": "{} Assigned", + "label": "Task", + "link_to": "Task", + "stats_filter": "{\n \"_assign\": [\"like\", '%' + frappe.session.user + '%'],\n \"status\": \"Open\"\n}", + "type": "DocType" + }, + { + "color": "Blue", + "format": "{} Open", + "label": "Project", + "link_to": "Project", + "stats_filter": "{\n \"status\": \"Open\"\n}", + "type": "DocType" + }, + { + "label": "Timesheet", + "link_to": "Timesheet", + "type": "DocType" + }, + { + "label": "Project Billing Summary", + "link_to": "Project Billing Summary", + "type": "Report" + }, + { + "label": "Dashboard", + "link_to": "Project", + "type": "Dashboard" + } + ] +} \ No newline at end of file diff --git a/erpnext/public/build.json b/erpnext/public/build.json index f0212db0b21..7a3cb838a99 100644 --- a/erpnext/public/build.json +++ b/erpnext/public/build.json @@ -2,7 +2,8 @@ "css/erpnext.css": [ "public/less/erpnext.less", "public/less/hub.less", - "public/less/call_popup.less" + "public/scss/call_popup.scss", + "public/scss/point-of-sale.scss" ], "css/marketplace.css": [ "public/less/hub.less" @@ -12,7 +13,8 @@ "public/js/shopping_cart.js" ], "css/erpnext-web.css": [ - "public/scss/website.scss" + "public/scss/website.scss", + "public/scss/shopping_cart.scss" ], "js/marketplace.min.js": [ "public/js/hub/marketplace.js" @@ -27,16 +29,6 @@ "public/js/payment/payments.js", "public/js/controllers/taxes_and_totals.js", "public/js/controllers/transaction.js", - "public/js/pos/pos.html", - "public/js/pos/pos_bill_item.html", - "public/js/pos/pos_bill_item_new.html", - "public/js/pos/pos_selected_item.html", - "public/js/pos/pos_item.html", - "public/js/pos/pos_tax_row.html", - "public/js/pos/customer_toolbar.html", - "public/js/pos/pos_invoice_list.html", - "public/js/payment/pos_payment.html", - "public/js/payment/payment_details.html", "public/js/templates/item_selector.html", "public/js/templates/employees_to_mark_attendance.html", "public/js/utils/item_selector.js", @@ -50,7 +42,8 @@ "public/js/hub/hub_factory.js", "public/js/call_popup/call_popup.js", "public/js/utils/dimension_tree_filter.js", - "public/js/telephony.js" + "public/js/telephony.js", + "public/js/templates/call_link.html" ], "js/item-dashboard.min.js": [ "stock/dashboard/item_dashboard.html", @@ -58,5 +51,20 @@ "stock/dashboard/item_dashboard.js", "stock/page/warehouse_capacity_summary/warehouse_capacity_summary.html", "stock/page/warehouse_capacity_summary/warehouse_capacity_summary_header.html" + ], + "js/point-of-sale.min.js": [ + "selling/page/point_of_sale/pos_item_selector.js", + "selling/page/point_of_sale/pos_item_cart.js", + "selling/page/point_of_sale/pos_item_details.js", + "selling/page/point_of_sale/pos_number_pad.js", + "selling/page/point_of_sale/pos_payment.js", + "selling/page/point_of_sale/pos_past_order_list.js", + "selling/page/point_of_sale/pos_past_order_summary.js", + "selling/page/point_of_sale/pos_controller.js" + ], + "js/bank-reconciliation-tool.min.js": [ + "public/js/bank_reconciliation_tool/data_table_manager.js", + "public/js/bank_reconciliation_tool/number_card.js", + "public/js/bank_reconciliation_tool/dialog_manager.js" ] } diff --git a/erpnext/public/css/pos.css b/erpnext/public/css/pos.css deleted file mode 100644 index 47f577131af..00000000000 --- a/erpnext/public/css/pos.css +++ /dev/null @@ -1,217 +0,0 @@ -[data-route="point-of-sale"] .layout-main-section { border: none; font-size: 12px; } -[data-route="point-of-sale"] .layout-main-section-wrapper { margin-bottom: 0; } -[data-route="point-of-sale"] .pos-items-wrapper { max-height: calc(100vh - 210px); } -:root { --border-color: #d1d8dd; --text-color: #8d99a6; --primary: #5e64ff; } -[data-route="point-of-sale"] .flex { display: flex; } -[data-route="point-of-sale"] .grid { display: grid; } -[data-route="point-of-sale"] .absolute { position: absolute; } -[data-route="point-of-sale"] .relative { position: relative; } -[data-route="point-of-sale"] .abs-center { top: 50%; left: 50%; transform: translate(-50%, -50%); } -[data-route="point-of-sale"] .inline { display: inline; } -[data-route="point-of-sale"] .float-right { float: right; } -[data-route="point-of-sale"] .grid-cols-1 { grid-template-columns: repeat(1, minmax(0, 1fr)); } -[data-route="point-of-sale"] .grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); } -[data-route="point-of-sale"] .grid-cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); } -[data-route="point-of-sale"] .grid-cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); } -[data-route="point-of-sale"] .grid-cols-5 { grid-template-columns: repeat(5, minmax(0, 1fr)); } -[data-route="point-of-sale"] .grid-cols-10 { grid-template-columns: repeat(10, minmax(0, 1fr)); } -[data-route="point-of-sale"] .gap-2 { grid-gap: 0.5rem; gap: 0.5rem; } -[data-route="point-of-sale"] .gap-4 { grid-gap: 1rem; gap: 1rem; } -[data-route="point-of-sale"] .gap-6 { grid-gap: 1.25rem; gap: 1.25rem; } -[data-route="point-of-sale"] .gap-8 { grid-gap: 1.5rem; gap: 1.5rem; } -[data-route="point-of-sale"] .row-gap-2 { grid-row-gap: 0.5rem; row-gap: 0.5rem; } -[data-route="point-of-sale"] .col-gap-4 { grid-column-gap: 1rem; column-gap: 1rem; } -[data-route="point-of-sale"] .col-span-2 { grid-column: span 2 / span 2; } -[data-route="point-of-sale"] .col-span-3 { grid-column: span 3 / span 3; } -[data-route="point-of-sale"] .col-span-4 { grid-column: span 4 / span 4; } -[data-route="point-of-sale"] .col-span-6 { grid-column: span 6 / span 6; } -[data-route="point-of-sale"] .col-span-10 { grid-column: span 10 / span 10; } -[data-route="point-of-sale"] .row-span-2 { grid-row: span 2 / span 2; } -[data-route="point-of-sale"] .grid-auto-row { grid-auto-rows: 5.5rem; } -[data-route="point-of-sale"] .d-none { display: none; } -[data-route="point-of-sale"] .flex-wrap { flex-wrap: wrap; } -[data-route="point-of-sale"] .flex-row { flex-direction: row; } -[data-route="point-of-sale"] .flex-col { flex-direction: column; } -[data-route="point-of-sale"] .flex-row-rev { flex-direction: row-reverse; } -[data-route="point-of-sale"] .flex-col-rev { flex-direction: column-reverse; } -[data-route="point-of-sale"] .flex-1 { flex: 1 1 0%; } -[data-route="point-of-sale"] .items-center { align-items: center; } -[data-route="point-of-sale"] .items-end { align-items: flex-end; } -[data-route="point-of-sale"] .f-grow-1 { flex-grow: 1; } -[data-route="point-of-sale"] .f-grow-2 { flex-grow: 2; } -[data-route="point-of-sale"] .f-grow-3 { flex-grow: 3; } -[data-route="point-of-sale"] .f-grow-4 { flex-grow: 4; } -[data-route="point-of-sale"] .f-shrink-0 { flex-shrink: 0; } -[data-route="point-of-sale"] .f-shrink-1 { flex-shrink: 1; } -[data-route="point-of-sale"] .f-shrink-2 { flex-shrink: 2; } -[data-route="point-of-sale"] .f-shrink-3 { flex-shrink: 3; } -[data-route="point-of-sale"] .shadow { box-shadow: 0 0px 3px 0 rgba(0, 0, 0, 0.2), 0 1px 2px 0 rgba(0, 0, 0, 0.06); } -[data-route="point-of-sale"] .shadow-sm { box-shadow: 0 0.5px 3px 0 rgba(0, 0, 0, 0.125); } -[data-route="point-of-sale"] .shadow-inner { box-shadow: inset 0 2px 4px 0 rgba(0, 0, 0, 0.1); } -[data-route="point-of-sale"] .rounded { border-radius: 0.3rem; } -[data-route="point-of-sale"] .rounded-b { border-bottom-left-radius: 0.3rem; border-bottom-right-radius: 0.3rem; } -[data-route="point-of-sale"] .p-8 { padding: 2rem; } -[data-route="point-of-sale"] .p-16 { padding: 4rem; } -[data-route="point-of-sale"] .p-32 { padding: 8rem; } -[data-route="point-of-sale"] .p-6 { padding: 1.5rem; } -[data-route="point-of-sale"] .p-4 { padding: 1rem; } -[data-route="point-of-sale"] .p-3 { padding: 0.75rem; } -[data-route="point-of-sale"] .p-2 { padding: 0.5rem; } -[data-route="point-of-sale"] .m-8 { margin: 2rem; } -[data-route="point-of-sale"] .p-1 { padding: 0.25rem; } -[data-route="point-of-sale"] .pr-0 { padding-right: 0rem; } -[data-route="point-of-sale"] .pl-0 { padding-left: 0rem; } -[data-route="point-of-sale"] .pt-0 { padding-top: 0rem; } -[data-route="point-of-sale"] .pb-0 { padding-bottom: 0rem; } -[data-route="point-of-sale"] .mr-0 { margin-right: 0rem; } -[data-route="point-of-sale"] .ml-0 { margin-left: 0rem; } -[data-route="point-of-sale"] .mt-0 { margin-top: 0rem; } -[data-route="point-of-sale"] .mb-0 { margin-bottom: 0rem; } -[data-route="point-of-sale"] .pr-2 { padding-right: 0.5rem; } -[data-route="point-of-sale"] .pl-2 { padding-left: 0.5rem; } -[data-route="point-of-sale"] .pt-2 { padding-top: 0.5rem; } -[data-route="point-of-sale"] .pb-2 { padding-bottom: 0.5rem; } -[data-route="point-of-sale"] .pr-3 { padding-right: 0.75rem; } -[data-route="point-of-sale"] .pl-3 { padding-left: 0.75rem; } -[data-route="point-of-sale"] .pt-3 { padding-top: 0.75rem; } -[data-route="point-of-sale"] .pb-3 { padding-bottom: 0.75rem; } -[data-route="point-of-sale"] .pr-4 { padding-right: 1rem; } -[data-route="point-of-sale"] .pl-4 { padding-left: 1rem; } -[data-route="point-of-sale"] .pt-4 { padding-top: 1rem; } -[data-route="point-of-sale"] .pb-4 { padding-bottom: 1rem; } -[data-route="point-of-sale"] .mr-4 { margin-right: 1rem; } -[data-route="point-of-sale"] .ml-4 { margin-left: 1rem; } -[data-route="point-of-sale"] .mt-4 { margin-top: 1rem; } -[data-route="point-of-sale"] .mb-4 { margin-bottom: 1rem; } -[data-route="point-of-sale"] .mr-2 { margin-right: 0.5rem; } -[data-route="point-of-sale"] .ml-2 { margin-left: 0.5rem; } -[data-route="point-of-sale"] .mt-2 { margin-top: 0.5rem; } -[data-route="point-of-sale"] .mb-2 { margin-bottom: 0.5rem; } -[data-route="point-of-sale"] .mr-1 { margin-right: 0.25rem; } -[data-route="point-of-sale"] .ml-1 { margin-left: 0.25rem; } -[data-route="point-of-sale"] .mt-1 { margin-top: 0.25rem; } -[data-route="point-of-sale"] .mb-1 { margin-bottom: 0.25rem; } -[data-route="point-of-sale"] .mr-auto { margin-right: auto; } -[data-route="point-of-sale"] .ml-auto { margin-left: auto; } -[data-route="point-of-sale"] .mt-auto { margin-top: auto; } -[data-route="point-of-sale"] .mb-auto { margin-bottom: auto; } -[data-route="point-of-sale"] .pr-6 { padding-right: 1.5rem; } -[data-route="point-of-sale"] .pl-6 { padding-left: 1.5rem; } -[data-route="point-of-sale"] .pt-6 { padding-top: 1.5rem; } -[data-route="point-of-sale"] .pb-6 { padding-bottom: 1.5rem; } -[data-route="point-of-sale"] .mr-6 { margin-right: 1.5rem; } -[data-route="point-of-sale"] .ml-6 { margin-left: 1.5rem; } -[data-route="point-of-sale"] .mt-6 { margin-top: 1.5rem; } -[data-route="point-of-sale"] .mb-6 { margin-bottom: 1.5rem; } -[data-route="point-of-sale"] .mr-8 { margin-right: 2rem; } -[data-route="point-of-sale"] .ml-8 { margin-left: 2rem; } -[data-route="point-of-sale"] .mt-8 { margin-top: 2rem; } -[data-route="point-of-sale"] .mb-8 { margin-bottom: 2rem; } -[data-route="point-of-sale"] .pr-8 { padding-right: 2rem; } -[data-route="point-of-sale"] .pl-8 { padding-left: 2rem; } -[data-route="point-of-sale"] .pt-8 { padding-top: 2rem; } -[data-route="point-of-sale"] .pb-8 { padding-bottom: 2rem; } -[data-route="point-of-sale"] .pr-16 { padding-right: 4rem; } -[data-route="point-of-sale"] .pl-16 { padding-left: 4rem; } -[data-route="point-of-sale"] .pt-16 { padding-top: 4rem; } -[data-route="point-of-sale"] .pb-16 { padding-bottom: 4rem; } -[data-route="point-of-sale"] .w-full { width: 100%; } -[data-route="point-of-sale"] .h-full { height: 100%; } -[data-route="point-of-sale"] .w-quarter { width: 25%; } -[data-route="point-of-sale"] .w-half { width: 50%; } -[data-route="point-of-sale"] .w-66 { width: 66.66%; } -[data-route="point-of-sale"] .w-33 { width: 33.33%; } -[data-route="point-of-sale"] .w-60 { width: 60%; } -[data-route="point-of-sale"] .w-40 { width: 40%; } -[data-route="point-of-sale"] .w-fit { width: fit-content; } -[data-route="point-of-sale"] .w-6 { width: 2rem; } -[data-route="point-of-sale"] .h-6 { min-height: 2rem; height: 2rem; } -[data-route="point-of-sale"] .w-8 { width: 2.5rem; } -[data-route="point-of-sale"] .h-8 { min-height: 2.5rem; height: 2.5rem; } -[data-route="point-of-sale"] .w-10 { width: 3rem; } -[data-route="point-of-sale"] .h-10 { min-height:3rem; height: 3rem; } -[data-route="point-of-sale"] .h-12 { min-height: 3.3rem; height: 3.3rem; } -[data-route="point-of-sale"] .w-12 { width: 3.3rem; } -[data-route="point-of-sale"] .h-14 { min-height: 4.2rem; height: 4.2rem; } -[data-route="point-of-sale"] .h-16 { min-height: 4.6rem; height: 4.6rem; } -[data-route="point-of-sale"] .h-18 { min-height: 5rem; height: 5rem; } -[data-route="point-of-sale"] .w-18 { width: 5.4rem; } -[data-route="point-of-sale"] .w-24 { width: 7.2rem; } -[data-route="point-of-sale"] .w-26 { width: 8.4rem; } -[data-route="point-of-sale"] .h-24 { min-height: 7.2rem; height: 7.2rem; } -[data-route="point-of-sale"] .h-32 { min-height: 9.6rem; height: 9.6rem; } -[data-route="point-of-sale"] .w-46 { width: 15rem; } -[data-route="point-of-sale"] .h-46 { min-height:15rem; height: 15rem; } -[data-route="point-of-sale"] .h-100 { height: 100vh; } -[data-route="point-of-sale"] .mx-h-70 { max-height: 67rem; } -[data-route="point-of-sale"] .border-grey-300 { border-color: #e2e8f0; } -[data-route="point-of-sale"] .border-grey { border: 1px solid #d1d8dd; } -[data-route="point-of-sale"] .border-white { border: 1px solid #fff; } -[data-route="point-of-sale"] .border-b-grey { border-bottom: 1px solid #d1d8dd; } -[data-route="point-of-sale"] .border-t-grey { border-top: 1px solid #d1d8dd; } -[data-route="point-of-sale"] .border-r-grey { border-right: 1px solid #d1d8dd; } -[data-route="point-of-sale"] .text-dark-grey { color: #5f5f5f; } -[data-route="point-of-sale"] .text-grey { color: #8d99a6; } -[data-route="point-of-sale"] .text-grey-100 { color: #d1d8dd; } -[data-route="point-of-sale"] .text-grey-200 { color: #a0aec0; } -[data-route="point-of-sale"] .bg-green-200 { background-color: #c6f6d5; } -[data-route="point-of-sale"] .text-bold { font-weight: bold; } -[data-route="point-of-sale"] .italic { font-style: italic; } -[data-route="point-of-sale"] .font-weight-450 { font-weight: 450; } -[data-route="point-of-sale"] .justify-around { justify-content: space-around; } -[data-route="point-of-sale"] .justify-between { justify-content: space-between; } -[data-route="point-of-sale"] .justify-center { justify-content: center; } -[data-route="point-of-sale"] .justify-end { justify-content: flex-end; } -[data-route="point-of-sale"] .bg-white { background-color: white; } -[data-route="point-of-sale"] .bg-light-grey { background-color: #f0f4f7; } -[data-route="point-of-sale"] .bg-grey-100 { background-color: #f7fafc; } -[data-route="point-of-sale"] .bg-grey-200 { background-color: #edf2f7; } -[data-route="point-of-sale"] .bg-grey { background-color: #f4f5f6; } -[data-route="point-of-sale"] .text-center { text-align: center; } -[data-route="point-of-sale"] .text-right { text-align: right; } -[data-route="point-of-sale"] .text-sm { font-size: 1rem; } -[data-route="point-of-sale"] .text-md-0 { font-size: 1.25rem; } -[data-route="point-of-sale"] .text-md { font-size: 1.4rem; } -[data-route="point-of-sale"] .text-lg { font-size: 1.6rem; } -[data-route="point-of-sale"] .text-xl { font-size: 2.2rem; } -[data-route="point-of-sale"] .text-2xl { font-size: 2.8rem; } -[data-route="point-of-sale"] .text-2-5xl { font-size: 3rem; } -[data-route="point-of-sale"] .text-3xl { font-size: 3.8rem; } -[data-route="point-of-sale"] .text-6xl { font-size: 4.8rem; } -[data-route="point-of-sale"] .line-through { text-decoration: line-through; } -[data-route="point-of-sale"] .text-primary { color: #5e64ff; } -[data-route="point-of-sale"] .text-white { color: #fff; } -[data-route="point-of-sale"] .text-green-500 { color: #48bb78; } -[data-route="point-of-sale"] .bg-primary { background-color: #5e64ff; } -[data-route="point-of-sale"] .border-primary { border-color: #5e64ff; } -[data-route="point-of-sale"] .text-danger { color: #e53e3e; } -[data-route="point-of-sale"] .scroll-x { overflow-x: scroll;overflow-y: hidden; } -[data-route="point-of-sale"] .scroll-y { overflow-y: scroll;overflow-x: hidden; } -[data-route="point-of-sale"] .overflow-hidden { overflow: hidden; } -[data-route="point-of-sale"] .whitespace-nowrap { white-space: nowrap; } -[data-route="point-of-sale"] .sticky { position: sticky; top: -1px; } -[data-route="point-of-sale"] .bg-white { background-color: #fff; } -[data-route="point-of-sale"] .bg-selected { background-color: #fffdf4; } -[data-route="point-of-sale"] .border-dashed { border-width:1px; border-style: dashed; } -[data-route="point-of-sale"] .z-100 { z-index: 100; } - -[data-route="point-of-sale"] .frappe-control { margin: 0 !important; width: 100%; } -[data-route="point-of-sale"] .form-control { font-size: 12px; } -[data-route="point-of-sale"] .form-group { margin: 0 !important; } -[data-route="point-of-sale"] .pointer { cursor: pointer; } -[data-route="point-of-sale"] .no-select { user-select: none; } -[data-route="point-of-sale"] .item-wrapper:hover { transform: scale(1.02, 1.02); } -[data-route="point-of-sale"] .hover-underline:hover { text-decoration: underline; } -[data-route="point-of-sale"] .item-wrapper { transition: scale 0.2s ease-in-out; } -[data-route="point-of-sale"] .cart-items-section .cart-item-wrapper:not(:first-child) { border-top: none; } -[data-route="point-of-sale"] .customer-transactions .invoice-wrapper:not(:first-child) { border-top: none; } - -[data-route="point-of-sale"] .payment-summary-wrapper:last-child { border-bottom: none; } -[data-route="point-of-sale"] .item-summary-wrapper:last-child { border-bottom: none; } -[data-route="point-of-sale"] .total-summary-wrapper:last-child { border-bottom: none; } -[data-route="point-of-sale"] .invoices-container .invoice-wrapper:last-child { border-bottom: none; } -[data-route="point-of-sale"] .new-btn { background-color: #5e64ff; color: white; border: none;} -[data-route="point-of-sale"] .summary-btns:last-child { margin-right: 0px; } -[data-route="point-of-sale"] ::-webkit-scrollbar { width: 1px } - -[data-route="point-of-sale"] .indicator.grey::before { background-color: #8d99a6; } \ No newline at end of file diff --git a/erpnext/public/images/erp-icon.svg b/erpnext/public/images/erp-icon.svg deleted file mode 100644 index 6bec40cc62e..00000000000 --- a/erpnext/public/images/erp-icon.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - - erpnext-logo - Created with Sketch. - - - - - \ No newline at end of file diff --git a/erpnext/public/images/erpnext-12.svg b/erpnext/public/images/erpnext-12.svg deleted file mode 100644 index fcc8e46fdd5..00000000000 --- a/erpnext/public/images/erpnext-12.svg +++ /dev/null @@ -1,26 +0,0 @@ - - - - version-12 - Created with Sketch. - - - - - - - - - - - - - - - - - 12 - - - - \ No newline at end of file diff --git a/erpnext/public/images/erpnext-favicon.svg b/erpnext/public/images/erpnext-favicon.svg new file mode 100644 index 00000000000..a3ac3bb2ce2 --- /dev/null +++ b/erpnext/public/images/erpnext-favicon.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/erpnext/public/images/erpnext-footer.png b/erpnext/public/images/erpnext-footer.png deleted file mode 100644 index ffff7756b77..00000000000 Binary files a/erpnext/public/images/erpnext-footer.png and /dev/null differ diff --git a/erpnext/public/images/erpnext-logo.png b/erpnext/public/images/erpnext-logo.png index 115faaa6a8f..3090727d8ff 100644 Binary files a/erpnext/public/images/erpnext-logo.png and b/erpnext/public/images/erpnext-logo.png differ diff --git a/erpnext/public/images/erpnext-logo.svg b/erpnext/public/images/erpnext-logo.svg new file mode 100644 index 00000000000..a3ac3bb2ce2 --- /dev/null +++ b/erpnext/public/images/erpnext-logo.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/erpnext/public/images/favicon.png b/erpnext/public/images/favicon.png deleted file mode 100644 index b6948856f88..00000000000 Binary files a/erpnext/public/images/favicon.png and /dev/null differ diff --git a/erpnext/public/images/splash.png b/erpnext/public/images/splash.png deleted file mode 100644 index 8e5d055c660..00000000000 Binary files a/erpnext/public/images/splash.png and /dev/null differ diff --git a/erpnext/public/images/ui-states/cart-empty-state.png b/erpnext/public/images/ui-states/cart-empty-state.png new file mode 100644 index 00000000000..e1ead0e175d Binary files /dev/null and b/erpnext/public/images/ui-states/cart-empty-state.png differ diff --git a/erpnext/public/js/bank_reconciliation_tool/data_table_manager.js b/erpnext/public/js/bank_reconciliation_tool/data_table_manager.js new file mode 100644 index 00000000000..5bb58faf2fc --- /dev/null +++ b/erpnext/public/js/bank_reconciliation_tool/data_table_manager.js @@ -0,0 +1,220 @@ +frappe.provide("erpnext.accounts.bank_reconciliation"); + +erpnext.accounts.bank_reconciliation.DataTableManager = class DataTableManager { + constructor(opts) { + Object.assign(this, opts); + this.dialog_manager = new erpnext.accounts.bank_reconciliation.DialogManager( + this.company, + this.bank_account + ); + this.make_dt(); + } + + make_dt() { + var me = this; + frappe.call({ + method: + "erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.get_bank_transactions", + args: { + bank_account: this.bank_account, + }, + callback: function (response) { + me.format_data(response.message); + me.get_dt_columns(); + me.get_datatable(); + me.set_listeners(); + }, + }); + } + + get_dt_columns() { + this.columns = [ + { + name: "Date", + editable: false, + width: 100, + }, + + { + name: "Party Type", + editable: false, + width: 95, + }, + { + name: "Party", + editable: false, + width: 100, + }, + { + name: "Description", + editable: false, + width: 350, + }, + { + name: "Deposit", + editable: false, + width: 100, + format: (value) => + "" + + format_currency(value, this.currency) + + "", + }, + { + name: "Withdrawal", + editable: false, + width: 100, + format: (value) => + "" + + format_currency(value, this.currency) + + "", + }, + { + name: "Unallocated Amount", + editable: false, + width: 100, + format: (value) => + "" + + format_currency(value, this.currency) + + "", + }, + { + name: "Reference Number", + editable: false, + width: 140, + }, + { + name: "Actions", + editable: false, + sortable: false, + focusable: false, + dropdown: false, + width: 80, + }, + ]; + } + + format_data(transactions) { + this.transactions = []; + if (transactions[0]) { + this.currency = transactions[0]["currency"]; + } + this.transaction_dt_map = {}; + let length; + transactions.forEach((row) => { + length = this.transactions.push(this.format_row(row)); + this.transaction_dt_map[row["name"]] = length - 1; + }); + } + + format_row(row) { + return [ + row["date"], + row["party_type"], + row["party"], + row["description"], + row["deposit"], + row["withdrawal"], + row["unallocated_amount"], + row["reference_number"], + ` + - - - - \ No newline at end of file diff --git a/erpnext/public/js/payment/pos_payment.html b/erpnext/public/js/payment/pos_payment.html deleted file mode 100644 index cb6971b46b5..00000000000 --- a/erpnext/public/js/payment/pos_payment.html +++ /dev/null @@ -1,42 +0,0 @@ -
    -
    -

    {{ __("Total Amount") }}: {%= format_currency(grand_total, currency) %}

    -
    -
    -
    -

    {{ __("Paid") }}

    -
    -
    -

    {{ __("Outstanding") }}

    {%= format_currency(outstanding_amount, currency) %}

    -
    -
    -

    {{ __("Change") }} -

    -
    -
    -

    {{ __("Write off") }} -

    -
    -
    -
    -
    -
    -
    -
    -
    -
    - {% for(var i=0; i<3; i++) { %} -
    - {% for(var j=i*3; j<(i+1)*3; j++) { %} - - {% } %} -
    - {% } %} -
    - - - -
    -
    -
    -
    diff --git a/erpnext/public/js/pos/clusterize.js b/erpnext/public/js/pos/clusterize.js deleted file mode 100644 index 075c9ca4ae6..00000000000 --- a/erpnext/public/js/pos/clusterize.js +++ /dev/null @@ -1,330 +0,0 @@ -/* eslint-disable */ -/*! Clusterize.js - v0.17.6 - 2017-03-05 -* http://NeXTs.github.com/Clusterize.js/ -* Copyright (c) 2015 Denis Lukov; Licensed GPLv3 */ - -;(function(name, definition) { - if (typeof module != 'undefined') module.exports = definition(); - else if (typeof define == 'function' && typeof define.amd == 'object') define(definition); - else this[name] = definition(); -}('Clusterize', function() { - "use strict" - - // detect ie9 and lower - // https://gist.github.com/padolsey/527683#comment-786682 - var ie = (function(){ - for( var v = 3, - el = document.createElement('b'), - all = el.all || []; - el.innerHTML = '', - all[0]; - ){} - return v > 4 ? v : document.documentMode; - }()), - is_mac = navigator.platform.toLowerCase().indexOf('mac') + 1; - var Clusterize = function(data) { - if( ! (this instanceof Clusterize)) - return new Clusterize(data); - var self = this; - - var defaults = { - rows_in_block: 50, - blocks_in_cluster: 4, - tag: null, - show_no_data_row: true, - no_data_class: 'clusterize-no-data', - no_data_text: 'No data', - keep_parity: true, - callbacks: {} - } - - // public parameters - self.options = {}; - var options = ['rows_in_block', 'blocks_in_cluster', 'show_no_data_row', 'no_data_class', 'no_data_text', 'keep_parity', 'tag', 'callbacks']; - for(var i = 0, option; option = options[i]; i++) { - self.options[option] = typeof data[option] != 'undefined' && data[option] != null - ? data[option] - : defaults[option]; - } - - var elems = ['scroll', 'content']; - for(var i = 0, elem; elem = elems[i]; i++) { - self[elem + '_elem'] = data[elem + 'Id'] - ? document.getElementById(data[elem + 'Id']) - : data[elem + 'Elem']; - if( ! self[elem + '_elem']) - throw new Error("Error! Could not find " + elem + " element"); - } - - // tabindex forces the browser to keep focus on the scrolling list, fixes #11 - if( ! self.content_elem.hasAttribute('tabindex')) - self.content_elem.setAttribute('tabindex', 0); - - // private parameters - var rows = isArray(data.rows) - ? data.rows - : self.fetchMarkup(), - cache = {}, - scroll_top = self.scroll_elem.scrollTop; - - // append initial data - self.insertToDOM(rows, cache); - - // restore the scroll position - self.scroll_elem.scrollTop = scroll_top; - - // adding scroll handler - var last_cluster = false, - scroll_debounce = 0, - pointer_events_set = false, - scrollEv = function() { - // fixes scrolling issue on Mac #3 - if (is_mac) { - if( ! pointer_events_set) self.content_elem.style.pointerEvents = 'none'; - pointer_events_set = true; - clearTimeout(scroll_debounce); - scroll_debounce = setTimeout(function () { - self.content_elem.style.pointerEvents = 'auto'; - pointer_events_set = false; - }, 50); - } - if (last_cluster != (last_cluster = self.getClusterNum())) - self.insertToDOM(rows, cache); - if (self.options.callbacks.scrollingProgress) - self.options.callbacks.scrollingProgress(self.getScrollProgress()); - }, - resize_debounce = 0, - resizeEv = function() { - clearTimeout(resize_debounce); - resize_debounce = setTimeout(self.refresh, 100); - } - on('scroll', self.scroll_elem, scrollEv); - on('resize', window, resizeEv); - - // public methods - self.destroy = function(clean) { - off('scroll', self.scroll_elem, scrollEv); - off('resize', window, resizeEv); - self.html((clean ? self.generateEmptyRow() : rows).join('')); - } - self.refresh = function(force) { - if(self.getRowsHeight(rows) || force) self.update(rows); - } - self.update = function(new_rows) { - rows = isArray(new_rows) - ? new_rows - : []; - var scroll_top = self.scroll_elem.scrollTop; - // fixes #39 - if(rows.length * self.options.item_height < scroll_top) { - self.scroll_elem.scrollTop = 0; - last_cluster = 0; - } - self.insertToDOM(rows, cache); - self.scroll_elem.scrollTop = scroll_top; - } - self.clear = function() { - self.update([]); - } - self.getRowsAmount = function() { - return rows.length; - } - self.getScrollProgress = function() { - return this.options.scroll_top / (rows.length * this.options.item_height) * 100 || 0; - } - - var add = function(where, _new_rows) { - var new_rows = isArray(_new_rows) - ? _new_rows - : []; - if( ! new_rows.length) return; - rows = where == 'append' - ? rows.concat(new_rows) - : new_rows.concat(rows); - self.insertToDOM(rows, cache); - } - self.append = function(rows) { - add('append', rows); - } - self.prepend = function(rows) { - add('prepend', rows); - } - } - - Clusterize.prototype = { - constructor: Clusterize, - // fetch existing markup - fetchMarkup: function() { - var rows = [], rows_nodes = this.getChildNodes(this.content_elem); - while (rows_nodes.length) { - rows.push(rows_nodes.shift().outerHTML); - } - return rows; - }, - // get tag name, content tag name, tag height, calc cluster height - exploreEnvironment: function(rows, cache) { - var opts = this.options; - opts.content_tag = this.content_elem.tagName.toLowerCase(); - if( ! rows.length) return; - if(ie && ie <= 9 && ! opts.tag) opts.tag = rows[0].match(/<([^>\s/]*)/)[1].toLowerCase(); - if(this.content_elem.children.length <= 1) cache.data = this.html(rows[0] + rows[0] + rows[0]); - if( ! opts.tag) opts.tag = this.content_elem.children[0].tagName.toLowerCase(); - this.getRowsHeight(rows); - }, - getRowsHeight: function(rows) { - var opts = this.options, - prev_item_height = opts.item_height; - opts.cluster_height = 0; - if( ! rows.length) return; - var nodes = this.content_elem.children; - var node = nodes[Math.floor(nodes.length / 2)]; - opts.item_height = node.offsetHeight; - // consider table's border-spacing - if(opts.tag == 'tr' && getStyle('borderCollapse', this.content_elem) != 'collapse') - opts.item_height += parseInt(getStyle('borderSpacing', this.content_elem), 10) || 0; - // consider margins (and margins collapsing) - if(opts.tag != 'tr') { - var marginTop = parseInt(getStyle('marginTop', node), 10) || 0; - var marginBottom = parseInt(getStyle('marginBottom', node), 10) || 0; - opts.item_height += Math.max(marginTop, marginBottom); - } - opts.block_height = opts.item_height * opts.rows_in_block; - opts.rows_in_cluster = opts.blocks_in_cluster * opts.rows_in_block; - opts.cluster_height = opts.blocks_in_cluster * opts.block_height; - return prev_item_height != opts.item_height; - }, - // get current cluster number - getClusterNum: function () { - this.options.scroll_top = this.scroll_elem.scrollTop; - return Math.floor(this.options.scroll_top / (this.options.cluster_height - this.options.block_height)) || 0; - }, - // generate empty row if no data provided - generateEmptyRow: function() { - var opts = this.options; - if( ! opts.tag || ! opts.show_no_data_row) return []; - var empty_row = document.createElement(opts.tag), - no_data_content = document.createTextNode(opts.no_data_text), td; - empty_row.className = opts.no_data_class; - if(opts.tag == 'tr') { - td = document.createElement('td'); - // fixes #53 - td.colSpan = 100; - td.appendChild(no_data_content); - } - empty_row.appendChild(td || no_data_content); - return [empty_row.outerHTML]; - }, - // generate cluster for current scroll position - generate: function (rows, cluster_num) { - var opts = this.options, - rows_len = rows.length; - if (rows_len < opts.rows_in_block) { - return { - top_offset: 0, - bottom_offset: 0, - rows_above: 0, - rows: rows_len ? rows : this.generateEmptyRow() - } - } - var items_start = Math.max((opts.rows_in_cluster - opts.rows_in_block) * cluster_num, 0), - items_end = items_start + opts.rows_in_cluster, - top_offset = Math.max(items_start * opts.item_height, 0), - bottom_offset = Math.max((rows_len - items_end) * opts.item_height, 0), - this_cluster_rows = [], - rows_above = items_start; - if(top_offset < 1) { - rows_above++; - } - for (var i = items_start; i < items_end; i++) { - rows[i] && this_cluster_rows.push(rows[i]); - } - return { - top_offset: top_offset, - bottom_offset: bottom_offset, - rows_above: rows_above, - rows: this_cluster_rows - } - }, - renderExtraTag: function(class_name, height) { - var tag = document.createElement(this.options.tag), - clusterize_prefix = 'clusterize-'; - tag.className = [clusterize_prefix + 'extra-row', clusterize_prefix + class_name].join(' '); - height && (tag.style.height = height + 'px'); - return tag.outerHTML; - }, - // if necessary verify data changed and insert to DOM - insertToDOM: function(rows, cache) { - // explore row's height - if( ! this.options.cluster_height) { - this.exploreEnvironment(rows, cache); - } - var data = this.generate(rows, this.getClusterNum()), - this_cluster_rows = data.rows.join(''), - this_cluster_content_changed = this.checkChanges('data', this_cluster_rows, cache), - top_offset_changed = this.checkChanges('top', data.top_offset, cache), - only_bottom_offset_changed = this.checkChanges('bottom', data.bottom_offset, cache), - callbacks = this.options.callbacks, - layout = []; - - if(this_cluster_content_changed || top_offset_changed) { - if(data.top_offset) { - this.options.keep_parity && layout.push(this.renderExtraTag('keep-parity')); - layout.push(this.renderExtraTag('top-space', data.top_offset)); - } - layout.push(this_cluster_rows); - data.bottom_offset && layout.push(this.renderExtraTag('bottom-space', data.bottom_offset)); - callbacks.clusterWillChange && callbacks.clusterWillChange(); - this.html(layout.join('')); - this.options.content_tag == 'ol' && this.content_elem.setAttribute('start', data.rows_above); - callbacks.clusterChanged && callbacks.clusterChanged(); - } else if(only_bottom_offset_changed) { - this.content_elem.lastChild.style.height = data.bottom_offset + 'px'; - } - }, - // unfortunately ie <= 9 does not allow to use innerHTML for table elements, so make a workaround - html: function(data) { - var content_elem = this.content_elem; - if(ie && ie <= 9 && this.options.tag == 'tr') { - var div = document.createElement('div'), last; - div.innerHTML = '' + data + '
    '; - while((last = content_elem.lastChild)) { - content_elem.removeChild(last); - } - var rows_nodes = this.getChildNodes(div.firstChild.firstChild); - while (rows_nodes.length) { - content_elem.appendChild(rows_nodes.shift()); - } - } else { - content_elem.innerHTML = data; - } - }, - getChildNodes: function(tag) { - var child_nodes = tag.children, nodes = []; - for (var i = 0, ii = child_nodes.length; i < ii; i++) { - nodes.push(child_nodes[i]); - } - return nodes; - }, - checkChanges: function(type, value, cache) { - var changed = value != cache[type]; - cache[type] = value; - return changed; - } - } - - // support functions - function on(evt, element, fnc) { - return element.addEventListener ? element.addEventListener(evt, fnc, false) : element.attachEvent("on" + evt, fnc); - } - function off(evt, element, fnc) { - return element.removeEventListener ? element.removeEventListener(evt, fnc, false) : element.detachEvent("on" + evt, fnc); - } - function isArray(arr) { - return Object.prototype.toString.call(arr) === '[object Array]'; - } - function getStyle(prop, elem) { - return window.getComputedStyle ? window.getComputedStyle(elem)[prop] : elem.currentStyle[prop]; - } - - return Clusterize; -})); \ No newline at end of file diff --git a/erpnext/public/js/pos/customer_toolbar.html b/erpnext/public/js/pos/customer_toolbar.html deleted file mode 100644 index 3ba5ccbc673..00000000000 --- a/erpnext/public/js/pos/customer_toolbar.html +++ /dev/null @@ -1,16 +0,0 @@ -
    -
    - - - - - -
    - - {% if (allow_delete) { %} - {% } %} -
    \ No newline at end of file diff --git a/erpnext/public/js/pos/pos.html b/erpnext/public/js/pos/pos.html deleted file mode 100644 index 89e2940c896..00000000000 --- a/erpnext/public/js/pos/pos.html +++ /dev/null @@ -1,136 +0,0 @@ -
    -
    -
    -
    {{ __("Item Cart") }}
    -
    -
    -
    - - - {{ __("Item Name")}} - - {{ __("Quantity") }} - {{ __("Discount") }} - {{ __("Rate") }} -
    -
    -
    - - -

    {{ __("Tap items to add them here") }}

    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    {%= __("Net Total") %}
    -
    -
    -
    -
    -
    {%= __("Taxes") %}
    -
    -
    -
    - {% if(allow_user_to_edit_discount) { %} -
    -
    -
    {%= __("Discount") %}
    -
    -
    - % - -
    -
    - {%= get_currency_symbol(currency) %} - -
    -
    -
    - {% } %} -
    -
    - - - -
    -
    {%= __("Grand Total") %}
    -
    -
    -
    -
    - - - -
    -
    {%= __("Qty Total") %}
    -
    -
    -
    -
    -
    - -
    - -
    -
    -
    -
    {{ __("Customers in Queue") }}
    -
    -
    {{ __("Customer") }}
    -
    {{ __("Status") }}
    -
    {{ __("Amount") }}
    -
    {{ __("Grand Total") }}
    -
    -
    -
    - - -

    {{ __("No Customers yet!") }}

    -
    -
    -
    -
    -
    -
    {{ __("Stock Items") }}
    -
    - -
    - -
    -
    - -
    -
    -
    - -
    -
    -
    -
    diff --git a/erpnext/public/js/pos/pos_bill_item.html b/erpnext/public/js/pos/pos_bill_item.html deleted file mode 100644 index 21868a6caed..00000000000 --- a/erpnext/public/js/pos/pos_bill_item.html +++ /dev/null @@ -1,34 +0,0 @@ -
    -
    {%= item_code || "" %}{%= __(item_name) || "" %}
    -
    -
    -
    -
    -
    - -
    - {% if(actual_qty != null) { %} -
    - {%= __("In Stock: ") %} {%= actual_qty || 0.0 %} -
    - {% } %} -
    -
    -
    -
    -
    -
    - -
    -
    -
    -
    - {% if(enabled) { %} - - {% } else { %} -
    {%= format_currency(rate) %}
    - {% } %} -
    -

    {%= amount %}

    -
    -
    diff --git a/erpnext/public/js/pos/pos_bill_item_new.html b/erpnext/public/js/pos/pos_bill_item_new.html deleted file mode 100644 index cb626cefcea..00000000000 --- a/erpnext/public/js/pos/pos_bill_item_new.html +++ /dev/null @@ -1,9 +0,0 @@ -
    - -
    {%= qty %}
    -
    {%= discount_percentage %}
    -
    {%= format_currency(rate) %}
    -
    diff --git a/erpnext/public/js/pos/pos_invoice_list.html b/erpnext/public/js/pos/pos_invoice_list.html deleted file mode 100644 index 13aa52055ac..00000000000 --- a/erpnext/public/js/pos/pos_invoice_list.html +++ /dev/null @@ -1,9 +0,0 @@ -
    - -
    {{ data.status }}
    -
    {%= paid_amount %}
    -
    {%= grand_total %}
    -
    diff --git a/erpnext/public/js/pos/pos_item.html b/erpnext/public/js/pos/pos_item.html deleted file mode 100755 index 52f3cf698ae..00000000000 --- a/erpnext/public/js/pos/pos_item.html +++ /dev/null @@ -1,32 +0,0 @@ - \ No newline at end of file diff --git a/erpnext/public/js/pos/pos_selected_item.html b/erpnext/public/js/pos/pos_selected_item.html deleted file mode 100644 index 03c73411a48..00000000000 --- a/erpnext/public/js/pos/pos_selected_item.html +++ /dev/null @@ -1,22 +0,0 @@ -
    -
    -
    {{ __("Quantity") }}:
    - -
    -
    -
    {{ __("Price List Rate") }}:
    - -
    -
    -
    {{ __("Discount") }}: %
    - -
    -
    -
    {{ __("Price") }}:
    - -
    -
    -
    {{ __("Amount") }}:
    - -
    -
    \ No newline at end of file diff --git a/erpnext/public/js/pos/pos_tax_row.html b/erpnext/public/js/pos/pos_tax_row.html deleted file mode 100644 index 3752a89bbdc..00000000000 --- a/erpnext/public/js/pos/pos_tax_row.html +++ /dev/null @@ -1,4 +0,0 @@ -
    -
    {%= description %}
    -
    {%= tax_amount %}
    -
    diff --git a/erpnext/public/js/setup_wizard.js b/erpnext/public/js/setup_wizard.js index 092f83903ea..ef03b01698c 100644 --- a/erpnext/public/js/setup_wizard.js +++ b/erpnext/public/js/setup_wizard.js @@ -127,11 +127,9 @@ erpnext.setup.slides_settings = [ options: "", fieldtype: 'Select' }, { fieldname: 'view_coa', label: __('View Chart of Accounts'), fieldtype: 'Button' }, - - { fieldtype: "Section Break", label: __('Financial Year') }, - { fieldname: 'fy_start_date', label: __('Start Date'), fieldtype: 'Date', reqd: 1 }, - { fieldtype: "Column Break" }, - { fieldname: 'fy_end_date', label: __('End Date'), fieldtype: 'Date', reqd: 1 }, + { fieldname: 'fy_start_date', label: __('Financial Year Begins On'), fieldtype: 'Date', reqd: 1 }, + // end date should be hidden (auto calculated) + { fieldname: 'fy_end_date', label: __('End Date'), fieldtype: 'Date', reqd: 1, hidden: 1 }, ], onload: function (slide) { diff --git a/erpnext/public/js/telephony.js b/erpnext/public/js/telephony.js index f9caadeed7f..9548d6c5f36 100644 --- a/erpnext/public/js/telephony.js +++ b/erpnext/public/js/telephony.js @@ -4,14 +4,24 @@ frappe.ui.form.ControlData = frappe.ui.form.ControlData.extend( { if (this.df.options == 'Phone') { this.setup_phone(); } + if (this.frm && this.frm.fields_dict) { + Object.values(this.frm.fields_dict).forEach(function(field) { + if (field.df.read_only === 1 && field.df.options === 'Phone' + && field.disp_area.style[0] != 'display' && !field.has_icon) { + field.setup_phone(); + field.has_icon = true; + } + }); + } }, setup_phone() { if (frappe.phone_call.handler) { - this.$wrapper.find('.control-input') + let control = this.df.read_only ? '.control-value' : '.control-input'; + this.$wrapper.find(control) .append(` - + ${frappe.utils.icon('call')} `) .find('.phone-btn') diff --git a/erpnext/public/js/templates/call_link.html b/erpnext/public/js/templates/call_link.html new file mode 100644 index 00000000000..071078c776e --- /dev/null +++ b/erpnext/public/js/templates/call_link.html @@ -0,0 +1,42 @@ +
    +
    +
    + {{ type }} Call + {% if (duration) %} + • {{ frappe.format(duration, { fieldtype: "Duration" }) }} + {% endif %} + • {{ comment_when(creation) }} +
    + + + + + + + +
    + + +
    + {% if (type === "Incoming") { %} + Incoming call from {{ from }}, received by {{ to }} + {% } else { %} + Outgoing Call made by {{ from }} to {{ to }} + {% } %} +
    + {% if (summary) { %} + {{ summary }} + {% } else { %} + {{ __("No Summary") }} + {% } %} +
    + {% if (recording_url) { %} +
    + +
    + {% } %} +
    +
    diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index c39609bd389..e5b50d86eda 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -513,6 +513,7 @@ erpnext.utils.update_child_items = function(opts) { }, { fieldtype:'Currency', fieldname:"rate", + options: "currency", default: 0, read_only: 0, in_list_view: 1, @@ -594,21 +595,7 @@ erpnext.utils.update_child_items = function(opts) { } erpnext.utils.map_current_doc = function(opts) { - let query_args = {}; - if (opts.get_query_filters) { - query_args.filters = opts.get_query_filters; - } - - if (opts.get_query_method) { - query_args.query = opts.get_query_method; - } - - if (query_args.filters || query_args.query) { - opts.get_query = () => { - return query_args; - } - } - var _map = function() { + function _map() { if($.isArray(cur_frm.doc.items) && cur_frm.doc.items.length > 0) { // remove first item row if empty if(!cur_frm.doc.items[0].item_code) { @@ -682,8 +669,22 @@ erpnext.utils.map_current_doc = function(opts) { } }); } - if(opts.source_doctype) { - var d = new frappe.ui.form.MultiSelectDialog({ + + let query_args = {}; + if (opts.get_query_filters) { + query_args.filters = opts.get_query_filters; + } + + if (opts.get_query_method) { + query_args.query = opts.get_query_method; + } + + if (query_args.filters || query_args.query) { + opts.get_query = () => query_args; + } + + if (opts.source_doctype) { + const d = new frappe.ui.form.MultiSelectDialog({ doctype: opts.source_doctype, target: opts.target, date_field: opts.date_field || undefined, @@ -702,7 +703,11 @@ erpnext.utils.map_current_doc = function(opts) { _map(); }, }); - } else if(opts.source_name) { + + return d; + } + + if (opts.source_name) { opts.source_name = [opts.source_name]; _map(); } diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js index 2623c3c1a73..d49a8138fb5 100644 --- a/erpnext/public/js/utils/serial_no_batch_selector.js +++ b/erpnext/public/js/utils/serial_no_batch_selector.js @@ -140,6 +140,7 @@ erpnext.SerialNoBatchSelector = Class.extend({ () => me.update_batch_serial_no_items(), () => { refresh_field("items"); + refresh_field("packed_items"); if (me.callback) { return me.callback(me.item); } @@ -154,7 +155,7 @@ erpnext.SerialNoBatchSelector = Class.extend({ if (this.item.serial_no) { this.dialog.fields_dict.serial_no.set_value(this.item.serial_no); } - + if (this.has_batch && !this.has_serial_no && d.batch_no) { this.frm.doc.items.forEach(data => { if(data.item_code == d.item_code) { @@ -231,7 +232,7 @@ erpnext.SerialNoBatchSelector = Class.extend({ this.map_row_values(row, batch, 'batch_no', 'selected_qty', this.values.warehouse); }); - } + } }, update_serial_no_item() { @@ -250,7 +251,7 @@ erpnext.SerialNoBatchSelector = Class.extend({ filters: { 'name': ["in", selected_serial_nos]}, fields: ["batch_no", "name"] }).then((data) => { - // data = [{batch_no: 'batch-1', name: "SR-001"}, + // data = [{batch_no: 'batch-1', name: "SR-001"}, // {batch_no: 'batch-2', name: "SR-003"}, {batch_no: 'batch-2', name: "SR-004"}] const batch_serial_map = data.reduce((acc, d) => { if (!acc[d['batch_no']]) acc[d['batch_no']] = []; @@ -298,6 +299,8 @@ erpnext.SerialNoBatchSelector = Class.extend({ } else { row.warehouse = values.warehouse || warehouse; } + + this.frm.dirty(); }, update_total_qty: function() { diff --git a/erpnext/public/less/call_popup.less b/erpnext/public/less/call_popup.less deleted file mode 100644 index 32e85ce16d9..00000000000 --- a/erpnext/public/less/call_popup.less +++ /dev/null @@ -1,9 +0,0 @@ -.call-popup { - a:hover { - text-decoration: underline; - } - .for-description { - max-height: 250px; - overflow: scroll; - } -} \ No newline at end of file diff --git a/erpnext/public/less/erpnext.less b/erpnext/public/less/erpnext.less index 8685837d33d..4076ebec1fd 100644 --- a/erpnext/public/less/erpnext.less +++ b/erpnext/public/less/erpnext.less @@ -39,8 +39,9 @@ .dashboard-list-item { background-color: inherit; - padding: 5px 0px; - border-bottom: 1px solid @border-color; + border-bottom: 1px solid var(--border-color); + font-size: var(--text-md); + color: var(--text-color); } #page-stock-balance .dashboard-list-item { @@ -446,20 +447,6 @@ body[data-route="pos"] { } -// Leaderboard - -.leaderboard { - .result { - border-top: 1px solid #d1d8dd; - } - .list-item { - padding-left: 45px; - } - .list-item_content { - padding-right: 45px; - } -} - // Healthcare .exercise-card { diff --git a/erpnext/public/less/hub.less b/erpnext/public/less/hub.less index 8cb7a9c1ce5..29deada8a41 100644 --- a/erpnext/public/less/hub.less +++ b/erpnext/public/less/hub.less @@ -32,7 +32,12 @@ body[data-route*="marketplace"] { } .hub-image-loading, .hub-image-broken { - .img-background(); + content: " "; + position: absolute; + left: 0; + height: 100%; + width: 100%; + background-color: var(--bg-light-gray); display: flex; align-items: center; justify-content: center; diff --git a/erpnext/public/scss/call_popup.scss b/erpnext/public/scss/call_popup.scss new file mode 100644 index 00000000000..95e31828c18 --- /dev/null +++ b/erpnext/public/scss/call_popup.scss @@ -0,0 +1,21 @@ +.call-popup { + a:hover { + text-decoration: underline; + } + .for-description { + max-height: 250px; + overflow: scroll; + } +} + +audio { + height: 40px; + width: 100%; + max-width: 500px; + background-color: var(--control-bg); + border-radius: var(--border-radius-sm); + &-webkit-media-controls-panel { + background: var(--control-bg); + } + outline: none; +} diff --git a/erpnext/public/scss/point-of-sale.scss b/erpnext/public/scss/point-of-sale.scss new file mode 100644 index 00000000000..0bb8e68b698 --- /dev/null +++ b/erpnext/public/scss/point-of-sale.scss @@ -0,0 +1,1110 @@ +.point-of-sale-app { + display: grid; + grid-template-columns: repeat(10, minmax(0, 1fr)); + gap: var(--margin-md); + + section { + min-height: 45rem; + height: calc(100vh - 200px); + max-height: calc(100vh - 200px); + } + + .frappe-control { + margin: 0 !important; + width: 100%; + } + + .form-group { + margin-bottom: 0px !important; + } + + .pointer-no-select { + cursor: pointer; + user-select: none; + } + + .nowrap { + overflow: hidden; + white-space: nowrap; + } + + .image { + height: 100% !important; + object-fit: cover; + } + + .abbr { + background-color: var(--gray-50); + font-size: var(--text-3xl); + } + + .label { + display: flex; + align-items: center; + font-weight: 700; + font-size: var(--text-lg); + } + + .pos-card { + background-color: var(--fg-color); + box-shadow: var(--shadow-base); + border-radius: var(--border-radius-md); + } + + .seperator { + margin-left: var(--margin-sm); + margin-right: var(--margin-sm); + border-bottom: 1px solid var(--gray-300); + } + + .primary-action { + @extend .pointer-no-select; + display: flex; + align-items: center; + justify-content: center; + padding: var(--padding-sm); + margin-top: var(--margin-sm); + border-radius: var(--border-radius-md); + font-size: var(--text-lg); + font-weight: 700; + } + + .highlighted-numpad-btn { + box-shadow: inset 0 0px 4px 0px rgba(0, 0, 0, 0.15) !important; + font-weight: 700; + background-color: var(--gray-50); + } + + > .items-selector { + @extend .pos-card; + grid-column: span 6 / span 6; + display: flex; + flex-direction: column; + overflow: hidden; + + > .filter-section { + display: grid; + grid-template-columns: repeat(12, minmax(0, 1fr)); + background-color: var(--fg-color); + padding: var(--padding-lg); + padding-bottom: var(--padding-sm); + align-items: center; + + > .label { + @extend .label; + grid-column: span 4 / span 4; + padding-bottom: var(--padding-xs); + } + + > .search-field { + grid-column: span 5 / span 5; + display: flex; + align-items: center; + margin: 0px var(--margin-sm); + } + + > .item-group-field { + grid-column: span 3 / span 3; + display: flex; + align-items: center; + } + } + + > .items-container { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: var(--margin-lg); + padding: var(--padding-lg); + padding-top: var(--padding-xs); + overflow-y: scroll; + overflow-x: hidden; + + &:after { + content: ""; + display: block; + height: 1px; + } + + > .item-wrapper { + @extend .pointer-no-select; + border-radius: var(--border-radius-md); + box-shadow: var(--shadow-base); + + &:hover { + transform: scale(1.02, 1.02); + } + + .item-display { + display: flex; + align-items: center; + justify-content: center; + border-radius: var(--border-radius-md); + margin: var(--margin-sm); + margin-bottom: 0px; + min-height: 8rem; + height: 8rem; + color: var(--gray-500); + + > img { + @extend .image; + } + } + + > .item-detail { + display: flex; + flex-direction: column; + justify-content: center; + min-height: 3.5rem; + height: 3.5rem; + padding-left: var(--padding-sm); + padding-right: var(--padding-sm); + + > .item-name { + @extend .nowrap; + display: flex; + align-items: center; + font-size: var(--text-md); + } + + > .item-rate { + font-weight: 700; + } + } + + } + } + } + + > .customer-cart-container { + grid-column: span 4 / span 4; + display: flex; + flex-direction: column; + + > .customer-section { + @extend .pos-card; + display: flex; + flex-direction: column; + padding: var(--padding-md) var(--padding-lg); + overflow: visible; + + > .customer-field { + display: flex; + align-items: center; + padding-top: var(--padding-xs); + } + + > .customer-details { + display: flex; + flex-direction: column; + background-color: var(--fg-color); + + > .header { + display: flex; + margin-bottom: var(--margin-md); + justify-content: space-between; + padding-top: var(--padding-md); + + > .label { + @extend .label; + } + + > .close-details-btn { + display: flex; + align-items: center; + cursor: pointer; + } + } + + > .customer-display { + display: flex; + align-items: center; + cursor: pointer; + + > .customer-image { + display: flex; + align-items: center; + justify-content: center; + width: 3rem; + height: 3rem; + border-radius: 50%; + color: var(--gray-500); + margin-right: var(--margin-md); + + > img { + @extend .image; + border-radius: 50%; + } + } + + > .customer-abbr { + @extend .abbr; + font-size: var(--text-2xl); + } + + > .customer-name-desc { + @extend .nowrap; + display: flex; + flex-direction: column; + margin-right: auto; + + >.customer-name { + font-weight: 700; + font-size: var(--text-lg); + } + + >.customer-desc { + color: var(--gray-600); + font-weight: 500; + font-size: var(--text-sm); + } + } + + > .reset-customer-btn { + display: flex; + align-items: center; + cursor: pointer; + } + + } + + > .customer-fields-container { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + margin-top: var(--margin-md); + column-gap: var(--padding-sm); + row-gap: var(--padding-xs); + } + + > .transactions-label { + @extend .label; + margin-top: var(--margin-md); + margin-bottom: var(--margin-sm); + } + } + + > .customer-transactions { + height: 100%; + overflow-x: hidden; + overflow-y: scroll; + margin-right: -12px; + padding-right: 12px; + margin-left: -10px; + + > .no-transactions-placeholder { + height: 100%; + display: flex; + align-items: center; + justify-content: center; + background-color: var(--gray-50); + border-radius: var(--border-radius-md); + } + } + } + + > .cart-container { + @extend .pos-card; + display: flex; + flex-direction: column; + align-items: center; + margin-top: var(--margin-md); + position: relative; + height: 100%; + + > .abs-cart-container { + position: absolute; + display: flex; + flex-direction: column; + padding: var(--padding-lg); + width: 100%; + height: 100%; + + > .cart-label { + @extend .label; + padding-bottom: var(--padding-md); + } + + > .cart-header { + display: flex; + width: 100%; + font-size: var(--text-md); + padding-bottom: var(--padding-md); + + > .name-header { + flex: 1 1 0%; + } + + > .qty-header { + margin-right: var(--margin-lg); + text-align: center; + } + + > .rate-amount-header { + text-align: right; + margin-right: var(--margin-sm); + } + } + + .no-item-wrapper { + display: flex; + align-items: center; + justify-content: center; + background-color: var(--gray-50); + border-radius: var(--border-radius-md); + font-size: var(--text-md); + font-weight: 500; + width: 100%; + height: 100%; + } + + > .cart-items-section { + display: flex; + flex-direction: column; + flex: 1 1 0%; + overflow-y: scroll; + + > .cart-item-wrapper { + @extend .pointer-no-select; + display: flex; + align-items: center; + padding: var(--padding-sm); + border-radius: var(--border-radius-md); + + &:hover { + background-color: var(--gray-50); + } + + > .item-image { + display: flex; + align-items: center; + justify-content: center; + width: 2rem; + height: 2rem; + border-radius: var(--border-radius-md); + color: var(--gray-500); + margin-right: var(--margin-md); + + > img { + @extend .image; + } + } + + > .item-abbr { + @extend .abbr; + font-size: var(--text-lg); + } + + + > .item-name-desc { + @extend .nowrap; + display: flex; + flex-direction: column; + flex: 1 1 0%; + flex-shrink: 1; + + > .item-name { + font-weight: 700; + } + + > .item-desc { + font-size: var(--text-sm); + color: var(--gray-600); + font-weight: 500; + } + } + + > .item-qty-rate { + display: flex; + flex-shrink: 0; + text-align: right; + margin-left: var(--margin-md); + + > .item-qty { + display: flex; + align-items: center; + margin-right: var(--margin-lg); + font-weight: 700; + } + + > .item-rate-amount { + display: flex; + flex-direction: column; + flex-shrink: 0; + text-align: right; + + > .item-rate { + font-weight: 700; + } + + > .item-amount { + font-size: var(--text-md); + font-weight: 600; + } + } + } + + } + } + + > .cart-totals-section { + display: flex; + flex-direction: column; + flex-shrink: 0; + width: 100%; + margin-top: var(--margin-md); + + > .add-discount-wrapper { + @extend .pointer-no-select; + display: none; + align-items: center; + border-radius: var(--border-radius-md); + border: 1px dashed var(--gray-500); + padding: var(--padding-sm) var(--padding-md); + margin-bottom: var(--margin-sm); + + > .add-discount-field { + width: 100%; + } + + .discount-icon { + margin-right: var(--margin-sm); + } + + .edit-discount-btn { + display: flex; + align-items: center; + font-weight: 500; + color: var(--dark-green-500); + } + } + + > .net-total-container { + display: flex; + align-items: center; + justify-content: space-between; + padding: var(--padding-sm) 0px; + font-weight: 500; + font-size: var(--text-md); + } + + > .taxes-container { + display: none; + flex-direction: column; + font-weight: 500; + font-size: var(--text-md); + + > .tax-row { + display: flex; + justify-content: space-between; + line-height: var(--text-3xl); + } + } + + > .grand-total-container { + display: flex; + align-items: center; + justify-content: space-between; + padding: var(--padding-sm) 0px; + font-weight: 700; + font-size: var(--text-lg); + } + + > .checkout-btn { + @extend .primary-action; + background-color: var(--blue-200); + color: white; + } + + > .edit-cart-btn { + @extend .primary-action; + display: none; + background-color: var(--gray-300); + font-weight: 500; + transition: all 0.15s ease-in-out; + + &:hover { + background-color: var(--gray-600); + color: white; + font-weight: 700; + } + } + } + + > .numpad-section { + display: none; + flex-direction: column; + flex-shrink: 0; + margin-top: var(--margin-sm); + padding: var(--padding-sm); + padding-bottom: 0px; + width: 100%; + + > .numpad-totals { + display: flex; + justify-content: space-between; + margin-bottom: var(--margin-md); + font-size: var(--text-md); + font-weight: 700; + } + + > .numpad-container { + display: grid; + grid-template-columns: repeat(5, minmax(0, 1fr)); + gap: var(--margin-md); + margin-bottom: var(--margin-md); + + > .numpad-btn { + @extend .pointer-no-select; + border-radius: var(--border-radius-md); + display: flex; + align-items: center; + justify-content: center; + padding: var(--padding-md); + box-shadow: var(--shadow-sm); + } + + > .col-span-2 { + grid-column: span 2 / span 2; + } + + > .remove-btn { + font-weight: 700; + color: var(--red-500); + } + } + + > .checkout-btn { + @extend .primary-action; + margin: 0px; + margin-bottom: var(--margin-sm); + background-color: var(--blue-200); + color: white; + } + } + } + } + } + + .invoice-wrapper { + @extend .pointer-no-select; + display: flex; + justify-content: space-between; + border-radius: var(--border-radius-md); + padding: var(--padding-sm); + + &:hover { + background-color: var(--gray-50); + } + + > .invoice-name-date { + display: flex; + flex-direction: column; + justify-content: space-around; + + > .invoice-name { + @extend .nowrap; + font-size: var(--text-md); + font-weight: 700; + } + + > .invoice-date { + @extend .nowrap; + font-size: var(--text-sm); + display: flex; + align-items: center; + } + } + + > .invoice-total-status { + display: flex; + flex-direction: column; + font-weight: 500; + font-size: var(--text-sm); + margin-left: var(--margin-md); + + > .invoice-total { + margin-bottom: var(--margin-xs); + font-size: var(--text-base); + font-weight: 700; + text-align: right; + } + + > .invoice-status { + display: flex; + align-items: center; + justify-content: right; + } + } + } + + > .item-details-container { + @extend .pos-card; + grid-column: span 4 / span 4; + display: none; + flex-direction: column; + padding: var(--padding-lg); + padding-top: var(--padding-md); + + > .item-details-header { + display: flex; + justify-content: space-between; + margin-bottom: var(--margin-md); + + > .close-btn { + @extend .pointer-no-select; + } + } + + > .item-display { + display: flex; + + > .item-name-desc-price { + flex: 1 1 0%; + display: flex; + flex-direction: column; + justify-content: flex-end; + margin-right: var(--margin-md); + + > .item-name { + font-size: var(--text-3xl); + font-weight: 600; + } + + > .item-desc { + font-size: var(--text-md); + font-weight: 500; + } + + > .item-price { + font-size: var(--text-3xl); + font-weight: 700; + } + } + + > .item-image { + display: flex; + align-items: center; + justify-content: center; + width: 11rem; + height: 11rem; + border-radius: var(--border-radius-md); + margin-left: var(--margin-md); + color: var(--gray-500); + + > img { + @extend .image; + } + + > .item-abbr { + @extend .abbr; + display: flex; + align-items: center; + justify-content: center; + border-radius: var(--border-radius-md); + font-size: var(--text-3xl); + width: 100%; + height: 100%; + } + } + } + + > .discount-section { + display: flex; + align-items: center; + margin-bottom: var(--margin-sm); + + > .item-rate { + font-weight: 500; + margin-right: var(--margin-sm); + text-decoration: line-through; + } + + > .item-discount { + padding: 3px var(--padding-sm); + border-radius: var(--border-radius-sm); + background-color: var(--green-100); + color: var(--dark-green-500); + font-size: var(--text-sm); + font-weight: 700; + } + } + + > .form-container { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + column-gap: var(--padding-md); + + > .auto-fetch-btn { + @extend .pointer-no-select; + margin: var(--margin-xs); + } + } + } + + > .payment-container { + @extend .pos-card; + grid-column: span 6 / span 6; + display: none; + flex-direction: column; + padding: var(--padding-lg); + + .border-primary { + border: 1px solid var(--blue-500); + } + + .submit-order-btn { + @extend .primary-action; + background-color: var(--blue-500); + color: white; + } + + .section-label { + @extend .label; + @extend .pointer-no-select; + margin-bottom: var(--margin-md); + } + + > .payment-modes { + display: flex; + padding-bottom: var(--padding-sm); + margin-bottom: var(--margin-xs); + overflow-x: scroll; + overflow-y: hidden; + + > .payment-mode-wrapper { + min-width: 40%; + padding: var(--padding-xs); + + > .mode-of-payment { + @extend .pos-card; + @extend .pointer-no-select; + padding: var(--padding-md) var(--padding-lg); + + > .pay-amount { + display: inline; + float: right; + font-weight: 700; + } + + > .mode-of-payment-control { + display: none; + align-items: center; + margin-top: var(--margin-sm); + margin-bottom: var(--margin-xs); + } + + > .loyalty-amount-name { + display: none; + float: right; + font-weight: 700; + } + + > .cash-shortcuts { + display: none; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: var(--margin-sm); + font-size: var(--text-sm); + text-align: center; + + > .shortcut { + @extend .pointer-no-select; + border-radius: var(--border-radius-sm); + background-color: var(--gray-100); + font-weight: 500; + padding: var(--padding-xs) var(--padding-sm); + transition: all 0.15s ease-in-out; + + &:hover { + background-color: var(--gray-300); + } + } + } + } + } + } + + > .fields-numpad-container { + display: flex; + flex: 1; + + > .fields-section { + flex: 1; + } + + > .number-pad { + flex: 1; + display: flex; + justify-content: flex-end; + align-items: flex-end; + + .numpad-container { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: var(--margin-md); + margin-bottom: var(--margin-md); + + > .numpad-btn { + @extend .pointer-no-select; + border-radius: var(--border-radius-md); + display: flex; + align-items: center; + justify-content: center; + padding: var(--padding-md); + box-shadow: var(--shadow-sm); + } + } + } + } + + > .totals-section { + display: flex; + margin-top: auto; + margin-bottom: var(--margin-sm); + justify-content: center; + flex-direction: column; + + > .totals { + display: flex; + background-color: var(--gray-100); + justify-content: center; + padding: var(--padding-md); + border-radius: var(--border-radius-md); + + > .col { + flex-grow: 1; + text-align: center; + + > .total-label { + font-size: var(--text-md); + font-weight: 500; + color: var(--gray-600); + } + + > .value { + font-size: var(--text-2xl); + font-weight: 700; + } + } + + > .seperator-y { + margin-left: var(--margin-sm); + margin-right: var(--margin-sm); + border-right: 1px solid var(--gray-300); + } + } + + > .number-pad { + display: none; + } + } + } + + > .past-order-list { + @extend .pos-card; + grid-column: span 4 / span 4; + display: none; + flex-direction: column; + overflow: hidden; + + > .filter-section { + display: flex; + flex-direction: column; + background-color: var(--fg-color); + padding: var(--padding-lg); + + > .search-field { + width: 100%; + display: flex; + align-items: center; + margin-top: var(--margin-md); + margin-bottom: var(--margin-xs); + } + + > .status-field { + width: 100%; + display: flex; + align-items: center; + } + } + + > .invoices-container { + padding: var(--padding-lg); + padding-top: 0px; + overflow-x: hidden; + overflow-y: scroll; + } + } + + > .past-order-summary { + display: none; + grid-column: span 6 / span 6; + flex-direction: column; + align-items: center; + justify-content: center; + + > .no-summary-placeholder { + display: flex; + align-items: center; + justify-content: center; + width: 100%; + height: 100%; + background-color: var(--gray-50); + font-weight: 500; + border-radius: var(--border-radius-md); + } + + > .invoice-summary-wrapper { + @extend .pos-card; + display: none; + position: relative; + width: 31rem; + height: 100%; + + > .abs-container { + position: absolute; + display: flex; + flex-direction: column; + width: 100%; + height: 100%; + padding: var(--padding-lg); + + > .upper-section { + display: flex; + justify-content: space-between; + width: 100%; + margin-bottom: var(--margin-md); + + > .left-section { + display: flex; + flex-direction: column; + align-items: flex-start; + justify-content: flex-end; + padding-right: var(--padding-sm); + + > .customer-name { + font-size: var(--text-2xl); + font-weight: 700; + } + + > .customer-email { + font-size: var(--text-md); + font-weight: 500; + color: var(--gray-600); + } + + > .cashier { + font-size: var(--text-md); + font-weight: 500; + color: var(--gray-600); + margin-top: auto; + } + } + + > .right-section { + display: flex; + flex-direction: column; + align-items: flex-end; + justify-content: space-between; + + > .paid-amount { + font-size: var(--text-2xl); + font-weight: 700; + } + + > .invoice-name { + font-size: var(--text-md); + font-weight: 500; + color: var(--gray-600); + margin-bottom: var(--margin-sm); + } + } + } + + > .summary-container { + display: flex; + flex-direction: column; + border-radius: var(--border-radius-md); + background-color: var(--gray-50); + margin: var(--margin-md) 0px; + + > .summary-row-wrapper { + display: flex; + align-items: center; + justify-content: space-between; + padding: var(--padding-sm) var(--padding-md); + } + + > .taxes-wrapper { + display: flex; + flex-direction: column; + padding: 0px var(--padding-md); + + > .tax-row { + display: flex; + justify-content: space-between; + font-size: var(--text-md); + line-height: var(--text-3xl); + } + } + + > .item-row-wrapper { + display: flex; + align-items: center; + padding: var(--padding-sm) var(--padding-md); + + > .item-name { + @extend .nowrap; + font-weight: 500; + margin-right: var(--margin-md); + } + + > .item-qty { + font-weight: 500; + margin-left: auto; + } + + > .item-rate-disc { + display: flex; + text-align: right; + margin-left: var(--margin-md); + justify-content: flex-end; + + > .item-disc { + color: var(--dark-green-500); + } + + > .item-rate { + font-weight: 500; + margin-left: var(--margin-md); + } + } + } + + > .grand-total { + font-weight: 700; + } + + > .payments { + font-weight: 700; + } + } + + + > .summary-btns { + display: flex; + justify-content: space-between; + + > .summary-btn { + flex: 1; + margin: 0px var(--margin-xs); + } + + > .new-btn { + background-color: var(--blue-500); + color:white; + font-weight: 500; + } + } + } + } + } +} \ No newline at end of file diff --git a/erpnext/public/scss/shopping_cart.scss b/erpnext/public/scss/shopping_cart.scss new file mode 100644 index 00000000000..159a8a47cd3 --- /dev/null +++ b/erpnext/public/scss/shopping_cart.scss @@ -0,0 +1,494 @@ +@import "frappe/public/scss/desk/variables"; +@import "frappe/public/scss/common/mixins"; + +body.product-page { + background: var(--gray-50); +} + + +.item-breadcrumbs { + .breadcrumb-container { + ol.breadcrumb { + background-color: var(--gray-50) !important; + } + + a { + color: var(--gray-900); + } + } +} + +.carousel-control { + height: 42px; + width: 42px; + display: flex; + align-items: center; + justify-content: center; + background: white; + box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.08), 0px 1px 2px 1px rgba(0, 0, 0, 0.06); + border-radius: 100px; +} + +.carousel-control-prev, +.carousel-control-next { + opacity: 1; +} + +.carousel-body { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; +} + +.carousel-content { + max-width: 400px; +} + +.card { + border: none; +} + +.product-category-section { + .card:hover { + box-shadow: 0px 16px 45px 6px rgba(0, 0, 0, 0.08), 0px 8px 10px -10px rgba(0, 0, 0, 0.04); + } + + .card-grid { + display: grid; + grid-gap: 15px; + grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); + } +} + +.item-card-group-section { + .card { + height: 360px; + align-items: center; + justify-content: center; + + &:hover { + box-shadow: 0px 16px 60px rgba(0, 0, 0, 0.08), 0px 8px 30px -20px rgba(0, 0, 0, 0.04); + transition: box-shadow 400ms; + } + } + + // .card-body { + // text-align: center; + // } + + // .featured-item { + // .card-body { + // text-align: left; + // } + // } + .card-img-container { + height: 210px; + width: 100%; + } + + .card-img { + max-height: 210px; + object-fit: contain; + margin-top: 1.25rem; + } + + .no-image { + @include flex(flex, center, center, null); + height: 200px; + margin: 0 auto; + margin-top: var(--margin-xl); + background: var(--gray-100); + width: 80%; + border-radius: var(--border-radius); + font-size: 2rem; + color: var(--gray-500); + } + + .product-title { + font-size: 14px; + color: var(--gray-800); + font-weight: 500; + } + + .product-description { + font-size: 12px; + color: var(--text-color); + margin: 20px 0; + display: -webkit-box; + -webkit-line-clamp: 6; + -webkit-box-orient: vertical; + + p { + margin-bottom: 0.5rem; + } + } + + .product-category { + font-size: 13px; + color: var(--text-muted); + margin: var(--margin-sm) 0; + } + + .product-price { + font-size: 18px; + font-weight: 600; + color: var(--text-color); + margin: var(--margin-sm) 0; + } + + .item-card { + padding: var(--padding-sm); + } +} + +[data-doctype="Item Group"], +#page-all-products { + .page-header { + font-size: 20px; + font-weight: 700; + color: var(--text-color); + } + + .filters-section { + .title-section { + border-bottom: 1px solid var(--table-border-color); + } + + .filter-title { + font-weight: 500; + } + + .clear-filters { + font-size: 13px; + } + + .filter-label { + font-size: 11px; + font-weight: 600; + color: var(--gray-700); + text-transform: uppercase; + } + + .filter-block { + border-bottom: 1px solid var(--table-border-color); + } + + .checkbox { + .label-area { + font-size: 13px; + color: var(--gray-800); + } + } + } +} + +.product-container { + @include card($padding: var(--padding-md)); + min-height: 70vh; + + .product-details { + max-width: 40%; + margin-left: -30px; + + .btn-add-to-cart { + font-size: var(--text-base); + } + } + + .product-title { + font-size: 24px; + font-weight: 600; + color: var(--text-color); + } + + .product-code { + color: var(--text-muted); + font-size: 13px; + } + + .product-description { + font-size: 13px; + color: var(--gray-800); + } + + .product-image { + border-color: var(--table-border-color) !important; + padding: 15px; + + @include media-breakpoint-between(xs, md) { + height: 300px; + width: 300px; + } + + @include media-breakpoint-up(lg) { + height: 350px; + width: 350px; + } + + img { + object-fit: contain; + } + } + + .item-slideshow { + @include media-breakpoint-between(xs, md) { + max-height: 320px; + } + + @include media-breakpoint-up(lg) { + max-height: 430px; + } + + overflow: scroll; + } + + .item-slideshow-image { + height: 4rem; + width: 6rem; + object-fit: contain; + padding: 0.5rem; + border: 1px solid var(--table-border-color); + border-radius: 4px; + cursor: pointer; + + &:hover, &.active { + border-color: $primary; + } + } + + .item-cart { + .product-price { + font-size: 20px; + color: var(--text-color); + font-weight: 600; + + .formatted-price { + color: var(--text-muted); + font-size: var(--text-base); + } + } + + .no-stock { + font-size: var(--text-base); + } + } +} + +.item-configurator-dialog { + .modal-header { + padding: var(--padding-md) var(--padding-xl); + } + + .modal-body { + padding: 0 var(--padding-xl); + padding-bottom: var(--padding-xl); + + .status-area { + .alert { + padding: var(--padding-xs) var(--padding-sm); + font-size: var(--text-sm); + } + } + + .form-layout { + max-height: 50vh; + overflow-y: auto; + } + + .section-body { + .form-column { + .form-group { + .control-label { + font-size: var(--text-md); + color: var(--gray-700); + } + + .help-box { + margin-top: 2px; + font-size: var(--text-sm); + } + } + } + } + } +} + +.item-group-slideshow { + .item-group-description { + // max-width: 900px; + } + + .carousel-inner.rounded-carousel { + border-radius: $card-border-radius; + } +} + +.cart-icon { + .cart-badge { + position: relative; + top: -10px; + left: -12px; + background: var(--red-600); + width: 16px; + align-items: center; + height: 16px; + font-size: 10px; + border-radius: 50%; + } +} + + +#page-cart { + .shopping-cart-header { + font-weight: bold; + } + + .cart-container { + color: var(--text-color); + + .frappe-card { + display: flex; + flex-direction: column; + justify-content: space-between; + } + + .cart-items-header { + font-weight: 600; + } + + .cart-table { + th, tr, td { + border-color: var(--border-color); + border-width: 1px; + } + + th { + font-weight: normal; + font-size: 13px; + color: var(--text-muted); + padding: var(--padding-sm) 0; + } + + td { + padding: var(--padding-sm) 0; + color: var(--text-color); + } + + .cart-items { + .item-title { + font-size: var(--text-base); + font-weight: 500; + color: var(--text-color); + } + + .item-subtitle { + color: var(--text-muted); + font-size: var(--text-md); + } + + .item-subtotal { + font-size: var(--text-base); + font-weight: 500; + } + + .item-rate { + font-size: var(--text-md); + color: var(--text-muted); + } + + textarea { + width: 40%; + } + } + + .cart-tax-items { + .item-grand-total { + font-size: 16px; + font-weight: 600; + color: var(--text-color); + } + } + } + + .cart-addresses { + hr { + border-color: var(--border-color); + } + } + + .number-spinner { + width: 75%; + .cart-btn { + border: none; + background: var(--gray-100); + box-shadow: none; + height: 28px; + align-items: center; + display: flex; + } + + .cart-qty { + height: 28px; + font-size: var(--text-md); + } + } + + .place-order-container { + .btn-place-order { + width: 62%; + } + } + } +} + +.cart-empty.frappe-card { + min-height: 76vh; + @include flex(flex, center, center, column); + + .cart-empty-message { + font-size: 18px; + color: var(--text-color); + font-weight: bold; + } +} + +.address-card { + .card-title { + font-size: var(--text-base); + font-weight: 500; + } + + .card-body { + max-width: 80%; + } + + .card-text { + font-size: var(--text-md); + color: var(--gray-700); + } + + .card-link { + font-size: var(--text-md); + + svg use { + stroke: var(--blue-500); + } + } + + .btn-change-address { + color: var(--blue-500); + box-shadow: none; + border: 1px solid var(--blue-500); + } +} + +.modal .address-card { + .card-body { + padding: var(--padding-sm); + border-radius: var(--border-radius); + border: 1px solid var(--dark-border-color); + } +} + diff --git a/erpnext/public/scss/website.scss b/erpnext/public/scss/website.scss index 617e916724d..56b717c4240 100644 --- a/erpnext/public/scss/website.scss +++ b/erpnext/public/scss/website.scss @@ -1,29 +1,10 @@ -@import "frappe/public/scss/variables"; - -.product-image img { - min-height: 20rem; - max-height: 30rem; -} +@import "frappe/public/scss/website/variables"; .filter-options { max-height: 300px; overflow: auto; } -.item-slideshow-image { - height: 3rem; - width: 3rem; - object-fit: contain; - padding: 0.5rem; - border: 1px solid $border-color; - border-radius: 4px; - cursor: pointer; - - &:hover, &.active { - border-color: $primary; - } -} - .address-card { cursor: pointer; position: relative; @@ -43,10 +24,10 @@ .check { display: inline-flex; - padding: 0.25rem; - background: $primary; - color: white; - border-radius: 50%; + padding: 0.25rem; + background: $primary; + color: white; + border-radius: 50%; font-size: 12px; width: 24px; height: 24px; diff --git a/erpnext/quality_management/desk_page/quality/quality.json b/erpnext/quality_management/desk_page/quality/quality.json deleted file mode 100644 index 7a049b21c0a..00000000000 --- a/erpnext/quality_management/desk_page/quality/quality.json +++ /dev/null @@ -1,88 +0,0 @@ -{ - "cards": [ - { - "hidden": 0, - "label": "Goal and Procedure", - "links": "[\n {\n \"description\": \"Quality Goal.\",\n \"label\": \"Quality Goal\",\n \"name\": \"Quality Goal\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Quality Procedure.\",\n \"label\": \"Quality Procedure\",\n \"name\": \"Quality Procedure\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Tree of Quality Procedures.\",\n \"icon\": \"fa fa-sitemap\",\n \"label\": \"Tree of Procedures\",\n \"name\": \"Quality Procedure\",\n \"route\": \"#Tree/Quality Procedure\",\n \"type\": \"doctype\"\n }\n]" - }, - { - "hidden": 0, - "label": "Feedback", - "links": "[\n {\n \"description\": \"Quality Feedback\",\n \"label\": \"Quality Feedback\",\n \"name\": \"Quality Feedback\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Quality Feedback Template\",\n \"label\": \"Quality Feedback Template\",\n \"name\": \"Quality Feedback Template\",\n \"type\": \"doctype\"\n }\n]" - }, - { - "hidden": 0, - "label": "Meeting", - "links": "[\n {\n \"description\": \"Quality Meeting\",\n \"label\": \"Quality Meeting\",\n \"name\": \"Quality Meeting\",\n \"type\": \"doctype\"\n }\n]" - }, - { - "hidden": 0, - "label": "Review and Action", - "links": "[\n {\n \"description\": \"Non Conformance\",\n \"label\": \"Non Conformance\",\n \"name\": \"Non Conformance\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Quality Review\",\n \"label\": \"Quality Review\",\n \"name\": \"Quality Review\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Quality Action\",\n \"label\": \"Quality Action\",\n \"name\": \"Quality Action\",\n \"type\": \"doctype\"\n }\n]" - } - ], - "category": "Modules", - "charts": [], - "creation": "2020-03-02 15:49:28.632014", - "developer_mode_only": 0, - "disable_user_customization": 0, - "docstatus": 0, - "doctype": "Desk Page", - "extends_another_page": 0, - "hide_custom": 0, - "idx": 0, - "is_standard": 1, - "label": "Quality", - "modified": "2020-10-27 16:28:54.138055", - "modified_by": "Administrator", - "module": "Quality Management", - "name": "Quality", - "owner": "Administrator", - "pin_to_bottom": 0, - "pin_to_top": 0, - "shortcuts": [ - { - "label": "Quality Goal", - "link_to": "Quality Goal", - "type": "DocType" - }, - { - "doc_view": "Tree", - "label": "Quality Procedure", - "link_to": "Quality Procedure", - "type": "DocType" - }, - { - "label": "Quality Inspection", - "link_to": "Quality Inspection", - "type": "DocType" - }, - { - "color": "#ff8989", - "doc_view": "", - "format": "{} Open", - "label": "Quality Review", - "link_to": "Quality Review", - "stats_filter": "{\"status\": \"Open\"}", - "type": "DocType" - }, - { - "color": "#ff8989", - "doc_view": "", - "format": "{} Open", - "label": "Quality Action", - "link_to": "Quality Action", - "stats_filter": "{\"status\": \"Open\"}", - "type": "DocType" - }, - { - "color": "#ff8989", - "doc_view": "", - "format": "{} Open", - "label": "Non Conformance", - "link_to": "Non Conformance", - "stats_filter": "{\"status\": \"Open\"}", - "type": "DocType" - } - ] -} \ No newline at end of file diff --git a/erpnext/quality_management/doctype/non_conformance/non_conformance.json b/erpnext/quality_management/doctype/non_conformance/non_conformance.json index bfeb96bcaf0..8dfe2d6859d 100644 --- a/erpnext/quality_management/doctype/non_conformance/non_conformance.json +++ b/erpnext/quality_management/doctype/non_conformance/non_conformance.json @@ -70,18 +70,18 @@ }, { "fieldname": "corrective_action", - "fieldtype": "Text", + "fieldtype": "Text Editor", "label": "Corrective Action" }, { "fieldname": "preventive_action", - "fieldtype": "Text", + "fieldtype": "Text Editor", "label": "Preventive Action" } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2020-10-26 15:27:47.247814", + "modified": "2021-02-26 15:27:47.247814", "modified_by": "Administrator", "module": "Quality Management", "name": "Non Conformance", @@ -115,4 +115,4 @@ "sort_field": "modified", "sort_order": "DESC", "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/quality_management/doctype/quality_meeting/quality_meeting.json b/erpnext/quality_management/doctype/quality_meeting/quality_meeting.json index ead403d453a..e2125c3933a 100644 --- a/erpnext/quality_management/doctype/quality_meeting/quality_meeting.json +++ b/erpnext/quality_management/doctype/quality_meeting/quality_meeting.json @@ -33,8 +33,7 @@ }, { "fieldname": "sb_00", - "fieldtype": "Section Break", - "label": "Agenda" + "fieldtype": "Section Break" }, { "fieldname": "agenda", @@ -44,13 +43,12 @@ }, { "fieldname": "sb_01", - "fieldtype": "Section Break", - "label": "Minutes" + "fieldtype": "Section Break" } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2020-10-27 16:36:45.657883", + "modified": "2021-02-27 16:36:45.657883", "modified_by": "Administrator", "module": "Quality Management", "name": "Quality Meeting", @@ -85,4 +83,4 @@ "sort_field": "modified", "sort_order": "DESC", "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/quality_management/workspace/quality/quality.json b/erpnext/quality_management/workspace/quality/quality.json new file mode 100644 index 00000000000..e5fef435505 --- /dev/null +++ b/erpnext/quality_management/workspace/quality/quality.json @@ -0,0 +1,190 @@ +{ + "category": "Modules", + "charts": [], + "creation": "2020-03-02 15:49:28.632014", + "developer_mode_only": 0, + "disable_user_customization": 0, + "docstatus": 0, + "doctype": "Workspace", + "extends_another_page": 0, + "hide_custom": 0, + "icon": "quality", + "idx": 0, + "is_standard": 1, + "label": "Quality", + "links": [ + { + "hidden": 0, + "is_query_report": 0, + "label": "Goal and Procedure", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Quality Goal", + "link_to": "Quality Goal", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Quality Procedure", + "link_to": "Quality Procedure", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Tree of Procedures", + "link_to": "Quality Procedure", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Feedback", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Quality Feedback", + "link_to": "Quality Feedback", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Quality Feedback Template", + "link_to": "Quality Feedback Template", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Meeting", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Quality Meeting", + "link_to": "Quality Meeting", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Review and Action", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Non Conformance", + "link_to": "Non Conformance", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Quality Review", + "link_to": "Quality Review", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Quality Action", + "link_to": "Quality Action", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + } + ], + "modified": "2020-12-01 13:38:35.120213", + "modified_by": "Administrator", + "module": "Quality Management", + "name": "Quality", + "owner": "Administrator", + "pin_to_bottom": 0, + "pin_to_top": 0, + "shortcuts": [ + { + "color": "Grey", + "label": "Quality Goal", + "link_to": "Quality Goal", + "type": "DocType" + }, + { + "color": "Grey", + "doc_view": "Tree", + "label": "Quality Procedure", + "link_to": "Quality Procedure", + "type": "DocType" + }, + { + "color": "Grey", + "label": "Quality Inspection", + "link_to": "Quality Inspection", + "type": "DocType" + }, + { + "color": "Grey", + "doc_view": "", + "format": "{} Open", + "label": "Quality Review", + "link_to": "Quality Review", + "stats_filter": "{\"status\": \"Open\"}", + "type": "DocType" + }, + { + "color": "Grey", + "doc_view": "", + "format": "{} Open", + "label": "Quality Action", + "link_to": "Quality Action", + "stats_filter": "{\"status\": \"Open\"}", + "type": "DocType" + }, + { + "color": "Grey", + "doc_view": "", + "format": "{} Open", + "label": "Non Conformance", + "link_to": "Non Conformance", + "stats_filter": "{\"status\": \"Open\"}", + "type": "DocType" + } + ] +} \ No newline at end of file diff --git a/erpnext/regional/doctype/gst_settings/gst_settings.json b/erpnext/regional/doctype/gst_settings/gst_settings.json index 98c33ad33bb..95b930c4c86 100644 --- a/erpnext/regional/doctype/gst_settings/gst_settings.json +++ b/erpnext/regional/doctype/gst_settings/gst_settings.json @@ -1,222 +1,86 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2017-06-27 15:09:01.318003", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", + "actions": [], + "creation": "2017-06-27 15:09:01.318003", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "gst_summary", + "column_break_2", + "round_off_gst_values", + "gstin_email_sent_on", + "section_break_4", + "gst_accounts", + "b2c_limit" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "gst_summary", - "fieldtype": "HTML", - "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": "GST Summary", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "gst_summary", + "fieldtype": "HTML", + "label": "GST Summary", + "show_days": 1, + "show_seconds": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_2", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "column_break_2", + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "gstin_email_sent_on", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "GSTIN Email Sent On", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "gstin_email_sent_on", + "fieldtype": "Date", + "label": "GSTIN Email Sent On", + "read_only": 1, + "show_days": 1, + "show_seconds": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_4", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "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, - "unique": 0 - }, + "fieldname": "section_break_4", + "fieldtype": "Section Break", + "show_days": 1, + "show_seconds": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "gst_accounts", - "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "GST Accounts", - "length": 0, - "no_copy": 0, - "options": "GST Account", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "gst_accounts", + "fieldtype": "Table", + "label": "GST Accounts", + "options": "GST Account", + "show_days": 1, + "show_seconds": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "250000", - "description": "Set Invoice Value for B2C. B2CL and B2CS calculated based on this invoice value.", - "fieldname": "b2c_limit", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "B2C Limit", - "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, - "unique": 0 + "default": "250000", + "description": "Set Invoice Value for B2C. B2CL and B2CS calculated based on this invoice value.", + "fieldname": "b2c_limit", + "fieldtype": "Data", + "in_list_view": 1, + "label": "B2C Limit", + "reqd": 1, + "show_days": 1, + "show_seconds": 1 + }, + { + "default": "0", + "description": "Enabling this option will round off individual GST components in all the Invoices", + "fieldname": "round_off_gst_values", + "fieldtype": "Check", + "label": "Round Off GST Values", + "show_days": 1, + "show_seconds": 1 } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 1, - "istable": 0, - "max_attachments": 0, - "modified": "2018-02-14 08:14:15.375181", - "modified_by": "Administrator", - "module": "Regional", - "name": "GST Settings", - "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 -} \ No newline at end of file + ], + "index_web_pages_for_search": 1, + "issingle": 1, + "links": [], + "modified": "2021-01-28 17:19:47.969260", + "modified_by": "Administrator", + "module": "Regional", + "name": "GST Settings", + "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/regional/doctype/gstr_3b_report/test_gstr_3b_report.py b/erpnext/regional/doctype/gstr_3b_report/test_gstr_3b_report.py index 8174da20cb7..023b4ed22bc 100644 --- a/erpnext/regional/doctype/gstr_3b_report/test_gstr_3b_report.py +++ b/erpnext/regional/doctype/gstr_3b_report/test_gstr_3b_report.py @@ -14,8 +14,20 @@ import json test_dependencies = ["Territory", "Customer Group", "Supplier Group", "Item"] class TestGSTR3BReport(unittest.TestCase): - def test_gstr_3b_report(self): + def setUp(self): + frappe.set_user("Administrator") + frappe.db.sql("delete from `tabSales Invoice` where company='_Test Company GST'") + frappe.db.sql("delete from `tabPurchase Invoice` where company='_Test Company GST'") + frappe.db.sql("delete from `tabGSTR 3B Report` where company='_Test Company GST'") + + make_company() + make_item("Milk", properties = {"is_nil_exempt": 1, "standard_rate": 0.000000}) + set_account_heads() + make_customers() + make_suppliers() + + def test_gstr_3b_report(self): month_number_mapping = { 1: "January", 2: "February", @@ -31,17 +43,6 @@ class TestGSTR3BReport(unittest.TestCase): 12: "December" } - frappe.set_user("Administrator") - - frappe.db.sql("delete from `tabSales Invoice` where company='_Test Company GST'") - frappe.db.sql("delete from `tabPurchase Invoice` where company='_Test Company GST'") - frappe.db.sql("delete from `tabGSTR 3B Report` where company='_Test Company GST'") - - make_company() - make_item("Milk", properties = {"is_nil_exempt": 1, "standard_rate": 0.000000}) - set_account_heads() - make_customers() - make_suppliers() make_sales_invoice() create_purchase_invoices() @@ -67,6 +68,42 @@ class TestGSTR3BReport(unittest.TestCase): self.assertEqual(output["itc_elg"]["itc_avl"][4]["samt"], 22.50) self.assertEqual(output["itc_elg"]["itc_avl"][4]["camt"], 22.50) + def test_gst_rounding(self): + gst_settings = frappe.get_doc('GST Settings') + gst_settings.round_off_gst_values = 1 + gst_settings.save() + + current_country = frappe.flags.country + frappe.flags.country = 'India' + + si = create_sales_invoice(company="_Test Company GST", + customer = '_Test GST Customer', + currency = 'INR', + warehouse = 'Finished Goods - _GST', + debit_to = 'Debtors - _GST', + income_account = 'Sales - _GST', + expense_account = 'Cost of Goods Sold - _GST', + cost_center = 'Main - _GST', + rate=216, + do_not_save=1 + ) + + si.append("taxes", { + "charge_type": "On Net Total", + "account_head": "IGST - _GST", + "cost_center": "Main - _GST", + "description": "IGST @ 18.0", + "rate": 18 + }) + + si.save() + # Check for 39 instead of 38.88 + self.assertEqual(si.taxes[0].base_tax_amount_after_discount_amount, 39) + + frappe.flags.country = current_country + gst_settings.round_off_gst_values = 1 + gst_settings.save() + def make_sales_invoice(): si = create_sales_invoice(company="_Test Company GST", customer = '_Test GST Customer', @@ -145,7 +182,6 @@ def make_sales_invoice(): si3.submit() def create_purchase_invoices(): - pi = make_purchase_invoice( company="_Test Company GST", supplier = '_Test Registered Supplier', @@ -193,7 +229,6 @@ def create_purchase_invoices(): pi1.submit() def make_suppliers(): - if not frappe.db.exists("Supplier", "_Test Registered Supplier"): frappe.get_doc({ "supplier_group": "_Test Supplier Group", @@ -257,7 +292,6 @@ def make_suppliers(): address.save() def make_customers(): - if not frappe.db.exists("Customer", "_Test GST Customer"): frappe.get_doc({ "customer_group": "_Test Customer Group", @@ -354,9 +388,9 @@ def make_customers(): address.save() def make_company(): - if frappe.db.exists("Company", "_Test Company GST"): return + company = frappe.new_doc("Company") company.company_name = "_Test Company GST" company.abbr = "_GST" @@ -388,7 +422,6 @@ def make_company(): address.save() def set_account_heads(): - gst_settings = frappe.get_doc("GST Settings") gst_account = frappe.get_all( diff --git a/erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.py b/erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.py index e8a8ed87505..ad60db05595 100644 --- a/erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.py +++ b/erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.py @@ -5,12 +5,16 @@ from __future__ import unicode_literals import frappe from frappe import _ -from frappe.utils import getdate +from frappe.utils import getdate, get_link_to_form from frappe.model.document import Document from erpnext.accounts.utils import get_fiscal_year class LowerDeductionCertificate(Document): def validate(self): + self.validate_dates() + self.validate_supplier_against_section_code() + + def validate_dates(self): if getdate(self.valid_upto) < getdate(self.valid_from): frappe.throw(_("Valid Upto date cannot be before Valid From date")) @@ -24,3 +28,20 @@ class LowerDeductionCertificate(Document): <= fiscal_year.year_end_date): frappe.throw(_("Valid Upto date not in Fiscal Year {0}").format(frappe.bold(self.fiscal_year))) + def validate_supplier_against_section_code(self): + duplicate_certificate = frappe.db.get_value('Lower Deduction Certificate', {'supplier': self.supplier, 'section_code': self.section_code}, ['name', 'valid_from', 'valid_upto'], as_dict=True) + if duplicate_certificate and self.are_dates_overlapping(duplicate_certificate): + certificate_link = get_link_to_form('Lower Deduction Certificate', duplicate_certificate.name) + frappe.throw(_("There is already a valid Lower Deduction Certificate {0} for Supplier {1} against Section Code {2} for this time period.") + .format(certificate_link, frappe.bold(self.supplier), frappe.bold(self.section_code))) + + def are_dates_overlapping(self,duplicate_certificate): + valid_from = duplicate_certificate.valid_from + valid_upto = duplicate_certificate.valid_upto + if valid_from <= getdate(self.valid_from) <= valid_upto: + return True + elif valid_from <= getdate(self.valid_upto) <= valid_upto: + return True + elif getdate(self.valid_from) <= valid_from and valid_upto <= getdate(self.valid_upto): + return True + return False \ No newline at end of file diff --git a/erpnext/regional/india/e_invoice/einvoice.js b/erpnext/regional/india/e_invoice/einvoice.js index e8a7c30e19a..5901461d610 100644 --- a/erpnext/regional/india/e_invoice/einvoice.js +++ b/erpnext/regional/india/e_invoice/einvoice.js @@ -1,12 +1,12 @@ erpnext.setup_einvoice_actions = (doctype) => { frappe.ui.form.on(doctype, { - refresh(frm) { - const einvoicing_enabled = frappe.db.get_value("E Invoice Settings", "E Invoice Settings", "enable"); + async refresh(frm) { + const einvoicing_enabled = await frappe.db.get_single_value("E Invoice Settings", "enable"); const supply_type = frm.doc.gst_category; const valid_supply_type = ['Registered Regular', 'SEZ', 'Overseas', 'Deemed Export'].includes(supply_type); const company_transaction = frm.doc.billing_address_gstin == frm.doc.company_gstin; - if (!einvoicing_enabled || !valid_supply_type || company_transaction) return; + if (cint(einvoicing_enabled) == 0 || !valid_supply_type || company_transaction) return; const { doctype, irn, irn_cancelled, ewaybill, eway_bill_cancelled, name, __unsaved } = frm.doc; @@ -83,7 +83,7 @@ erpnext.setup_einvoice_actions = (doctype) => { const action = () => { const d = new frappe.ui.Dialog({ title: __('Generate E-Way Bill'), - wide: 1, + size: "large", fields: get_ewaybill_fields(frm), primary_action: function() { const data = d.get_values(); @@ -252,7 +252,7 @@ const request_irn_generation = (frm) => { const get_preview_dialog = (frm, action) => { const dialog = new frappe.ui.Dialog({ title: __("Preview"), - wide: 1, + size: "large", fields: [ { "label": "Preview", diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 438ec79ed16..6267635f60e 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -10,6 +10,7 @@ import sys import json import base64 import frappe +import six import traceback import io from frappe import _, bold @@ -108,11 +109,13 @@ def get_party_details(address_name): pincode = 999999 return frappe._dict(dict( - gstin=d.gstin, legal_name=d.address_title, - location=d.city, pincode=d.pincode, + gstin=d.gstin, + legal_name=sanitize_for_json(d.address_title), + location=sanitize_for_json(d.city), + pincode=d.pincode, state_code=d.gst_state_number, - address_line1=d.address_line1, - address_line2=d.address_line2 + address_line1=sanitize_for_json(d.address_line1), + address_line2=sanitize_for_json(d.address_line2) )) def get_gstin_details(gstin): @@ -146,8 +149,11 @@ def get_overseas_address_details(address_name): ) return frappe._dict(dict( - gstin='URP', legal_name=address_title, location=city, - address_line1=address_line1, address_line2=address_line2, + gstin='URP', + legal_name=sanitize_for_json(address_title), + location=city, + address_line1=sanitize_for_json(address_line1), + address_line2=sanitize_for_json(address_line2), pincode=999999, state_code=96, place_of_supply=96 )) @@ -160,7 +166,7 @@ def get_item_list(invoice): item.update(d.as_dict()) item.sr_no = d.idx - item.description = json.dumps(d.item_name)[1:-1] + item.description = sanitize_for_json(d.item_name) item.qty = abs(item.qty) item.discount_amount = 0 @@ -196,9 +202,11 @@ def update_item_taxes(invoice, item): item[attr] = 0 for t in invoice.taxes: - # this contains item wise tax rate & tax amount (incl. discount) - item_tax_detail = json.loads(t.item_wise_tax_detail).get(item.item_code) - if t.account_head in gst_accounts_list: + is_applicable = t.tax_amount and t.account_head in gst_accounts_list + if is_applicable: + # this contains item wise tax rate & tax amount (incl. discount) + item_tax_detail = json.loads(t.item_wise_tax_detail).get(item.item_code) + item_tax_rate = item_tax_detail[0] # item tax amount excluding discount amount item_tax_amount = (item_tax_rate / 100) * item.base_net_amount @@ -223,7 +231,7 @@ def get_invoice_value_details(invoice): if invoice.apply_discount_on == 'Net Total' and invoice.discount_amount: invoice_value_details.base_total = abs(invoice.base_total) - invoice_value_details.invoice_discount_amt = invoice.base_discount_amount + invoice_value_details.invoice_discount_amt = abs(invoice.base_discount_amount) else: invoice_value_details.base_total = abs(invoice.base_net_total) # since tax already considers discount amount @@ -326,7 +334,7 @@ def make_einvoice(invoice): buyer_details = get_overseas_address_details(invoice.customer_address) else: buyer_details = get_party_details(invoice.customer_address) - place_of_supply = get_place_of_supply(invoice, invoice.doctype) or invoice.billing_address_gstin + place_of_supply = get_place_of_supply(invoice, invoice.doctype) or sanitize_for_json(invoice.billing_address_gstin) place_of_supply = place_of_supply[:2] buyer_details.update(dict(place_of_supply=place_of_supply)) @@ -356,7 +364,7 @@ def make_einvoice(invoice): period_details=period_details, prev_doc_details=prev_doc_details, export_details=export_details, eway_bill_details=eway_bill_details ) - einvoice = json.loads(einvoice) + einvoice = safe_json_load(einvoice) validations = json.loads(read_json('einv_validation')) errors = validate_einvoice(validations, einvoice) @@ -371,6 +379,18 @@ def make_einvoice(invoice): return einvoice +def safe_json_load(json_string): + JSONDecodeError = ValueError if six.PY2 else json.JSONDecodeError + + try: + return json.loads(json_string) + except JSONDecodeError as e: + # print a snippet of 40 characters around the location where error occured + pos = e.pos + start, end = max(0, pos-20), min(len(json_string)-1, pos+20) + snippet = json_string[start:end] + frappe.throw(_("Error in input data. Please check for any special characters near following input:
    {}").format(snippet)) + def validate_einvoice(validations, einvoice, errors=[]): for fieldname, field_validation in validations.items(): value = einvoice.get(fieldname, None) @@ -795,6 +815,13 @@ class GSPConnector(): self.invoice.flags.ignore_validate = True self.invoice.save() + +def sanitize_for_json(string): + """Escape JSON specific characters from a string.""" + + # json.dumps adds double-quotes to the string. Indexing to remove them. + return json.dumps(string)[1:-1] + @frappe.whitelist() def get_einvoice(doctype, docname): invoice = frappe.get_doc(doctype, docname) diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index 40247f7e3d5..ee49aae0501 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -21,6 +21,7 @@ def setup_company_independent_fixtures(): add_permissions() add_custom_roles_for_reports() frappe.enqueue('erpnext.regional.india.setup.add_hsn_sac_codes', now=frappe.flags.in_test) + create_gratuity_rule() add_print_formats() def add_hsn_sac_codes(): @@ -105,8 +106,9 @@ def add_print_formats(): frappe.reload_doc("accounts", "print_format", "gst_pos_invoice") frappe.reload_doc("accounts", "print_format", "GST E-Invoice") - frappe.db.sql(""" update `tabPrint Format` set disabled = 0 where - name in('GST POS Invoice', 'GST Tax Invoice', 'GST E-Invoice') """) + frappe.db.set_value("Print Format", "GST POS Invoice", "disabled", 0) + frappe.db.set_value("Print Format", "GST Tax Invoice", "disabled", 0) + frappe.db.set_value("Print Format", "GST E-Invoice", "disabled", 0) def make_custom_fields(update=True): hsn_sac_field = dict(fieldname='gst_hsn_code', label='HSN/SAC', @@ -838,4 +840,24 @@ def get_tds_details(accounts, fiscal_year): doctype="Tax Withholding Category", accounts=accounts, rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 20, "single_threshold": 2500, "cumulative_threshold": 0}]) - ] \ No newline at end of file + ] + +def create_gratuity_rule(): + + # Standard Indain Gratuity Rule + if not frappe.db.exists("Gratuity Rule", "Indian Standard Gratuity Rule"): + rule = frappe.new_doc("Gratuity Rule") + rule.name = "Indian Standard Gratuity Rule" + rule.calculate_gratuity_amount_based_on = "Current Slab" + rule.work_experience_calculation_method = "Round Off Work Experience" + rule.minimum_year_for_gratuity = 5 + + fraction = 15/26 + rule.append("gratuity_rule_slabs", { + "from_year": 0, + "to_year":0, + "fraction_of_applicable_earnings": fraction + }) + + rule.flags.ignore_mandatory = True + rule.save() \ No newline at end of file diff --git a/erpnext/regional/india/taxes.js b/erpnext/regional/india/taxes.js index f09d3d08ad2..d3b7ea3b1a0 100644 --- a/erpnext/regional/india/taxes.js +++ b/erpnext/regional/india/taxes.js @@ -37,6 +37,7 @@ erpnext.setup_auto_gst_taxation = (doctype) => { doctype: frm.doc.doctype, company: frm.doc.company }, + debounce: 2000, callback: function(r) { if(r.message) { frm.set_value('taxes_and_charges', r.message.taxes_and_charges); diff --git a/erpnext/regional/india/test_utils.py b/erpnext/regional/india/test_utils.py new file mode 100644 index 00000000000..7ce27f6cf5a --- /dev/null +++ b/erpnext/regional/india/test_utils.py @@ -0,0 +1,38 @@ +from __future__ import unicode_literals + +import unittest +import frappe +from unittest.mock import patch +from erpnext.regional.india.utils import validate_document_name + + +class TestIndiaUtils(unittest.TestCase): + @patch("frappe.get_cached_value") + def test_validate_document_name(self, mock_get_cached): + mock_get_cached.return_value = "India" # mock country + posting_date = "2021-05-01" + + invalid_names = [ "SI$1231", "012345678901234567", "SI 2020 05", + "SI.2020.0001", "PI2021 - 001" ] + for name in invalid_names: + doc = frappe._dict(name=name, posting_date=posting_date) + self.assertRaises(frappe.ValidationError, validate_document_name, doc) + + valid_names = [ "012345678901236", "SI/2020/0001", "SI/2020-0001", + "2020-PI-0001", "PI2020-0001" ] + for name in valid_names: + doc = frappe._dict(name=name, posting_date=posting_date) + try: + validate_document_name(doc) + except frappe.ValidationError: + self.fail("Valid name {} throwing error".format(name)) + + @patch("frappe.get_cached_value") + def test_validate_document_name_not_india(self, mock_get_cached): + mock_get_cached.return_value = "Not India" + doc = frappe._dict(name="SI$123", posting_date="2021-05-01") + + try: + validate_document_name(doc) + except frappe.ValidationError: + self.fail("Regional validation related to India are being applied to other countries") diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index d6200c9fd63..25eecb4a528 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -2,7 +2,7 @@ from __future__ import unicode_literals import frappe, re, json from frappe import _ import erpnext -from frappe.utils import cstr, flt, date_diff, nowdate, round_based_on_smallest_currency_fraction, money_in_words +from frappe.utils import cstr, flt, date_diff, nowdate, round_based_on_smallest_currency_fraction, money_in_words, getdate from erpnext.regional.india import states, state_numbers from erpnext.controllers.taxes_and_totals import get_itemised_tax, get_itemised_taxable_amount from erpnext.controllers.accounts_controller import get_taxes_and_charges @@ -14,6 +14,13 @@ from erpnext.accounts.general_ledger import make_gl_entries from erpnext.accounts.utils import get_account_currency from frappe.model.utils import get_fetch_values + +GST_INVOICE_NUMBER_FORMAT = re.compile(r"^[a-zA-Z0-9\-/]+$") #alphanumeric and - / +GSTIN_FORMAT = re.compile("^[0-9]{2}[A-Z]{4}[0-9A-Z]{1}[0-9]{4}[A-Z]{1}[1-9A-Z]{1}[1-9A-Z]{1}[0-9A-Z]{1}$") +GSTIN_UIN_FORMAT = re.compile("^[0-9]{4}[A-Z]{3}[0-9]{5}[0-9A-Z]{3}") +PAN_NUMBER_FORMAT = re.compile("[A-Z]{5}[0-9]{4}[A-Z]{1}") + + def validate_gstin_for_india(doc, method): if hasattr(doc, 'gst_state') and doc.gst_state: doc.gst_state_number = state_numbers[doc.gst_state] @@ -37,12 +44,10 @@ def validate_gstin_for_india(doc, method): frappe.throw(_("Invalid GSTIN! A GSTIN must have 15 characters.")) if gst_category and gst_category == 'UIN Holders': - p = re.compile("^[0-9]{4}[A-Z]{3}[0-9]{5}[0-9A-Z]{3}") - if not p.match(doc.gstin): + if not GSTIN_UIN_FORMAT.match(doc.gstin): frappe.throw(_("Invalid GSTIN! The input you've entered doesn't match the GSTIN format for UIN Holders or Non-Resident OIDAR Service Providers")) else: - p = re.compile("^[0-9]{2}[A-Z]{4}[0-9A-Z]{1}[0-9]{4}[A-Z]{1}[1-9A-Z]{1}[1-9A-Z]{1}[0-9A-Z]{1}$") - if not p.match(doc.gstin): + if not GSTIN_FORMAT.match(doc.gstin): frappe.throw(_("Invalid GSTIN! The input you've entered doesn't match the format of GSTIN.")) validate_gstin_check_digit(doc.gstin) @@ -55,6 +60,13 @@ def validate_gstin_for_india(doc, method): frappe.throw(_("Invalid GSTIN! First 2 digits of GSTIN should match with State number {0}.") .format(doc.gst_state_number)) +def validate_pan_for_india(doc, method): + if doc.get('country') != 'India' or not doc.pan: + return + + if not PAN_NUMBER_FORMAT.match(doc.pan): + frappe.throw(_("Invalid PAN No. The input you've entered doesn't match the format of PAN.")) + def validate_tax_category(doc, method): if doc.get('gst_state') and frappe.db.get_value('Tax Category', {'gst_state': doc.gst_state, 'is_inter_state': doc.is_inter_state}): if doc.is_inter_state: @@ -140,6 +152,20 @@ def get_itemised_tax_breakup_data(doc, account_wise=False): def set_place_of_supply(doc, method=None): doc.place_of_supply = get_place_of_supply(doc, doc.doctype) +def validate_document_name(doc, method=None): + """Validate GST invoice number requirements.""" + country = frappe.get_cached_value("Company", doc.company, "country") + + # Date was chosen as start of next FY to avoid irritating current users. + if country != "India" or getdate(doc.posting_date) < getdate("2021-04-01"): + return + + if len(doc.name) > 16: + frappe.throw(_("Maximum length of document number should be 16 characters as per GST rules. Please change the naming series.")) + + if not GST_INVOICE_NUMBER_FORMAT.match(doc.name): + frappe.throw(_("Document name should only contain alphanumeric values, dash(-) and slash(/) characters as per GST rules. Please change the naming series.")) + # don't remove this function it is used in tests def test_method(): '''test function''' @@ -775,3 +801,24 @@ def make_regional_gl_entries(gl_entries, doc): ) return gl_entries + +@frappe.whitelist() +def get_regional_round_off_accounts(company, account_list): + country = frappe.get_cached_value('Company', company, 'country') + + if country != 'India': + return + + if isinstance(account_list, string_types): + account_list = json.loads(account_list) + + if not frappe.db.get_single_value('GST Settings', 'round_off_gst_values'): + return + + gst_accounts = get_gst_accounts(company) + gst_account_list = gst_accounts.get('cgst_account') + gst_accounts.get('sgst_account') \ + + gst_accounts.get('igst_account') + + account_list.extend(gst_account_list) + + return account_list diff --git a/erpnext/regional/italy/setup.py b/erpnext/regional/italy/setup.py index 6ab73413df2..95b92e76a69 100644 --- a/erpnext/regional/italy/setup.py +++ b/erpnext/regional/italy/setup.py @@ -127,7 +127,7 @@ def make_custom_fields(update=True): options="\n".join(map(lambda x: frappe.safe_decode(x, encoding='utf-8'), vat_collectability_options)), fetch_from="company.vat_collectability"), dict(fieldname='sb_e_invoicing_reference', label='E-Invoicing', - fieldtype='Section Break', insert_after='pos_total_qty', print_hide=1), + fieldtype='Section Break', insert_after='against_income_account', print_hide=1), dict(fieldname='company_tax_id', label='Company Tax ID', fieldtype='Data', insert_after='sb_e_invoicing_reference', print_hide=1, read_only=1, fetch_from="company.tax_id"), @@ -189,9 +189,7 @@ def make_custom_fields(update=True): def setup_report(): report_name = 'Electronic Invoice Register' - - frappe.db.sql(""" update `tabReport` set disabled = 0 where - name = %s """, report_name) + frappe.db.set_value("Report", report_name, "disabled", 0) if not frappe.db.get_value('Custom Role', dict(report=report_name)): frappe.get_doc(dict( diff --git a/erpnext/regional/report/irs_1099/irs_1099.py b/erpnext/regional/report/irs_1099/irs_1099.py index c1c8aedc9f3..4e57ff7ea37 100644 --- a/erpnext/regional/report/irs_1099/irs_1099.py +++ b/erpnext/regional/report/irs_1099/irs_1099.py @@ -32,6 +32,10 @@ def execute(filters=None): data = [] columns = get_columns() + conditions = "" + if filters.supplier_group: + conditions += "AND s.supplier_group = %s" %frappe.db.escape(filters.get("supplier_group")) + data = frappe.db.sql(""" SELECT s.supplier_group as "supplier_group", @@ -46,15 +50,17 @@ def execute(filters=None): AND s.irs_1099 = 1 AND gl.fiscal_year = %(fiscal_year)s AND gl.party_type = "Supplier" + AND gl.company = %(company)s + {conditions} + GROUP BY gl.party + ORDER BY - gl.party DESC - """, { - "fiscal_year": filters.fiscal_year, - "supplier_group": filters.supplier_group, - "company": filters.company - }, as_dict=True) + gl.party DESC""".format(conditions=conditions), { + "fiscal_year": filters.fiscal_year, + "company": filters.company + }, as_dict=True) return columns, data @@ -79,13 +85,13 @@ def get_columns(): "fieldname": "tax_id", "label": _("Tax ID"), "fieldtype": "Data", - "width": 120 + "width": 200 }, { "fieldname": "payments", "label": _("Total Payments"), "fieldtype": "Currency", - "width": 120 + "width": 200 } ] diff --git a/erpnext/regional/united_arab_emirates/setup.py b/erpnext/regional/united_arab_emirates/setup.py index 776a82c7306..68208ab31bf 100644 --- a/erpnext/regional/united_arab_emirates/setup.py +++ b/erpnext/regional/united_arab_emirates/setup.py @@ -7,12 +7,15 @@ import frappe, os, json from frappe.custom.doctype.custom_field.custom_field import create_custom_fields from frappe.permissions import add_permission, update_permission_property from erpnext.setup.setup_wizard.operations.taxes_setup import create_sales_tax +from erpnext.payroll.doctype.gratuity_rule.gratuity_rule import get_gratuity_rule def setup(company=None, patch=True): make_custom_fields() add_print_formats() add_custom_roles_for_reports() add_permissions() + create_gratuity_rule() + if company: create_sales_tax(company) @@ -155,3 +158,93 @@ def add_permissions(): add_permission(doctype, role, 0) update_permission_property(doctype, role, 0, 'write', 1) update_permission_property(doctype, role, 0, 'create', 1) + +def create_gratuity_rule(): + rule_1 = rule_2 = rule_3 = None + + # Rule Under Limited Contract + slabs = get_slab_for_limited_contract() + if not frappe.db.exists("Gratuity Rule", "Rule Under Limited Contract (UAE)"): + rule_1 = get_gratuity_rule("Rule Under Limited Contract (UAE)", slabs, calculate_gratuity_amount_based_on="Sum of all previous slabs") + + # Rule Under Unlimited Contract on termination + slabs = get_slab_for_unlimited_contract_on_termination() + if not frappe.db.exists("Gratuity Rule", "Rule Under Unlimited Contract on termination (UAE)"): + rule_2 = get_gratuity_rule("Rule Under Unlimited Contract on termination (UAE)", slabs) + + # Rule Under Unlimited Contract on resignation + slabs = get_slab_for_unlimited_contract_on_resignation() + if not frappe.db.exists("Gratuity Rule", "Rule Under Unlimited Contract on resignation (UAE)"): + rule_3 = get_gratuity_rule("Rule Under Unlimited Contract on resignation (UAE)", slabs) + + #for applicable salary component user need to set this by its own + if rule_1: + rule_1.flags.ignore_mandatory = True + rule_1.save() + if rule_2: + rule_2.flags.ignore_mandatory = True + rule_2.save() + if rule_3: + rule_3.flags.ignore_mandatory = True + rule_3.save() + + +def get_slab_for_limited_contract(): + return [{ + "from_year": 0, + "to_year":1, + "fraction_of_applicable_earnings": 0 + }, + { + "from_year": 1, + "to_year":5, + "fraction_of_applicable_earnings": 21/30 + }, + { + "from_year": 5, + "to_year":0, + "fraction_of_applicable_earnings": 1 + }] + +def get_slab_for_unlimited_contract_on_termination(): + return [{ + "from_year": 0, + "to_year":1, + "fraction_of_applicable_earnings": 0 + }, + { + "from_year": 1, + "to_year":5, + "fraction_of_applicable_earnings": 21/30 + }, + { + "from_year": 5, + "to_year":0, + "fraction_of_applicable_earnings": 1 + }] + +def get_slab_for_unlimited_contract_on_resignation(): + fraction_1 = 1/3 * 21/30 + fraction_2 = 2/3 * 21/30 + fraction_3 = 21/30 + + return [{ + "from_year": 0, + "to_year":1, + "fraction_of_applicable_earnings": 0 + }, + { + "from_year": 1, + "to_year":3, + "fraction_of_applicable_earnings": fraction_1 + }, + { + "from_year": 3, + "to_year":5, + "fraction_of_applicable_earnings": fraction_2 + }, + { + "from_year": 5, + "to_year":0, + "fraction_of_applicable_earnings": fraction_3 + }] diff --git a/erpnext/regional/united_states/setup.py b/erpnext/regional/united_states/setup.py index 2b0ecafebc5..24ab1cf049f 100644 --- a/erpnext/regional/united_states/setup.py +++ b/erpnext/regional/united_states/setup.py @@ -36,5 +36,4 @@ def make_custom_fields(update=True): def add_print_formats(): frappe.reload_doc("regional", "print_format", "irs_1099_form") - frappe.db.sql(""" update `tabPrint Format` set disabled = 0 where - name in('IRS 1099 Form') """) + frappe.db.set_value("Print Format", "IRS 1099 Form", "disabled", 0) diff --git a/erpnext/selling/desk_page/retail/retail.json b/erpnext/selling/desk_page/retail/retail.json deleted file mode 100644 index c4ddf26a901..00000000000 --- a/erpnext/selling/desk_page/retail/retail.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "cards": [ - { - "hidden": 0, - "label": "Settings & Configurations", - "links": "[\n {\n \"description\": \"Setup default values for POS Invoices\",\n \"label\": \"Point-of-Sale Profile\",\n \"name\": \"POS Profile\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"POS Settings\",\n \"name\": \"POS Settings\",\n \"type\": \"doctype\"\n }\n]" - }, - { - "hidden": 0, - "label": "Loyalty Program", - "links": "[\n {\n \"description\": \"To make Customer based incentive schemes.\",\n \"label\": \"Loyalty Program\",\n \"name\": \"Loyalty Program\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"To view logs of Loyalty Points assigned to a Customer.\",\n \"label\": \"Loyalty Point Entry\",\n \"name\": \"Loyalty Point Entry\",\n \"type\": \"doctype\"\n }\n]" - }, - { - "hidden": 0, - "label": "Opening & Closing", - "links": "[\n {\n \"label\": \"POS Opening Entry\",\n \"name\": \"POS Opening Entry\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"POS Closing Entry\",\n \"name\": \"POS Closing Entry\",\n \"type\": \"doctype\"\n }\n]" - } - ], - "category": "Domains", - "charts": [], - "creation": "2020-03-02 17:18:32.505616", - "developer_mode_only": 0, - "disable_user_customization": 0, - "docstatus": 0, - "doctype": "Desk Page", - "extends_another_page": 0, - "hide_custom": 0, - "idx": 0, - "is_standard": 1, - "label": "Retail", - "modified": "2020-09-09 11:46:28.297435", - "modified_by": "Administrator", - "module": "Selling", - "name": "Retail", - "owner": "Administrator", - "pin_to_bottom": 0, - "pin_to_top": 0, - "restrict_to_domain": "Retail", - "shortcuts": [ - { - "doc_view": "", - "label": "Point Of Sale", - "link_to": "point-of-sale", - "type": "Page" - } - ] -} \ No newline at end of file diff --git a/erpnext/selling/desk_page/selling/selling.json b/erpnext/selling/desk_page/selling/selling.json deleted file mode 100644 index b15df980b12..00000000000 --- a/erpnext/selling/desk_page/selling/selling.json +++ /dev/null @@ -1,92 +0,0 @@ -{ - "cards": [ - { - "hidden": 0, - "label": "Selling", - "links": "[\n {\n \"description\": \"Customer Database.\",\n \"label\": \"Customer\",\n \"name\": \"Customer\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\",\n \"Customer\"\n ],\n \"description\": \"Quotes to Leads or Customers.\",\n \"label\": \"Quotation\",\n \"name\": \"Quotation\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\",\n \"Customer\"\n ],\n \"description\": \"Confirmed orders from Customers.\",\n \"label\": \"Sales Order\",\n \"name\": \"Sales Order\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\",\n \"Customer\"\n ],\n \"label\": \"Sales Invoice\",\n \"name\": \"Sales Invoice\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\",\n \"Customer\"\n ],\n \"description\": \"Blanket Orders from Costumers.\",\n \"label\": \"Blanket Order\",\n \"name\": \"Blanket Order\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"description\": \"Manage Sales Partners.\",\n \"label\": \"Sales Partner\",\n \"name\": \"Sales Partner\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\",\n \"Customer\"\n ],\n \"description\": \"Manage Sales Person Tree.\",\n \"icon\": \"fa fa-sitemap\",\n \"label\": \"Sales Person\",\n \"link\": \"Tree/Sales Person\",\n \"name\": \"Sales Person\",\n \"type\": \"doctype\"\n }\n]" - }, - { - "hidden": 0, - "label": "Items and Pricing", - "links": "[\n {\n \"description\": \"All Products or Services.\",\n \"label\": \"Item\",\n \"name\": \"Item\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\",\n \"Price List\"\n ],\n \"description\": \"Multiple Item prices.\",\n \"label\": \"Item Price\",\n \"name\": \"Item Price\",\n \"onboard\": 1,\n \"route\": \"#Report/Item Price\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Price List master.\",\n \"label\": \"Price List\",\n \"name\": \"Price List\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Tree of Item Groups.\",\n \"icon\": \"fa fa-sitemap\",\n \"label\": \"Item Group\",\n \"link\": \"Tree/Item Group\",\n \"name\": \"Item Group\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"description\": \"Bundle items at time of sale.\",\n \"label\": \"Product Bundle\",\n \"name\": \"Product Bundle\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Rules for applying different promotional schemes.\",\n \"label\": \"Promotional Scheme\",\n \"name\": \"Promotional Scheme\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"description\": \"Rules for applying pricing and discount.\",\n \"label\": \"Pricing Rule\",\n \"name\": \"Pricing Rule\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Rules for adding shipping costs.\",\n \"label\": \"Shipping Rule\",\n \"name\": \"Shipping Rule\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Define coupon codes.\",\n \"label\": \"Coupon Code\",\n \"name\": \"Coupon Code\",\n \"type\": \"doctype\"\n }\n]" - }, - { - "hidden": 0, - "label": "Settings", - "links": "[\n {\n \"description\": \"Default settings for selling transactions.\",\n \"label\": \"Selling Settings\",\n \"name\": \"Selling Settings\",\n \"settings\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Template of terms or contract.\",\n \"label\": \"Terms and Conditions Template\",\n \"name\": \"Terms and Conditions\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Tax template for selling transactions.\",\n \"label\": \"Sales Taxes and Charges Template\",\n \"name\": \"Sales Taxes and Charges Template\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Track Leads by Lead Source.\",\n \"label\": \"Lead Source\",\n \"name\": \"Lead Source\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Manage Customer Group Tree.\",\n \"icon\": \"fa fa-sitemap\",\n \"label\": \"Customer Group\",\n \"link\": \"Tree/Customer Group\",\n \"name\": \"Customer Group\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"All Contacts.\",\n \"label\": \"Contact\",\n \"name\": \"Contact\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"All Addresses.\",\n \"label\": \"Address\",\n \"name\": \"Address\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Manage Territory Tree.\",\n \"icon\": \"fa fa-sitemap\",\n \"label\": \"Territory\",\n \"link\": \"Tree/Territory\",\n \"name\": \"Territory\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Sales campaigns.\",\n \"label\": \"Campaign\",\n \"name\": \"Campaign\",\n \"type\": \"doctype\"\n }\n]" - }, - { - "hidden": 0, - "label": "Key Reports", - "links": "[\n {\n \n \"doctype\": \"Sales Order\",\n \"is_query_report\": true,\n \"label\": \"Sales Analytics\",\n \"name\": \"Sales Analytics\",\n \"onboard\": 1,\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Order\"\n ],\n \"doctype\": \"Sales Order\",\n \"is_query_report\": true,\n \"label\": \"Sales Order Analysis\",\n \"name\": \"Sales Order Analysis\",\n \"onboard\": 1,\n \"type\": \"report\"\n },\n {\n \"icon\": \"fa fa-bar-chart\",\n \"label\": \"Sales Funnel\",\n \"name\": \"sales-funnel\",\n \"onboard\": 1,\n \"type\": \"page\"\n },\n {\n \"dependencies\": [\n \"Sales Order\"\n ],\n \"doctype\": \"Sales Order\",\n \"is_query_report\": true,\n \"label\": \"Sales Order Trends\",\n \"name\": \"Sales Order Trends\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Quotation\"\n ],\n \"doctype\": \"Quotation\",\n \"is_query_report\": true,\n \"label\": \"Quotation Trends\",\n \"name\": \"Quotation Trends\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Customer\"\n ],\n \"doctype\": \"Customer\",\n \"icon\": \"fa fa-bar-chart\",\n \"is_query_report\": true,\n \"label\": \"Customer Acquisition and Loyalty\",\n \"name\": \"Customer Acquisition and Loyalty\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Order\"\n ],\n \"doctype\": \"Sales Order\",\n \"is_query_report\": true,\n \"label\": \"Inactive Customers\",\n \"name\": \"Inactive Customers\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Order\"\n ],\n \"doctype\": \"Sales Order\",\n \"is_query_report\": true,\n \"label\": \"Sales Person-wise Transaction Summary\",\n \"name\": \"Sales Person-wise Transaction Summary\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"doctype\": \"Item\",\n \"is_query_report\": true,\n \"label\": \"Item-wise Sales History\",\n \"name\": \"Item-wise Sales History\",\n \"type\": \"report\"\n }\n]" - }, - { - "hidden": 0, - "label": "Other Reports", - "links": "[\n {\n \"dependencies\": [\n \"Lead\"\n ],\n \"doctype\": \"Lead\",\n \"is_query_report\": true,\n \"label\": \"Lead Details\",\n \"name\": \"Lead Details\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Address\"\n ],\n \"doctype\": \"Address\",\n \"is_query_report\": true,\n \"label\": \"Customer Addresses And Contacts\",\n \"name\": \"Address And Contacts\",\n \"route_options\": {\n \"party_type\": \"Customer\"\n },\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"doctype\": \"Item\",\n \"is_query_report\": true,\n \"label\": \"Available Stock for Packing Items\",\n \"name\": \"Available Stock for Packing Items\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Order\"\n ],\n \"doctype\": \"Sales Order\",\n \"is_query_report\": true,\n \"label\": \"Pending SO Items For Purchase Request\",\n \"name\": \"Pending SO Items For Purchase Request\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Delivery Note\"\n ],\n \"doctype\": \"Delivery Note\",\n \"is_query_report\": true,\n \"label\": \"Delivery Note Trends\",\n \"name\": \"Delivery Note Trends\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Invoice\"\n ],\n \"doctype\": \"Sales Invoice\",\n \"is_query_report\": true,\n \"label\": \"Sales Invoice Trends\",\n \"name\": \"Sales Invoice Trends\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Customer\"\n ],\n \"doctype\": \"Customer\",\n \"is_query_report\": true,\n \"label\": \"Customer Credit Balance\",\n \"name\": \"Customer Credit Balance\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Customer\"\n ],\n \"doctype\": \"Customer\",\n \"is_query_report\": true,\n \"label\": \"Customers Without Any Sales Transactions\",\n \"name\": \"Customers Without Any Sales Transactions\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Customer\"\n ],\n \"doctype\": \"Customer\",\n \"is_query_report\": true,\n \"label\": \"Sales Partners Commission\",\n \"name\": \"Sales Partners Commission\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Order\"\n ],\n \"doctype\": \"Sales Order\",\n \"is_query_report\": true,\n \"label\": \"Territory Target Variance Based On Item Group\",\n \"name\": \"Territory Target Variance Based On Item Group\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Order\"\n ],\n \"doctype\": \"Sales Order\",\n \"is_query_report\": true,\n \"label\": \"Sales Person Target Variance Based On Item Group\",\n \"name\": \"Sales Person Target Variance Based On Item Group\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Order\"\n ],\n \"doctype\": \"Sales Order\",\n \"is_query_report\": true,\n \"label\": \"Sales Partner Target Variance Based On Item Group\",\n \"name\": \"Sales Partner Target Variance based on Item Group\",\n \"type\": \"report\"\n }\n \n]" - } - ], - "category": "Modules", - "charts": [ - { - "chart_name": "Sales Order Trends", - "label": "Sales Order Trends" - } - ], - "charts_label": "Selling ", - "creation": "2020-01-28 11:49:12.092882", - "developer_mode_only": 0, - "disable_user_customization": 0, - "docstatus": 0, - "doctype": "Desk Page", - "extends_another_page": 0, - "hide_custom": 1, - "idx": 0, - "is_standard": 1, - "label": "Selling", - "modified": "2020-10-08 10:23:09.984377", - "modified_by": "Administrator", - "module": "Selling", - "name": "Selling", - "onboarding": "Selling", - "owner": "Administrator", - "pin_to_bottom": 0, - "pin_to_top": 0, - "shortcuts": [ - { - "color": "#cef6d1", - "format": "{} Available", - "label": "Item", - "link_to": "Item", - "stats_filter": "{\n \"disabled\":0\n}", - "type": "DocType" - }, - { - "color": "#ffe8cd", - "format": "{} To Deliver", - "label": "Sales Order", - "link_to": "Sales Order", - "stats_filter": "{\n \"company\": [\"like\", '%' + frappe.defaults.get_global_default(\"company\") + '%'],\n \"status\":[\"in\", [\"To Deliver\", \"To Deliver and Bill\"]]\n}", - "type": "DocType" - }, - { - "color": "#cef6d1", - "format": "{} Open", - "label": "Sales Analytics", - "link_to": "Sales Analytics", - "stats_filter": "{ \"Status\": \"Open\" }", - "type": "Report" - }, - { - "label": "Sales Order Analysis", - "link_to": "Sales Order Analysis", - "type": "Report" - }, - { - "label": "Dashboard", - "link_to": "Selling", - "type": "Dashboard" - } - ], - "shortcuts_label": "Quick Access" -} \ No newline at end of file diff --git a/erpnext/selling/doctype/customer/customer.json b/erpnext/selling/doctype/customer/customer.json index 557c7151d96..7d5e84df52f 100644 --- a/erpnext/selling/doctype/customer/customer.json +++ b/erpnext/selling/doctype/customer/customer.json @@ -16,6 +16,8 @@ "customer_name", "gender", "customer_type", + "pan", + "tax_withholding_category", "default_bank_account", "lead_name", "image", @@ -34,9 +36,8 @@ "companies", "currency_and_price_list", "default_currency", - "default_price_list", "column_break_14", - "language", + "default_price_list", "address_contacts", "address_html", "website", @@ -59,6 +60,7 @@ "column_break_45", "market_segment", "industry", + "language", "is_frozen", "column_break_38", "loyalty_program", @@ -479,13 +481,25 @@ "fieldname": "dn_required", "fieldtype": "Check", "label": "Allow Sales Invoice Creation Without Delivery Note" + }, + { + "fieldname": "pan", + "fieldtype": "Data", + "label": "PAN" + }, + { + "fieldname": "tax_withholding_category", + "fieldtype": "Link", + "label": "Tax Withholding Category", + "options": "Tax Withholding Category" } ], "icon": "fa fa-user", "idx": 363, "image_field": "image", + "index_web_pages_for_search": 1, "links": [], - "modified": "2020-03-17 11:03:42.706907", + "modified": "2021-01-28 12:54:57.258959", "modified_by": "Administrator", "module": "Selling", "name": "Customer", diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index bf8b7fc128a..c4525946088 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -126,7 +126,9 @@ class Customer(TransactionBase): '''If Customer created from Lead, update lead status to "Converted" update Customer link in Quotation, Opportunity''' if self.lead_name: - frappe.db.set_value('Lead', self.lead_name, 'status', 'Converted', update_modified=False) + lead = frappe.get_doc('Lead', self.lead_name) + lead.status = 'Converted' + lead.save() def create_lead_address_contact(self): if self.lead_name: diff --git a/erpnext/selling/doctype/customer/test_customer.py b/erpnext/selling/doctype/customer/test_customer.py index 87fdaa366f1..7761aa70fb2 100644 --- a/erpnext/selling/doctype/customer/test_customer.py +++ b/erpnext/selling/doctype/customer/test_customer.py @@ -54,7 +54,11 @@ class TestCustomer(unittest.TestCase): details = get_party_details("_Test Customer") for key, value in iteritems(to_check): - self.assertEqual(value, details.get(key)) + val = details.get(key) + if not val and not isinstance(val, list): + val = None + + self.assertEqual(value, val) def test_party_details_tax_category(self): from erpnext.accounts.party import get_party_details diff --git a/erpnext/selling/doctype/quotation/quotation.py b/erpnext/selling/doctype/quotation/quotation.py index 36b584d4886..5da248c1b52 100644 --- a/erpnext/selling/doctype/quotation/quotation.py +++ b/erpnext/selling/doctype/quotation/quotation.py @@ -19,7 +19,7 @@ class Quotation(SellingController): self.indicator_color = 'blue' self.indicator_title = 'Submitted' if self.valid_till and getdate(self.valid_till) < getdate(nowdate()): - self.indicator_color = 'darkgrey' + self.indicator_color = 'gray' self.indicator_title = 'Expired' def validate(self): diff --git a/erpnext/selling/doctype/quotation/quotation_list.js b/erpnext/selling/doctype/quotation/quotation_list.js index f425acf180a..b631685bd19 100644 --- a/erpnext/selling/doctype/quotation/quotation_list.js +++ b/erpnext/selling/doctype/quotation/quotation_list.js @@ -20,9 +20,9 @@ frappe.listview_settings['Quotation'] = { } else if(doc.status==="Ordered") { return [__("Ordered"), "green", "status,=,Ordered"]; } else if(doc.status==="Lost") { - return [__("Lost"), "darkgrey", "status,=,Lost"]; + return [__("Lost"), "gray", "status,=,Lost"]; } else if(doc.status==="Expired") { - return [__("Expired"), "darkgrey", "status,=,Expired"]; + return [__("Expired"), "gray", "status,=,Expired"]; } } }; diff --git a/erpnext/selling/doctype/quotation_item/quotation_item.py b/erpnext/selling/doctype/quotation_item/quotation_item.py index 966b542c41f..7384871ed44 100644 --- a/erpnext/selling/doctype/quotation_item/quotation_item.py +++ b/erpnext/selling/doctype/quotation_item/quotation_item.py @@ -5,8 +5,6 @@ from __future__ import unicode_literals import frappe from frappe.model.document import Document -from erpnext.controllers.print_settings import print_settings_for_item_table class QuotationItem(Document): - def __setup__(self): - print_settings_for_item_table(self) + pass diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js index 78f9df9588a..e3b41e66fbc 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.js +++ b/erpnext/selling/doctype/sales_order/sales_order.js @@ -328,7 +328,7 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend( if(r.message) { frappe.msgprint({ message: __('Work Orders Created: {0}', [r.message.map(function(d) { - return repl('%(name)s', {name:d}) + return repl('%(name)s', {name:d}) }).join(', ')]), indicator: 'green' }) @@ -437,7 +437,7 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend( callback: function(r) { if(r.message) { frappe.msgprint(__('Material Request {0} submitted.', - ['' + r.message.name+ ''])); + ['' + r.message.name+ ''])); } d.hide(); me.frm.reload_doc(); @@ -514,7 +514,7 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend( make_delivery_note: function() { frappe.model.open_mapped_doc({ method: "erpnext.selling.doctype.sales_order.sales_order.make_delivery_note", - frm: me.frm + frm: this.frm }) }, diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index 52a0174798e..ee16f441715 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -555,12 +555,12 @@ class TestSalesOrder(unittest.TestCase): new_item_with_tax = frappe.get_doc("Item", "Test Item with Tax") new_item_with_tax.append("taxes", { - "item_tax_template": "Test Update Items Template", + "item_tax_template": "Test Update Items Template - _TC", "valid_from": nowdate() }) new_item_with_tax.save() - tax_template = "_Test Account Excise Duty @ 10" + tax_template = "_Test Account Excise Duty @ 10 - _TC" item = "_Test Item Home Desktop 100" if not frappe.db.exists("Item Tax", {"parent":item, "item_tax_template":tax_template}): item_doc = frappe.get_doc("Item", item) @@ -614,7 +614,7 @@ class TestSalesOrder(unittest.TestCase): so.cancel() so.delete() new_item_with_tax.delete() - frappe.get_doc("Item Tax Template", "Test Update Items Template").delete() + frappe.get_doc("Item Tax Template", "Test Update Items Template - _TC").delete() frappe.db.set_value("Stock Settings", None, "default_warehouse", old_stock_settings_value) def test_warehouse_user(self): diff --git a/erpnext/selling/doctype/sales_order_item/sales_order_item.py b/erpnext/selling/doctype/sales_order_item/sales_order_item.py index 4a87a0c28af..27f303d43b1 100644 --- a/erpnext/selling/doctype/sales_order_item/sales_order_item.py +++ b/erpnext/selling/doctype/sales_order_item/sales_order_item.py @@ -5,11 +5,9 @@ from __future__ import unicode_literals import frappe from frappe.model.document import Document -from erpnext.controllers.print_settings import print_settings_for_item_table class SalesOrderItem(Document): - def __setup__(self): - print_settings_for_item_table(self) + pass def on_doctype_update(): frappe.db.add_index("Sales Order Item", ["item_code", "warehouse"]) \ No newline at end of file diff --git a/erpnext/selling/doctype/selling_settings/selling_settings.json b/erpnext/selling/doctype/selling_settings/selling_settings.json index 4044f09c855..2104c0131c4 100644 --- a/erpnext/selling/doctype/selling_settings/selling_settings.json +++ b/erpnext/selling/doctype/selling_settings/selling_settings.json @@ -140,7 +140,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2020-10-13 12:12:56.784014", + "modified": "2021-03-02 17:35:53.603607", "modified_by": "Administrator", "module": "Selling", "name": "Selling Settings", @@ -157,5 +157,6 @@ } ], "sort_field": "modified", - "sort_order": "DESC" + "sort_order": "DESC", + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/selling/page/point_of_sale/onscan.js b/erpnext/selling/page/point_of_sale/onscan.js deleted file mode 100644 index 428dc75cf82..00000000000 --- a/erpnext/selling/page/point_of_sale/onscan.js +++ /dev/null @@ -1 +0,0 @@ -!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t()):e.onScan=t()}(this,function(){var d={attachTo:function(e,t){if(void 0!==e.scannerDetectionData)throw new Error("onScan.js is already initialized for DOM element "+e);var n={onScan:function(e,t){},onScanError:function(e){},onKeyProcess:function(e,t){},onKeyDetect:function(e,t){},onPaste:function(e,t){},keyCodeMapper:function(e){return d.decodeKeyEvent(e)},onScanButtonLongPress:function(){},scanButtonKeyCode:!1,scanButtonLongPressTime:500,timeBeforeScanTest:100,avgTimeByChar:30,minLength:6,suffixKeyCodes:[9,13],prefixKeyCodes:[],ignoreIfFocusOn:!1,stopPropagation:!1,preventDefault:!1,captureEvents:!1,reactToKeydown:!0,reactToPaste:!1,singleScanQty:1};return t=this._mergeOptions(n,t),e.scannerDetectionData={options:t,vars:{firstCharTime:0,lastCharTime:0,accumulatedString:"",testTimer:!1,longPressTimeStart:0,longPressed:!1}},!0===t.reactToPaste&&e.addEventListener("paste",this._handlePaste,t.captureEvents),!1!==t.scanButtonKeyCode&&e.addEventListener("keyup",this._handleKeyUp,t.captureEvents),!0!==t.reactToKeydown&&!1===t.scanButtonKeyCode||e.addEventListener("keydown",this._handleKeyDown,t.captureEvents),this},detachFrom:function(e){e.scannerDetectionData.options.reactToPaste&&e.removeEventListener("paste",this._handlePaste),!1!==e.scannerDetectionData.options.scanButtonKeyCode&&e.removeEventListener("keyup",this._handleKeyUp),e.removeEventListener("keydown",this._handleKeyDown),e.scannerDetectionData=void 0},getOptions:function(e){return e.scannerDetectionData.options},setOptions:function(e,t){switch(e.scannerDetectionData.options.reactToPaste){case!0:!1===t.reactToPaste&&e.removeEventListener("paste",this._handlePaste);break;case!1:!0===t.reactToPaste&&e.addEventListener("paste",this._handlePaste)}switch(e.scannerDetectionData.options.scanButtonKeyCode){case!1:!1!==t.scanButtonKeyCode&&e.addEventListener("keyup",this._handleKeyUp);break;default:!1===t.scanButtonKeyCode&&e.removeEventListener("keyup",this._handleKeyUp)}return e.scannerDetectionData.options=this._mergeOptions(e.scannerDetectionData.options,t),this._reinitialize(e),this},decodeKeyEvent:function(e){var t=this._getNormalizedKeyNum(e);switch(!0){case 48<=t&&t<=90:case 106<=t&&t<=111:if(void 0!==e.key&&""!==e.key)return e.key;var n=String.fromCharCode(t);switch(e.shiftKey){case!1:n=n.toLowerCase();break;case!0:n=n.toUpperCase()}return n;case 96<=t&&t<=105:return t-96}return""},simulate:function(e,t){return this._reinitialize(e),Array.isArray(t)?t.forEach(function(e){var t={};"object"!=typeof e&&"function"!=typeof e||null===e?t.keyCode=parseInt(e):t=e;var n=new KeyboardEvent("keydown",t);document.dispatchEvent(n)}):this._validateScanCode(e,t),this},_reinitialize:function(e){var t=e.scannerDetectionData.vars;t.firstCharTime=0,t.lastCharTime=0,t.accumulatedString=""},_isFocusOnIgnoredElement:function(e){var t=e.scannerDetectionData.options.ignoreIfFocusOn;if(!t)return!1;var n=document.activeElement;if(Array.isArray(t)){for(var a=0;at.length*i.avgTimeByChar:c={message:"Receieved code was not entered in time"};break;default:return i.onScan.call(e,t,o),n=new CustomEvent("scan",{detail:{scanCode:t,qty:o}}),e.dispatchEvent(n),d._reinitialize(e),!0}return c.scanCode=t,c.scanDuration=s-r,c.avgTimeByChar=i.avgTimeByChar,c.minLength=i.minLength,i.onScanError.call(e,c),n=new CustomEvent("scanError",{detail:c}),e.dispatchEvent(n),d._reinitialize(e),!1},_mergeOptions:function(e,t){var n,a={};for(n in e)Object.prototype.hasOwnProperty.call(e,n)&&(a[n]=e[n]);for(n in t)Object.prototype.hasOwnProperty.call(t,n)&&(a[n]=t[n]);return a},_getNormalizedKeyNum:function(e){return e.which||e.keyCode},_handleKeyDown:function(e){var t=d._getNormalizedKeyNum(e),n=this.scannerDetectionData.options,a=this.scannerDetectionData.vars,i=!1;if(!1!==n.onKeyDetect.call(this,t,e)&&!d._isFocusOnIgnoredElement(this))if(!1===n.scanButtonKeyCode||t!=n.scanButtonKeyCode){switch(!0){case a.firstCharTime&&-1!==n.suffixKeyCodes.indexOf(t):e.preventDefault(),e.stopImmediatePropagation(),i=!0;break;case!a.firstCharTime&&-1!==n.prefixKeyCodes.indexOf(t):e.preventDefault(),e.stopImmediatePropagation(),i=!1;break;default:var o=n.keyCodeMapper.call(this,e);if(null===o)return;a.accumulatedString+=o,n.preventDefault&&e.preventDefault(),n.stopPropagation&&e.stopImmediatePropagation(),i=!1}a.firstCharTime||(a.firstCharTime=Date.now()),a.lastCharTime=Date.now(),a.testTimer&&clearTimeout(a.testTimer),i?(d._validateScanCode(this,a.accumulatedString),a.testTimer=!1):a.testTimer=setTimeout(d._validateScanCode,n.timeBeforeScanTest,this,a.accumulatedString),n.onKeyProcess.call(this,o,e)}else a.longPressed||(a.longPressTimer=setTimeout(n.onScanButtonLongPress,n.scanButtonLongPressTime,this),a.longPressed=!0)},_handlePaste:function(e){if(!d._isFocusOnIgnoredElement(this)){e.preventDefault(),oOptions.stopPropagation&&e.stopImmediatePropagation();var t=(event.clipboardData||window.clipboardData).getData("text");this.scannerDetectionData.options.onPaste.call(this,t,event);var n=this.scannerDetectionData.vars;n.firstCharTime=0,n.lastCharTime=0,d._validateScanCode(this,t)}},_handleKeyUp:function(e){d._isFocusOnIgnoredElement(this)||d._getNormalizedKeyNum(e)==this.scannerDetectionData.options.scanButtonKeyCode&&(clearTimeout(this.scannerDetectionData.vars.longPressTimer),this.scannerDetectionData.vars.longPressed=!1)},isScanInProgressFor:function(e){return 0 { if (d.idx == this.doc.idx) { @@ -98,7 +85,7 @@ erpnext.PointOfSale.Controller = class { fields: table_fields } ], - primary_action: async ({ company, pos_profile, balance_details }) => { + primary_action: async function({ company, pos_profile, balance_details }) { if (!balance_details.length) { frappe.show_alert({ message: __("Please add Mode of payments and opening balance details."), @@ -108,7 +95,7 @@ erpnext.PointOfSale.Controller = class { } const method = "erpnext.selling.page.point_of_sale.point_of_sale.create_opening_voucher"; const res = await frappe.call({ method, args: { pos_profile, company, balance_details }, freeze:true }); - !res.exc && this.prepare_app_defaults(res.message); + !res.exc && me.prepare_app_defaults(res.message); dialog.hide(); }, primary_action_label: __('Submit') @@ -145,26 +132,18 @@ erpnext.PointOfSale.Controller = class { } make_app() { - return frappe.run_serially([ - () => frappe.dom.freeze(), - () => { - this.set_opening_entry_status(); - this.prepare_dom(); - this.prepare_components(); - this.prepare_menu(); - }, - () => this.make_new_invoice(), - () => frappe.dom.unfreeze(), - () => this.page.set_title(__('Point of Sale')), - ]); + this.prepare_dom(); + this.prepare_components(); + this.prepare_menu(); + this.make_new_invoice(); } prepare_dom() { this.wrapper.append( - `
    ` + `
    ` ); - this.$components_wrapper = this.wrapper.find('.app'); + this.$components_wrapper = this.wrapper.find('.point-of-sale-app'); } prepare_components() { @@ -194,7 +173,7 @@ erpnext.PointOfSale.Controller = class { } toggle_recent_order() { - const show = this.recent_order_list.$component.hasClass('d-none'); + const show = this.recent_order_list.$component.is(':hidden'); this.toggle_recent_order_list(show); } @@ -203,7 +182,7 @@ erpnext.PointOfSale.Controller = class { if (this.frm.doc.items.length == 0) { frappe.show_alert({ - message:__("You must add atleast one item to save it as draft."), + message: __("You must add atleast one item to save it as draft."), indicator:'red' }); frappe.utils.play_sound("error"); @@ -212,8 +191,8 @@ erpnext.PointOfSale.Controller = class { this.frm.save(undefined, undefined, undefined, () => { frappe.show_alert({ - message:__("There was an error saving the document."), - indicator:'red' + message: __("There was an error saving the document."), + indicator: 'red' }); frappe.utils.play_sound("error"); }).then(() => { @@ -222,7 +201,7 @@ erpnext.PointOfSale.Controller = class { () => this.make_new_invoice(), () => frappe.dom.unfreeze(), ]); - }) + }); } close_pos() { @@ -357,10 +336,10 @@ erpnext.PointOfSale.Controller = class { toggle_other_sections: (show) => { if (show) { - this.item_details.$component.hasClass('d-none') ? '' : this.item_details.$component.addClass('d-none'); - this.item_selector.$component.addClass('d-none'); + this.item_details.$component.is(':visible') ? this.item_details.$component.css('display', 'none') : ''; + this.item_selector.$component.css('display', 'none'); } else { - this.item_selector.$component.removeClass('d-none'); + this.item_selector.$component.css('display', 'flex'); } }, @@ -389,7 +368,7 @@ erpnext.PointOfSale.Controller = class { this.order_summary.load_summary_of(doc); }); }, - reset_summary: () => this.order_summary.show_summary_placeholder() + reset_summary: () => this.order_summary.toggle_summary_placeholder(true) } }) } @@ -435,8 +414,6 @@ erpnext.PointOfSale.Controller = class { }) } - - toggle_recent_order_list(show) { this.toggle_components(!show); this.recent_order_list.toggle_component(show); @@ -453,10 +430,12 @@ erpnext.PointOfSale.Controller = class { make_new_invoice() { return frappe.run_serially([ + () => frappe.dom.freeze(), () => this.make_sales_invoice_frm(), () => this.set_pos_profile_data(), () => this.set_pos_profile_status(), () => this.cart.load_invoice(), + () => frappe.dom.unfreeze() ]); } @@ -513,26 +492,17 @@ erpnext.PointOfSale.Controller = class { return this.frm.trigger("set_pos_data"); } - raise_exception_for_pos_profile() { - setTimeout(() => frappe.set_route('List', 'POS Profile'), 2000); - frappe.throw(__("POS Profile is required to use Point-of-Sale")); - } - - set_invoice_status() { - const [status, indicator] = frappe.listview_settings["POS Invoice"].get_indicator(this.frm.doc); - this.page.set_indicator(status, indicator); - } - set_pos_profile_status() { this.page.set_indicator(this.pos_profile, "blue"); } async on_cart_update(args) { frappe.dom.freeze(); + let item_row = undefined; try { let { field, value, item } = args; const { item_code, batch_no, serial_no, uom } = item; - let item_row = this.get_item_from_frm(item_code, batch_no, uom); + item_row = this.get_item_from_frm(item_code, batch_no, uom); const item_selected_from_selector = field === 'qty' && value === "+1" @@ -545,7 +515,7 @@ erpnext.PointOfSale.Controller = class { const qty_needed = field === 'qty' ? value * item_row.conversion_factor : item_row.qty * value; await this.check_stock_availability(item_row, qty_needed, this.frm.doc.set_warehouse); } - + if (this.is_current_item_being_edited(item_row) || item_selected_from_selector) { await frappe.model.set_value(item_row.doctype, item_row.name, field, value); this.update_cart_html(item_row); @@ -583,18 +553,20 @@ erpnext.PointOfSale.Controller = class { this.check_serial_batch_selection_needed(item_row) && this.edit_item_details_of(item_row); this.update_cart_html(item_row); - } + } + } catch (error) { console.log(error); } finally { frappe.dom.unfreeze(); + return item_row; } } get_item_from_frm(item_code, batch_no, uom) { const has_batch_no = batch_no; return this.frm.doc.items.find( - i => i.item_code === item_code + i => i.item_code === item_code && (!has_batch_no || (has_batch_no && i.batch_no === batch_no)) && (i.uom === uom) ); @@ -623,7 +595,7 @@ erpnext.PointOfSale.Controller = class { const no_serial_selected = !item_row.serial_no; const no_batch_selected = !item_row.batch_no; - if ((serialized && no_serial_selected) || (batched && no_batch_selected) || + if ((serialized && no_serial_selected) || (batched && no_batch_selected) || (serialized && batched && (no_batch_selected || no_serial_selected))) { return true; } @@ -713,5 +685,5 @@ erpnext.PointOfSale.Controller = class { }) .catch(e => console.log(e)); } -} +}; diff --git a/erpnext/selling/page/point_of_sale/pos_item_cart.js b/erpnext/selling/page/point_of_sale/pos_item_cart.js index de70f167a52..11a63b3d4a6 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_cart.js +++ b/erpnext/selling/page/point_of_sale/pos_item_cart.js @@ -7,10 +7,9 @@ erpnext.PointOfSale.ItemCart = class { this.allowed_customer_groups = settings.customer_groups; this.allow_rate_change = settings.allow_rate_change; this.allow_discount_change = settings.allow_discount_change; - this.init_component(); } - + init_component() { this.prepare_dom(); this.init_child_components(); @@ -20,10 +19,10 @@ erpnext.PointOfSale.ItemCart = class { prepare_dom() { this.wrapper.append( - `
    ` + `
    ` ) - this.$component = this.wrapper.find('.item-cart'); + this.$component = this.wrapper.find('.customer-cart-container'); } init_child_components() { @@ -33,33 +32,33 @@ erpnext.PointOfSale.ItemCart = class { init_customer_selector() { this.$component.append( - `
    ` + `
    ` ) this.$customer_section = this.$component.find('.customer-section'); this.make_customer_selector(); } - + reset_customer_selector() { const frm = this.events.get_frm(); frm.set_value('customer', ''); - this.$customer_section.removeClass('border pr-4 pl-4'); this.make_customer_selector(); this.customer_field.set_focus(); } - + init_cart_components() { this.$component.append( - `
    -
    -
    -
    Item
    -
    Qty
    -
    Amount
    + `
    +
    +
    Item Cart
    +
    +
    Item
    +
    Qty
    +
    Amount
    -
    -
    -
    -
    +
    +
    +
    +
    ` ); this.$cart_container = this.$component.find('.cart-container'); @@ -75,54 +74,48 @@ erpnext.PointOfSale.ItemCart = class { this.make_no_items_placeholder(); } - + make_no_items_placeholder() { - this.$cart_header.addClass('d-none'); + this.$cart_header.css('display', 'none'); this.$cart_items_wrapper.html( - `
    -
    No items in cart
    -
    ` - ) - this.$cart_items_wrapper.addClass('mt-4 border-grey border-dashed'); + `
    No items in cart
    ` + ); + } + + get_discount_icon() { + return ( + ` + + + + + ` + ); } make_cart_totals_section() { this.$totals_section = this.$component.find('.cart-totals-section'); this.$totals_section.append( - `
    - + Add Discount + `
    + ${this.get_discount_icon()} Add Discount
    -
    -
    -
    -
    Net Total
    -
    -
    -
    0.00
    -
    -
    -
    -
    -
    -
    Grand Total
    -
    -
    -
    0.00
    -
    -
    -
    - Checkout -
    -
    - Edit Cart -
    -
    ` +
    +
    Net Total
    +
    0.00
    +
    +
    +
    +
    Grand Total
    +
    0.00
    +
    +
    Checkout
    +
    Edit Cart
    ` ) - this.$add_discount_elem = this.$component.find(".add-discount"); + this.$add_discount_elem = this.$component.find(".add-discount-wrapper"); } - + make_cart_numpad() { this.$numpad_section = this.$component.find('.numpad-section'); @@ -142,39 +135,37 @@ erpnext.PointOfSale.ItemCart = class { [ '', '', '', 'col-span-2' ], [ '', '', '', 'col-span-2' ], [ '', '', '', 'col-span-2' ], - [ '', '', '', 'col-span-2 text-bold text-danger' ] + [ '', '', '', 'col-span-2 remove-btn' ] ], fieldnames_map: { 'Quantity': 'qty', 'Discount': 'discount_percentage' } }) this.$numpad_section.prepend( - `
    + `
    ` ) this.$numpad_section.append( - `
    - Checkout -
    ` + `
    Checkout
    ` ) } - + bind_events() { const me = this; - this.$customer_section.on('click', '.add-remove-customer', function (e) { - const customer_info_is_visible = me.$cart_container.hasClass('d-none'); - customer_info_is_visible ? - me.toggle_customer_info(false) : me.reset_customer_selector(); + this.$customer_section.on('click', '.reset-customer-btn', function () { + me.reset_customer_selector(); }); - this.$customer_section.on('click', '.customer-header', function(e) { - // don't triggger the event if .add-remove-customer btn is clicked which is under .customer-header - if ($(e.target).closest('.add-remove-customer').length) return; + this.$customer_section.on('click', '.close-details-btn', function () { + me.toggle_customer_info(false); + }); - const show = !me.$cart_container.hasClass('d-none'); + this.$customer_section.on('click', '.customer-display', function(e) { + if ($(e.target).closest('.reset-customer-btn').length) return; + + const show = me.$cart_container.is(':visible'); me.toggle_customer_info(show); }); @@ -183,7 +174,7 @@ erpnext.PointOfSale.ItemCart = class { me.toggle_item_highlight(this); - const payment_section_hidden = me.$totals_section.find('.edit-cart-btn').hasClass('d-none'); + const payment_section_hidden = !me.$totals_section.find('.edit-cart-btn').is(':visible'); if (!payment_section_hidden) { // payment section is visible // edit cart first and then open item details section @@ -198,8 +189,8 @@ erpnext.PointOfSale.ItemCart = class { }); this.$component.on('click', '.checkout-btn', function() { - if (!$(this).hasClass('bg-primary')) return; - + if ($(this).attr('style').indexOf('--blue-500') == -1) return; + me.events.checkout(); me.toggle_checkout_btn(false); @@ -209,12 +200,10 @@ erpnext.PointOfSale.ItemCart = class { this.$totals_section.on('click', '.edit-cart-btn', () => { this.events.edit_cart(); this.toggle_checkout_btn(true); - - this.$add_discount_elem.addClass("d-none"); }); - this.$component.on('click', '.add-discount', () => { - const can_edit_discount = this.$add_discount_elem.find('.edit-discount').length; + this.$component.on('click', '.add-discount-wrapper', () => { + const can_edit_discount = this.$add_discount_elem.find('.edit-discount-btn').length; if(!this.discount_field || can_edit_discount) this.show_discount_control(); }); @@ -236,7 +225,7 @@ erpnext.PointOfSale.ItemCart = class { if (btn === '.') shortcut_key = 'ctrl+>'; // to account for fieldname map - const fieldname = this.number_pad.fieldnames[btn] ? this.number_pad.fieldnames[btn] : + const fieldname = this.number_pad.fieldnames[btn] ? this.number_pad.fieldnames[btn] : typeof btn === 'string' ? frappe.scrub(btn) : btn; let shortcut_label = shortcut_key.split('+').map(frappe.utils.to_title_case).join('+'); @@ -247,7 +236,7 @@ erpnext.PointOfSale.ItemCart = class { const cart_is_visible = this.$component.is(":visible"); if (cart_is_visible && this.item_is_selected && this.$numpad_section.is(":visible")) { this.$numpad_section.find(`.numpad-btn[data-button-value="${fieldname}"]`).click(); - } + } }) } } @@ -256,7 +245,7 @@ erpnext.PointOfSale.ItemCart = class { frappe.ui.keys.add_shortcut({ shortcut: "ctrl+enter", action: () => this.$component.find(".checkout-btn").click(), - condition: () => this.$component.is(":visible") && this.$totals_section.find('.edit-cart-btn').hasClass('d-none'), + condition: () => this.$component.is(":visible") && !this.$totals_section.find('.edit-cart-btn').is(':visible'), description: __("Checkout Order / Submit Order / New Order"), ignore_inputs: true, page: cur_page.page.page @@ -264,14 +253,15 @@ erpnext.PointOfSale.ItemCart = class { this.$component.find(".edit-cart-btn").attr("title", `${ctrl_label}+E`); frappe.ui.keys.on("ctrl+e", () => { const item_cart_visible = this.$component.is(":visible"); - if (item_cart_visible && this.$totals_section.find('.checkout-btn').hasClass('d-none')) { - this.$component.find(".edit-cart-btn").click() + const checkout_btn_invisible = !this.$totals_section.find('.checkout-btn').is('visible'); + if (item_cart_visible && checkout_btn_invisible) { + this.$component.find(".edit-cart-btn").click(); } }); - this.$component.find(".add-discount").attr("title", `${ctrl_label}+D`); + this.$component.find(".add-discount-wrapper").attr("title", `${ctrl_label}+D`); frappe.ui.keys.add_shortcut({ shortcut: "ctrl+d", - action: () => this.$component.find(".add-discount").click(), + action: () => this.$component.find(".add-discount-wrapper").click(), condition: () => this.$add_discount_elem.is(":visible"), description: __("Add Order Discount"), ignore_inputs: true, @@ -284,27 +274,25 @@ erpnext.PointOfSale.ItemCart = class { } }); } - + toggle_item_highlight(item) { const $cart_item = $(item); - const item_is_highlighted = $cart_item.hasClass("shadow"); + const item_is_highlighted = $cart_item.attr("style") == "background-color:var(--gray-50);"; if (!item || item_is_highlighted) { this.item_is_selected = false; - this.$cart_container.find('.cart-item-wrapper').removeClass("shadow").css("opacity", "1"); + this.$cart_container.find('.cart-item-wrapper').css("background-color", ""); } else { - $cart_item.addClass("shadow"); + $cart_item.css("background-color", "var(--gray-50)"); this.item_is_selected = true; - this.$cart_container.find('.cart-item-wrapper').css("opacity", "1"); - this.$cart_container.find('.cart-item-wrapper').not(item).removeClass("shadow").css("opacity", "0.65"); + this.$cart_container.find('.cart-item-wrapper').not(item).css("background-color", ""); } - // highlight with inner shadow - // $cart_item.addClass("shadow-inner bg-selected"); - // me.$cart_container.find('.cart-item-wrapper').not(this).removeClass("shadow-inner bg-selected"); } make_customer_selector() { - this.$customer_section.html(`
    `); + this.$customer_section.html(` +
    + `); const me = this; const query = { query: 'erpnext.controllers.queries.customer_query' }; const allowed_customer_group = this.allowed_customer_groups || []; @@ -337,12 +325,12 @@ erpnext.PointOfSale.ItemCart = class { } }, }, - parent: this.$customer_section.find('.customer-search-field'), + parent: this.$customer_section.find('.customer-field'), render_input: true, }); this.customer_field.toggle_label(false); } - + fetch_customer_details(customer) { if (customer) { return new Promise((resolve) => { @@ -376,9 +364,9 @@ erpnext.PointOfSale.ItemCart = class { } show_discount_control() { - this.$add_discount_elem.removeClass("pr-4 pl-4"); + this.$add_discount_elem.css({ 'padding': '0px', 'border': 'none' }); this.$add_discount_elem.html( - `
    ` + `
    ` ); const me = this; @@ -387,14 +375,19 @@ erpnext.PointOfSale.ItemCart = class { label: __('Discount'), fieldtype: 'Data', placeholder: __('Enter discount percentage.'), + input_class: 'input-xs', onchange: function() { const frm = me.events.get_frm(); - if (this.value.length || this.value === 0) { + if (flt(this.value) != 0) { frappe.model.set_value(frm.doc.doctype, frm.doc.name, 'additional_discount_percentage', flt(this.value)); me.hide_discount_control(this.value); } else { frappe.model.set_value(frm.doc.doctype, frm.doc.name, 'additional_discount_percentage', 0); - me.$add_discount_elem.html(`+ Add Discount`); + me.$add_discount_elem.css({ + 'border': '1px dashed var(--gray-500)', + 'padding': 'var(--padding-sm) var(--padding-md)' + }); + me.$add_discount_elem.html(`${me.get_discount_icon()} Add Discount`); me.discount_field = undefined; } }, @@ -408,39 +401,37 @@ erpnext.PointOfSale.ItemCart = class { hide_discount_control(discount) { if (!discount) { - this.$add_discount_elem.removeClass("pr-4 pl-4"); + this.$add_discount_elem.css({ 'padding': '0px', 'border': 'none' }); this.$add_discount_elem.html( - `
    ` + `
    ` ); } else { - this.$add_discount_elem.addClass('pr-4 pl-4'); + this.$add_discount_elem.css({ + 'border': '1px dashed var(--dark-green-500)', + 'padding': 'var(--padding-sm) var(--padding-md)' + }); this.$add_discount_elem.html( - ` - - -
    - ${String(discount).bold()}% off -
    - ` + `
    + ${this.get_discount_icon()} Additional ${String(discount).bold()}% discount applied +
    ` ); } } - + update_customer_section() { const me = this; const { customer, email_id='', mobile_no='', image } = this.customer_info || {}; if (customer) { - this.$customer_section.addClass('border pr-4 pl-4').html( - `
    -
    - ${get_customer_image()} -
    -
    ${customer}
    + this.$customer_section.html( + `
    +
    + ${this.get_customer_image()} +
    +
    ${customer}
    ${get_customer_description()}
    -
    +
    @@ -455,34 +446,33 @@ erpnext.PointOfSale.ItemCart = class { function get_customer_description() { if (!email_id && !mobile_no) { - return `
    Click to add email / phone
    ` + return `
    Click to add email / phone
    `; } else if (email_id && !mobile_no) { - return `
    ${email_id}
    ` + return `
    ${email_id}
    `; } else if (mobile_no && !email_id) { - return `
    ${mobile_no}
    ` + return `
    ${mobile_no}
    `; } else { - return `
    ${email_id} | ${mobile_no}
    ` + return `
    ${email_id} - ${mobile_no}
    `; } } - function get_customer_image() { - if (!me.hide_images && image) { - return `
    - ${image} -
    ` - } else { - return `
    - ${frappe.get_abbr(customer)} -
    ` - } + } + + get_customer_image() { + const { customer, image } = this.customer_info || {}; + if (image) { + return `
    ${image}
    `; + } else { + return `
    ${frappe.get_abbr(customer)}
    `; } } - + update_totals_section(frm) { if (!frm) frm = this.events.get_frm(); this.render_net_total(frm.doc.net_total); - this.render_grand_total(frm.doc.grand_total); + const grand_total = cint(frappe.sys_defaults.disable_rounded_total) ? frm.doc.grand_total : frm.doc.rounded_total; + this.render_grand_total(grand_total); const taxes = frm.doc.taxes.map(t => { return { @@ -491,60 +481,42 @@ erpnext.PointOfSale.ItemCart = class { }); this.render_taxes(frm.doc.total_taxes_and_charges, taxes); } - + render_net_total(value) { const currency = this.events.get_frm().doc.currency; - this.$totals_section.find('.net-total').html( - `
    -
    Net Total
    -
    -
    -
    ${format_currency(value, currency)}
    -
    ` + this.$totals_section.find('.net-total-container').html( + `
    Net Total
    ${format_currency(value, currency)}
    ` ) - this.$numpad_section.find('.numpad-net-total').html(`Net Total: ${format_currency(value, currency)}`) + this.$numpad_section.find('.numpad-net-total').html( + `
    Net Total: ${format_currency(value, currency)}
    ` + ); } - + render_grand_total(value) { const currency = this.events.get_frm().doc.currency; - this.$totals_section.find('.grand-total').html( - `
    -
    Grand Total
    -
    -
    -
    ${format_currency(value, currency)}
    -
    ` + this.$totals_section.find('.grand-total-container').html( + `
    Grand Total
    ${format_currency(value, currency)}
    ` ) - this.$numpad_section.find('.numpad-grand-total').html(`Grand Total: ${format_currency(value, currency)}`) + this.$numpad_section.find('.numpad-grand-total').html( + `
    Grand Total: ${format_currency(value, currency)}
    ` + ); } render_taxes(value, taxes) { if (taxes.length) { const currency = this.events.get_frm().doc.currency; - this.$totals_section.find('.taxes').html( - `
    -
    -
    Tax Charges
    -
    - ${ - taxes.map((t, i) => { - let margin_left = ''; - if (i !== 0) margin_left = 'ml-2'; - const description = /[0-9]+/.test(t.description) ? t.description : `${t.description} @ ${t.rate}%`; - return `${description}` - }).join('') - } -
    -
    -
    -
    ${format_currency(value, currency)}
    -
    -
    ` - ) + const taxes_html = taxes.map(t => { + const description = /[0-9]+/.test(t.description) ? t.description : `${t.description} @ ${t.rate}%`; + return `
    +
    ${description}
    +
    ${format_currency(value, currency)}
    +
    `; + }).join(''); + this.$totals_section.find('.taxes-container').css('display', 'flex').html(taxes_html); } else { - this.$totals_section.find('.taxes').html('') + this.$totals_section.find('.taxes-container').css('display', 'none').html(''); } } @@ -553,64 +525,65 @@ erpnext.PointOfSale.ItemCart = class { const item_code_attr = `[data-item-code="${escape(item_code)}"]`; const uom_attr = `[data-uom="${escape(uom)}"]`; - const item_selector = batch_no ? + const item_selector = batch_no ? `.cart-item-wrapper${batch_attr}${uom_attr}` : `.cart-item-wrapper${item_code_attr}${uom_attr}`; - + return this.$cart_items_wrapper.find(item_selector); } - + update_item_html(item, remove_item) { const $item = this.get_cart_item(item); if (remove_item) { - $item && $item.remove(); + $item && $item.next().remove() && $item.remove(); } else { const { item_code, batch_no, uom } = item; const search_field = batch_no ? 'batch_no' : 'item_code'; const search_value = batch_no || item_code; const item_row = this.events.get_frm().doc.items.find(i => i[search_field] === search_value && i.uom === uom); - + this.render_cart_item(item_row, $item); } - const no_of_cart_items = this.$cart_items_wrapper.children().length; - no_of_cart_items > 0 && this.highlight_checkout_btn(no_of_cart_items > 0); - + const no_of_cart_items = this.$cart_items_wrapper.find('.cart-item-wrapper').length; + this.highlight_checkout_btn(no_of_cart_items > 0); + this.update_empty_cart_section(no_of_cart_items); } - + render_cart_item(item_data, $item_to_update) { const currency = this.events.get_frm().doc.currency; const me = this; - + if (!$item_to_update.length) { this.$cart_items_wrapper.append( - `
    -
    ` +
    +
    ` ) $item_to_update = this.get_cart_item(item_data); } $item_to_update.html( - `
    -
    + `${get_item_image_html()} +
    +
    ${item_data.item_name}
    ${get_description_html()}
    - ${get_rate_discount_html()} -
    ` + ${get_rate_discount_html()}` ) set_dynamic_rate_header_width(); this.scroll_to_item($item_to_update); function set_dynamic_rate_header_width() { - const rate_cols = Array.from(me.$cart_items_wrapper.find(".rate-col")); - me.$cart_header.find(".rate-list-header").css("width", ""); - me.$cart_items_wrapper.find(".rate-col").css("width", ""); + const rate_cols = Array.from(me.$cart_items_wrapper.find(".item-rate-amount")); + me.$cart_header.find(".rate-amount-header").css("width", ""); + me.$cart_items_wrapper.find(".item-rate-amount").css("width", ""); let max_width = rate_cols.reduce((max_width, elm) => { if ($(elm).width() > max_width) max_width = $(elm).width(); @@ -620,30 +593,26 @@ erpnext.PointOfSale.ItemCart = class { max_width += 1; if (max_width == 1) max_width = ""; - me.$cart_header.find(".rate-list-header").css("width", max_width); - me.$cart_items_wrapper.find(".rate-col").css("width", max_width); + me.$cart_header.find(".rate-amount-header").css("width", max_width); + me.$cart_items_wrapper.find(".item-rate-amount").css("width", max_width); } - + function get_rate_discount_html() { if (item_data.rate && item_data.amount && item_data.rate !== item_data.amount) { return ` -
    -
    - ${item_data.qty || 0} -
    -
    -
    ${format_currency(item_data.amount, currency)}
    -
    ${format_currency(item_data.rate, currency)}
    +
    +
    ${item_data.qty || 0}
    +
    +
    ${format_currency(item_data.amount, currency)}
    +
    ${format_currency(item_data.rate, currency)}
    ` } else { return ` -
    -
    - ${item_data.qty || 0} -
    -
    -
    ${format_currency(item_data.rate, currency)}
    +
    +
    ${item_data.qty || 0}
    +
    +
    ${format_currency(item_data.rate, currency)}
    ` } @@ -659,10 +628,19 @@ erpnext.PointOfSale.ItemCart = class { } } item_data.description = frappe.ellipsis(item_data.description, 45); - return `
    ${item_data.description}
    ` + return `
    ${item_data.description}
    `; } return ``; } + + function get_item_image_html() { + const { image, item_name } = item_data; + if (image) { + return `
    ${image}
    `; + } else { + return `
    ${frappe.get_abbr(item_name)}
    `; + } + } } scroll_to_item($item) { @@ -670,7 +648,7 @@ erpnext.PointOfSale.ItemCart = class { const scrollTop = $item.offset().top - this.$cart_items_wrapper.offset().top + this.$cart_items_wrapper.scrollTop(); this.$cart_items_wrapper.animate({ scrollTop }); } - + update_selector_value_in_cart_item(selector, value, item) { const $item_to_update = this.get_cart_item(item); $item_to_update.attr(`data-${selector}`, escape(value)); @@ -678,33 +656,37 @@ erpnext.PointOfSale.ItemCart = class { toggle_checkout_btn(show_checkout) { if (show_checkout) { - this.$totals_section.find('.checkout-btn').removeClass('d-none'); - this.$totals_section.find('.edit-cart-btn').addClass('d-none'); + this.$totals_section.find('.checkout-btn').css('display', 'flex'); + this.$totals_section.find('.edit-cart-btn').css('display', 'none'); } else { - this.$totals_section.find('.checkout-btn').addClass('d-none'); - this.$totals_section.find('.edit-cart-btn').removeClass('d-none'); + this.$totals_section.find('.checkout-btn').css('display', 'none'); + this.$totals_section.find('.edit-cart-btn').css('display', 'flex'); } } highlight_checkout_btn(toggle) { - const has_primary_class = this.$totals_section.find('.checkout-btn').hasClass('bg-primary'); - if (toggle && !has_primary_class) { - this.$totals_section.find('.checkout-btn').addClass('bg-primary text-white text-lg'); - } else if (!toggle && has_primary_class) { - this.$totals_section.find('.checkout-btn').removeClass('bg-primary text-white text-lg'); + if (toggle) { + this.$add_discount_elem.css('display', 'flex'); + this.$cart_container.find('.checkout-btn').css({ + 'background-color': 'var(--blue-500)' + }); + } else { + this.$add_discount_elem.css('display', 'none'); + this.$cart_container.find('.checkout-btn').css({ + 'background-color': 'var(--blue-200)' + }); } } - + update_empty_cart_section(no_of_cart_items) { const $no_item_element = this.$cart_items_wrapper.find('.no-item-wrapper'); // if cart has items and no item is present - no_of_cart_items > 0 && $no_item_element && $no_item_element.remove() - && this.$cart_items_wrapper.removeClass('mt-4 border-grey border-dashed') && this.$cart_header.removeClass('d-none'); + no_of_cart_items > 0 && $no_item_element && $no_item_element.remove() && this.$cart_header.css('display', 'flex'); no_of_cart_items === 0 && !$no_item_element.length && this.make_no_items_placeholder(); } - + on_numpad_event($btn) { const current_action = $btn.attr('data-button-value'); const action_is_field_edit = ['qty', 'discount_percentage', 'rate'].includes(current_action); @@ -735,7 +717,7 @@ erpnext.PointOfSale.ItemCart = class { this.prev_action = undefined; } this.numpad_value = ''; - + } else if (current_action === 'checkout') { this.prev_action = undefined; this.toggle_item_highlight(); @@ -761,7 +743,7 @@ erpnext.PointOfSale.ItemCart = class { frappe.utils.play_sound("error"); return; } - + if (flt(this.numpad_value) > 100 && this.prev_action === 'discount_percentage') { frappe.show_alert({ message: __('Discount cannot be greater than 100%'), @@ -774,38 +756,38 @@ erpnext.PointOfSale.ItemCart = class { this.highlight_numpad_btn($btn, current_action); this.events.numpad_event(this.numpad_value, this.prev_action); } - + highlight_numpad_btn($btn, curr_action) { - const curr_action_is_highlighted = $btn.hasClass('shadow-inner'); + const curr_action_is_highlighted = $btn.hasClass('highlighted-numpad-btn'); const curr_action_is_action = ['qty', 'discount_percentage', 'rate', 'done'].includes(curr_action); if (!curr_action_is_highlighted) { - $btn.addClass('shadow-inner bg-selected'); + $btn.addClass('highlighted-numpad-btn'); } if (this.prev_action === curr_action && curr_action_is_highlighted) { // if Qty is pressed twice - $btn.removeClass('shadow-inner bg-selected'); + $btn.removeClass('highlighted-numpad-btn'); } if (this.prev_action && this.prev_action !== curr_action && curr_action_is_action) { // Order: Qty -> Rate then remove Qty highlight const prev_btn = $(`[data-button-value='${this.prev_action}']`); - prev_btn.removeClass('shadow-inner bg-selected'); + prev_btn.removeClass('highlighted-numpad-btn'); } if (!curr_action_is_action || curr_action === 'done') { // if numbers are clicked setTimeout(() => { - $btn.removeClass('shadow-inner bg-selected'); - }, 100); + $btn.removeClass('highlighted-numpad-btn'); + }, 200); } } toggle_numpad(show) { if (show) { - this.$totals_section.addClass('d-none'); - this.$numpad_section.removeClass('d-none'); + this.$totals_section.css('display', 'none'); + this.$numpad_section.css('display', 'flex'); } else { - this.$totals_section.removeClass('d-none'); - this.$numpad_section.addClass('d-none'); + this.$totals_section.css('display', 'flex'); + this.$numpad_section.css('display', 'none'); } this.reset_numpad(); } @@ -813,7 +795,7 @@ erpnext.PointOfSale.ItemCart = class { reset_numpad() { this.numpad_value = ''; this.prev_action = undefined; - this.$numpad_section.find('.shadow-inner').removeClass('shadow-inner bg-selected'); + this.$numpad_section.find('.highlighted-numpad-btn').removeClass('highlighted-numpad-btn'); } toggle_numpad_field_edit(fieldname) { @@ -824,48 +806,56 @@ erpnext.PointOfSale.ItemCart = class { toggle_customer_info(show) { if (show) { - this.$cart_container.addClass('d-none') - this.$customer_section.addClass('flex-1 scroll-y').removeClass('mb-0 border pr-4 pl-4') - this.$customer_section.find('.icon').addClass('w-24 h-24 text-2xl').removeClass('w-12 h-12 text-md') - this.$customer_section.find('.customer-header').removeClass('h-18'); - this.$customer_section.find('.customer-details').addClass('sticky z-100 bg-white'); + const { customer } = this.customer_info || {}; - this.$customer_section.find('.customer-name').html( - `
    ${this.customer_info.customer}
    -
    ` - ) - - this.$customer_section.find('.customer-details').append( - `
    -
    CONTACT DETAILS
    -
    - -
    -
    -
    + this.$cart_container.css('display', 'none'); + this.$customer_section.css({ + 'height': '100%', + 'padding-top': '0px' + }); + this.$customer_section.find('.customer-details').html( + `
    +
    Contact Details
    +
    + + +
    -
    RECENT TRANSACTIONS
    -
    ` - ) +
    +
    + ${this.get_customer_image()} +
    +
    ${customer}
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    Recent Transactions
    ` + ); // transactions need to be in diff div from sticky elem for scrolling - this.$customer_section.append(`
    `) + this.$customer_section.append(`
    `); - this.render_customer_info_form(); + this.render_customer_fields(); this.fetch_customer_transactions(); } else { - this.$cart_container.removeClass('d-none'); - this.$customer_section.removeClass('flex-1 scroll-y').addClass('mb-0 border pr-4 pl-4'); - this.$customer_section.find('.icon').addClass('w-12 h-12 text-md').removeClass('w-24 h-24 text-2xl'); - this.$customer_section.find('.customer-header').addClass('h-18') - this.$customer_section.find('.customer-details').removeClass('sticky z-100 bg-white'); + this.$cart_container.css('display', 'flex'); + this.$customer_section.css({ + 'height': '', + 'padding-top': '' + }); this.update_customer_section(); } } - render_customer_info_form() { - const $customer_form = this.$customer_section.find('.customer-form'); + render_customer_fields() { + const $customer_form = this.$customer_section.find('.customer-fields-container'); const dfs = [{ fieldname: 'email_id', @@ -887,7 +877,7 @@ erpnext.PointOfSale.ItemCart = class { },{ fieldname: 'loyalty_points', label: __('Loyalty Points'), - fieldtype: 'Int', + fieldtype: 'Data', read_only: 1 }]; @@ -931,7 +921,7 @@ erpnext.PointOfSale.ItemCart = class { } fetch_customer_transactions() { - frappe.db.get_list('POS Invoice', { + frappe.db.get_list('POS Invoice', { filters: { customer: this.customer_info.customer, docstatus: 1 }, fields: ['name', 'grand_total', 'status', 'posting_date', 'posting_time', 'currency'], limit: 20 @@ -939,41 +929,45 @@ erpnext.PointOfSale.ItemCart = class { const transaction_container = this.$customer_section.find('.customer-transactions'); if (!res.length) { - transaction_container.removeClass('flex-1 border rounded').html( - `
    No recent transactions found
    ` + transaction_container.html( + `
    No recent transactions found
    ` ) return; }; const elapsed_time = moment(res[0].posting_date+" "+res[0].posting_time).fromNow(); - this.$customer_section.find('.last-transacted-on').html(`Last transacted ${elapsed_time}`); + this.$customer_section.find('.customer-desc').html(`Last transacted ${elapsed_time}`); res.forEach(invoice => { const posting_datetime = moment(invoice.posting_date+" "+invoice.posting_time).format("Do MMMM, h:mma"); - let indicator_color = ''; - - if (in_list(['Paid', 'Consolidated'], invoice.status)) (indicator_color = 'green'); - if (invoice.status === 'Draft') (indicator_color = 'red'); - if (invoice.status === 'Return') (indicator_color = 'grey'); + let indicator_color = { + 'Paid': 'green', + 'Draft': 'red', + 'Return': 'gray', + 'Consolidated': 'blue' + }; transaction_container.append( - `
    -
    -
    ${invoice.name}
    -
    - ${posting_datetime} -
    + `
    +
    +
    ${invoice.name}
    +
    ${posting_datetime}
    -
    -
    +
    +
    ${format_currency(invoice.grand_total, invoice.currency, 0) || 0}
    -
    ${invoice.status}
    +
    + + ${invoice.status} + +
    -
    ` +
    +
    ` ) }); - }) + }); } load_invoice() { @@ -981,8 +975,8 @@ erpnext.PointOfSale.ItemCart = class { this.fetch_customer_details(frm.doc.customer).then(() => { this.events.customer_details_updated(this.customer_info); this.update_customer_section(); - }) - + }); + this.$cart_items_wrapper.html(''); if (frm.doc.items.length) { frm.doc.items.forEach(item => { @@ -996,20 +990,18 @@ erpnext.PointOfSale.ItemCart = class { this.update_totals_section(frm); if(frm.doc.docstatus === 1) { - this.$totals_section.find('.checkout-btn').addClass('d-none'); - this.$totals_section.find('.edit-cart-btn').addClass('d-none'); - this.$totals_section.find('.grand-total').removeClass('border-b-grey'); + this.$totals_section.find('.checkout-btn').css('display', 'none'); + this.$totals_section.find('.edit-cart-btn').css('display', 'none'); } else { - this.$totals_section.find('.checkout-btn').removeClass('d-none'); - this.$totals_section.find('.edit-cart-btn').addClass('d-none'); - this.$totals_section.find('.grand-total').addClass('border-b-grey'); + this.$totals_section.find('.checkout-btn').css('display', 'flex'); + this.$totals_section.find('.edit-cart-btn').css('display', 'none'); } this.toggle_component(true); } toggle_component(show) { - show ? this.$component.removeClass('d-none') : this.$component.addClass('d-none'); + show ? this.$component.css('display', 'flex') : this.$component.css('display', 'none'); } - + } 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 259631d14d2..32a4556766a 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_details.js +++ b/erpnext/selling/page/point_of_sale/pos_item_details.js @@ -18,35 +18,36 @@ erpnext.PointOfSale.ItemDetails = class { prepare_dom() { this.wrapper.append( - `
    ` + `
    ` ) - this.$component = this.wrapper.find('.item-details'); + this.$component = this.wrapper.find('.item-details-container'); } init_child_components() { this.$component.html( - `
    -
    -
    ITEM DETAILS
    -
    Close
    + `
    +
    Item Details
    +
    + + +
    -
    -
    -
    -
    -
    -
    -
    +
    +
    +
    +
    +
    +
    -
    -
    STOCK DETAILS
    -
    -
    ` +
    +
    +
    +
    ` ) this.$item_name = this.$component.find('.item-name'); - this.$item_description = this.$component.find('.item-description'); + this.$item_description = this.$component.find('.item-desc'); this.$item_price = this.$component.find('.item-price'); this.$item_image = this.$component.find('.item-image'); this.$form_container = this.$component.find('.form-container'); @@ -54,7 +55,7 @@ erpnext.PointOfSale.ItemDetails = class { } toggle_item_details_section(item) { - const { item_code, batch_no, uom } = this.current_item; + const { item_code, batch_no, uom } = this.current_item; const item_code_is_same = item && item_code === item.item_code; const batch_is_same = item && batch_no == item.batch_no; const uom_is_same = item && uom === item.uom; @@ -63,16 +64,16 @@ erpnext.PointOfSale.ItemDetails = class { this.events.toggle_item_selector(this.item_has_changed); this.toggle_component(this.item_has_changed); - + if (this.item_has_changed) { this.doctype = item.doctype; this.item_meta = frappe.get_meta(this.doctype); this.name = item.name; this.item_row = item; this.currency = this.events.get_frm().doc.currency; - + this.current_item = { item_code: item.item_code, batch_no: item.batch_no, uom: item.uom }; - + this.render_dom(item); this.render_discount_dom(item); this.render_form(item); @@ -81,7 +82,7 @@ erpnext.PointOfSale.ItemDetails = class { this.current_item = {}; } } - + validate_serial_batch_item() { const doc = this.events.get_frm().doc; const item_row = doc.items.find(item => item.name === this.name); @@ -93,7 +94,7 @@ erpnext.PointOfSale.ItemDetails = class { const no_serial_selected = !item_row.serial_no; const no_batch_selected = !item_row.batch_no; - if ((serialized && no_serial_selected) || (batched && no_batch_selected) || + if ((serialized && no_serial_selected) || (batched && no_batch_selected) || (serialized && batched && (no_batch_selected || no_serial_selected))) { frappe.show_alert({ @@ -104,40 +105,34 @@ erpnext.PointOfSale.ItemDetails = class { this.events.remove_item_from_cart(); } } - + render_dom(item) { - let { item_code ,item_name, description, image, price_list_rate } = item; + let { item_name, description, image, price_list_rate } = item; function get_description_html() { if (description) { - description = description.indexOf('...') === -1 && description.length > 75 ? description.substr(0, 73) + '...' : description; + description = description.indexOf('...') === -1 && description.length > 140 ? description.substr(0, 139) + '...' : description; return description; } return ``; } - + this.$item_name.html(item_name); this.$item_description.html(get_description_html()); this.$item_price.html(format_currency(price_list_rate, this.currency)); if (image) { - this.$item_image.html( - `${image}` - ); + this.$item_image.html(`${image}`); } else { - this.$item_image.html(frappe.get_abbr(item_code)); + this.$item_image.html(`
    ${frappe.get_abbr(item_name)}
    `); } } - + render_discount_dom(item) { if (item.discount_percentage) { this.$dicount_section.html( - `
    - ${format_currency(item.price_list_rate, this.currency)} -
    -
    - ${item.discount_percentage}% off -
    ` + `
    ${format_currency(item.price_list_rate, this.currency)}
    +
    ${item.discount_percentage}% off
    ` ) this.$item_price.html(format_currency(item.rate, this.currency)); } else { @@ -151,18 +146,16 @@ erpnext.PointOfSale.ItemDetails = class { fields_to_display.forEach((fieldname, idx) => { this.$form_container.append( - `
    -
    -
    ` + `
    ` ) const field_meta = this.item_meta.fields.find(df => df.fieldname === fieldname); fieldname === 'discount_percentage' ? (field_meta.label = __('Discount (%)')) : ''; const me = this; - + this[`${fieldname}_control`] = frappe.ui.form.make_control({ - df: { - ...field_meta, + df: { + ...field_meta, onchange: function() { me.events.form_updated(me.doctype, me.name, fieldname, this.value); } @@ -187,25 +180,18 @@ erpnext.PointOfSale.ItemDetails = class { make_auto_serial_selection_btn(item) { if (item.has_serial_no) { - this.$form_container.append( - `
    ` - ) if (!item.has_batch_no) { this.$form_container.append( `
    ` - ) + ); } this.$form_container.append( - `
    - Auto Fetch Serial Numbers -
    ` - ) - this.$form_container.find('.serial_no-control').find('textarea').css('height', '9rem'); - this.$form_container.find('.serial_no-control').parent().addClass('row-span-2'); + `
    Auto Fetch Serial Numbers
    ` + ); + this.$form_container.find('.serial_no-control').find('textarea').css('height', '6rem'); } } - + bind_custom_control_change_event() { const me = this; if (this.rate_control) { @@ -215,7 +201,6 @@ erpnext.PointOfSale.ItemDetails = class { me.events.form_updated(me.doctype, me.name, 'rate', this.value).then(() => { const item_row = frappe.get_doc(me.doctype, me.name); const doc = me.events.get_frm().doc; - me.$item_price.html(format_currency(item_row.rate, doc.currency)); me.render_discount_dom(item_row); }); @@ -298,7 +283,7 @@ erpnext.PointOfSale.ItemDetails = class { me.events.set_value_in_current_cart_item('uom', this.value); me.events.form_updated(me.doctype, me.name, 'uom', this.value); me.current_item.uom = this.value; - + const item_row = frappe.get_doc(me.doctype, me.name); me.conversion_factor_control.df.read_only = (item_row.stock_uom == this.value); me.conversion_factor_control.refresh(); @@ -306,28 +291,26 @@ erpnext.PointOfSale.ItemDetails = class { } frappe.model.on("POS Invoice Item", "*", (fieldname, value, item_row) => { - const { item_code, batch_no, uom } = this.current_item; + const field_control = this[`${fieldname}_control`]; + const { item_code, batch_no, uom } = this.current_item; const item_code_is_same = item_code === item_row.item_code; const batch_is_same = batch_no == item_row.batch_no; const uom_is_same = uom === item_row.uom; - // check if current_item is same as item_row const item_is_same = item_code_is_same && batch_is_same && uom_is_same ? true : false; - const field_control = me[`${fieldname}_control`]; - if (item_is_same && field_control && field_control.get_value() !== value) { field_control.set_value(value); cur_pos.update_cart_html(item_row); } }); } - + async auto_update_batch_no() { if (this.serial_no_control && this.batch_no_control) { const selected_serial_nos = this.serial_no_control.get_value().split(`\n`).filter(s => s); if (!selected_serial_nos.length) return; - // find batch nos of the selected serial no + // find batch nos of the selected serial no const serials_with_batch_no = await frappe.db.get_list("Serial No", { filters: { 'name': ["in", selected_serial_nos]}, fields: ["batch_no", "name"] @@ -342,7 +325,7 @@ erpnext.PointOfSale.ItemDetails = class { const batch_serial_nos = batch_serial_map[batch_no].join(`\n`); // eg. 10 selected serial no. -> 5 belongs to first batch other 5 belongs to second batch const serial_nos_belongs_to_other_batch = selected_serial_nos.length !== batch_serial_map[batch_no].length; - + const current_batch_no = this.batch_no_control.get_value(); current_batch_no != batch_no && await this.batch_no_control.set_value(batch_no); @@ -357,7 +340,7 @@ erpnext.PointOfSale.ItemDetails = class { this.events.clone_new_batch_item_in_frm(batch_serial_map, this.current_item); } } - + bind_events() { this.bind_auto_serial_fetch_event(); this.bind_fields_to_numpad_fields(); @@ -387,7 +370,7 @@ erpnext.PointOfSale.ItemDetails = class { } }); } - + bind_auto_serial_fetch_event() { this.$form_container.on('click', '.auto-fetch-btn', () => { this.batch_no_control && this.batch_no_control.set_value(''); @@ -429,6 +412,6 @@ erpnext.PointOfSale.ItemDetails = class { } toggle_component(show) { - show ? this.$component.removeClass('d-none') : this.$component.addClass('d-none'); + show ? this.$component.css('display', 'flex') : this.$component.css('display', 'none'); } } \ No newline at end of file diff --git a/erpnext/selling/page/point_of_sale/pos_item_selector.js b/erpnext/selling/page/point_of_sale/pos_item_selector.js index a06b3942f73..e0d5b731665 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_selector.js +++ b/erpnext/selling/page/point_of_sale/pos_item_selector.js @@ -1,14 +1,17 @@ +import onScan from 'onscan.js'; + erpnext.PointOfSale.ItemSelector = class { + // eslint-disable-next-line no-unused-vars constructor({ frm, wrapper, events, pos_profile, settings }) { this.wrapper = wrapper; this.events = events; this.pos_profile = pos_profile; this.hide_images = settings.hide_images; this.auto_add_item = settings.auto_add_item_to_cart; - + this.inti_component(); } - + inti_component() { this.prepare_dom(); this.make_search_bar(); @@ -19,21 +22,16 @@ erpnext.PointOfSale.ItemSelector = class { prepare_dom() { this.wrapper.append( - `
    -
    -
    -
    -
    -
    -
    -
    ALL ITEMS
    -
    -
    -
    + `
    +
    +
    All Items
    +
    +
    +
    ` ); - + this.$component = this.wrapper.find('.items-selector'); this.$items_container = this.$component.find('.items-container'); } @@ -42,7 +40,7 @@ erpnext.PointOfSale.ItemSelector = class { if (!this.item_group) { const res = await frappe.db.get_value("Item Group", {lft: 1, is_group: 1}, "name"); this.parent_item_group = res.message.name; - }; + } if (!this.price_list) { const res = await frappe.db.get_value("POS Profile", this.pos_profile, "selling_price_list"); this.price_list = res.message.selling_price_list; @@ -54,11 +52,12 @@ erpnext.PointOfSale.ItemSelector = class { } get_items({start = 0, page_length = 40, search_value=''}) { - const price_list = this.events.get_frm().doc?.selling_price_list || this.price_list; + const doc = this.events.get_frm().doc; + const price_list = (doc && doc.selling_price_list) || this.price_list; let { item_group, pos_profile } = this; !item_group && (item_group = this.parent_item_group); - + return frappe.call({ method: "erpnext.selling.page.point_of_sale.point_of_sale.get_items", freeze: true, @@ -73,11 +72,12 @@ erpnext.PointOfSale.ItemSelector = class { items.forEach(item => { const item_html = this.get_item_html(item); this.$items_container.append(item_html); - }) + }); } get_item_html(item) { const me = this; + // eslint-disable-next-line no-unused-vars const { item_image, serial_no, batch_no, barcode, actual_qty, stock_uom } = item; const indicator_color = actual_qty > 10 ? "green" : actual_qty <= 0 ? "red" : "orange"; @@ -85,32 +85,34 @@ erpnext.PointOfSale.ItemSelector = class { if (!me.hide_images && item_image) { return `
    ${frappe.get_abbr(item.item_name)} -
    ` +
    `; } else { - return `
    - ${frappe.get_abbr(item.item_name)} -
    ` + return `
    ${frappe.get_abbr(item.item_name)}
    `; } } return ( - `
    + ${get_item_image_html()} -
    -
    + +
    +
    ${frappe.ellipsis(item.item_name, 18)}
    -
    ${format_currency(item.price_list_rate, item.currency, 0) || 0}
    +
    ${format_currency(item.price_list_rate, item.currency, 0) || 0}
    ` - ) + ); } make_search_bar() { const me = this; + const doc = me.events.get_frm().doc; this.$component.find('.search-field').html(''); this.$component.find('.item-group-field').html(''); @@ -118,7 +120,7 @@ erpnext.PointOfSale.ItemSelector = class { df: { label: __('Search'), fieldtype: 'Data', - placeholder: __('Search by item code, serial number, batch no or barcode') + placeholder: __('Search by item code, serial number or barcode') }, parent: this.$component.find('.search-field'), render_input: true, @@ -138,9 +140,9 @@ erpnext.PointOfSale.ItemSelector = class { return { query: 'erpnext.selling.page.point_of_sale.point_of_sale.item_group_query', filters: { - pos_profile: me.events.get_frm().doc?.pos_profile + pos_profile: doc ? doc.pos_profile : '' } - } + }; }, }, parent: this.$component.find('.item-group-field'), @@ -150,13 +152,18 @@ erpnext.PointOfSale.ItemSelector = class { this.item_group_field.toggle_label(false); } + set_search_value(value) { + $(this.search_field.$input[0]).val(value).trigger("input"); + } + bind_events() { const me = this; + window.onScan = onScan; onScan.attachTo(document, { onScan: (sScancode) => { if (this.search_field && this.$component.is(':visible')) { this.search_field.set_focus(); - $(this.search_field.$input[0]).val(sScancode).trigger("input"); + this.set_search_value(sScancode); this.barcode_scanned = true; } } @@ -168,14 +175,15 @@ erpnext.PointOfSale.ItemSelector = class { let batch_no = unescape($item.attr('data-batch-no')); let serial_no = unescape($item.attr('data-serial-no')); let uom = unescape($item.attr('data-uom')); - + // escape(undefined) returns "undefined" then unescape returns "undefined" batch_no = batch_no === "undefined" ? undefined : batch_no; serial_no = serial_no === "undefined" ? undefined : serial_no; uom = uom === "undefined" ? undefined : uom; me.events.item_selected({ field: 'qty', value: "+1", item: { item_code, batch_no, serial_no, uom }}); - }) + me.set_search_value(''); + }); this.search_field.$input.on('input', (e) => { clearTimeout(this.last_search); @@ -228,7 +236,7 @@ erpnext.PointOfSale.ItemSelector = class { } }); } - + filter_items({ search_term='' }={}) { if (search_term) { search_term = search_term.toLowerCase(); @@ -246,6 +254,7 @@ erpnext.PointOfSale.ItemSelector = class { this.get_items({ search_value: search_term }) .then(({ message }) => { + // eslint-disable-next-line no-unused-vars const { items, serial_no, batch_no, barcode } = message; if (search_term && !barcode) { this.search_index[search_term] = items; @@ -259,26 +268,26 @@ erpnext.PointOfSale.ItemSelector = class { add_filtered_item_to_cart() { this.$items_container.find(".item-wrapper").click(); } - + resize_selector(minimize) { - minimize ? - this.$component.find('.search-field').removeClass('mr-8') : - this.$component.find('.search-field').addClass('mr-8'); - - minimize ? - this.$component.find('.filter-section').addClass('flex-col') : - this.$component.find('.filter-section').removeClass('flex-col'); + minimize ? + this.$component.find('.filter-section').css('grid-template-columns', 'repeat(1, minmax(0, 1fr))') : + this.$component.find('.filter-section').css('grid-template-columns', 'repeat(12, minmax(0, 1fr))'); minimize ? - this.$component.removeClass('col-span-6').addClass('col-span-2') : - this.$component.removeClass('col-span-2').addClass('col-span-6') + this.$component.find('.search-field').css('margin', 'var(--margin-sm) 0px') : + this.$component.find('.search-field').css('margin', '0px var(--margin-sm)'); minimize ? - this.$items_container.removeClass('grid-cols-4').addClass('grid-cols-1') : - this.$items_container.removeClass('grid-cols-1').addClass('grid-cols-4') + this.$component.css('grid-column', 'span 2 / span 2') : + this.$component.css('grid-column', 'span 6 / span 6'); + + minimize ? + this.$items_container.css('grid-template-columns', 'repeat(1, minmax(0, 1fr))') : + this.$items_container.css('grid-template-columns', 'repeat(4, minmax(0, 1fr))'); } toggle_component(show) { - show ? this.$component.removeClass('d-none') : this.$component.addClass('d-none'); + show ? this.$component.css('display', 'flex') : this.$component.css('display', 'none'); } -} \ No newline at end of file +}; \ No newline at end of file diff --git a/erpnext/selling/page/point_of_sale/pos_number_pad.js b/erpnext/selling/page/point_of_sale/pos_number_pad.js index 4b8e8418055..962bcaf0963 100644 --- a/erpnext/selling/page/point_of_sale/pos_number_pad.js +++ b/erpnext/selling/page/point_of_sale/pos_number_pad.js @@ -22,17 +22,16 @@ erpnext.PointOfSale.NumberPad = class { return keys.reduce((a, row, i) => { return a + row.reduce((a2, number, j) => { const class_to_append = css_classes && css_classes[i] ? css_classes[i][j] : ''; - const fieldname = fieldnames && fieldnames[number] ? + const fieldname = fieldnames && fieldnames[number] ? fieldnames[number] : typeof number === 'string' ? frappe.scrub(number) : number; - return a2 + `
    ${number}
    ` - }, '') + return a2 + `
    ${number}
    `; + }, ''); }, ''); } this.wrapper.html( - `
    + `
    ${get_keys()}
    ` ) diff --git a/erpnext/selling/page/point_of_sale/pos_past_order_list.js b/erpnext/selling/page/point_of_sale/pos_past_order_list.js index b2562479240..ec392313f5e 100644 --- a/erpnext/selling/page/point_of_sale/pos_past_order_list.js +++ b/erpnext/selling/page/point_of_sale/pos_past_order_list.js @@ -14,17 +14,13 @@ erpnext.PointOfSale.PastOrderList = class { prepare_dom() { this.wrapper.append( - `
    -
    -
    -
    -
    -
    -
    -
    RECENT ORDERS
    -
    -
    + `
    +
    +
    Recent Orders
    +
    +
    +
    ` ); @@ -66,7 +62,7 @@ erpnext.PointOfSale.PastOrderList = class { options: `Draft\nPaid\nConsolidated\nReturn`, placeholder: __('Filter by invoice status'), onchange: function() { - me.refresh_list(me.search_field.get_value(), this.value); + if (me.$component.is(':visible')) me.refresh_list(); } }, parent: this.$component.find('.status-field'), @@ -77,10 +73,6 @@ erpnext.PointOfSale.PastOrderList = class { this.status_field.set_value('Draft'); } - toggle_component(show) { - show ? this.$component.removeClass('d-none') && this.refresh_list() : this.$component.addClass('d-none'); - } - refresh_list() { frappe.dom.freeze(); this.events.reset_summary(); @@ -106,23 +98,26 @@ erpnext.PointOfSale.PastOrderList = class { get_invoice_html(invoice) { const posting_datetime = moment(invoice.posting_date+" "+invoice.posting_time).format("Do MMMM, h:mma"); return ( - `
    -
    -
    ${invoice.name}
    -
    -
    - - - - ${invoice.customer} -
    + `
    +
    +
    ${invoice.name}
    +
    + + + + ${invoice.customer}
    -
    -
    ${format_currency(invoice.grand_total, invoice.currency, 0) || 0}
    -
    ${posting_datetime}
    +
    +
    ${format_currency(invoice.grand_total, invoice.currency, 0) || 0}
    +
    ${posting_datetime}
    -
    ` +
    +
    ` ); } + + toggle_component(show) { + show ? this.$component.css('display', 'flex') && this.refresh_list() : this.$component.css('display', 'none'); + } }; \ No newline at end of file diff --git a/erpnext/selling/page/point_of_sale/pos_past_order_summary.js b/erpnext/selling/page/point_of_sale/pos_past_order_summary.js index 598f50f1921..39f54fa1f83 100644 --- a/erpnext/selling/page/point_of_sale/pos_past_order_summary.js +++ b/erpnext/selling/page/point_of_sale/pos_past_order_summary.js @@ -8,85 +8,39 @@ erpnext.PointOfSale.PastOrderSummary = class { init_component() { this.prepare_dom(); - this.init_child_components(); + this.init_email_print_dialog(); this.bind_events(); this.attach_shortcuts(); } prepare_dom() { this.wrapper.append( - `
    -
    -
    -
    Select an invoice to load summary data
    -
    + `
    +
    + Select an invoice to load summary data
    -
    -
    +
    +
    +
    +
    Items
    +
    +
    Totals
    +
    +
    Payments
    +
    +
    +
    ` ); this.$component = this.wrapper.find('.past-order-summary'); - this.$summary_wrapper = this.$component.find('.summary-wrapper'); - this.$summary_container = this.$component.find('.summary-container'); - } - - init_child_components() { - this.init_upper_section(); - this.init_items_summary(); - this.init_totals_summary(); - this.init_payments_summary(); - this.init_summary_buttons(); - this.init_email_print_dialog(); - } - - init_upper_section() { - this.$summary_container.append( - `
    ` - ); - + this.$summary_wrapper = this.$component.find('.invoice-summary-wrapper'); + this.$summary_container = this.$component.find('.abs-container'); this.$upper_section = this.$summary_container.find('.upper-section'); - } - - init_items_summary() { - this.$summary_container.append( - `
    -
    ITEMS
    -
    -
    ` - ); - - this.$items_summary_container = this.$summary_container.find('.items-summary-container'); - } - - init_totals_summary() { - this.$summary_container.append( - `
    -
    TOTALS
    -
    -
    ` - ); - - this.$totals_summary_container = this.$summary_container.find('.summary-totals-container'); - } - - init_payments_summary() { - this.$summary_container.append( - `
    -
    PAYMENTS
    -
    -
    ` - ); - - this.$payment_summary_container = this.$summary_container.find('.payments-summary-container'); - } - - init_summary_buttons() { - this.$summary_container.append( - `
    ` - ); - + this.$items_container = this.$summary_container.find('.items-container'); + this.$totals_container = this.$summary_container.find('.totals-container'); + this.$payment_container = this.$summary_container.find('.payments-container'); this.$summary_btns = this.$summary_container.find('.summary-btns'); } @@ -94,7 +48,7 @@ erpnext.PointOfSale.PastOrderSummary = class { const email_dialog = new frappe.ui.Dialog({ title: 'Email Receipt', fields: [ - {fieldname:'email_id', fieldtype:'Data', options: 'Email', label:'Email ID'}, + {fieldname: 'email_id', fieldtype: 'Data', options: 'Email', label: 'Email ID'}, // {fieldname:'remarks', fieldtype:'Text', label:'Remarks (if any)'} ], primary_action: () => { @@ -107,7 +61,7 @@ erpnext.PointOfSale.PastOrderSummary = class { const print_dialog = new frappe.ui.Dialog({ title: 'Print Receipt', fields: [ - {fieldname:'print', fieldtype:'Data', label:'Print Preview'} + {fieldname: 'print', fieldtype: 'Data', label: 'Print Preview'} ], primary_action: () => { const frm = this.events.get_frm(); @@ -121,132 +75,87 @@ erpnext.PointOfSale.PastOrderSummary = class { } get_upper_section_html(doc) { - const { status } = doc; let indicator_color = ''; + const { status } = doc; + let indicator_color = ''; in_list(['Paid', 'Consolidated'], status) && (indicator_color = 'green'); status === 'Draft' && (indicator_color = 'red'); status === 'Return' && (indicator_color = 'grey'); - return `
    -
    ${doc.customer}
    -
    ${this.customer_email}
    -
    Sold by: ${doc.owner}
    + return `
    +
    ${doc.customer}
    +
    ${this.customer_email}
    +
    Sold by: ${doc.owner}
    -
    -
    ${format_currency(doc.paid_amount, doc.currency)}
    -
    -
    ${doc.name}
    -
    ${doc.status}
    -
    +
    + +
    ${doc.name}
    + ${doc.status}
    `; } + get_item_html(doc, item_data) { + return `
    +
    ${item_data.item_name}
    +
    ${item_data.qty || 0}
    +
    ${get_rate_discount_html()}
    +
    `; + + function get_rate_discount_html() { + if (item_data.rate && item_data.price_list_rate && item_data.rate !== item_data.price_list_rate) { + return `(${item_data.discount_percentage}% off) +
    ${format_currency(item_data.rate, doc.currency)}
    `; + } else { + return `
    ${format_currency(item_data.price_list_rate || item_data.rate, doc.currency)}
    `; + } + } + } + get_discount_html(doc) { if (doc.discount_amount) { - return `
    -
    -
    - Discount -
    - (${doc.additional_discount_percentage} %) -
    -
    -
    ${format_currency(doc.discount_amount, doc.currency)}
    -
    -
    `; + return `
    +
    Discount (${doc.additional_discount_percentage} %)
    +
    ${format_currency(doc.discount_amount, doc.currency)}
    +
    `; } else { return ``; } } get_net_total_html(doc) { - return `
    -
    -
    - Net Total -
    -
    -
    -
    ${format_currency(doc.net_total, doc.currency)}
    -
    + return `
    +
    Net Total
    +
    ${format_currency(doc.net_total, doc.currency)}
    `; } get_taxes_html(doc) { - const taxes = doc.taxes.map((t, i) => { - let margin_left = ''; - if (i !== 0) margin_left = 'ml-2'; - return `${t.description} @${t.rate}%`; + if (!doc.taxes.length) return ''; + + let taxes_html = doc.taxes.map(t => { + const description = /[0-9]+/.test(t.description) ? t.description : `${t.description} @ ${t.rate}%`; + return ` +
    +
    ${description}
    +
    ${format_currency(t.tax_amount_after_discount_amount, doc.currency)}
    +
    + `; }).join(''); - return ` -
    -
    -
    Tax Charges
    -
    ${taxes}
    -
    -
    -
    - ${format_currency(doc.base_total_taxes_and_charges, doc.currency)} -
    -
    -
    `; + return `
    ${taxes_html}
    `; } get_grand_total_html(doc) { - return `
    -
    -
    - Grand Total -
    -
    -
    -
    ${format_currency(doc.grand_total, doc.currency)}
    -
    + return `
    +
    Grand Total
    +
    ${format_currency(doc.grand_total, doc.currency)}
    `; } - get_item_html(doc, item_data) { - return `
    -
    - ${item_data.qty || 0} -
    -
    -
    - ${item_data.item_name} -
    -
    -
    - ${get_rate_discount_html()} -
    -
    `; - - function get_rate_discount_html() { - if (item_data.rate && item_data.price_list_rate && item_data.rate !== item_data.price_list_rate) { - return ` - (${item_data.discount_percentage}% off) - -
    - ${format_currency(item_data.rate, doc.currency)} -
    `; - } else { - return `
    - ${format_currency(item_data.price_list_rate || item_data.rate, doc.currency)} -
    `; - } - } - } - get_payment_html(doc, payment) { - return `
    -
    -
    - ${payment.mode_of_payment} -
    -
    -
    -
    ${format_currency(payment.amount, doc.currency)}
    -
    + return `
    +
    ${payment.mode_of_payment}
    +
    ${format_currency(payment.amount, doc.currency)}
    `; } @@ -254,15 +163,20 @@ erpnext.PointOfSale.PastOrderSummary = class { this.$summary_container.on('click', '.return-btn', () => { this.events.process_return(this.doc.name); this.toggle_component(false); - this.$component.find('.no-summary-placeholder').removeClass('d-none'); - this.$summary_wrapper.addClass('d-none'); + this.$component.find('.no-summary-placeholder').css('display', 'flex'); + this.$summary_wrapper.css('display', 'none'); }); this.$summary_container.on('click', '.edit-btn', () => { this.events.edit_order(this.doc.name); this.toggle_component(false); - this.$component.find('.no-summary-placeholder').removeClass('d-none'); - this.$summary_wrapper.addClass('d-none'); + this.$component.find('.no-summary-placeholder').css('display', 'flex'); + this.$summary_wrapper.css('display', 'none'); + }); + + this.$summary_container.on('click', '.delete-btn', () => { + this.events.delete_order(this.doc.name); + this.show_summary_placeholder(); }); this.$summary_container.on('click', '.delete-btn', () => { @@ -276,8 +190,8 @@ erpnext.PointOfSale.PastOrderSummary = class { this.$summary_container.on('click', '.new-btn', () => { this.events.new_order(); this.toggle_component(false); - this.$component.find('.no-summary-placeholder').removeClass('d-none'); - this.$summary_wrapper.addClass('d-none'); + this.$component.find('.no-summary-placeholder').css('display', 'flex'); + this.$summary_wrapper.css('display', 'none'); }); this.$summary_container.on('click', '.email-btn', () => { @@ -320,10 +234,6 @@ erpnext.PointOfSale.PastOrderSummary = class { }); } - toggle_component(show) { - show ? this.$component.removeClass('d-none') : this.$component.addClass('d-none'); - } - send_email() { const frm = this.events.get_frm(); const recipients = this.email_dialog.get_values().recipients; @@ -331,7 +241,7 @@ erpnext.PointOfSale.PastOrderSummary = class { const print_format = frm.pos_print_format; frappe.call({ - method:"frappe.core.doctype.communication.email.make", + method: "frappe.core.doctype.communication.email.make", args: { recipients: recipients, subject: __(frm.meta.name) + ': ' + doc.name, @@ -340,14 +250,16 @@ erpnext.PointOfSale.PastOrderSummary = class { send_email: 1, print_format, sender_full_name: frappe.user.full_name(), - _lang : doc.language + _lang: doc.language }, callback: r => { - if(!r.exc) { + if (!r.exc) { frappe.utils.play_sound("email"); - if(r.message["emails_not_sent_to"]) { - frappe.msgprint(__("Email not sent to {0} (unsubscribed / disabled)", - [ frappe.utils.escape_html(r.message["emails_not_sent_to"]) ]) ); + if (r.message["emails_not_sent_to"]) { + frappe.msgprint(__( + "Email not sent to {0} (unsubscribed / disabled)", + [ frappe.utils.escape_html(r.message["emails_not_sent_to"]) ] + )); } else { frappe.show_alert({ message: __('Email sent successfully.'), @@ -369,9 +281,7 @@ erpnext.PointOfSale.PastOrderSummary = class { m.visible_btns.forEach(b => { const class_name = b.split(' ')[0].toLowerCase(); this.$summary_btns.append( - `
    - ${b} -
    ` + `
    ${b}
    ` ); }); } @@ -379,29 +289,14 @@ erpnext.PointOfSale.PastOrderSummary = class { this.$summary_btns.children().last().removeClass('mr-4'); } - show_summary_placeholder() { - this.$summary_wrapper.addClass("d-none"); - this.$component.find('.no-summary-placeholder').removeClass('d-none'); - } - - switch_to_post_submit_summary() { - // switch to full width view - this.$component.removeClass('col-span-6').addClass('col-span-10'); - this.$summary_wrapper.removeClass('w-66').addClass('w-40'); - - // switch place holder with summary container - this.$component.find('.no-summary-placeholder').addClass('d-none'); - this.$summary_wrapper.removeClass('d-none'); - } - - switch_to_recent_invoice_summary() { - // switch full width view with 60% view - this.$component.removeClass('col-span-10').addClass('col-span-6'); - this.$summary_wrapper.removeClass('w-40').addClass('w-66'); - - // switch place holder with summary container - this.$component.find('.no-summary-placeholder').addClass('d-none'); - this.$summary_wrapper.removeClass('d-none'); + toggle_summary_placeholder(show) { + if (show) { + this.$summary_wrapper.css('display', 'none'); + this.$component.find('.no-summary-placeholder').css('display', 'flex'); + } else { + this.$summary_wrapper.css('display', 'flex'); + this.$component.find('.no-summary-placeholder').css('display', 'none'); + } } get_condition_btn_map(after_submission) { @@ -416,14 +311,15 @@ erpnext.PointOfSale.PastOrderSummary = class { } load_summary_of(doc, after_submission=false) { - this.$summary_wrapper.removeClass("d-none"); - after_submission ? - this.switch_to_post_submit_summary() : this.switch_to_recent_invoice_summary(); + this.$component.css('grid-column', 'span 10 / span 10') : + this.$component.css('grid-column', 'span 6 / span 6'); + + this.toggle_summary_placeholder(false); this.doc = doc; - this.attach_basic_info(doc); + this.attach_document_info(doc); this.attach_items_info(doc); @@ -436,7 +332,7 @@ erpnext.PointOfSale.PastOrderSummary = class { this.add_summary_btns(condition_btns_map); } - attach_basic_info(doc) { + attach_document_info(doc) { frappe.db.get_value('Customer', this.doc.customer, 'email_id').then(({ message }) => { this.customer_email = message.email_id || ''; const upper_section_dom = this.get_upper_section_html(doc); @@ -445,19 +341,35 @@ erpnext.PointOfSale.PastOrderSummary = class { } attach_items_info(doc) { - this.$items_summary_container.html(''); + this.$items_container.html(''); doc.items.forEach(item => { const item_dom = this.get_item_html(doc, item); - this.$items_summary_container.append(item_dom); + this.$items_container.append(item_dom); + this.set_dynamic_rate_header_width(); }); } + set_dynamic_rate_header_width() { + const rate_cols = Array.from(this.$items_container.find(".item-rate-disc")); + this.$items_container.find(".item-rate-disc").css("width", ""); + let max_width = rate_cols.reduce((max_width, elm) => { + if ($(elm).width() > max_width) + max_width = $(elm).width(); + return max_width; + }, 0); + + max_width += 1; + if (max_width == 1) max_width = ""; + + this.$items_container.find(".item-rate-disc").css("width", max_width); + } + attach_payments_info(doc) { - this.$payment_summary_container.html(''); + this.$payment_container.html(''); doc.payments.forEach(p => { if (p.amount) { const payment_dom = this.get_payment_html(doc, p); - this.$payment_summary_container.append(payment_dom); + this.$payment_container.append(payment_dom); } }); if (doc.redeem_loyalty_points && doc.loyalty_amount) { @@ -465,20 +377,24 @@ erpnext.PointOfSale.PastOrderSummary = class { mode_of_payment: 'Loyalty Points', amount: doc.loyalty_amount, }); - this.$payment_summary_container.append(payment_dom); + this.$payment_container.append(payment_dom); } } attach_totals_info(doc) { - this.$totals_summary_container.html(''); + this.$totals_container.html(''); - const discount_dom = this.get_discount_html(doc); const net_total_dom = this.get_net_total_html(doc); const taxes_dom = this.get_taxes_html(doc); + const discount_dom = this.get_discount_html(doc); const grand_total_dom = this.get_grand_total_html(doc); - this.$totals_summary_container.append(discount_dom); - this.$totals_summary_container.append(net_total_dom); - this.$totals_summary_container.append(taxes_dom); - this.$totals_summary_container.append(grand_total_dom); + this.$totals_container.append(net_total_dom); + this.$totals_container.append(taxes_dom); + this.$totals_container.append(discount_dom); + this.$totals_container.append(grand_total_dom); + } + + toggle_component(show) { + show ? this.$component.css('display', 'flex') : this.$component.css('display', 'none'); } }; \ No newline at end of file diff --git a/erpnext/selling/page/point_of_sale/pos_payment.js b/erpnext/selling/page/point_of_sale/pos_payment.js index e150271fd0b..600f1604900 100644 --- a/erpnext/selling/page/point_of_sale/pos_payment.js +++ b/erpnext/selling/page/point_of_sale/pos_payment.js @@ -1,5 +1,4 @@ -{% include "erpnext/selling/page/point_of_sale/pos_number_pad.js" %} - +/* eslint-disable no-unused-vars */ erpnext.PointOfSale.Payment = class { constructor({ events, wrapper }) { this.wrapper = wrapper; @@ -13,43 +12,33 @@ erpnext.PointOfSale.Payment = class { this.initialize_numpad(); this.bind_events(); this.attach_shortcuts(); - + } prepare_dom() { this.wrapper.append( - `
    -
    -
    - PAYMENT METHOD -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - Complete Order -
    -
    -
    + `
    + +
    +
    +
    + +
    +
    +
    +
    +
    +
    Complete Order
    ` - ) - this.$component = this.wrapper.find('.payment-section'); + ); + this.$component = this.wrapper.find('.payment-container'); this.$payment_modes = this.$component.find('.payment-modes'); - this.$totals_remarks = this.$component.find('.totals-remarks'); + this.$totals_section = this.$component.find('.totals-section'); this.$totals = this.$component.find('.totals'); - this.$remarks = this.$component.find('.remarks'); this.$numpad = this.$component.find('.number-pad'); - this.$invoice_details_section = this.$component.find('.invoice-details-section'); + this.$invoice_fields_section = this.$component.find('.fields-section'); } make_invoice_fields_control() { @@ -57,13 +46,8 @@ erpnext.PointOfSale.Payment = class { const fields = doc.invoice_fields; if (!fields.length) return; - this.$invoice_details_section.html( - `
    - ADDITIONAL INFORMATION -
    -
    ` - ); - this.$invoice_fields = this.$invoice_details_section.find('.invoice-fields'); + this.$invoice_fields = this.$invoice_fields_section.find('.invoice-fields'); + this.$invoice_fields.html(''); const frm = this.events.get_frm(); fields.forEach(df => { @@ -71,8 +55,10 @@ erpnext.PointOfSale.Payment = class { `
    ` ); let df_events = { - onchange: function() { frm.set_value(this.df.fieldname, this.value); } - } + onchange: function() { + frm.set_value(this.df.fieldname, this.value); + } + }; if (df.fieldtype == "Button") { df_events = { click: function() { @@ -80,11 +66,11 @@ erpnext.PointOfSale.Payment = class { frm.script_manager.trigger(df.fieldname, frm.doc.doctype, frm.doc.docname); } } - } + }; } this[`${df.fieldname}_field`] = frappe.ui.form.make_control({ - df: { + df: { ...df, ...df_events }, @@ -92,7 +78,7 @@ erpnext.PointOfSale.Payment = class { render_input: true, }); this[`${df.fieldname}_field`].set_value(frm.doc[df.fieldname]); - }) + }); }); } @@ -112,13 +98,12 @@ erpnext.PointOfSale.Payment = class { [ 7, 8, 9 ], [ '.', 0, 'Delete' ] ], - }) + }); this.numpad_value = ''; } on_numpad_clicked($btn) { - const me = this; const button_value = $btn.attr('data-button-value'); highlight_numpad_btn($btn); @@ -127,9 +112,9 @@ erpnext.PointOfSale.Payment = class { this.selected_mode.set_value(this.numpad_value); function highlight_numpad_btn($btn) { - $btn.addClass('shadow-inner bg-selected'); + $btn.addClass('shadow-base-inner bg-selected'); setTimeout(() => { - $btn.removeClass('shadow-inner bg-selected'); + $btn.removeClass('shadow-base-inner bg-selected'); }, 100); } } @@ -142,13 +127,16 @@ erpnext.PointOfSale.Payment = class { // if clicked element doesn't have .mode-of-payment class then return if (!$(e.target).is(mode_clicked)) return; + const scrollLeft = mode_clicked.offset().left - me.$payment_modes.offset().left + me.$payment_modes.scrollLeft(); + me.$payment_modes.animate({ scrollLeft }); + const mode = mode_clicked.attr('data-mode'); // hide all control fields and shortcuts - $(`.mode-of-payment-control`).addClass('d-none'); - $(`.cash-shortcuts`).addClass('d-none'); - me.$payment_modes.find(`.pay-amount`).removeClass('d-none'); - me.$payment_modes.find(`.loyalty-amount-name`).addClass('d-none'); + $(`.mode-of-payment-control`).css('display', 'none'); + $(`.cash-shortcuts`).css('display', 'none'); + me.$payment_modes.find(`.pay-amount`).css('display', 'inline'); + me.$payment_modes.find(`.loyalty-amount-name`).css('display', 'none'); // remove highlight from all mode-of-payments $('.mode-of-payment').removeClass('border-primary'); @@ -157,21 +145,19 @@ erpnext.PointOfSale.Payment = class { // clicked one is selected then unselect it mode_clicked.removeClass('border-primary'); me.selected_mode = ''; - me.toggle_numpad(false); } else { // clicked one is not selected then select it mode_clicked.addClass('border-primary'); - mode_clicked.find('.mode-of-payment-control').removeClass('d-none'); - mode_clicked.find('.cash-shortcuts').removeClass('d-none'); - me.$payment_modes.find(`.${mode}-amount`).addClass('d-none'); - me.$payment_modes.find(`.${mode}-name`).removeClass('d-none'); - me.toggle_numpad(true); + mode_clicked.find('.mode-of-payment-control').css('display', 'flex'); + mode_clicked.find('.cash-shortcuts').css('display', 'grid'); + me.$payment_modes.find(`.${mode}-amount`).css('display', 'none'); + me.$payment_modes.find(`.${mode}-name`).css('display', 'inline'); me.selected_mode = me[`${mode}_control`]; me.selected_mode && me.selected_mode.$input.get(0).focus(); me.auto_set_remaining_amount(); } - }) + }); frappe.ui.form.on('POS Invoice', 'contact_mobile', (frm) => { const contact = frm.doc.contact_mobile; @@ -180,39 +166,39 @@ erpnext.PointOfSale.Payment = class { request_button.removeClass('btn-default').addClass('btn-primary'); } else { request_button.removeClass('btn-primary').addClass('btn-default'); - } - }); + } + }); this.setup_listener_for_payments(); - this.$payment_modes.on('click', '.shortcut', function(e) { + this.$payment_modes.on('click', '.shortcut', () => { const value = $(this).attr('data-value'); me.selected_mode.set_value(value); - }) + }); - this.$component.on('click', '.submit-order', () => { + this.$component.on('click', '.submit-order-btn', () => { const doc = this.events.get_frm().doc; const paid_amount = doc.paid_amount; const items = doc.items; if (paid_amount == 0 || !items.length) { - const message = items.length ? __("You cannot submit the order without payment.") : __("You cannot submit empty order.") + const message = items.length ? __("You cannot submit the order without payment.") : __("You cannot submit empty order."); frappe.show_alert({ message, indicator: "orange" }); frappe.utils.play_sound("error"); return; } this.events.submit_invoice(); - }) + }); frappe.ui.form.on('POS Invoice', 'paid_amount', (frm) => { this.update_totals_section(frm.doc); // need to re calculate cash shortcuts after discount is applied - const is_cash_shortcuts_invisible = this.$payment_modes.find('.cash-shortcuts').hasClass('d-none'); + const is_cash_shortcuts_invisible = !this.$payment_modes.find('.cash-shortcuts').is(':visible'); this.attach_cash_shortcuts(frm.doc); - !is_cash_shortcuts_invisible && this.$payment_modes.find('.cash-shortcuts').removeClass('d-none'); - }) + !is_cash_shortcuts_invisible && this.$payment_modes.find('.cash-shortcuts').css('display', 'grid'); + }); frappe.ui.form.on('POS Invoice', 'loyalty_amount', (frm) => { const formatted_currency = format_currency(frm.doc.loyalty_amount, frm.doc.currency); @@ -227,19 +213,43 @@ erpnext.PointOfSale.Payment = class { this[`${mode}_control`].set_value(default_mop.amount); } }); + } - this.$component.on('click', '.invoice-details-section', function(e) { - if ($(e.target).closest('.invoice-fields').length) return; + setup_listener_for_payments() { + frappe.realtime.on("process_phone_payment", (data) => { + const doc = this.events.get_frm().doc; + const { response, amount, success, failure_message } = data; + let message, title; - me.$payment_modes.addClass('d-none'); - me.$invoice_fields.toggleClass("d-none"); - me.toggle_numpad(false); + if (success) { + title = __("Payment Received"); + const grand_total = cint(frappe.sys_defaults.disable_rounded_total) ? doc.grand_total : doc.rounded_total; + if (amount >= grand_total) { + frappe.dom.unfreeze(); + message = __("Payment of {0} received successfully.", [format_currency(amount, doc.currency, 0)]); + this.events.submit_invoice(); + cur_frm.reload_doc(); + + } else { + message = __("Payment of {0} received successfully. Waiting for other requests to complete...", [format_currency(amount, doc.currency, 0)]); + } + } else if (failure_message) { + message = failure_message; + title = __("Payment Failed"); + } + + frappe.msgprint({ "message": message, "title": title }); }); - this.$component.on('click', '.payment-section', () => { - this.$invoice_fields.addClass("d-none"); - this.$payment_modes.toggleClass('d-none'); - this.toggle_numpad(true); - }) + } + + auto_set_remaining_amount() { + const doc = this.events.get_frm().doc; + const grand_total = cint(frappe.sys_defaults.disable_rounded_total) ? doc.grand_total : doc.rounded_total; + const remaining_amount = grand_total - doc.paid_amount; + const current_value = this.selected_mode ? this.selected_mode.get_value() : undefined; + if (!current_value && remaining_amount > 0 && this.selected_mode) { + this.selected_mode.set_value(remaining_amount); + } } setup_listener_for_payments() { @@ -279,12 +289,12 @@ erpnext.PointOfSale.Payment = class { attach_shortcuts() { const ctrl_label = frappe.utils.is_mac() ? '⌘' : 'Ctrl'; - this.$component.find('.submit-order').attr("title", `${ctrl_label}+Enter`); + this.$component.find('.submit-order-btn').attr("title", `${ctrl_label}+Enter`); frappe.ui.keys.on("ctrl+enter", () => { const payment_is_visible = this.$component.is(":visible"); const active_mode = this.$payment_modes.find(".border-primary"); if (payment_is_visible && active_mode.length) { - this.$component.find('.submit-order').click(); + this.$component.find('.submit-order-btn').click(); } }); @@ -294,14 +304,14 @@ erpnext.PointOfSale.Payment = class { const payment_is_visible = this.$component.is(":visible"); let active_mode = this.$payment_modes.find(".border-primary"); active_mode = active_mode.length ? active_mode.attr("data-mode") : undefined; - + if (!active_mode) return; - + const mode_of_payments = Array.from(this.$payment_modes.find(".mode-of-payment")).map(m => $(m).attr("data-mode")); const mode_index = mode_of_payments.indexOf(active_mode); const next_mode_index = (mode_index + 1) % mode_of_payments.length; const next_mode_to_be_clicked = this.$payment_modes.find(`.mode-of-payment[data-mode="${mode_of_payments[next_mode_index]}"]`); - + if (payment_is_visible && mode_index != next_mode_index) { next_mode_to_be_clicked.click(); } @@ -313,16 +323,8 @@ erpnext.PointOfSale.Payment = class { }); } - toggle_numpad(show) { - if (show) { - this.$numpad.removeClass('d-none'); - this.$remarks.addClass('d-none'); - this.$totals_remarks.addClass('w-60 justify-center').removeClass('justify-end w-full'); - } else { - this.$numpad.addClass('d-none'); - this.$remarks.removeClass('d-none'); - this.$totals_remarks.removeClass('w-60 justify-center').addClass('justify-end w-full'); - } + toggle_numpad() { + // pass } render_payment_section() { @@ -354,7 +356,7 @@ erpnext.PointOfSale.Payment = class { fieldtype: 'Data', onchange: function() {} }, - parent: this.$totals_remarks.find(`.remarks`), + parent: this.$totals_section.find(`.remarks`), render_input: true, }); this[`remark_control`].set_value(''); @@ -366,27 +368,24 @@ erpnext.PointOfSale.Payment = class { const payments = doc.payments; const currency = doc.currency; - this.$payment_modes.html( - `${ - payments.map((p, i) => { + this.$payment_modes.html(`${ + payments.map((p, i) => { const mode = p.mode_of_payment.replace(/ +/g, "_").toLowerCase(); const payment_type = p.type; const margin = i % 2 === 0 ? 'pr-2' : 'pl-2'; const amount = p.amount > 0 ? format_currency(p.amount, currency) : ''; - return ( - `
    -
    + return (` +
    +
    ${p.mode_of_payment} -
    ${amount}
    -
    +
    ${amount}
    +
    -
    ` - ) - }).join('') - }` - ) +
    + `); + }).join('') + }`); payments.forEach(p => { const mode = p.mode_of_payment.replace(/ +/g, "_").toLowerCase(); @@ -419,31 +418,26 @@ erpnext.PointOfSale.Payment = class { 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) { - const grand_total = doc.grand_total; + const grand_total = cint(frappe.sys_defaults.disable_rounded_total) ? doc.grand_total : doc.rounded_total; const currency = doc.currency; const shortcuts = this.get_cash_shortcuts(flt(grand_total)); this.$payment_modes.find('.cash-shortcuts').remove(); - this.$payment_modes.find('[data-payment-type="Cash"]').find('.mode-of-payment-control').after( - `
    - ${ - shortcuts.map(s => { - return `
    - ${format_currency(s, currency, 0)} -
    ` - }).join('') - } -
    ` - ) + let shortcuts_html = shortcuts.map(s => { + return `
    ${format_currency(s, currency, 0)}
    `; + }).join(''); + + this.$payment_modes.find('[data-payment-type="Cash"]').find('.mode-of-payment-control') + .after(`
    ${shortcuts_html}
    `); } get_cash_shortcuts(grand_total) { @@ -455,13 +449,13 @@ erpnext.PointOfSale.Payment = class { const get_nearest = (amount, x) => { let nearest_x = Math.ceil((amount / x)) * x; return nearest_x === amount ? nearest_x + x : nearest_x; - } + }; return steps.reduce((finalArr, x) => { let nearest_x = get_nearest(grand_total, x); nearest_x = finalArr.indexOf(nearest_x) != -1 ? nearest_x + x : nearest_x; return [...finalArr, nearest_x]; - }, []); + }, []); } render_loyalty_points_payment_mode() { @@ -470,7 +464,7 @@ erpnext.PointOfSale.Payment = class { const { loyalty_program, loyalty_points, conversion_factor } = this.events.get_customer_details(); this.$payment_modes.find(`.mode-of-payment[data-mode="loyalty-amount"]`).parent().remove(); - + if (!loyalty_program) return; let description, read_only, max_redeemable_amount; @@ -478,7 +472,7 @@ erpnext.PointOfSale.Payment = class { description = __("You don't have enough points to redeem."); read_only = true; } else { - max_redeemable_amount = flt(flt(loyalty_points) * flt(conversion_factor), precision("loyalty_amount", doc)) + max_redeemable_amount = flt(flt(loyalty_points) * flt(conversion_factor), precision("loyalty_amount", doc)); description = __("You can redeem upto {0}.", [format_currency(max_redeemable_amount)]); read_only = false; } @@ -486,16 +480,15 @@ erpnext.PointOfSale.Payment = class { const margin = this.$payment_modes.children().length % 2 === 0 ? 'pr-2' : 'pl-2'; const amount = doc.loyalty_amount > 0 ? format_currency(doc.loyalty_amount, doc.currency) : ''; this.$payment_modes.append( - `
    -
    + `
    +
    Redeem Loyalty Points -
    ${amount}
    -
    ${loyalty_program}
    -
    +
    ${amount}
    +
    ${loyalty_program}
    +
    ` - ) + ); this['loyalty-amount_control'] = frappe.ui.form.make_control({ df: { @@ -537,30 +530,37 @@ erpnext.PointOfSale.Payment = class { `
    + Add Payment Method
    ` - ) + ); } update_totals_section(doc) { if (!doc) doc = this.events.get_frm().doc; const paid_amount = doc.paid_amount; - const remaining = doc.grand_total - doc.paid_amount; + const grand_total = cint(frappe.sys_defaults.disable_rounded_total) ? doc.grand_total : doc.rounded_total; + const remaining = grand_total - doc.paid_amount; const change = doc.change_amount || remaining <= 0 ? -1 * remaining : undefined; - const currency = doc.currency + const currency = doc.currency; const label = change ? __('Change') : __('To Be Paid'); this.$totals.html( - `
    -
    Paid Amount
    -
    ${format_currency(paid_amount, currency)}
    + `
    +
    Grand Total
    +
    ${format_currency(grand_total, currency)}
    -
    -
    ${label}
    -
    ${format_currency(change || remaining, currency)}
    +
    +
    +
    Paid Amount
    +
    ${format_currency(paid_amount, currency)}
    +
    +
    +
    +
    ${label}
    +
    ${format_currency(change || remaining, currency)}
    ` - ) + ); } toggle_component(show) { - show ? this.$component.removeClass('d-none') : this.$component.addClass('d-none'); + show ? this.$component.css('display', 'flex') : this.$component.css('display', 'none'); } - } \ No newline at end of file +}; \ No newline at end of file diff --git a/erpnext/selling/workspace/retail/retail.json b/erpnext/selling/workspace/retail/retail.json new file mode 100644 index 00000000000..e20f8347c25 --- /dev/null +++ b/erpnext/selling/workspace/retail/retail.json @@ -0,0 +1,114 @@ +{ + "category": "Domains", + "charts": [], + "creation": "2020-03-02 17:18:32.505616", + "developer_mode_only": 0, + "disable_user_customization": 0, + "docstatus": 0, + "doctype": "Workspace", + "extends_another_page": 0, + "hide_custom": 0, + "icon": "retail", + "idx": 0, + "is_standard": 1, + "label": "Retail", + "links": [ + { + "hidden": 0, + "is_query_report": 0, + "label": "Settings & Configurations", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Point-of-Sale Profile", + "link_to": "POS Profile", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "POS Settings", + "link_to": "POS Settings", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Loyalty Program", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Loyalty Program", + "link_to": "Loyalty Program", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Loyalty Point Entry", + "link_to": "Loyalty Point Entry", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Opening & Closing", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "POS Opening Entry", + "link_to": "POS Opening Entry", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "POS Closing Entry", + "link_to": "POS Closing Entry", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + } + ], + "modified": "2020-12-01 13:38:36.758038", + "modified_by": "Administrator", + "module": "Selling", + "name": "Retail", + "owner": "Administrator", + "pin_to_bottom": 0, + "pin_to_top": 0, + "restrict_to_domain": "Retail", + "shortcuts": [ + { + "doc_view": "", + "label": "Point Of Sale", + "link_to": "point-of-sale", + "type": "Page" + } + ] +} \ No newline at end of file diff --git a/erpnext/selling/workspace/selling/selling.json b/erpnext/selling/workspace/selling/selling.json new file mode 100644 index 00000000000..879034a0dfc --- /dev/null +++ b/erpnext/selling/workspace/selling/selling.json @@ -0,0 +1,563 @@ +{ + "category": "Modules", + "charts": [ + { + "chart_name": "Sales Order Trends", + "label": "Sales Order Trends" + } + ], + "charts_label": "Selling ", + "creation": "2020-01-28 11:49:12.092882", + "developer_mode_only": 0, + "disable_user_customization": 0, + "docstatus": 0, + "doctype": "Workspace", + "extends_another_page": 0, + "hide_custom": 1, + "icon": "sell", + "idx": 0, + "is_standard": 1, + "label": "Selling", + "links": [ + { + "hidden": 0, + "is_query_report": 0, + "label": "Selling", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Customer", + "link_to": "Customer", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "Item, Customer", + "hidden": 0, + "is_query_report": 0, + "label": "Quotation", + "link_to": "Quotation", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "Item, Customer", + "hidden": 0, + "is_query_report": 0, + "label": "Sales Order", + "link_to": "Sales Order", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "Item, Customer", + "hidden": 0, + "is_query_report": 0, + "label": "Sales Invoice", + "link_to": "Sales Invoice", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "Item, Customer", + "hidden": 0, + "is_query_report": 0, + "label": "Blanket Order", + "link_to": "Blanket Order", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "Item", + "hidden": 0, + "is_query_report": 0, + "label": "Sales Partner", + "link_to": "Sales Partner", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Item, Customer", + "hidden": 0, + "is_query_report": 0, + "label": "Sales Person", + "link_to": "Sales Person", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Items and Pricing", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Item", + "link_to": "Item", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "Item, Price List", + "hidden": 0, + "is_query_report": 0, + "label": "Item Price", + "link_to": "Item Price", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Price List", + "link_to": "Price List", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Item Group", + "link_to": "Item Group", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "Item", + "hidden": 0, + "is_query_report": 0, + "label": "Product Bundle", + "link_to": "Product Bundle", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Promotional Scheme", + "link_to": "Promotional Scheme", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Item", + "hidden": 0, + "is_query_report": 0, + "label": "Pricing Rule", + "link_to": "Pricing Rule", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Shipping Rule", + "link_to": "Shipping Rule", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Coupon Code", + "link_to": "Coupon Code", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Settings", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Selling Settings", + "link_to": "Selling Settings", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Terms and Conditions Template", + "link_to": "Terms and Conditions", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Sales Taxes and Charges Template", + "link_to": "Sales Taxes and Charges Template", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Lead Source", + "link_to": "Lead Source", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Customer Group", + "link_to": "Customer Group", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Contact", + "link_to": "Contact", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Address", + "link_to": "Address", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Territory", + "link_to": "Territory", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Campaign", + "link_to": "Campaign", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Key Reports", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 1, + "label": "Sales Analytics", + "link_to": "Sales Analytics", + "link_type": "Report", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "Sales Order", + "hidden": 0, + "is_query_report": 1, + "label": "Sales Order Analysis", + "link_to": "Sales Order Analysis", + "link_type": "Report", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Sales Funnel", + "link_to": "sales-funnel", + "link_type": "Page", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "Sales Order", + "hidden": 0, + "is_query_report": 1, + "label": "Sales Order Trends", + "link_to": "Sales Order Trends", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Quotation", + "hidden": 0, + "is_query_report": 1, + "label": "Quotation Trends", + "link_to": "Quotation Trends", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Customer", + "hidden": 0, + "is_query_report": 1, + "label": "Customer Acquisition and Loyalty", + "link_to": "Customer Acquisition and Loyalty", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Sales Order", + "hidden": 0, + "is_query_report": 1, + "label": "Inactive Customers", + "link_to": "Inactive Customers", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Sales Order", + "hidden": 0, + "is_query_report": 1, + "label": "Sales Person-wise Transaction Summary", + "link_to": "Sales Person-wise Transaction Summary", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Item", + "hidden": 0, + "is_query_report": 1, + "label": "Item-wise Sales History", + "link_to": "Item-wise Sales History", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Other Reports", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "Lead", + "hidden": 0, + "is_query_report": 1, + "label": "Lead Details", + "link_to": "Lead Details", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Address", + "hidden": 0, + "is_query_report": 1, + "label": "Customer Addresses And Contacts", + "link_to": "Address And Contacts", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Item", + "hidden": 0, + "is_query_report": 1, + "label": "Available Stock for Packing Items", + "link_to": "Available Stock for Packing Items", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Sales Order", + "hidden": 0, + "is_query_report": 1, + "label": "Pending SO Items For Purchase Request", + "link_to": "Pending SO Items For Purchase Request", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Delivery Note", + "hidden": 0, + "is_query_report": 1, + "label": "Delivery Note Trends", + "link_to": "Delivery Note Trends", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Sales Invoice", + "hidden": 0, + "is_query_report": 1, + "label": "Sales Invoice Trends", + "link_to": "Sales Invoice Trends", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Customer", + "hidden": 0, + "is_query_report": 1, + "label": "Customer Credit Balance", + "link_to": "Customer Credit Balance", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Customer", + "hidden": 0, + "is_query_report": 1, + "label": "Customers Without Any Sales Transactions", + "link_to": "Customers Without Any Sales Transactions", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Customer", + "hidden": 0, + "is_query_report": 1, + "label": "Sales Partners Commission", + "link_to": "Sales Partners Commission", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Sales Order", + "hidden": 0, + "is_query_report": 1, + "label": "Territory Target Variance Based On Item Group", + "link_to": "Territory Target Variance Based On Item Group", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Sales Order", + "hidden": 0, + "is_query_report": 1, + "label": "Sales Person Target Variance Based On Item Group", + "link_to": "Sales Person Target Variance Based On Item Group", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Sales Order", + "hidden": 0, + "is_query_report": 1, + "label": "Sales Partner Target Variance Based On Item Group", + "link_to": "Sales Partner Target Variance based on Item Group", + "link_type": "Report", + "onboard": 0, + "type": "Link" + } + ], + "modified": "2020-12-01 13:38:35.971277", + "modified_by": "Administrator", + "module": "Selling", + "name": "Selling", + "onboarding": "Selling", + "owner": "Administrator", + "pin_to_bottom": 0, + "pin_to_top": 0, + "shortcuts": [ + { + "color": "Grey", + "format": "{} Available", + "label": "Item", + "link_to": "Item", + "stats_filter": "{\n \"disabled\":0\n}", + "type": "DocType" + }, + { + "color": "Yellow", + "format": "{} To Deliver", + "label": "Sales Order", + "link_to": "Sales Order", + "stats_filter": "{\n \"company\": [\"like\", '%' + frappe.defaults.get_global_default(\"company\") + '%'],\n \"status\":[\"in\", [\"To Deliver\", \"To Deliver and Bill\"]]\n}", + "type": "DocType" + }, + { + "color": "Grey", + "format": "{} Open", + "label": "Sales Analytics", + "link_to": "Sales Analytics", + "stats_filter": "{ \"Status\": \"Open\" }", + "type": "Report" + }, + { + "label": "Sales Order Analysis", + "link_to": "Sales Order Analysis", + "type": "Report" + }, + { + "label": "Dashboard", + "link_to": "Selling", + "type": "Dashboard" + } + ], + "shortcuts_label": "Quick Access" +} \ No newline at end of file diff --git a/erpnext/setup/desk_page/home/home.json b/erpnext/setup/desk_page/home/home.json deleted file mode 100644 index 0fbd0eccda8..00000000000 --- a/erpnext/setup/desk_page/home/home.json +++ /dev/null @@ -1,80 +0,0 @@ -{ - "cards": [ - { - "hidden": 0, - "label": "Stock", - "links": "[\n {\n \"label\": \"Warehouse\",\n \"name\": \"Warehouse\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Brand\",\n \"name\": \"Brand\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Unit of Measure (UOM)\",\n \"name\": \"UOM\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Stock Reconciliation\",\n \"name\": \"Stock Reconciliation\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n }\n]" - }, - { - "hidden": 0, - "label": "Human Resources", - "links": "[\n {\n \"label\": \"Employee\",\n \"name\": \"Employee\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"hide_count\": true,\n \"label\": \"Employee Attendance Tool\",\n \"name\": \"Employee Attendance Tool\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Salary Structure\",\n \"name\": \"Salary Structure\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n }\n]" - }, - { - "hidden": 0, - "label": "CRM", - "links": "[\n {\n \"description\": \"Database of potential customers.\",\n \"label\": \"Lead\",\n \"name\": \"Lead\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Manage Customer Group Tree.\",\n \"icon\": \"fa fa-sitemap\",\n \"label\": \"Customer Group\",\n \"link\": \"Tree/Customer Group\",\n \"name\": \"Customer Group\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Manage Territory Tree.\",\n \"icon\": \"fa fa-sitemap\",\n \"label\": \"Territory\",\n \"link\": \"Tree/Territory\",\n \"name\": \"Territory\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n }\n]" - }, - { - "hidden": 0, - "label": "Accounting", - "links": "[\n {\n \"label\": \"Item\",\n \"name\": \"Item\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Customer database.\",\n \"label\": \"Customer\",\n \"name\": \"Customer\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Supplier database.\",\n \"label\": \"Supplier\",\n \"name\": \"Supplier\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Company (not Customer or Supplier) master.\",\n \"label\": \"Company\",\n \"name\": \"Company\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Tree of financial accounts.\",\n \"icon\": \"fa fa-sitemap\",\n \"label\": \"Chart of Accounts\",\n \"name\": \"Account\",\n \"onboard\": 1,\n \"route\": \"#Tree/Account\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Create Opening Sales and Purchase Invoices\",\n \"label\": \"Opening Invoice Creation Tool\",\n \"name\": \"Opening Invoice Creation Tool\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n }\n]" - }, - { - "hidden": 0, - "label": "Data Import and Settings", - "links": "[\n {\n \"description\": \"Import Data from CSV / Excel files.\",\n \"icon\": \"octicon octicon-cloud-upload\",\n \"label\": \"Import Data\",\n \"name\": \"Data Import\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Import Chart of Accounts from CSV / Excel files\",\n \"label\": \"Chart of Accounts Importer\",\n \"label\": \"Chart of Accounts Importer\",\n \"name\": \"Chart of Accounts Importer\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Letter Heads for print templates.\",\n \"label\": \"Letter Head\",\n \"name\": \"Letter Head\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Add / Manage Email Accounts.\",\n \"label\": \"Email Account\",\n \"name\": \"Email Account\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n }\n]" - } - ], - "category": "Modules", - "charts": [], - "creation": "2020-01-23 13:46:38.833076", - "developer_mode_only": 0, - "disable_user_customization": 0, - "docstatus": 0, - "doctype": "Desk Page", - "extends_another_page": 0, - "hide_custom": 0, - "idx": 0, - "is_standard": 1, - "label": "Home", - "modified": "2020-12-07 14:22:38.667767", - "modified_by": "Administrator", - "module": "Setup", - "name": "Home", - "owner": "Administrator", - "pin_to_bottom": 0, - "pin_to_top": 1, - "shortcuts": [ - { - "label": "Item", - "link_to": "Item", - "type": "DocType" - }, - { - "label": "Customer", - "link_to": "Customer", - "type": "DocType" - }, - { - "label": "Supplier", - "link_to": "Supplier", - "type": "DocType" - }, - { - "label": "Sales Invoice", - "link_to": "Sales Invoice", - "type": "DocType" - }, - { - "label": "Dashboard", - "link_to": "dashboard", - "type": "Page" - }, - { - "label": "Leaderboard", - "link_to": "leaderboard", - "type": "Page" - } - ] -} diff --git a/erpnext/setup/doctype/company/company.js b/erpnext/setup/doctype/company/company.js index 36033d9daee..c041d269a76 100644 --- a/erpnext/setup/doctype/company/company.js +++ b/erpnext/setup/doctype/company/company.js @@ -140,7 +140,7 @@ frappe.ui.form.on("Company", { doc: frm.doc, freeze: true, callback: function() { - frappe.msgprint(__("Default tax templates for sales and purchase are created.")); + frappe.msgprint(__("Default tax templates for sales, purchase and items are created.")); } }) }, diff --git a/erpnext/setup/doctype/company/company.json b/erpnext/setup/doctype/company/company.json index d49ae7ce8ac..56f60dfcff0 100644 --- a/erpnext/setup/doctype/company/company.json +++ b/erpnext/setup/doctype/company/company.json @@ -725,7 +725,7 @@ { "fieldname": "default_in_transit_warehouse", "fieldtype": "Link", - "label": "Default In Transit Warehouse", + "label": "Default In-Transit Warehouse", "options": "Warehouse" }, { @@ -740,7 +740,7 @@ "image_field": "company_logo", "is_tree": 1, "links": [], - "modified": "2020-12-03 12:27:27.085094", + "modified": "2021-02-16 15:53:37.167589", "modified_by": "Administrator", "module": "Setup", "name": "Company", diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py index 4a438f70e62..433851cde53 100644 --- a/erpnext/setup/doctype/company/company.py +++ b/erpnext/setup/doctype/company/company.py @@ -75,7 +75,7 @@ class Company(NestedSet): def validate_default_accounts(self): accounts = [ - ["Default Bank Account", "default_bank_account"], ["Default Cash Account", "default_cash_account"], + ["Default Bank Account", "default_bank_account"], ["Default Cash Account", "default_cash_account"], ["Default Receivable Account", "default_receivable_account"], ["Default Payable Account", "default_payable_account"], ["Default Expense Account", "default_expense_account"], ["Default Income Account", "default_income_account"], ["Stock Received But Not Billed Account", "stock_received_but_not_billed"], ["Stock Adjustment Account", "stock_adjustment_account"], @@ -89,8 +89,9 @@ class Company(NestedSet): frappe.throw(_("Account {0} does not belong to company: {1}").format(self.get(account[1]), self.name)) if get_account_currency(self.get(account[1])) != self.default_currency: - frappe.throw(_("""{0} currency must be same as company's default currency. - Please select another account""").format(frappe.bold(account[0]))) + error_message = _("{0} currency must be same as company's default currency. Please select another account.") \ + .format(frappe.bold(account[0])) + frappe.throw(error_message) def validate_currency(self): if self.is_new(): @@ -389,8 +390,10 @@ class Company(NestedSet): frappe.db.sql("delete from tabDepartment where company=%s", self.name) frappe.db.sql("delete from `tabTax Withholding Account` where company=%s", self.name) + # delete tax templates frappe.db.sql("delete from `tabSales Taxes and Charges Template` where company=%s", self.name) frappe.db.sql("delete from `tabPurchase Taxes and Charges Template` where company=%s", self.name) + frappe.db.sql("delete from `tabItem Tax Template` where company=%s", self.name) @frappe.whitelist() def enqueue_replace_abbr(company, old, new): diff --git a/erpnext/setup/doctype/company/company_list.js b/erpnext/setup/doctype/company/company_list.js index 017286560fe..1d1184f04d3 100644 --- a/erpnext/setup/doctype/company/company_list.js +++ b/erpnext/setup/doctype/company/company_list.js @@ -1,10 +1,5 @@ frappe.listview_settings['Company'] = { - onload: () => { - frappe.breadcrumbs.add({ - type: 'Custom', - module: __('Accounts'), - label: __('Accounts'), - route: '#modules/Accounts' - }); - } -} \ No newline at end of file + onload() { + frappe.breadcrumbs.add('Accounts'); + }, +}; diff --git a/erpnext/setup/doctype/company/delete_company_transactions.py b/erpnext/setup/doctype/company/delete_company_transactions.py index 7a72fe31023..0df4c87f51f 100644 --- a/erpnext/setup/doctype/company/delete_company_transactions.py +++ b/erpnext/setup/doctype/company/delete_company_transactions.py @@ -28,7 +28,7 @@ def delete_company_transactions(company_name): "Party Account", "Employee", "Sales Taxes and Charges Template", "Purchase Taxes and Charges Template", "POS Profile", "BOM", "Company", "Bank Account", "Item Tax Template", "Mode Of Payment", - "Item Default", "Customer", "Supplier"): + "Item Default", "Customer", "Supplier", "GST Account"): delete_for_doctype(doctype, company_name) # reset company values diff --git a/erpnext/setup/doctype/item_group/item_group.js b/erpnext/setup/doctype/item_group/item_group.js index 9892dc3dcc0..1413cb28622 100644 --- a/erpnext/setup/doctype/item_group/item_group.js +++ b/erpnext/setup/doctype/item_group/item_group.js @@ -61,6 +61,19 @@ frappe.ui.form.on("Item Group", { frappe.set_route("List", "Item", {"item_group": frm.doc.name}); }); } + + frappe.model.with_doctype('Item', () => { + const item_meta = frappe.get_meta('Item'); + + const valid_fields = item_meta.fields.filter( + df => ['Link', 'Table MultiSelect'].includes(df.fieldtype) && !df.hidden + ).map(df => ({ label: df.label, value: df.fieldname })); + + const field = frappe.meta.get_docfield("Website Filter Field", "fieldname", frm.docname); + field.fieldtype = 'Select'; + field.options = valid_fields; + frm.fields_dict.filter_fields.grid.refresh(); + }); }, set_root_readonly: function(frm) { diff --git a/erpnext/setup/doctype/item_group/item_group.json b/erpnext/setup/doctype/item_group/item_group.json index fc464b237c6..e8352144872 100644 --- a/erpnext/setup/doctype/item_group/item_group.json +++ b/erpnext/setup/doctype/item_group/item_group.json @@ -24,8 +24,12 @@ "route", "weightage", "slideshow", + "website_title", "description", "website_specifications", + "website_filters_section", + "filter_fields", + "filter_attributes", "lft", "rgt", "old_parent" @@ -180,6 +184,28 @@ "options": "Item Group", "print_hide": 1, "report_hide": 1 + }, + { + "fieldname": "website_filters_section", + "fieldtype": "Section Break", + "label": "Website Filters" + }, + { + "fieldname": "filter_fields", + "fieldtype": "Table", + "label": "Item Fields", + "options": "Website Filter Field" + }, + { + "fieldname": "filter_attributes", + "fieldtype": "Table", + "label": "Attributes", + "options": "Website Attribute" + }, + { + "fieldname": "website_title", + "fieldtype": "Data", + "label": "Title" } ], "icon": "fa fa-sitemap", @@ -188,7 +214,7 @@ "is_tree": 1, "links": [], "max_attachments": 3, - "modified": "2020-03-18 18:10:34.383363", + "modified": "2021-02-08 17:02:44.951572", "modified_by": "Administrator", "module": "Setup", "name": "Item Group", diff --git a/erpnext/setup/doctype/item_group/item_group.py b/erpnext/setup/doctype/item_group/item_group.py index 43778404b60..bff806d5472 100644 --- a/erpnext/setup/doctype/item_group/item_group.py +++ b/erpnext/setup/doctype/item_group/item_group.py @@ -13,13 +13,16 @@ from frappe.website.doctype.website_slideshow.website_slideshow import get_slide from erpnext.shopping_cart.product_info import set_product_info_for_website from erpnext.utilities.product import get_qty_in_stock from six.moves.urllib.parse import quote +from erpnext.shopping_cart.product_query import ProductQuery +from erpnext.shopping_cart.filters import ProductFiltersBuilder class ItemGroup(NestedSet, WebsiteGenerator): nsm_parent_field = 'parent_item_group' website = frappe._dict( condition_field = "show_in_website", template = "templates/generators/item_group.html", - no_cache = 1 + no_cache = 1, + no_breadcrumbs = 1 ) def autoname(self): @@ -70,18 +73,58 @@ class ItemGroup(NestedSet, WebsiteGenerator): context.page_length = cint(frappe.db.get_single_value('Products Settings', 'products_per_page')) or 6 context.search_link = '/product_search' - start = int(frappe.form_dict.start or 0) - if start < 0: + if frappe.form_dict: + search = frappe.form_dict.search + field_filters = frappe.parse_json(frappe.form_dict.field_filters) + attribute_filters = frappe.parse_json(frappe.form_dict.attribute_filters) + start = frappe.parse_json(frappe.form_dict.start) + else: + search = None + attribute_filters = None + field_filters = {} start = 0 + + if not field_filters: + field_filters = {} + + # Ensure the query remains within current item group + field_filters['item_group'] = self.name + + engine = ProductQuery() + context.items = engine.query(attribute_filters, field_filters, search, start) + + filter_engine = ProductFiltersBuilder(self.name) + + context.field_filters = filter_engine.get_field_filters() + context.attribute_filters = filter_engine.get_attribute_fitlers() + context.update({ - "items": get_product_list_for_group(product_group = self.name, start=start, - limit=context.page_length + 1, search=frappe.form_dict.get("search")), "parents": get_parent_item_groups(self.parent_item_group), "title": self.name }) if self.slideshow: - context.update(get_slideshow(self)) + values = { + 'show_indicators': 1, + 'show_controls': 0, + 'rounded': 1, + 'slider_name': self.slideshow + } + slideshow = frappe.get_doc("Website Slideshow", self.slideshow) + slides = slideshow.get({"doctype":"Website Slideshow Item"}) + for index, slide in enumerate(slides): + values[f"slide_{index + 1}_image"] = slide.image + values[f"slide_{index + 1}_title"] = slide.heading + values[f"slide_{index + 1}_subtitle"] = slide.description + values[f"slide_{index + 1}_theme"] = slide.theme or "Light" + values[f"slide_{index + 1}_content_align"] = slide.content_align or "Centre" + values[f"slide_{index + 1}_primary_action_label"] = slide.label + values[f"slide_{index + 1}_primary_action"] = slide.url + + context.slideshow = values + + context.breadcrumbs = 0 + context.title = self.website_title or self.name return context diff --git a/erpnext/setup/doctype/item_group/test_records.json b/erpnext/setup/doctype/item_group/test_records.json index 71159643209..146da87bddc 100644 --- a/erpnext/setup/doctype/item_group/test_records.json +++ b/erpnext/setup/doctype/item_group/test_records.json @@ -79,13 +79,13 @@ { "doctype": "Item Tax", "parentfield": "taxes", - "item_tax_template": "_Test Account Excise Duty @ 10", + "item_tax_template": "_Test Account Excise Duty @ 10 - _TC", "tax_category": "" }, { "doctype": "Item Tax", "parentfield": "taxes", - "item_tax_template": "_Test Account Excise Duty @ 12", + "item_tax_template": "_Test Account Excise Duty @ 12 - _TC", "tax_category": "_Test Tax Category 1" } ] @@ -99,7 +99,7 @@ { "doctype": "Item Tax", "parentfield": "taxes", - "item_tax_template": "_Test Account Excise Duty @ 15", + "item_tax_template": "_Test Account Excise Duty @ 15 - _TC", "tax_category": "" } ] diff --git a/erpnext/setup/install.py b/erpnext/setup/install.py index 2225fe169f5..0bb480bd4b6 100644 --- a/erpnext/setup/install.py +++ b/erpnext/setup/install.py @@ -28,6 +28,7 @@ def after_install(): create_default_energy_point_rules() add_company_to_session_defaults() add_standard_navbar_items() + add_app_name() frappe.db.commit() @@ -158,3 +159,7 @@ def add_standard_navbar_items(): }) navbar_settings.save() + +def add_app_name(): + settings = frappe.get_doc("System Settings") + settings.app_name = _("ERPNext") \ No newline at end of file diff --git a/erpnext/setup/page/welcome_to_erpnext/welcome_to_erpnext.html b/erpnext/setup/page/welcome_to_erpnext/welcome_to_erpnext.html index 5808ce73ee9..7166ba37867 100644 --- a/erpnext/setup/page/welcome_to_erpnext/welcome_to_erpnext.html +++ b/erpnext/setup/page/welcome_to_erpnext/welcome_to_erpnext.html @@ -21,7 +21,6 @@

    {%= __("Next Steps") %}

    diff --git a/erpnext/setup/setup_wizard/operations/taxes_setup.py b/erpnext/setup/setup_wizard/operations/taxes_setup.py index e66fa76f93a..c3c1593c046 100644 --- a/erpnext/setup/setup_wizard/operations/taxes_setup.py +++ b/erpnext/setup/setup_wizard/operations/taxes_setup.py @@ -29,6 +29,7 @@ def make_tax_account_and_template(company, account_name, tax_rate, template_name try: if accounts: make_sales_and_purchase_tax_templates(accounts, template_name) + make_item_tax_templates(accounts, template_name) except frappe.NameError: if frappe.message_log: frappe.message_log.pop() except RootNotEditable: @@ -84,6 +85,27 @@ def make_sales_and_purchase_tax_templates(accounts, template_name=None): doc = frappe.get_doc(purchase_tax_template) doc.insert(ignore_permissions=True) +def make_item_tax_templates(accounts, template_name=None): + if not template_name: + template_name = accounts[0].name + + item_tax_template = { + "doctype": "Item Tax Template", + "title": template_name, + "company": accounts[0].company, + 'taxes': [] + } + + + for account in accounts: + item_tax_template['taxes'].append({ + "tax_type": account.name, + "tax_rate": account.tax_rate + }) + + # Items + frappe.get_doc(copy.deepcopy(item_tax_template)).insert(ignore_permissions=True) + def get_tax_account_group(company): tax_group = frappe.db.get_value("Account", {"account_name": "Duties and Taxes", "is_group": 1, "company": company}) diff --git a/erpnext/setup/desk_page/erpnext_settings/erpnext_settings.json b/erpnext/setup/workspace/erpnext_settings/erpnext_settings.json similarity index 77% rename from erpnext/setup/desk_page/erpnext_settings/erpnext_settings.json rename to erpnext/setup/workspace/erpnext_settings/erpnext_settings.json index 253d711b322..014f4095c15 100644 --- a/erpnext/setup/desk_page/erpnext_settings/erpnext_settings.json +++ b/erpnext/setup/workspace/erpnext_settings/erpnext_settings.json @@ -1,18 +1,20 @@ { - "cards": [], "category": "Modules", "charts": [], "creation": "2020-03-12 14:47:51.166455", "developer_mode_only": 0, "disable_user_customization": 0, "docstatus": 0, - "doctype": "Desk Page", + "doctype": "Workspace", "extends": "Settings", "extends_another_page": 1, + "hide_custom": 0, + "icon": "settings", "idx": 0, "is_standard": 1, "label": "ERPNext Settings", - "modified": "2020-04-01 11:28:51.400851", + "links": [], + "modified": "2020-12-01 13:38:37.759596", "modified_by": "Administrator", "module": "Setup", "name": "ERPNext Settings", @@ -21,89 +23,89 @@ "pin_to_top": 0, "shortcuts": [ { - "icon": "octicon octicon-rocket", + "icon": "project", "label": "Projects Settings", "link_to": "Projects Settings", "type": "DocType" }, { - "icon": "octicon octicon-repo", + "icon": "accounting", "label": "Accounts Settings", "link_to": "Accounts Settings", "type": "DocType" }, { - "icon": "octicon octicon-package", + "icon": "stock", "label": "Stock Settings", "link_to": "Stock Settings", "type": "DocType" }, { - "icon": "octicon octicon-organization", + "icon": "hr", "label": "HR Settings", "link_to": "HR Settings", "type": "DocType" }, { - "icon": "octicon octicon-tag", + "icon": "sell", "label": "Selling Settings", "link_to": "Selling Settings", "type": "DocType" }, { - "icon": "octicon octicon-briefcase", + "icon": "buying", "label": "Buying Settings", "link_to": "Buying Settings", "type": "DocType" }, { - "icon": "fa fa-life-ring", + "icon": "support", "label": "Support Settings", "link_to": "Support Settings", "type": "DocType" }, { - "icon": "fa fa-shopping-cart", + "icon": "retail", "label": "Shopping Cart Settings", "link_to": "Shopping Cart Settings", "type": "DocType" }, { - "icon": "fa fa-globe", + "icon": "website", "label": "Portal Settings", "link_to": "Portal Settings", "type": "DocType" }, { - "icon": "octicon octicon-tools", + "icon": "organization", "label": "Manufacturing Settings", "link_to": "Manufacturing Settings", "restrict_to_domain": "Manufacturing", "type": "DocType" }, { - "icon": "octicon octicon-mortar-board", + "icon": "education", "label": "Education Settings", "link_to": "Education Settings", "restrict_to_domain": "Education", "type": "DocType" }, { - "icon": "fa fa-bed", + "icon": "organization", "label": "Hotel Settings", "link_to": "Hotel Settings", "restrict_to_domain": "Hospitality", "type": "DocType" }, { - "icon": "fa fa-heartbeat", + "icon": "non-profit", "label": "Healthcare Settings", "link_to": "Healthcare Settings", "restrict_to_domain": "Healthcare", "type": "DocType" }, { - "icon": "fa fa-cog", + "icon": "setting", "label": "Domain Settings", "link_to": "Domain Settings", "type": "DocType" diff --git a/erpnext/setup/workspace/home/home.json b/erpnext/setup/workspace/home/home.json new file mode 100644 index 00000000000..69ca7cf9add --- /dev/null +++ b/erpnext/setup/workspace/home/home.json @@ -0,0 +1,454 @@ +{ + "category": "Modules", + "charts": [], + "creation": "2020-01-23 13:46:38.833076", + "developer_mode_only": 0, + "disable_user_customization": 0, + "docstatus": 0, + "doctype": "Workspace", + "extends_another_page": 0, + "hide_custom": 0, + "icon": "getting-started", + "idx": 0, + "is_standard": 1, + "label": "Home", + "links": [ + { + "hidden": 0, + "is_query_report": 0, + "label": "Healthcare", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Patient", + "link_to": "Patient", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Diagnosis", + "link_to": "Diagnosis", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Agriculture", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Crop", + "link_to": "Crop", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Crop Cycle", + "link_to": "Crop Cycle", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Location", + "link_to": "Location", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Fertilizer", + "link_to": "Fertilizer", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Education", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Student", + "link_to": "Student", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Course", + "link_to": "Course", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Instructor", + "link_to": "Instructor", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Room", + "link_to": "Room", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Non Profit", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Member", + "link_to": "Member", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Volunteer", + "link_to": "Volunteer", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Chapter", + "link_to": "Chapter", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Donor", + "link_to": "Donor", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Stock", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Warehouse", + "link_to": "Warehouse", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Brand", + "link_to": "Brand", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Unit of Measure (UOM)", + "link_to": "UOM", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Stock Reconciliation", + "link_to": "Stock Reconciliation", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Human Resources", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Employee", + "link_to": "Employee", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "Employee", + "hidden": 0, + "is_query_report": 0, + "label": "Employee Attendance Tool", + "link_to": "Employee Attendance Tool", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Salary Structure", + "link_to": "Salary Structure", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "CRM", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Lead", + "link_to": "Lead", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Customer Group", + "link_to": "Customer Group", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Territory", + "link_to": "Territory", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Accounting", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Item", + "link_to": "Item", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Customer", + "link_to": "Customer", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Supplier", + "link_to": "Supplier", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Company", + "link_to": "Company", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Chart of Accounts", + "link_to": "Account", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Opening Invoice Creation Tool", + "link_to": "Opening Invoice Creation Tool", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Data Import and Settings", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Import Data", + "link_to": "Data Import", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Chart of Accounts Importer", + "link_to": "Chart of Accounts Importer", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Letter Head", + "link_to": "Letter Head", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Email Account", + "link_to": "Email Account", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + } + ], + "modified": "2021-01-01 12:13:16.055668", + "modified_by": "Administrator", + "module": "Setup", + "name": "Home", + "owner": "Administrator", + "pin_to_bottom": 0, + "pin_to_top": 1, + "shortcuts": [ + { + "label": "Item", + "link_to": "Item", + "type": "DocType" + }, + { + "label": "Customer", + "link_to": "Customer", + "type": "DocType" + }, + { + "label": "Supplier", + "link_to": "Supplier", + "type": "DocType" + }, + { + "label": "Sales Invoice", + "link_to": "Sales Invoice", + "type": "DocType" + }, + { + "label": "Leaderboard", + "link_to": "leaderboard", + "type": "Page" + } + ] +} \ No newline at end of file diff --git a/erpnext/shopping_cart/cart.py b/erpnext/shopping_cart/cart.py index 9f50aba61cc..8515db3300d 100644 --- a/erpnext/shopping_cart/cart.py +++ b/erpnext/shopping_cart/cart.py @@ -42,14 +42,30 @@ def get_cart_quotation(doc=None): return { "doc": decorate_quotation_doc(doc), - "shipping_addresses": [{"name": address.name, "title": address.address_title, "display": address.display} - for address in addresses if address.address_type == "Shipping"], - "billing_addresses": [{"name": address.name, "title": address.address_title, "display": address.display} - for address in addresses if address.address_type == "Billing"], + "shipping_addresses": get_shipping_addresses(party), + "billing_addresses": get_billing_addresses(party), "shipping_rules": get_applicable_shipping_rules(party), "cart_settings": frappe.get_cached_doc("Shopping Cart Settings") } +@frappe.whitelist() +def get_shipping_addresses(party=None): + if not party: + party = get_party() + addresses = get_address_docs(party=party) + return [{"name": address.name, "title": address.address_title, "display": address.display} + for address in addresses if address.address_type == "Shipping" + ] + +@frappe.whitelist() +def get_billing_addresses(party=None): + if not party: + party = get_party() + addresses = get_address_docs(party=party) + return [{"name": address.name, "title": address.address_title, "display": address.display} + for address in addresses if address.address_type == "Billing" + ] + @frappe.whitelist() def place_order(): quotation = _get_cart_quotation() @@ -208,27 +224,33 @@ def get_terms_and_conditions(terms_name): @frappe.whitelist() def update_cart_address(address_type, address_name): quotation = _get_cart_quotation() - address_display = get_address_display(frappe.get_doc("Address", address_name).as_dict()) + address_doc = frappe.get_doc("Address", address_name).as_dict() + address_display = get_address_display(address_doc) if address_type.lower() == "billing": quotation.customer_address = address_name quotation.address_display = address_display quotation.shipping_address_name == quotation.shipping_address_name or address_name + address_doc = next((doc for doc in get_billing_addresses() if doc["name"] == address_name), None) elif address_type.lower() == "shipping": quotation.shipping_address_name = address_name quotation.shipping_address = address_display quotation.customer_address == quotation.customer_address or address_name - + address_doc = next((doc for doc in get_shipping_addresses() if doc["name"] == address_name), None) apply_cart_settings(quotation=quotation) quotation.flags.ignore_permissions = True quotation.save() context = get_cart_quotation(quotation) + context['address'] = address_doc + return { "taxes": frappe.render_template("templates/includes/order/order_taxes.html", context), - } + "address": frappe.render_template("templates/includes/cart/address_card.html", + context) + } def guess_territory(): territory = None @@ -438,6 +460,9 @@ def get_party(user=None): return customer def get_debtors_account(cart_settings): + if not cart_settings.payment_gateway_account: + frappe.throw(_("Payment Gateway Account not set"), _("Mandatory")) + payment_gateway_account_currency = \ frappe.get_doc("Payment Gateway Account", cart_settings.payment_gateway_account).currency diff --git a/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.js b/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.js index 20c6342d6c2..b38828e0d75 100644 --- a/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.js +++ b/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.js @@ -12,6 +12,18 @@ frappe.ui.form.on("Shopping Cart Settings", { return { 'filters': { 'payment_channel': "Email" } }; }); }, + refresh: function(frm) { + if (frm.doc.enabled) { + frm.get_field('store_page_docs').$wrapper.removeClass('hide-control').html( + `
    ${__("Follow these steps to create a landing page for your store")}: + + docs/store-landing-page + +
    ` + ); + } + }, enabled: function(frm) { if (frm.doc.enabled === 1) { frm.set_value('enable_variants', 1); diff --git a/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.json b/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.json index 98a7eeda23c..7a4bb20136f 100644 --- a/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.json +++ b/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.json @@ -7,6 +7,7 @@ "engine": "InnoDB", "field_order": [ "enabled", + "store_page_docs", "display_settings", "show_attachments", "show_price", @@ -25,10 +26,10 @@ "quotation_series", "section_break_8", "enable_checkout", - "payment_success_url", - "column_break_11", "save_quotations_as_draft", - "payment_gateway_account" + "column_break_11", + "payment_gateway_account", + "payment_success_url" ], "fields": [ { @@ -142,10 +143,12 @@ }, { "default": "Orders", + "depends_on": "enable_checkout", "description": "After payment completion redirect user to selected page.", "fieldname": "payment_success_url", "fieldtype": "Select", "label": "Payment Success Url", + "mandatory_depends_on": "enable_checkout", "options": "\nOrders\nInvoices\nMy Account" }, { @@ -153,9 +156,11 @@ "fieldtype": "Column Break" }, { + "depends_on": "enable_checkout", "fieldname": "payment_gateway_account", "fieldtype": "Link", "label": "Payment Gateway Account", + "mandatory_depends_on": "enable_checkout", "options": "Payment Gateway Account" }, { @@ -167,13 +172,25 @@ "fieldname": "enable_variants", "fieldtype": "Check", "label": "Enable Variants" + }, + { + "default": "0", + "depends_on": "eval: doc.enable_checkout == 0", + "fieldname": "save_quotations_as_draft", + "fieldtype": "Check", + "label": "Save Quotations as Draft" + }, + { + "depends_on": "doc.enabled", + "fieldname": "store_page_docs", + "fieldtype": "HTML" } ], "icon": "fa fa-shopping-cart", "idx": 1, "issingle": 1, "links": [], - "modified": "2020-08-02 18:21:43.873303", + "modified": "2021-03-02 17:34:57.642565", "modified_by": "Administrator", "module": "Shopping Cart", "name": "Shopping Cart Settings", @@ -190,5 +207,6 @@ } ], "sort_field": "modified", - "sort_order": "ASC" + "sort_order": "ASC", + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/shopping_cart/filters.py b/erpnext/shopping_cart/filters.py new file mode 100644 index 00000000000..6c63d8759b4 --- /dev/null +++ b/erpnext/shopping_cart/filters.py @@ -0,0 +1,82 @@ +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals +import frappe +from frappe import _dict + +class ProductFiltersBuilder: + def __init__(self, item_group=None): + if not item_group or item_group == "Products Settings": + self.doc = frappe.get_doc("Products Settings") + else: + self.doc = frappe.get_doc("Item Group", item_group) + + self.item_group = item_group + + def get_field_filters(self): + filter_fields = [row.fieldname for row in self.doc.filter_fields] + + meta = frappe.get_meta('Item') + fields = [df for df in meta.fields if df.fieldname in filter_fields] + + filter_data = [] + for df in fields: + filters = {} + if df.fieldtype == "Link": + if self.item_group: + filters['item_group'] = self.item_group + + values = frappe.get_all("Item", fields=[df.fieldname], filters=filters, distinct="True", pluck=df.fieldname) + else: + doctype = df.get_link_doctype() + + # apply enable/disable/show_in_website filter + meta = frappe.get_meta(doctype) + + if meta.has_field('enabled'): + filters['enabled'] = 1 + if meta.has_field('disabled'): + filters['disabled'] = 0 + if meta.has_field('show_in_website'): + filters['show_in_website'] = 1 + + values = [d.name for d in frappe.get_all(doctype, filters)] + + # Remove None + values = values.remove(None) if None in values else values + if values: + filter_data.append([df, values]) + + return filter_data + + def get_attribute_fitlers(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 = [] + + for attr_doc in attribute_docs: + selected_attributes = [] + for attr in attr_doc.item_attribute_values: + filters= [ + ["Item Variant Attribute", "attribute", "=", attr.parent], + ["Item Variant Attribute", "attribute_value", "=", attr.attribute_value] + ] + if self.item_group: + filters.append(["item_group", "=", self.item_group]) + + if frappe.db.get_all("Item", filters, limit=1): + selected_attributes.append(attr) + + if selected_attributes: + valid_attributes.append( + _dict( + item_attribute_values=selected_attributes, + name=attr_doc.name + ) + ) + + return valid_attributes diff --git a/erpnext/shopping_cart/product_query.py b/erpnext/shopping_cart/product_query.py new file mode 100644 index 00000000000..36d446ed0fd --- /dev/null +++ b/erpnext/shopping_cart/product_query.py @@ -0,0 +1,123 @@ +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt + +import frappe +from erpnext.shopping_cart.product_info import get_product_info_for_website + +class ProductQuery: + """Query engine for product listing + + Attributes: + cart_settings (Document): Settings for Cart + fields (list): Fields to fetch in query + filters (TYPE): Description + or_filters (list): Description + page_length (Int): Length of page for the query + settings (Document): Products Settings DocType + filters (list) + or_filters (list) + """ + + def __init__(self): + self.settings = frappe.get_doc("Products Settings") + self.cart_settings = frappe.get_doc("Shopping Cart Settings") + self.page_length = self.settings.products_per_page or 20 + self.fields = ['name', 'item_name', 'item_code', 'website_image', 'variant_of', 'has_variants', 'item_group', 'image', 'web_long_description', 'description', 'route'] + self.filters = [] + self.or_filters = [['show_in_website', '=', 1]] + if not self.settings.get('hide_variants'): + self.or_filters.append(['show_variant_in_website', '=', 1]) + + def query(self, attributes=None, fields=None, search_term=None, start=0): + """Summary + + Args: + attributes (dict, optional): Item Attribute filters + fields (dict, optional): Field level filters + search_term (str, optional): Search term to lookup + start (int, optional): Page start + + Returns: + list: List of results with set fields + """ + if fields: self.build_fields_filters(fields) + if search_term: self.build_search_filters(search_term) + + result = [] + + if attributes: + all_items = [] + for attribute, values in attributes.items(): + if not isinstance(values, list): + values = [values] + + items = frappe.get_all( + "Item", + fields=self.fields, + filters=[ + *self.filters, + ["Item Variant Attribute", "attribute", "=", attribute], + ["Item Variant Attribute", "attribute_value", "in", values], + ], + or_filters=self.or_filters, + start=start, + limit=self.page_length + ) + + items_dict = {item.name: item for item in items} + # TODO: Replace Variants by their parent templates + + all_items.append(set(items_dict.keys())) + + result = [items_dict.get(item) for item in list(set.intersection(*all_items))] + else: + result = frappe.get_all("Item", fields=self.fields, filters=self.filters, or_filters=self.or_filters, start=start, limit=self.page_length) + + for item in result: + product_info = get_product_info_for_website(item.item_code, skip_quotation_creation=True).get('product_info') + if product_info: + item.formatted_price = product_info['price'].get('formatted_price') if product_info['price'] else None + + return result + + def build_fields_filters(self, filters): + """Build filters for field values + + Args: + filters (dict): Filters + """ + for field, values in filters.items(): + if not values: + continue + + if isinstance(values, list): + # If value is a list use `IN` query + self.filters.append([field, 'IN', values]) + else: + # `=` will be faster than `IN` for most cases + self.filters.append([field, '=', values]) + + def build_search_filters(self, search_term): + """Query search term in specified fields + + Args: + search_term (str): Search candidate + """ + # Default fields to search from + default_fields = {'name', 'item_name', 'description', 'item_group'} + + # Get meta search fields + meta = frappe.get_meta("Item") + meta_fields = set(meta.get_search_fields()) + + # Join the meta fields and default fields set + search_fields = default_fields.union(meta_fields) + try: + if frappe.db.count('Item', cache=True) > 50000: + search_fields.remove('description') + except KeyError: + pass + + # Build or filters for query + search = '%{}%'.format(search_term) + self.or_filters += [[field, 'like', search] for field in search_fields] diff --git a/erpnext/shopping_cart/search.py b/erpnext/shopping_cart/search.py new file mode 100644 index 00000000000..63e9fe1b31b --- /dev/null +++ b/erpnext/shopping_cart/search.py @@ -0,0 +1,126 @@ +import frappe +from frappe.search.full_text_search import FullTextSearch +from whoosh.fields import TEXT, ID, KEYWORD, Schema +from frappe.utils import strip_html_tags +from whoosh.qparser import MultifieldParser, FieldsPlugin, WildcardPlugin +from whoosh.analysis import StemmingAnalyzer +from whoosh.query import Prefix + +INDEX_NAME = "products" + +class ProductSearch(FullTextSearch): + """ Wrapper for WebsiteSearch """ + + def get_schema(self): + return Schema( + title=TEXT(stored=True, field_boost=1.5), + name=ID(stored=True), + path=ID(stored=True), + content=TEXT(stored=True, analyzer=StemmingAnalyzer()), + keywords=KEYWORD(stored=True, scorable=True, commas=True), + ) + + def get_id(self): + return "name" + + def get_items_to_index(self): + """Get all routes to be indexed, this includes the static pages + in www/ and routes from published documents + + Returns: + self (object): FullTextSearch Instance + """ + items = get_all_published_items() + documents = [self.get_document_to_index(item) for item in items] + return documents + + def get_document_to_index(self, item): + try: + item = frappe.get_doc("Item", item) + title = item.item_name + keywords = [item.item_group] + + if item.brand: + keywords.append(item.brand) + + if item.website_image_alt: + keywords.append(item.website_image_alt) + + if item.has_variants and item.variant_based_on == "Item Attribute": + keywords = keywords + [attr.attribute for attr in item.attributes] + + if item.web_long_description: + content = strip_html_tags(item.web_long_description) + elif item.description: + content = strip_html_tags(item.description) + + return frappe._dict( + title=title, + name=item.name, + path=item.route, + content=content, + keywords=", ".join(keywords), + ) + except Exception: + pass + + def search(self, text, scope=None, limit=20): + """Search from the current index + + Args: + text (str): String to search for + scope (str, optional): Scope to limit the search. Defaults to None. + limit (int, optional): Limit number of search results. Defaults to 20. + + Returns: + [List(_dict)]: Search results + """ + ix = self.get_index() + + results = None + out = [] + + with ix.searcher() as searcher: + parser = MultifieldParser(["title", "content", "keywords"], ix.schema) + parser.remove_plugin_class(FieldsPlugin) + parser.remove_plugin_class(WildcardPlugin) + query = parser.parse(text) + + filter_scoped = None + if scope: + filter_scoped = Prefix(self.id, scope) + results = searcher.search(query, limit=limit, filter=filter_scoped) + + for r in results: + out.append(self.parse_result(r)) + + return out + + def parse_result(self, result): + title_highlights = result.highlights("title") + content_highlights = result.highlights("content") + keyword_highlights = result.highlights("keywords") + + return frappe._dict( + title=result["title"], + path=result["path"], + keywords=result["keywords"], + title_highlights=title_highlights, + content_highlights=content_highlights, + keyword_highlights=keyword_highlights, + ) + +def get_all_published_items(): + return frappe.get_all("Item", filters={"variant_of": "", "show_in_website": 1},pluck="name") + +def update_index_for_path(path): + search = ProductSearch(INDEX_NAME) + return search.update_index_by_name(path) + +def remove_document_from_index(path): + search = ProductSearch(INDEX_NAME) + return search.remove_document_from_index(path) + +def build_index_for_all_routes(): + search = ProductSearch(INDEX_NAME) + return search.build() \ No newline at end of file diff --git a/erpnext/config/__init__.py b/erpnext/shopping_cart/web_template/__init__.py similarity index 100% rename from erpnext/config/__init__.py rename to erpnext/shopping_cart/web_template/__init__.py diff --git a/erpnext/shopping_cart/web_template/hero_slider/__init__.py b/erpnext/shopping_cart/web_template/hero_slider/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/shopping_cart/web_template/hero_slider/hero_slider.html b/erpnext/shopping_cart/web_template/hero_slider/hero_slider.html new file mode 100644 index 00000000000..1b3953435e4 --- /dev/null +++ b/erpnext/shopping_cart/web_template/hero_slider/hero_slider.html @@ -0,0 +1,85 @@ +{%- macro slide(image, title, subtitle, action, label, index, align="Left", theme="Dark") -%} +{%- set align_class = resolve_class({ + 'text-right': align == 'Right', + 'text-centre': align == 'Center', + 'text-left': align == 'Left', +}) -%} + +{%- set heading_class = resolve_class({ + 'text-white': theme == 'Dark', + '': theme == 'Light', +}) -%} + +{%- endmacro -%} + + + + + + \ No newline at end of file diff --git a/erpnext/shopping_cart/web_template/hero_slider/hero_slider.json b/erpnext/shopping_cart/web_template/hero_slider/hero_slider.json new file mode 100644 index 00000000000..04fb1d27059 --- /dev/null +++ b/erpnext/shopping_cart/web_template/hero_slider/hero_slider.json @@ -0,0 +1,284 @@ +{ + "creation": "2020-11-17 15:21:51.207221", + "docstatus": 0, + "doctype": "Web Template", + "fields": [ + { + "fieldname": "slider_name", + "fieldtype": "Data", + "label": "Slider Name", + "reqd": 1 + }, + { + "default": "1", + "fieldname": "show_indicators", + "fieldtype": "Check", + "label": "Show Indicators", + "reqd": 0 + }, + { + "default": "1", + "fieldname": "show_controls", + "fieldtype": "Check", + "label": "Show Controls", + "reqd": 0 + }, + { + "fieldname": "slide_1", + "fieldtype": "Section Break", + "label": "Slide 1", + "reqd": 0 + }, + { + "fieldname": "slide_1_image", + "fieldtype": "Attach Image", + "label": "Image", + "reqd": 0 + }, + { + "fieldname": "slide_1_title", + "fieldtype": "Data", + "label": "Title", + "reqd": 0 + }, + { + "fieldname": "slide_1_subtitle", + "fieldtype": "Small Text", + "label": "Subtitle", + "reqd": 0 + }, + { + "fieldname": "slide_1_primary_action_label", + "fieldtype": "Data", + "label": "Primary Action Label", + "reqd": 0 + }, + { + "fieldname": "slide_1_primary_action", + "fieldtype": "Data", + "label": "Primary Action", + "reqd": 0 + }, + { + "fieldname": "slide_1_content_align", + "fieldtype": "Select", + "label": "Content Align", + "options": "Left\nCentre\nRight", + "reqd": 0 + }, + { + "fieldname": "slide_1_theme", + "fieldtype": "Select", + "label": "Slide Theme", + "options": "Dark\nLight", + "reqd": 0 + }, + { + "fieldname": "slide_2", + "fieldtype": "Section Break", + "label": "Slide 2", + "reqd": 0 + }, + { + "fieldname": "slide_2_image", + "fieldtype": "Attach Image", + "label": "Image ", + "reqd": 0 + }, + { + "fieldname": "slide_2_title", + "fieldtype": "Data", + "label": "Title ", + "reqd": 0 + }, + { + "fieldname": "slide_2_subtitle", + "fieldtype": "Small Text", + "label": "Subtitle ", + "reqd": 0 + }, + { + "fieldname": "slide_2_primary_action_label", + "fieldtype": "Data", + "label": "Primary Action Label ", + "reqd": 0 + }, + { + "fieldname": "slide_2_primary_action", + "fieldtype": "Data", + "label": "Primary Action ", + "reqd": 0 + }, + { + "default": "Left", + "fieldname": "slide_2_content_align", + "fieldtype": "Select", + "label": "Content Align", + "options": "Left\nCentre\nRight", + "reqd": 0 + }, + { + "fieldname": "slide_2_theme", + "fieldtype": "Select", + "label": "Slide Theme", + "options": "Dark\nLight", + "reqd": 0 + }, + { + "fieldname": "slide_3", + "fieldtype": "Section Break", + "label": "Slide 3", + "reqd": 0 + }, + { + "fieldname": "slide_3_image", + "fieldtype": "Attach Image", + "label": "Image", + "reqd": 0 + }, + { + "fieldname": "slide_3_title", + "fieldtype": "Data", + "label": "Title", + "reqd": 0 + }, + { + "fieldname": "slide_3_subtitle", + "fieldtype": "Small Text", + "label": "Subtitle", + "reqd": 0 + }, + { + "fieldname": "slide_3_primary_action_label", + "fieldtype": "Data", + "label": "Primary Action Label", + "reqd": 0 + }, + { + "fieldname": "slide_3_primary_action", + "fieldtype": "Data", + "label": "Primary Action", + "reqd": 0 + }, + { + "fieldname": "slide_3_content_align", + "fieldtype": "Select", + "label": "Content Align", + "reqd": 0 + }, + { + "fieldname": "slide_3_theme", + "fieldtype": "Select", + "label": "Slide Theme", + "options": "Dark\nLight", + "reqd": 0 + }, + { + "fieldname": "slide_4", + "fieldtype": "Section Break", + "label": "Slide 4", + "reqd": 0 + }, + { + "fieldname": "slide_4_image", + "fieldtype": "Attach Image", + "label": "Image", + "reqd": 0 + }, + { + "fieldname": "slide_4_title", + "fieldtype": "Data", + "label": "Title", + "reqd": 0 + }, + { + "fieldname": "slide_4_subtitle", + "fieldtype": "Small Text", + "label": "Subtitle", + "reqd": 0 + }, + { + "fieldname": "slide_4_primary_action_label", + "fieldtype": "Data", + "label": "Primary Action Label", + "reqd": 0 + }, + { + "fieldname": "slide_4_primary_action", + "fieldtype": "Data", + "label": "Primary Action", + "reqd": 0 + }, + { + "fieldname": "slide_4_content_align", + "fieldtype": "Select", + "label": "Content Align", + "reqd": 0 + }, + { + "fieldname": "slide_4_theme", + "fieldtype": "Select", + "label": "Slide Theme", + "options": "Dark\nLight", + "reqd": 0 + }, + { + "fieldname": "slide_5", + "fieldtype": "Section Break", + "label": "Slide 5", + "reqd": 0 + }, + { + "fieldname": "slide_5_image", + "fieldtype": "Attach Image", + "label": "Image", + "reqd": 0 + }, + { + "fieldname": "slide_5_title", + "fieldtype": "Data", + "label": "Title", + "reqd": 0 + }, + { + "fieldname": "slide_5_subtitle", + "fieldtype": "Small Text", + "label": "Subtitle", + "reqd": 0 + }, + { + "fieldname": "slide_5_primary_action_label", + "fieldtype": "Data", + "label": "Primary Action Label", + "reqd": 0 + }, + { + "fieldname": "slide_5_primary_action", + "fieldtype": "Data", + "label": "Primary Action", + "reqd": 0 + }, + { + "fieldname": "slide_5_content_align", + "fieldtype": "Select", + "label": "Content Align", + "reqd": 0 + }, + { + "fieldname": "slide_5_theme", + "fieldtype": "Select", + "label": "Slide Theme", + "options": "Dark\nLight", + "reqd": 0 + } + ], + "idx": 2, + "modified": "2020-12-29 12:30:02.794994", + "modified_by": "Administrator", + "module": "Shopping Cart", + "name": "Hero Slider", + "owner": "Administrator", + "standard": 1, + "template": "", + "type": "Section" +} \ No newline at end of file diff --git a/erpnext/shopping_cart/web_template/item_card_group/__init__.py b/erpnext/shopping_cart/web_template/item_card_group/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/shopping_cart/web_template/item_card_group/item_card_group.html b/erpnext/shopping_cart/web_template/item_card_group/item_card_group.html new file mode 100644 index 00000000000..890ae502c82 --- /dev/null +++ b/erpnext/shopping_cart/web_template/item_card_group/item_card_group.html @@ -0,0 +1,38 @@ +{% from "erpnext/templates/includes/macros.html" import item_card, item_card_body %} + +
    +
    +
    + {%- if title -%} +

    {{ title }}

    + {%- endif -%} + {%- if subtitle -%} +

    {{ subtitle }}

    + {%- endif -%} +
    +
    + {%- if primary_action -%} + + {{ primary_action_label }} + + {%- endif -%} +
    +
    + +
    + {%- for index in ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12'] -%} + {%- set item = values['card_' + index + '_item'] -%} + {%- if item -%} + {%- set item = frappe.get_doc("Item", item) -%} + {{ item_card( + item.item_name, item.image, item.route, item.description, + None, item.item_group, values['card_' + index + '_featured'], + True, "Center" + ) }} + {%- endif -%} + {%- endfor -%} +
    +
    + + \ No newline at end of file diff --git a/erpnext/shopping_cart/web_template/item_card_group/item_card_group.json b/erpnext/shopping_cart/web_template/item_card_group/item_card_group.json new file mode 100644 index 00000000000..ad087b04704 --- /dev/null +++ b/erpnext/shopping_cart/web_template/item_card_group/item_card_group.json @@ -0,0 +1,273 @@ +{ + "__unsaved": 1, + "creation": "2020-11-17 15:35:05.285322", + "docstatus": 0, + "doctype": "Web Template", + "fields": [ + { + "fieldname": "title", + "fieldtype": "Data", + "label": "Title", + "reqd": 1 + }, + { + "fieldname": "subtitle", + "fieldtype": "Data", + "label": "Subtitle", + "reqd": 0 + }, + { + "__unsaved": 1, + "fieldname": "primary_action_label", + "fieldtype": "Data", + "label": "Primary Action Label", + "reqd": 0 + }, + { + "__islocal": 1, + "__unsaved": 1, + "fieldname": "primary_action", + "fieldtype": "Data", + "label": "Primary Action", + "reqd": 0 + }, + { + "fieldname": "card_1", + "fieldtype": "Section Break", + "label": "Card 1", + "reqd": 0 + }, + { + "fieldname": "card_1_item", + "fieldtype": "Link", + "label": "Item", + "options": "Item", + "reqd": 0 + }, + { + "fieldname": "card_1_featured", + "fieldtype": "Check", + "label": "Featured", + "reqd": 0 + }, + { + "fieldname": "card_2", + "fieldtype": "Section Break", + "label": "Card 2", + "reqd": 0 + }, + { + "fieldname": "card_2_item", + "fieldtype": "Link", + "label": "Item", + "options": "Item", + "reqd": 0 + }, + { + "fieldname": "card_2_featured", + "fieldtype": "Check", + "label": "Featured", + "reqd": 0 + }, + { + "fieldname": "card_3", + "fieldtype": "Section Break", + "label": "Card 3", + "options": "", + "reqd": 0 + }, + { + "fieldname": "card_3_item", + "fieldtype": "Link", + "label": "Item", + "options": "Item", + "reqd": 0 + }, + { + "fieldname": "card_3_featured", + "fieldtype": "Check", + "label": "Featured", + "reqd": 0 + }, + { + "fieldname": "card_4", + "fieldtype": "Section Break", + "label": "Card 4", + "reqd": 0 + }, + { + "fieldname": "card_4_item", + "fieldtype": "Link", + "label": "Item", + "options": "Item", + "reqd": 0 + }, + { + "fieldname": "card_4_featured", + "fieldtype": "Check", + "label": "Featured", + "reqd": 0 + }, + { + "fieldname": "card_5", + "fieldtype": "Section Break", + "label": "Card 5", + "reqd": 0 + }, + { + "fieldname": "card_5_item", + "fieldtype": "Link", + "label": "Item", + "options": "Item", + "reqd": 0 + }, + { + "fieldname": "card_5_featured", + "fieldtype": "Check", + "label": "Featured", + "reqd": 0 + }, + { + "fieldname": "card_6", + "fieldtype": "Section Break", + "label": "Card 6", + "reqd": 0 + }, + { + "fieldname": "card_6_item", + "fieldtype": "Link", + "label": "Item", + "options": "Item", + "reqd": 0 + }, + { + "fieldname": "card_6_featured", + "fieldtype": "Check", + "label": "Featured", + "reqd": 0 + }, + { + "fieldname": "card_7", + "fieldtype": "Section Break", + "label": "Card 7", + "reqd": 0 + }, + { + "fieldname": "card_7_item", + "fieldtype": "Link", + "label": "Item", + "options": "Item", + "reqd": 0 + }, + { + "fieldname": "card_7_featured", + "fieldtype": "Check", + "label": "Featured", + "reqd": 0 + }, + { + "fieldname": "card_8", + "fieldtype": "Section Break", + "label": "Card 8", + "reqd": 0 + }, + { + "fieldname": "card_8_item", + "fieldtype": "Link", + "label": "Item", + "options": "Item", + "reqd": 0 + }, + { + "fieldname": "card_8_featured", + "fieldtype": "Check", + "label": "Featured", + "reqd": 0 + }, + { + "fieldname": "card_9", + "fieldtype": "Section Break", + "label": "Card 9", + "reqd": 0 + }, + { + "fieldname": "card_9_item", + "fieldtype": "Link", + "label": "Item", + "options": "Item", + "reqd": 0 + }, + { + "fieldname": "card_9_featured", + "fieldtype": "Check", + "label": "Featured", + "reqd": 0 + }, + { + "fieldname": "card_10", + "fieldtype": "Section Break", + "label": "Card 10", + "reqd": 0 + }, + { + "fieldname": "card_10_item", + "fieldtype": "Link", + "label": "Item", + "options": "Item", + "reqd": 0 + }, + { + "fieldname": "card_10_featured", + "fieldtype": "Check", + "label": "Featured", + "reqd": 0 + }, + { + "fieldname": "card_11", + "fieldtype": "Section Break", + "label": "Card 11", + "reqd": 0 + }, + { + "fieldname": "card_11_item", + "fieldtype": "Link", + "label": "Item", + "options": "Item", + "reqd": 0 + }, + { + "fieldname": "card_11_featured", + "fieldtype": "Check", + "label": "Featured", + "reqd": 0 + }, + { + "fieldname": "card_12", + "fieldtype": "Section Break", + "label": "Card 12", + "reqd": 0 + }, + { + "fieldname": "card_12_item", + "fieldtype": "Link", + "label": "Item", + "options": "Item", + "reqd": 0 + }, + { + "fieldname": "card_12_featured", + "fieldtype": "Check", + "label": "Featured", + "reqd": 0 + } + ], + "idx": 0, + "modified": "2020-11-19 18:48:52.633045", + "modified_by": "Administrator", + "module": "Shopping Cart", + "name": "Item Card Group", + "owner": "Administrator", + "standard": 1, + "template": "", + "type": "Section" +} \ No newline at end of file diff --git a/erpnext/shopping_cart/web_template/product_card/__init__.py b/erpnext/shopping_cart/web_template/product_card/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/shopping_cart/web_template/product_card/product_card.html b/erpnext/shopping_cart/web_template/product_card/product_card.html new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/shopping_cart/web_template/product_card/product_card.json b/erpnext/shopping_cart/web_template/product_card/product_card.json new file mode 100644 index 00000000000..1059c1b2519 --- /dev/null +++ b/erpnext/shopping_cart/web_template/product_card/product_card.json @@ -0,0 +1,33 @@ +{ + "__unsaved": 1, + "creation": "2020-11-17 15:28:47.809342", + "docstatus": 0, + "doctype": "Web Template", + "fields": [ + { + "__unsaved": 1, + "fieldname": "item", + "fieldtype": "Link", + "label": "Item", + "options": "Item", + "reqd": 0 + }, + { + "__unsaved": 1, + "fieldname": "featured", + "fieldtype": "Check", + "label": "Featured", + "options": "", + "reqd": 0 + } + ], + "idx": 0, + "modified": "2020-11-17 15:33:34.982515", + "modified_by": "Administrator", + "module": "Shopping Cart", + "name": "Product Card", + "owner": "Administrator", + "standard": 1, + "template": "", + "type": "Component" +} \ No newline at end of file diff --git a/erpnext/shopping_cart/web_template/product_category_cards/__init__.py b/erpnext/shopping_cart/web_template/product_category_cards/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/shopping_cart/web_template/product_category_cards/product_category_cards.html b/erpnext/shopping_cart/web_template/product_category_cards/product_category_cards.html new file mode 100644 index 00000000000..06b76af9018 --- /dev/null +++ b/erpnext/shopping_cart/web_template/product_category_cards/product_category_cards.html @@ -0,0 +1,40 @@ +{%- macro card(title, image, url, text_primary=False) -%} +{%- set align_class = resolve_class({ + 'text-right': text_primary, + 'text-centre': align == 'Center', + 'text-left': align == 'Left', +}) -%} +
    + {% if image %} + {{ title }} + {% endif %} +
    + {{ title or '' }} +
    + +
    +{%- endmacro -%} + +
    + {%- if title -%} +

    {{ title }}

    + {%- endif -%} + {%- if subtitle -%} +

    {{ subtitle }}

    + {%- endif -%} + +
    +
    + {%- for index in ['1', '2', '3', '4', '5', '6', '7', '8'] -%} + {%- set category = values['category_' + index] -%} + {%- if category -%} + {%- set category = frappe.get_doc("Item Group", category) -%} + {{ card(category.name, category.image, category.route) }} + {%- endif -%} + {%- endfor -%} +
    +
    +
    + + diff --git a/erpnext/shopping_cart/web_template/product_category_cards/product_category_cards.json b/erpnext/shopping_cart/web_template/product_category_cards/product_category_cards.json new file mode 100644 index 00000000000..ba5f63b48b2 --- /dev/null +++ b/erpnext/shopping_cart/web_template/product_category_cards/product_category_cards.json @@ -0,0 +1,85 @@ +{ + "__unsaved": 1, + "creation": "2020-11-17 15:25:50.855934", + "docstatus": 0, + "doctype": "Web Template", + "fields": [ + { + "fieldname": "title", + "fieldtype": "Data", + "label": "Title", + "reqd": 1 + }, + { + "fieldname": "subtitle", + "fieldtype": "Data", + "label": "Subtitle", + "reqd": 0 + }, + { + "fieldname": "category_1", + "fieldtype": "Link", + "label": "Item Group", + "options": "Item Group", + "reqd": 0 + }, + { + "fieldname": "category_2", + "fieldtype": "Link", + "label": "Item Group", + "options": "Item Group", + "reqd": 0 + }, + { + "fieldname": "category_3", + "fieldtype": "Link", + "label": "Item Group", + "options": "Item Group", + "reqd": 0 + }, + { + "fieldname": "category_4", + "fieldtype": "Link", + "label": "Item Group", + "options": "Item Group", + "reqd": 0 + }, + { + "fieldname": "category_5", + "fieldtype": "Link", + "label": "Item Group", + "options": "Item Group", + "reqd": 0 + }, + { + "fieldname": "category_6", + "fieldtype": "Link", + "label": "Item Group", + "options": "Item Group", + "reqd": 0 + }, + { + "fieldname": "category_7", + "fieldtype": "Link", + "label": "Item Group", + "options": "Item Group", + "reqd": 0 + }, + { + "fieldname": "category_8", + "fieldtype": "Link", + "label": "Item Group", + "options": "Item Group", + "reqd": 0 + } + ], + "idx": 0, + "modified": "2020-11-18 17:26:28.726260", + "modified_by": "Administrator", + "module": "Shopping Cart", + "name": "Product Category Cards", + "owner": "Administrator", + "standard": 1, + "template": "", + "type": "Section" +} \ No newline at end of file diff --git a/erpnext/startup/filters.py b/erpnext/startup/filters.py index a99e49b4917..ec07329dedf 100644 --- a/erpnext/startup/filters.py +++ b/erpnext/startup/filters.py @@ -2,13 +2,13 @@ import frappe def get_filters_config(): - filters_config = { + filters_config = { "fiscal year": { "label": "Fiscal Year", "get_field": "erpnext.accounts.utils.get_fiscal_year_filter_field", "valid_for_fieldtypes": ["Date", "Datetime", "DateRange"], "depends_on": "company", } - } + } - return filters_config \ No newline at end of file + return filters_config \ No newline at end of file diff --git a/erpnext/startup/leaderboard.py b/erpnext/startup/leaderboard.py index ef238f1165d..8819a55c0ab 100644 --- a/erpnext/startup/leaderboard.py +++ b/erpnext/startup/leaderboard.py @@ -12,6 +12,7 @@ def get_leaderboards(): {'fieldname': 'outstanding_amount', 'fieldtype': 'Currency'} ], "method": "erpnext.startup.leaderboard.get_all_customers", + "icon": "customer" }, "Item": { "fields": [ @@ -23,6 +24,7 @@ def get_leaderboards(): {'fieldname': 'available_stock_value', 'fieldtype': 'Currency'} ], "method": "erpnext.startup.leaderboard.get_all_items", + "icon": "stock" }, "Supplier": { "fields": [ @@ -31,6 +33,7 @@ def get_leaderboards(): {'fieldname': 'outstanding_amount', 'fieldtype': 'Currency'} ], "method": "erpnext.startup.leaderboard.get_all_suppliers", + "icon": "buying" }, "Sales Partner": { "fields": [ @@ -38,12 +41,14 @@ def get_leaderboards(): {'fieldname': 'total_commission', 'fieldtype': 'Currency'} ], "method": "erpnext.startup.leaderboard.get_all_sales_partner", + "icon": "hr" }, "Sales Person": { "fields": [ {'fieldname': 'total_sales_amount', 'fieldtype': 'Currency'} ], "method": "erpnext.startup.leaderboard.get_all_sales_person", + "icon": "customer" } } diff --git a/erpnext/stock/dashboard/item_dashboard.js b/erpnext/stock/dashboard/item_dashboard.js index d3c442d3039..95cb92b1b36 100644 --- a/erpnext/stock/dashboard/item_dashboard.js +++ b/erpnext/stock/dashboard/item_dashboard.js @@ -132,7 +132,7 @@ erpnext.stock.ItemDashboard = Class.extend({ var message = __("No Stock Available Currently"); this.content.find('.result').css('text-align', 'center'); - $(`
    + $(`
    ${message}
    `).appendTo(this.result); } }, @@ -241,7 +241,7 @@ erpnext.stock.move_item = function(item, source, target, actual_qty, rate, callb freeze: true, callback: function(r) { frappe.show_alert(__('Stock Entry {0} created', - ['' + r.message.name+ ''])); + ['' + r.message.name+ ''])); dialog.hide(); callback(r); }, diff --git a/erpnext/stock/dashboard/item_dashboard_list.html b/erpnext/stock/dashboard/item_dashboard_list.html index e1914ed76a2..0c10be462a1 100644 --- a/erpnext/stock/dashboard/item_dashboard_list.html +++ b/erpnext/stock/dashboard/item_dashboard_list.html @@ -1,10 +1,10 @@ {% for d in data %}
    -
    + -
    +
    {% if show_item %} {{ d.item_code }} @@ -12,7 +12,7 @@ {% endif %}
    -
    +
    {{ d.total_reserved }} @@ -40,7 +40,7 @@
    {% if can_write %} -
    +
    {% if d.actual_qty %} ') - .appendTo(frm.timeline.wrapper.find('.comment-header .asset-details:not([data-communication-type="Comment"])')) - .on("click", function() { - var content = $(this).parents(".timeline-item:first").find(".timeline-item-content").html(); - var doc = frappe.model.get_new_doc("Help Article"); - doc.title = frm.doc.subject; - doc.content = content; - frappe.set_route("Form", "Help Article", doc.name); - }); - } - if (!frm.timeline.wrapper.find(".btn-split-issue").length) { - let split_issue = __("Split Issue") - $(``) - .appendTo(frm.timeline.wrapper.find('.comment-header .asset-details:not([data-communication-type="Comment"])')) - if (!frm.timeline.wrapper.data("split-issue-event-attached")){ - frm.timeline.wrapper.on("click", ".btn-split-issue", (e) => { + let split_issue_btn = $(` + + ${frappe.utils.icon('branch', 'sm')} + + `); + + let communication_box = frm.timeline.wrapper.find('.timeline-item[data-doctype="Communication"]'); + communication_box.find('.actions').prepend(split_issue_btn); + + if (!frm.timeline.wrapper.data("split-issue-event-attached")) { + frm.timeline.wrapper.on('click', '.btn-split-issue', (e) => { var dialog = new frappe.ui.Dialog({ title: __("Split Issue"), fields: [ - {fieldname: "subject", fieldtype: "Data", reqd: 1, label: __("Subject"), description: __("All communications including and above this shall be moved into the new Issue")} + { + fieldname: "subject", + fieldtype: "Data", + reqd: 1, + label: __("Subject"), + description: __("All communications including and above this shall be moved into the new Issue") + } ], primary_action_label: __("Split"), - primary_action: function() { + primary_action: () => { frm.call("split_issue", { subject: dialog.fields_dict.subject.value, communication_id: e.currentTarget.closest(".timeline-item").getAttribute("data-name") }, (r) => { - let url = window.location.href - let arr = url.split("/"); - let result = arr[0] + "//" + arr[2] - frappe.msgprint(`New issue created: ${r.message}`) + frappe.msgprint(`New issue created: ${r.message}`); frm.reload_doc(); dialog.hide(); }); } }); - dialog.show() - }) - frm.timeline.wrapper.data("split-issue-event-attached", true) + dialog.show(); + }); + frm.timeline.wrapper.data("split-issue-event-attached", true); } } + + // create button for "Help Article" + // if (frappe.model.can_create("Help Article")) { + // // Removing Help Article button if exists to avoid multiple occurrence + // frm.timeline.wrapper.find('.action-btn .btn-add-to-kb').remove(); + + // let help_article = $(` + // + // ${frappe.utils.icon('solid-info', 'sm')} + // + // `); + + // let communication_box = frm.timeline.wrapper.find('.timeline-item[data-doctype="Communication"]'); + // communication_box.find('.actions').prepend(help_article); + // if (!frm.timeline.wrapper.data("help-article-event-attached")) { + // frm.timeline.wrapper.on('click', '.btn-add-to-kb', function () { + // const content = $(this).parents('.timeline-item[data-doctype="Communication"]:first').find(".content").html(); + // const doc = frappe.model.get_new_doc("Help Article"); + // doc.title = frm.doc.subject; + // doc.content = content; + // frappe.set_route("Form", "Help Article", doc.name); + // }); + // } + // frm.timeline.wrapper.data("help-article-event-attached", true); + // } }, }); diff --git a/erpnext/support/doctype/issue/issue.py b/erpnext/support/doctype/issue/issue.py index 1ac295919b5..bbbbc4a5270 100644 --- a/erpnext/support/doctype/issue/issue.py +++ b/erpnext/support/doctype/issue/issue.py @@ -207,7 +207,7 @@ class Issue(Document): "comment_type": "Info", "reference_doctype": "Issue", "reference_name": replicated_issue.name, - "content": " - Split the Issue from {1}".format(self.name, frappe.bold(self.name)), + "content": " - Split the Issue from {1}".format(self.name, frappe.bold(self.name)), }).insert(ignore_permissions=True) return replicated_issue.name diff --git a/erpnext/support/doctype/issue/issue_list.js b/erpnext/support/doctype/issue/issue_list.js index 513a8dca222..e04498e29ee 100644 --- a/erpnext/support/doctype/issue/issue_list.js +++ b/erpnext/support/doctype/issue/issue_list.js @@ -28,7 +28,7 @@ frappe.listview_settings['Issue'] = { } else if (doc.status === 'Closed') { return [__(doc.status), "green", "status,=," + doc.status]; } else { - return [__(doc.status), "darkgrey", "status,=," + doc.status]; + return [__(doc.status), "gray", "status,=," + doc.status]; } } } diff --git a/erpnext/support/report/issue_analytics/test_issue_analytics.py b/erpnext/support/report/issue_analytics/test_issue_analytics.py index fc6bb587be8..77483198ecc 100644 --- a/erpnext/support/report/issue_analytics/test_issue_analytics.py +++ b/erpnext/support/report/issue_analytics/test_issue_analytics.py @@ -22,7 +22,7 @@ class TestIssueAnalytics(unittest.TestCase): if current_month_date.year != last_month_date.year: self.current_month += '_' + str(current_month_date.year) self.last_month += '_' + str(last_month_date.year) - + def test_issue_analytics(self): create_service_level_agreements_for_issues() create_issue_types() diff --git a/erpnext/support/workspace/support/support.json b/erpnext/support/workspace/support/support.json new file mode 100644 index 00000000000..01a8676f05d --- /dev/null +++ b/erpnext/support/workspace/support/support.json @@ -0,0 +1,186 @@ +{ + "category": "Modules", + "charts": [], + "creation": "2020-03-02 15:48:23.224699", + "developer_mode_only": 0, + "disable_user_customization": 0, + "docstatus": 0, + "doctype": "Workspace", + "extends_another_page": 0, + "hide_custom": 0, + "icon": "support", + "idx": 0, + "is_standard": 1, + "label": "Support", + "links": [ + { + "hidden": 0, + "is_query_report": 0, + "label": "Issues", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Issue", + "link_to": "Issue", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Issue Type", + "link_to": "Issue Type", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Issue Priority", + "link_to": "Issue Priority", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Maintenance", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Maintenance Schedule", + "link_to": "Maintenance Schedule", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Maintenance Visit", + "link_to": "Maintenance Visit", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Service Level Agreement", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Service Level Agreement", + "link_to": "Service Level Agreement", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Warranty", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Warranty Claim", + "link_to": "Warranty Claim", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Serial No", + "link_to": "Serial No", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Settings", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Support Settings", + "link_to": "Support Settings", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Reports", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "Issue", + "hidden": 0, + "is_query_report": 1, + "label": "First Response Time for Issues", + "link_to": "First Response Time for Issues", + "link_type": "Report", + "onboard": 0, + "type": "Link" + } + ], + "modified": "2020-12-01 13:38:37.073482", + "modified_by": "Administrator", + "module": "Support", + "name": "Support", + "owner": "Administrator", + "pin_to_bottom": 0, + "pin_to_top": 0, + "shortcuts": [ + { + "color": "Yellow", + "format": "{} Assigned", + "label": "Issue", + "link_to": "Issue", + "stats_filter": "{\n \"_assign\": [\"like\", '%' + frappe.session.user + '%'],\n \"status\": \"Open\"\n}", + "type": "DocType" + }, + { + "label": "Maintenance Visit", + "link_to": "Maintenance Visit", + "type": "DocType" + }, + { + "label": "Service Level Agreement", + "link_to": "Service Level Agreement", + "type": "DocType" + } + ] +} \ No newline at end of file diff --git a/erpnext/telephony/doctype/call_log/call_log.js b/erpnext/telephony/doctype/call_log/call_log.js index 977f86da0dd..e7afa0b7d09 100644 --- a/erpnext/telephony/doctype/call_log/call_log.js +++ b/erpnext/telephony/doctype/call_log/call_log.js @@ -2,7 +2,26 @@ // For license information, please see license.txt frappe.ui.form.on('Call Log', { - // refresh: function(frm) { - - // } + refresh: function(frm) { + frm.events.setup_recording_audio_control(frm); + const incoming_call = frm.doc.type == 'Incoming'; + frm.add_custom_button(incoming_call ? __('Callback'): __('Call Again'), () => { + const number = incoming_call ? frm.doc.from : frm.doc.to; + frappe.phone_call.handler(number, frm); + }); + }, + setup_recording_audio_control(frm) { + const recording_wrapper = frm.get_field('recording_html').$wrapper; + if (!frm.doc.recording_url || frm.doc.recording_url == 'null') { + recording_wrapper.empty(); + } else { + recording_wrapper.addClass('input-max-width'); + recording_wrapper.html(` + + `); + } + } }); diff --git a/erpnext/telephony/doctype/call_log/call_log.json b/erpnext/telephony/doctype/call_log/call_log.json index 55ad2baefdb..1d6c39edf6e 100644 --- a/erpnext/telephony/doctype/call_log/call_log.json +++ b/erpnext/telephony/doctype/call_log/call_log.json @@ -5,34 +5,26 @@ "doctype": "DocType", "engine": "InnoDB", "field_order": [ + "call_details_section", "id", "from", "to", - "column_break_3", - "received_by", "medium", - "caller_information", - "contact", - "contact_name", - "column_break_10", + "start_time", + "end_time", + "column_break_4", + "type", "customer", - "lead", - "lead_name", - "section_break_5", "status", "duration", - "recording_url" + "recording_url", + "recording_html", + "section_break_11", + "summary", + "section_break_19", + "links" ], "fields": [ - { - "fieldname": "column_break_3", - "fieldtype": "Column Break" - }, - { - "fieldname": "section_break_5", - "fieldtype": "Section Break", - "label": "Call Details" - }, { "fieldname": "id", "fieldtype": "Data", @@ -50,6 +42,7 @@ { "fieldname": "to", "fieldtype": "Data", + "in_list_view": 1, "label": "To", "read_only": 1 }, @@ -58,13 +51,13 @@ "fieldtype": "Select", "in_list_view": 1, "label": "Status", - "options": "Ringing\nIn Progress\nCompleted\nMissed", + "options": "Ringing\nIn Progress\nCompleted\nFailed\nBusy\nNo Answer\nQueued\nCanceled", "read_only": 1 }, { "description": "Call Duration in seconds", "fieldname": "duration", - "fieldtype": "Int", + "fieldtype": "Duration", "in_list_view": 1, "label": "Duration", "read_only": 1 @@ -72,8 +65,8 @@ { "fieldname": "recording_url", "fieldtype": "Data", - "label": "Recording URL", - "read_only": 1 + "hidden": 1, + "label": "Recording URL" }, { "fieldname": "medium", @@ -82,51 +75,52 @@ "read_only": 1 }, { - "fieldname": "received_by", - "fieldtype": "Link", - "label": "Received By", - "options": "Employee", + "fieldname": "type", + "fieldtype": "Select", + "label": "Type", + "options": "Incoming\nOutgoing", "read_only": 1 }, { - "fieldname": "caller_information", + "fieldname": "recording_html", + "fieldtype": "HTML", + "label": "Recording HTML" + }, + { + "fieldname": "section_break_19", "fieldtype": "Section Break", - "label": "Caller Information" + "label": "Reference" }, { - "fieldname": "contact", - "fieldtype": "Link", - "label": "Contact", - "options": "Contact", - "read_only": 1 + "fieldname": "links", + "fieldtype": "Table", + "label": "Links", + "options": "Dynamic Link" }, { - "fieldname": "lead", - "fieldtype": "Link", - "label": "Lead ", - "options": "Lead", - "read_only": 1 - }, - { - "fetch_from": "contact.name", - "fieldname": "contact_name", - "fieldtype": "Data", - "hidden": 1, - "in_list_view": 1, - "label": "Contact Name", - "read_only": 1 - }, - { - "fieldname": "column_break_10", + "fieldname": "column_break_4", "fieldtype": "Column Break" }, { - "fetch_from": "lead.lead_name", - "fieldname": "lead_name", - "fieldtype": "Data", - "hidden": 1, - "in_list_view": 1, - "label": "Lead Name", + "fieldname": "summary", + "fieldtype": "Small Text" + }, + { + "fieldname": "section_break_11", + "fieldtype": "Section Break", + "hide_border": 1, + "label": "Call Summary" + }, + { + "fieldname": "start_time", + "fieldtype": "Datetime", + "label": "Start Time", + "read_only": 1 + }, + { + "fieldname": "end_time", + "fieldtype": "Datetime", + "label": "End Time", "read_only": 1 }, { @@ -135,11 +129,17 @@ "label": "Customer", "options": "Customer", "read_only": 1 + }, + { + "fieldname": "call_details_section", + "fieldtype": "Section Break", + "label": "Call Details" } ], + "in_create": 1, "index_web_pages_for_search": 1, "links": [], - "modified": "2020-11-25 14:32:44.407815", + "modified": "2021-02-08 14:23:28.744844", "modified_by": "Administrator", "module": "Telephony", "name": "Call Log", @@ -162,8 +162,8 @@ "role": "Employee" } ], - "sort_field": "modified", - "sort_order": "ASC", + "sort_field": "creation", + "sort_order": "DESC", "title_field": "from", "track_changes": 1, "track_views": 1 diff --git a/erpnext/telephony/doctype/call_log/call_log.py b/erpnext/telephony/doctype/call_log/call_log.py index 296473efe1f..4d553df08b8 100644 --- a/erpnext/telephony/doctype/call_log/call_log.py +++ b/erpnext/telephony/doctype/call_log/call_log.py @@ -8,40 +8,83 @@ from frappe import _ from frappe.model.document import Document from erpnext.crm.doctype.utils import get_scheduled_employees_for_popup, strip_number from frappe.contacts.doctype.contact.contact import get_contact_with_phone_number +from frappe.core.doctype.dynamic_link.dynamic_link import deduplicate_dynamic_links + from erpnext.crm.doctype.lead.lead import get_lead_with_phone_number +END_CALL_STATUSES = ['No Answer', 'Completed', 'Busy', 'Failed'] +ONGOING_CALL_STATUSES = ['Ringing', 'In Progress'] + + class CallLog(Document): + def validate(self): + deduplicate_dynamic_links(self) + def before_insert(self): - number = strip_number(self.get('from')) - self.contact = get_contact_with_phone_number(number) - self.lead = get_lead_with_phone_number(number) - if self.contact: - contact = frappe.get_doc("Contact", self.contact) - self.customer = contact.get_link_for("Customer") + """Add lead(third party person) links to the document. + """ + lead_number = self.get('from') if self.is_incoming_call() else self.get('to') + lead_number = strip_number(lead_number) + + contact = get_contact_with_phone_number(strip_number(lead_number)) + if contact: + self.add_link(link_type='Contact', link_name=contact) + + lead = get_lead_with_phone_number(lead_number) + if lead: + self.add_link(link_type='Lead', link_name=lead) def after_insert(self): self.trigger_call_popup() def on_update(self): + def _is_call_missed(doc_before_save, doc_after_save): + # FIXME: This works for Exotel but not for all telepony providers + return doc_before_save.to != doc_after_save.to and doc_after_save.status not in END_CALL_STATUSES + + def _is_call_ended(doc_before_save, doc_after_save): + return doc_before_save.status not in END_CALL_STATUSES and self.status in END_CALL_STATUSES + doc_before_save = self.get_doc_before_save() if not doc_before_save: return - if doc_before_save.status in ['Ringing'] and self.status in ['Missed', 'Completed']: - frappe.publish_realtime('call_{id}_disconnected'.format(id=self.id), self) - elif doc_before_save.to != self.to: + + if _is_call_missed(doc_before_save, self): + frappe.publish_realtime('call_{id}_missed'.format(id=self.id), self) self.trigger_call_popup() + if _is_call_ended(doc_before_save, self): + frappe.publish_realtime('call_{id}_ended'.format(id=self.id), self) + + def is_incoming_call(self): + return self.type == 'Incoming' + + def add_link(self, link_type, link_name): + self.append('links', { + 'link_doctype': link_type, + 'link_name': link_name + }) + def trigger_call_popup(self): - scheduled_employees = get_scheduled_employees_for_popup(self.medium) - employee_emails = get_employees_with_number(self.to) + if self.is_incoming_call(): + scheduled_employees = get_scheduled_employees_for_popup(self.medium) + employee_emails = get_employees_with_number(self.to) - # check if employees with matched number are scheduled to receive popup - emails = set(scheduled_employees).intersection(employee_emails) + # check if employees with matched number are scheduled to receive popup + emails = set(scheduled_employees).intersection(employee_emails) - # # if no employee found with matching phone number then show popup to scheduled employees - # emails = emails or scheduled_employees if employee_emails + if frappe.conf.developer_mode: + self.add_comment(text=f""" + Scheduled Employees: {scheduled_employees} + Matching Employee: {employee_emails} + Show Popup To: {emails} + """) + + if employee_emails and not emails: + self.add_comment(text=_("No employee was scheduled for call popup")) + + for email in emails: + frappe.publish_realtime('show_call_popup', self, user=email) - for email in emails: - frappe.publish_realtime('show_call_popup', self, user=email) @frappe.whitelist() def add_call_summary(call_log, summary): @@ -65,34 +108,69 @@ def get_employees_with_number(number): return employee_emails -def set_caller_information(doc, state): - '''Called from hooks on creation of Lead or Contact''' - if doc.doctype not in ['Lead', 'Contact']: return - - numbers = [doc.get('phone'), doc.get('mobile_no')] - # contact for Contact and lead for Lead - fieldname = doc.doctype.lower() - - # contact_name or lead_name - display_name_field = '{}_name'.format(fieldname) - - # Contact now has all the nos saved in child table - if doc.doctype == 'Contact': +def link_existing_conversations(doc, state): + """ + Called from hooks on creation of Contact or Lead to link all the existing conversations. + """ + if doc.doctype != 'Contact': return + try: numbers = [d.phone for d in doc.phone_nos] - for number in numbers: - number = strip_number(number) - if not number: continue + for number in numbers: + number = strip_number(number) + if not number: continue + logs = frappe.db.sql_list(""" + SELECT cl.name FROM `tabCall Log` cl + LEFT JOIN `tabDynamic Link` dl + ON cl.name = dl.parent + WHERE (cl.`from` like %(phone_number)s or cl.`to` like %(phone_number)s) + GROUP BY cl.name + HAVING SUM( + CASE + WHEN dl.link_doctype = %(doctype)s AND dl.link_name = %(docname)s + THEN 1 + ELSE 0 + END + )=0 + """, dict( + phone_number='%{}'.format(number), + docname=doc.name, + doctype = doc.doctype + ) + ) - filters = frappe._dict({ - 'from': ['like', '%{}'.format(number)], - fieldname: '' + for log in logs: + call_log = frappe.get_doc('Call Log', log) + call_log.add_link(link_type=doc.doctype, link_name=doc.name) + call_log.save() + frappe.db.commit() + except Exception: + frappe.log_error(title=_('Error during caller information update')) + +def get_linked_call_logs(doctype, docname): + # content will be shown in timeline + logs = frappe.get_all('Dynamic Link', fields=['parent'], filters={ + 'parenttype': 'Call Log', + 'link_doctype': doctype, + 'link_name': docname + }) + + logs = set([log.parent for log in logs]) + + logs = frappe.get_all('Call Log', fields=['*'], filters={ + 'name': ['in', logs] + }) + + timeline_contents = [] + for log in logs: + log.show_call_button = 0 + timeline_contents.append({ + 'icon': 'call', + 'is_card': True, + 'creation': log.creation, + 'template': 'call_link', + 'template_data': log }) - logs = frappe.get_all('Call Log', filters=filters) + return timeline_contents - for log in logs: - frappe.db.set_value('Call Log', log.name, { - fieldname: doc.name, - display_name_field: doc.get_title() - }, update_modified=False) diff --git a/erpnext/templates/emails/birthday_reminder.html b/erpnext/templates/emails/birthday_reminder.html new file mode 100644 index 00000000000..12cdf1ec600 --- /dev/null +++ b/erpnext/templates/emails/birthday_reminder.html @@ -0,0 +1,25 @@ +
    +
    + {% for person in birthday_persons %} + {% if person.image %} + + + {% else %} + + {{ frappe.utils.get_abbr(person.name) }} + + {% endif %} + {% endfor %} +
    +
    + {{ reminder_text }} +

    {{ message }}

    +
    +
    \ No newline at end of file diff --git a/erpnext/templates/generators/item/item.html b/erpnext/templates/generators/item/item.html index d3691a6e99e..135982d7090 100644 --- a/erpnext/templates/generators/item/item.html +++ b/erpnext/templates/generators/item/item.html @@ -3,21 +3,25 @@ {% block title %} {{ title }} {% endblock %} {% block breadcrumbs %} +
    {% include "templates/includes/breadcrumbs.html" %} +
    {% endblock %} {% block page_content %} -{% from "erpnext/templates/includes/macros.html" import product_image %} -
    -
    -
    - {% include "templates/generators/item/item_image.html" %} - {% include "templates/generators/item/item_details.html" %} +
    + {% from "erpnext/templates/includes/macros.html" import product_image %} +
    +
    +
    + {% include "templates/generators/item/item_image.html" %} + {% include "templates/generators/item/item_details.html" %} +
    + + {% include "templates/generators/item/item_specifications.html" %} + + {{ doc.website_content or '' }}
    - - {% include "templates/generators/item/item_specifications.html" %} - - {{ doc.website_content or '' }}
    {% endblock %} diff --git a/erpnext/templates/generators/item/item_add_to_cart.html b/erpnext/templates/generators/item/item_add_to_cart.html index c619963a918..167c848eff1 100644 --- a/erpnext/templates/generators/item/item_add_to_cart.html +++ b/erpnext/templates/generators/item/item_add_to_cart.html @@ -6,10 +6,10 @@
    {% if cart_settings.show_price and product_info.price %} -

    +
    {{ product_info.price.formatted_price_sales_uom }} - ({{ product_info.price.formatted_price }} / {{ product_info.uom }}) -

    + ({{ product_info.price.formatted_price }} / {{ product_info.uom }}) +
    {% else %} {{ _("UOM") }} : {{ product_info.uom }} {% endif %} @@ -17,11 +17,11 @@ {% if cart_settings.show_stock_availability %}
    {% if product_info.in_stock == 0 %} - + {{ _('Not in stock') }} {% elif product_info.in_stock == 1 %} - + {{ _('In stock') }} {% if product_info.show_stock_qty and product_info.stock_qty %} ({{ product_info.stock_qty[0][0] }}) @@ -30,7 +30,7 @@ {% endif %}
    {% endif %} -
    +
    {% if product_info.price and (cart_settings.allow_items_not_in_stock or product_info.in_stock) %} {% endif %} diff --git a/erpnext/templates/generators/item/item_configure.html b/erpnext/templates/generators/item/item_configure.html index 73f9ec99b34..b61ac73072d 100644 --- a/erpnext/templates/generators/item/item_configure.html +++ b/erpnext/templates/generators/item/item_configure.html @@ -1,9 +1,9 @@ {% if shopping_cart and shopping_cart.cart_settings.enabled %} {% set cart_settings = shopping_cart.cart_settings %} -
    +
    {% if cart_settings.enable_variants | int %} - + ` : ''; const items_found = filtered_items_count === 1 ? __('{0} item found.', [filtered_items_count]) : __('{0} items found.', [filtered_items_count]); - const item_found_status = ` - ` + : ``; + /* eslint-disable indent */ return ` - ${item_add_to_cart} ${item_found_status} + ${item_add_to_cart} `; } @@ -254,8 +267,8 @@ class ItemConfigure { } append_status_area() { - this.dialog.$status_area = $('
    '); - this.dialog.$wrapper.find('.modal-body').prepend(this.dialog.$status_area); + this.dialog.$status_area = $('
    '); + this.dialog.$wrapper.find('.modal-body').append(this.dialog.$status_area); this.dialog.$wrapper.on('click', '[data-action]', (e) => { e.preventDefault(); const $target = $(e.currentTarget); @@ -263,7 +276,7 @@ class ItemConfigure { const method = this[action]; method.call(this, e); }); - this.dialog.$body.css({ maxHeight: '75vh', overflow: 'auto', overflowX: 'hidden' }); + this.dialog.$wrapper.addClass('item-configurator-dialog'); } get_next_attribute_and_values(selected_attributes) { diff --git a/erpnext/templates/generators/item/item_details.html b/erpnext/templates/generators/item/item_details.html index 4cbecb02155..3b775858276 100644 --- a/erpnext/templates/generators/item/item_details.html +++ b/erpnext/templates/generators/item/item_details.html @@ -1,14 +1,21 @@ -
    +
    -

    +

    {{ item_name }}

    -

    +

    {{ _("Item Code") }}: {{ doc.name }}

    +{% if has_variants %} + + {% include "templates/generators/item/item_configure.html" %} +{% else %} + + {% include "templates/generators/item/item_add_to_cart.html" %} +{% endif %} -
    +
    {% if frappe.utils.strip_html(doc.web_long_description or '') %} {{ doc.web_long_description | safe }} {% elif frappe.utils.strip_html(doc.description or '') %} @@ -17,12 +24,4 @@ {{ _("No description given") }} {% endif %}
    - -{% if has_variants %} - - {% include "templates/generators/item/item_configure.html" %} -{% else %} - - {% include "templates/generators/item/item_add_to_cart.html" %} -{% endif %}
    diff --git a/erpnext/templates/generators/item/item_image.html b/erpnext/templates/generators/item/item_image.html index 5d46a45053d..39a30d0d7cb 100644 --- a/erpnext/templates/generators/item/item_image.html +++ b/erpnext/templates/generators/item/item_image.html @@ -1,42 +1,42 @@ -
    -{% if slides %} -{{ product_image(slides[0].image, 'product-image') }} -
    - {% for item in slides %} - {{ item.heading }} - {% endfor %} -
    - - -{% else %} -{{ product_image(website_image or image or 'no-image.jpg', alt=website_image_alt or item_name) }} -{% endif %} + $('.item-slideshow-image').removeClass('active'); + $img.addClass('active'); + }); + }) + + {% else %} + {{ product_image(website_image or image or 'no-image.jpg', alt=website_image_alt or item_name) }} + {% endif %} - + - +